Set up picture upload mechanism.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,4 +35,6 @@ build/
|
||||
**/docker/postgresql/pgdata
|
||||
|
||||
**/node_modules
|
||||
**/.angular
|
||||
**/.angular
|
||||
|
||||
**/pictures-folder
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.codiki.application.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.codiki.domain.picture.model.builder.PictureBuilder.aPicture;
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
import org.codiki.domain.picture.port.PicturePort;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PictureUseCases {
|
||||
private final PicturePort picturePort;
|
||||
|
||||
public PictureUseCases(PicturePort picturePort) {
|
||||
this.picturePort = picturePort;
|
||||
}
|
||||
|
||||
public Picture createPicture(File pictureFile) {
|
||||
Picture newPicture = aPicture()
|
||||
.withId(UUID.randomUUID())
|
||||
.withContentFile(pictureFile)
|
||||
.build();
|
||||
|
||||
picturePort.save(newPicture);
|
||||
|
||||
return newPicture;
|
||||
}
|
||||
|
||||
public void deletePicture(UUID pictureId) {
|
||||
picturePort.deleteById(pictureId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.codiki.domain.picture.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.exception.FunctionnalException;
|
||||
|
||||
public class PictureContentLoadingErrorException extends FunctionnalException {
|
||||
public PictureContentLoadingErrorException(UUID pictureId) {
|
||||
super(String.format("An error occurred while loading picture content (picture id=%s).", pictureId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.codiki.domain.picture.exception;
|
||||
|
||||
import org.codiki.domain.exception.FunctionnalException;
|
||||
|
||||
public class PictureStorageErrorException extends FunctionnalException {
|
||||
public PictureStorageErrorException() {
|
||||
super("An error occurred while storing picture content file.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.codiki.domain.picture.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
public record Picture(
|
||||
UUID id,
|
||||
File contentFile
|
||||
) {}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.codiki.domain.picture.model.builder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
|
||||
public class PictureBuilder {
|
||||
private UUID id;
|
||||
private File contentFile;
|
||||
|
||||
private PictureBuilder() {}
|
||||
|
||||
public static PictureBuilder aPicture() {
|
||||
return new PictureBuilder();
|
||||
}
|
||||
|
||||
public PictureBuilder basedOn(Picture picture) {
|
||||
id = picture.id();
|
||||
contentFile = picture.contentFile();
|
||||
return this;
|
||||
}
|
||||
|
||||
public PictureBuilder withId(UUID id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public PictureBuilder withContentFile(File contentFile) {
|
||||
this.contentFile = contentFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Picture build() {
|
||||
return new Picture(id, contentFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.codiki.domain.picture.port;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
|
||||
public interface PicturePort {
|
||||
boolean existsById(UUID pictureId);
|
||||
|
||||
Optional<Picture> findById(UUID pictureId);
|
||||
|
||||
void save(Picture picture);
|
||||
|
||||
void deleteById(UUID pictureId);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.codiki.exposition.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Component
|
||||
public class MultipartFileConverter {
|
||||
private final String tempPicturesForlderPath;
|
||||
|
||||
public MultipartFileConverter(@Value("${application.pictures.temp-path}") String tempPicturesForlderPath) {
|
||||
this.tempPicturesForlderPath = tempPicturesForlderPath;
|
||||
}
|
||||
|
||||
public File transformToFile(MultipartFile fileContent) {
|
||||
File pictureFile = new File(String.format("%s/%s", tempPicturesForlderPath, UUID.randomUUID()));
|
||||
try {
|
||||
fileContent.transferTo(pictureFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return pictureFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.codiki.exposition.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import org.codiki.application.picture.PictureUseCases;
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/pictures")
|
||||
public class PictureController {
|
||||
private final MultipartFileConverter multipartFileConverter;
|
||||
private final PictureUseCases pictureUseCases;
|
||||
|
||||
public PictureController(
|
||||
MultipartFileConverter multipartFileConverter,
|
||||
PictureUseCases pictureUseCases
|
||||
) {
|
||||
this.multipartFileConverter = multipartFileConverter;
|
||||
this.pictureUseCases = pictureUseCases;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
|
||||
public UUID uploadPicture(@RequestParam("file") MultipartFile fileContent) {
|
||||
File pictureFile = multipartFileConverter.transformToFile(fileContent);
|
||||
Picture newPicture = pictureUseCases.createPicture(pictureFile);
|
||||
return newPicture.id();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.codiki.infrastructure.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.picture.exception.PictureStorageErrorException;
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
import org.codiki.domain.picture.port.PicturePort;
|
||||
import org.codiki.infrastructure.picture.model.PictureEntity;
|
||||
import org.codiki.infrastructure.picture.repository.PictureRepository;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@Component
|
||||
public class PictureJpaAdapter implements PicturePort {
|
||||
private final PictureRepository repository;
|
||||
private final String pictureFolderPath;
|
||||
|
||||
public PictureJpaAdapter(
|
||||
PictureRepository repository,
|
||||
@Value("${application.pictures.path}") String pictureFolderPath
|
||||
) {
|
||||
this.repository = repository;
|
||||
this.pictureFolderPath = pictureFolderPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(UUID pictureId) {
|
||||
return repository.existsById(pictureId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Picture> findById(UUID pictureId) {
|
||||
return repository.findById(pictureId)
|
||||
.map(PictureEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void save(Picture picture) {
|
||||
PictureEntity pictureEntity = new PictureEntity(picture);
|
||||
repository.save(pictureEntity);
|
||||
|
||||
boolean isMoved = picture.contentFile().renameTo(new File(String.format("%s/%s", pictureFolderPath, picture.id())));
|
||||
if (!isMoved) {
|
||||
throw new PictureStorageErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(UUID pictureId) {
|
||||
repository.deleteById(pictureId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.codiki.infrastructure.picture.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "picture")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class PictureEntity {
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
public PictureEntity(Picture picture) {
|
||||
id = picture.id();
|
||||
}
|
||||
|
||||
public Picture toDomain() {
|
||||
return new Picture(id, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.codiki.infrastructure.picture.repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.infrastructure.picture.model.PictureEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface PictureRepository extends JpaRepository<PictureEntity, UUID> {
|
||||
}
|
||||
@@ -47,3 +47,8 @@ CREATE TABLE IF NOT EXISTS publication (
|
||||
);
|
||||
CREATE INDEX publication_author_id_idx ON publication (author_id);
|
||||
CREATE INDEX publication_category_id_idx ON publication (category_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS picture (
|
||||
id UUID NOT NULL,
|
||||
CONSTRAINT picture_pk PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
10
codiki-launcher/src/main/resources/application-local.yml
Normal file
10
codiki-launcher/src/main/resources/application-local.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
application:
|
||||
pictures:
|
||||
path: /Users/florian_thierry/Documents/Developpement/codiki-hexa/pictures-folder/
|
||||
temp-path : /Users/florian_thierry/Documents/Developpement/codiki-hexa/pictures-folder/temp/
|
||||
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 1MB
|
||||
max-request-size: 1MB
|
||||
@@ -5,12 +5,17 @@ application:
|
||||
expirationDelayInMinutes: 30
|
||||
refreshToken:
|
||||
expirationDelayInDays: 7
|
||||
pictures:
|
||||
path: /opt/codiki/pictures/
|
||||
temp-path: /opt/codiki/pictures/temp/
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
server:
|
||||
http2:
|
||||
enabled: true
|
||||
error:
|
||||
whitelabel:
|
||||
enabled: false # Disable html error responses.
|
||||
@@ -22,3 +27,7 @@ spring:
|
||||
url: jdbc:postgresql://localhost:50001/codiki_db
|
||||
username: codiki_user
|
||||
password: password
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 150MB
|
||||
max-request-size: 151MB
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
vars {
|
||||
url: http://localhost:8080
|
||||
publicationId: fce1de27-11c6-4deb-a248-b63288c00037
|
||||
bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNWExM2RjNy0wMjlkLTRlYWItYTYzZC1jMWU5NmY5MDI0MWQiLCJleHAiOjE3MTAyNjU4MTZ9.t8tZce0gAXZ_DC2QEsdvJn6m-Ykjou1v4zDUIPWhzfWYR-JTeiFsfa68jkFwK2WT1aMvZppnVuc991g-yAjrPg
|
||||
bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTAzMzc2Njd9.ExV8xDeqqKk5WjIVb16NBqF1gPoRqx7uL4jQIhWjjY0QVhB5EAGdHMIbLr4s9Ck2f6z8U4sRlpPAQquDOr_9NA
|
||||
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user