feat(photos): GET /photos, PATCH /photos/:id, POST /photos (upload)
- Domaine : Photo avec statuts todo/ok/flag, valDate automatique au passage ok - Ports in : GetPhotos, UpdatePhoto, UploadPhotos - Ports out : PhotoRepository, PhotoStoragePort - Infrastructure : PhotoEntity, mapper, adapter JPA, LocalPhotoStorage - Controller : liste, patch, upload multipart, endpoint image - Migration V3 : table photos - Tests unitaires UpdatePhotoService - application.yml : multipart 50MB, photos.storage-path Closes #2, #4, #5 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package com.olhar.olharapi.application.port.in;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GetPhotosUseCase {
|
||||
List<Photo> getAll();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.olhar.olharapi.application.port.in;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface UpdatePhotoUseCase {
|
||||
Photo update(UUID id, Command command);
|
||||
|
||||
record Command(
|
||||
String title,
|
||||
String date,
|
||||
String place,
|
||||
List<String> people,
|
||||
String comment,
|
||||
String status,
|
||||
String flagReason,
|
||||
String flagBy
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.olhar.olharapi.application.port.in;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
public interface UploadPhotosUseCase {
|
||||
List<Photo> upload(List<FileEntry> files, String uploadedBy);
|
||||
|
||||
record FileEntry(String originalFilename, String contentType, InputStream inputStream) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.olhar.olharapi.application.port.out;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PhotoRepository {
|
||||
List<Photo> findAll();
|
||||
Optional<Photo> findById(UUID id);
|
||||
Photo save(Photo photo);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.olhar.olharapi.application.port.out;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PhotoStoragePort {
|
||||
String store(UUID photoId, String filename, InputStream data);
|
||||
InputStream load(UUID photoId);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.olhar.olharapi.application.usecase;
|
||||
|
||||
import com.olhar.olharapi.application.port.in.GetPhotosUseCase;
|
||||
import com.olhar.olharapi.application.port.out.PhotoRepository;
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GetPhotosService implements GetPhotosUseCase {
|
||||
|
||||
private final PhotoRepository photoRepository;
|
||||
|
||||
@Override
|
||||
public List<Photo> getAll() {
|
||||
return photoRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.olhar.olharapi.application.usecase;
|
||||
|
||||
import com.olhar.olharapi.application.port.in.UpdatePhotoUseCase;
|
||||
import com.olhar.olharapi.application.port.out.PhotoRepository;
|
||||
import com.olhar.olharapi.domain.exception.PhotoNotFoundException;
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UpdatePhotoService implements UpdatePhotoUseCase {
|
||||
|
||||
private final PhotoRepository photoRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Photo update(UUID id, Command command) {
|
||||
Photo existing = photoRepository.findById(id)
|
||||
.orElseThrow(() -> new PhotoNotFoundException(id));
|
||||
|
||||
Photo.Status newStatus = command.status() != null
|
||||
? Photo.Status.fromApiStatus(command.status())
|
||||
: existing.status();
|
||||
|
||||
Instant valDate = switch (newStatus) {
|
||||
case OK -> existing.valDate() != null ? existing.valDate() : Instant.now();
|
||||
default -> null;
|
||||
};
|
||||
|
||||
Photo updated = new Photo(
|
||||
existing.id(),
|
||||
command.title() != null ? command.title() : existing.title(),
|
||||
command.date() != null ? command.date() : existing.date(),
|
||||
command.place() != null ? command.place() : existing.place(),
|
||||
command.people() != null ? command.people() : existing.people(),
|
||||
command.comment() != null ? command.comment() : existing.comment(),
|
||||
newStatus,
|
||||
existing.by(),
|
||||
valDate,
|
||||
command.flagReason() != null ? command.flagReason() : existing.flagReason(),
|
||||
command.flagBy() != null ? command.flagBy() : existing.flagBy(),
|
||||
existing.imageUrl(),
|
||||
existing.createdAt()
|
||||
);
|
||||
|
||||
return photoRepository.save(updated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.olhar.olharapi.application.usecase;
|
||||
|
||||
import com.olhar.olharapi.application.port.in.UploadPhotosUseCase;
|
||||
import com.olhar.olharapi.application.port.out.PhotoRepository;
|
||||
import com.olhar.olharapi.application.port.out.PhotoStoragePort;
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UploadPhotosService implements UploadPhotosUseCase {
|
||||
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
|
||||
private final PhotoRepository photoRepository;
|
||||
private final PhotoStoragePort photoStorage;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<Photo> upload(List<FileEntry> files, String uploadedBy) {
|
||||
return files.stream()
|
||||
.map(file -> importOne(file, uploadedBy))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Photo importOne(FileEntry file, String uploadedBy) {
|
||||
UUID id = UUID.randomUUID();
|
||||
String imageUrl = photoStorage.store(id, file.originalFilename(), file.inputStream());
|
||||
String today = LocalDate.now().format(DATE_FMT);
|
||||
|
||||
Photo photo = new Photo(
|
||||
id,
|
||||
null,
|
||||
today,
|
||||
null,
|
||||
List.of(),
|
||||
null,
|
||||
Photo.Status.TODO,
|
||||
uploadedBy,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
imageUrl,
|
||||
Instant.now()
|
||||
);
|
||||
|
||||
return photoRepository.save(photo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.olhar.olharapi.domain.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PhotoNotFoundException extends RuntimeException {
|
||||
public PhotoNotFoundException(UUID id) {
|
||||
super("Photo introuvable : " + id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.olhar.olharapi.domain.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record Photo(
|
||||
UUID id,
|
||||
String title,
|
||||
String date,
|
||||
String place,
|
||||
List<String> people,
|
||||
String comment,
|
||||
Status status,
|
||||
String by,
|
||||
Instant valDate,
|
||||
String flagReason,
|
||||
String flagBy,
|
||||
String imageUrl,
|
||||
Instant createdAt
|
||||
) {
|
||||
public enum Status {
|
||||
TODO, OK, FLAG;
|
||||
|
||||
public String toApiStatus() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
||||
public static Status fromApiStatus(String value) {
|
||||
return valueOf(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.olhar.olharapi.infrastructure.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Converter
|
||||
public class StringListConverter implements AttributeConverter<List<String>, String> {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(List<String> attribute) {
|
||||
if (attribute == null || attribute.isEmpty()) return "[]";
|
||||
try {
|
||||
return MAPPER.writeValueAsString(attribute);
|
||||
} catch (Exception e) {
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null || dbData.isBlank()) return List.of();
|
||||
try {
|
||||
return MAPPER.readValue(dbData, new TypeReference<>() {});
|
||||
} catch (Exception e) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.olhar.olharapi.infrastructure.persistence.entity;
|
||||
|
||||
import com.olhar.olharapi.infrastructure.persistence.converter.StringListConverter;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "photos")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class PhotoEntity {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
private String title;
|
||||
|
||||
private String date;
|
||||
|
||||
private String place;
|
||||
|
||||
@Convert(converter = StringListConverter.class)
|
||||
@Column(columnDefinition = "text")
|
||||
private List<String> people;
|
||||
|
||||
@Column(columnDefinition = "text")
|
||||
private String comment;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String status;
|
||||
|
||||
@Column(name = "by_user", nullable = false)
|
||||
private String by;
|
||||
|
||||
@Column(name = "val_date")
|
||||
private Instant valDate;
|
||||
|
||||
@Column(name = "flag_reason")
|
||||
private String flagReason;
|
||||
|
||||
@Column(name = "flag_by")
|
||||
private String flagBy;
|
||||
|
||||
@Column(name = "image_url", nullable = false)
|
||||
private String imageUrl;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.olhar.olharapi.infrastructure.persistence.mapper;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import com.olhar.olharapi.infrastructure.persistence.entity.PhotoEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PhotoPersistenceMapper {
|
||||
|
||||
public Photo toDomain(PhotoEntity entity) {
|
||||
return new Photo(
|
||||
entity.getId(),
|
||||
entity.getTitle(),
|
||||
entity.getDate(),
|
||||
entity.getPlace(),
|
||||
entity.getPeople(),
|
||||
entity.getComment(),
|
||||
Photo.Status.valueOf(entity.getStatus()),
|
||||
entity.getBy(),
|
||||
entity.getValDate(),
|
||||
entity.getFlagReason(),
|
||||
entity.getFlagBy(),
|
||||
entity.getImageUrl(),
|
||||
entity.getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
public PhotoEntity toEntity(Photo photo) {
|
||||
return PhotoEntity.builder()
|
||||
.id(photo.id())
|
||||
.title(photo.title())
|
||||
.date(photo.date())
|
||||
.place(photo.place())
|
||||
.people(photo.people())
|
||||
.comment(photo.comment())
|
||||
.status(photo.status().name())
|
||||
.by(photo.by())
|
||||
.valDate(photo.valDate())
|
||||
.flagReason(photo.flagReason())
|
||||
.flagBy(photo.flagBy())
|
||||
.imageUrl(photo.imageUrl())
|
||||
.createdAt(photo.createdAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.olhar.olharapi.infrastructure.persistence.repository;
|
||||
|
||||
import com.olhar.olharapi.infrastructure.persistence.entity.PhotoEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface JpaPhotoRepository extends JpaRepository<PhotoEntity, UUID> {
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.olhar.olharapi.infrastructure.persistence.repository;
|
||||
|
||||
import com.olhar.olharapi.application.port.out.PhotoRepository;
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import com.olhar.olharapi.infrastructure.persistence.mapper.PhotoPersistenceMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PhotoRepositoryAdapter implements PhotoRepository {
|
||||
|
||||
private final JpaPhotoRepository jpa;
|
||||
private final PhotoPersistenceMapper mapper;
|
||||
|
||||
@Override
|
||||
public List<Photo> findAll() {
|
||||
return jpa.findAll().stream().map(mapper::toDomain).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Photo> findById(UUID id) {
|
||||
return jpa.findById(id).map(mapper::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Photo save(Photo photo) {
|
||||
return mapper.toDomain(jpa.save(mapper.toEntity(photo)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.olhar.olharapi.infrastructure.storage;
|
||||
|
||||
import com.olhar.olharapi.application.port.out.PhotoStoragePort;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class LocalPhotoStorage implements PhotoStoragePort {
|
||||
|
||||
private final Path storageRoot;
|
||||
|
||||
public LocalPhotoStorage(@Value("${photos.storage-path:/app/uploads}") String storagePath) {
|
||||
this.storageRoot = Path.of(storagePath);
|
||||
try {
|
||||
Files.createDirectories(storageRoot);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Impossible de créer le répertoire de stockage", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(UUID photoId, String filename, InputStream data) {
|
||||
String ext = extractExtension(filename);
|
||||
Path target = storageRoot.resolve(photoId + ext);
|
||||
try {
|
||||
Files.copy(data, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Échec de l'écriture du fichier", e);
|
||||
}
|
||||
return "/api/v1/photos/" + photoId + "/image";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream load(UUID photoId) {
|
||||
try {
|
||||
return Files.list(storageRoot)
|
||||
.filter(p -> p.getFileName().toString().startsWith(photoId.toString()))
|
||||
.findFirst()
|
||||
.map(p -> {
|
||||
try { return Files.newInputStream(p); }
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
})
|
||||
.orElseThrow(() -> new IllegalArgumentException("Fichier introuvable : " + photoId));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractExtension(String filename) {
|
||||
if (filename == null) return ".jpg";
|
||||
int dot = filename.lastIndexOf('.');
|
||||
return dot >= 0 ? filename.substring(dot) : ".jpg";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.olhar.olharapi.interfaces.exception;
|
||||
|
||||
import com.olhar.olharapi.domain.exception.EmailAlreadyUsedException;
|
||||
import com.olhar.olharapi.domain.exception.PhotoNotFoundException;
|
||||
import com.olhar.olharapi.domain.exception.UserNotFoundException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
@@ -26,6 +27,11 @@ public class GlobalExceptionHandler {
|
||||
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(PhotoNotFoundException.class)
|
||||
public ProblemDetail handlePhotoNotFound(PhotoNotFoundException ex) {
|
||||
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadCredentialsException.class)
|
||||
public ProblemDetail handleBadCredentials(BadCredentialsException ex) {
|
||||
return ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.olhar.olharapi.interfaces.rest.controller;
|
||||
|
||||
import com.olhar.olharapi.application.port.in.GetPhotosUseCase;
|
||||
import com.olhar.olharapi.application.port.in.UpdatePhotoUseCase;
|
||||
import com.olhar.olharapi.application.port.in.UploadPhotosUseCase;
|
||||
import com.olhar.olharapi.application.port.out.PhotoStoragePort;
|
||||
import com.olhar.olharapi.interfaces.rest.dto.request.UpdatePhotoRequest;
|
||||
import com.olhar.olharapi.interfaces.rest.dto.response.PhotoResponse;
|
||||
import com.olhar.olharapi.interfaces.rest.mapper.PhotoRestMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/photos")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Photos", description = "Gestion des photos")
|
||||
public class PhotoController {
|
||||
|
||||
private final GetPhotosUseCase getPhotosUseCase;
|
||||
private final UpdatePhotoUseCase updatePhotoUseCase;
|
||||
private final UploadPhotosUseCase uploadPhotosUseCase;
|
||||
private final PhotoStoragePort photoStorage;
|
||||
private final PhotoRestMapper mapper;
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "Lister toutes les photos")
|
||||
public List<PhotoResponse> getAll() {
|
||||
return getPhotosUseCase.getAll().stream().map(mapper::toResponse).toList();
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
@Operation(summary = "Mettre à jour les métadonnées ou le statut d'une photo")
|
||||
public PhotoResponse update(@PathVariable UUID id, @RequestBody UpdatePhotoRequest request) {
|
||||
return mapper.toResponse(updatePhotoUseCase.update(id,
|
||||
new UpdatePhotoUseCase.Command(
|
||||
request.title(), request.date(), request.place(), request.people(),
|
||||
request.comment(), request.status(), request.flagReason(), request.flagBy()
|
||||
)));
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@Operation(summary = "Importer des photos depuis la galerie")
|
||||
public List<PhotoResponse> upload(
|
||||
@RequestParam("files[]") List<MultipartFile> files,
|
||||
Authentication authentication) throws IOException {
|
||||
|
||||
List<UploadPhotosUseCase.FileEntry> entries = files.stream()
|
||||
.map(f -> {
|
||||
try {
|
||||
return new UploadPhotosUseCase.FileEntry(
|
||||
f.getOriginalFilename(), f.getContentType(), f.getInputStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
|
||||
return uploadPhotosUseCase.upload(entries, authentication.getName())
|
||||
.stream().map(mapper::toResponse).toList();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/image")
|
||||
@Operation(summary = "Télécharger l'image d'une photo")
|
||||
public ResponseEntity<byte[]> getImage(@PathVariable UUID id) throws IOException {
|
||||
byte[] data = photoStorage.load(id).readAllBytes();
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.body(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.olhar.olharapi.interfaces.rest.dto.request;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record UpdatePhotoRequest(
|
||||
String title,
|
||||
String date,
|
||||
String place,
|
||||
List<String> people,
|
||||
String comment,
|
||||
String status,
|
||||
String flagReason,
|
||||
String flagBy
|
||||
) {}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.olhar.olharapi.interfaces.rest.dto.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PhotoResponse(
|
||||
String id,
|
||||
String title,
|
||||
String date,
|
||||
String place,
|
||||
List<String> people,
|
||||
String comment,
|
||||
String status,
|
||||
String by,
|
||||
String valDate,
|
||||
String flagReason,
|
||||
String flagBy,
|
||||
String imageUrl
|
||||
) {}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.olhar.olharapi.interfaces.rest.mapper;
|
||||
|
||||
import com.olhar.olharapi.domain.model.Photo;
|
||||
import com.olhar.olharapi.interfaces.rest.dto.response.PhotoResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class PhotoRestMapper {
|
||||
|
||||
private static final DateTimeFormatter ISO = DateTimeFormatter.ISO_INSTANT;
|
||||
|
||||
public PhotoResponse toResponse(Photo photo) {
|
||||
return new PhotoResponse(
|
||||
photo.id().toString(),
|
||||
photo.title(),
|
||||
photo.date(),
|
||||
photo.place(),
|
||||
photo.people(),
|
||||
photo.comment(),
|
||||
photo.status().toApiStatus(),
|
||||
photo.by(),
|
||||
formatInstant(photo.valDate()),
|
||||
photo.flagReason(),
|
||||
photo.flagBy(),
|
||||
photo.imageUrl()
|
||||
);
|
||||
}
|
||||
|
||||
private String formatInstant(Instant instant) {
|
||||
return instant != null ? ISO.format(instant.atOffset(ZoneOffset.UTC)) : null;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@ spring:
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring.servlet.multipart:
|
||||
max-file-size: 50MB
|
||||
max-request-size: 200MB
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
@@ -33,3 +37,6 @@ security:
|
||||
|
||||
cors:
|
||||
allowed-origin: ${CORS_ALLOWED_ORIGIN_PROD:http://localhost:4200}
|
||||
|
||||
photos:
|
||||
storage-path: ${PHOTOS_STORAGE_PATH:/app/uploads}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE photos (
|
||||
id UUID PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
date VARCHAR(100),
|
||||
place VARCHAR(255),
|
||||
people TEXT,
|
||||
comment TEXT,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'TODO',
|
||||
by_user VARCHAR(255) NOT NULL,
|
||||
val_date TIMESTAMPTZ,
|
||||
flag_reason TEXT,
|
||||
flag_by VARCHAR(255),
|
||||
image_url VARCHAR(500) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
Reference in New Issue
Block a user