Set up picture upload mechanism.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,4 +35,6 @@ build/
|
|||||||
**/docker/postgresql/pgdata
|
**/docker/postgresql/pgdata
|
||||||
|
|
||||||
**/node_modules
|
**/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_author_id_idx ON publication (author_id);
|
||||||
CREATE INDEX publication_category_id_idx ON publication (category_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
|
expirationDelayInMinutes: 30
|
||||||
refreshToken:
|
refreshToken:
|
||||||
expirationDelayInDays: 7
|
expirationDelayInDays: 7
|
||||||
|
pictures:
|
||||||
|
path: /opt/codiki/pictures/
|
||||||
|
temp-path: /opt/codiki/pictures/temp/
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
org.springframework.security: DEBUG
|
org.springframework.security: DEBUG
|
||||||
|
|
||||||
server:
|
server:
|
||||||
|
http2:
|
||||||
|
enabled: true
|
||||||
error:
|
error:
|
||||||
whitelabel:
|
whitelabel:
|
||||||
enabled: false # Disable html error responses.
|
enabled: false # Disable html error responses.
|
||||||
@@ -22,3 +27,7 @@ spring:
|
|||||||
url: jdbc:postgresql://localhost:50001/codiki_db
|
url: jdbc:postgresql://localhost:50001/codiki_db
|
||||||
username: codiki_user
|
username: codiki_user
|
||||||
password: password
|
password: password
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 150MB
|
||||||
|
max-request-size: 151MB
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
vars {
|
vars {
|
||||||
url: http://localhost:8080
|
url: http://localhost:8080
|
||||||
publicationId: fce1de27-11c6-4deb-a248-b63288c00037
|
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
|
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user