diff --git a/photo-service/src/main/java/kr/mafoo/photo/api/PhotoApi.java b/photo-service/src/main/java/kr/mafoo/photo/api/PhotoApi.java index 9f8018c..84915f1 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/api/PhotoApi.java +++ b/photo-service/src/main/java/kr/mafoo/photo/api/PhotoApi.java @@ -3,8 +3,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import kr.mafoo.photo.annotation.RequestMemberId; import kr.mafoo.photo.controller.dto.request.PhotoCreateRequest; -import kr.mafoo.photo.controller.dto.request.PhotoAlbumUpdateRequest; +import kr.mafoo.photo.controller.dto.request.PhotoUpdateAlbumIdRequest; import kr.mafoo.photo.controller.dto.response.PhotoResponse; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; @@ -15,15 +16,21 @@ public interface PhotoApi { @Operation(summary = "사진 조회", description = "사진 목록을 조회합니다.") @GetMapping - Flux getAlbumPhotos( + Flux getPhotos( + @RequestMemberId + String memberId, + @Parameter(description = "앨범 ID", example = "test_album_id") - @RequestParam(required = false) + @RequestParam String albumId ); @Operation(summary = "사진 생성", description = "사진을 생성합니다.") @PostMapping Mono createPhoto( + @RequestMemberId + String memberId, + @RequestBody PhotoCreateRequest request ); @@ -31,17 +38,23 @@ Mono createPhoto( @Operation(summary = "사진 앨범 수정", description = "사진을 다른 앨범으로 이동시킵니다.") @PatchMapping("/{photoId}/album") Mono updatePhotoAlbum( + @RequestMemberId + String memberId, + @Parameter(description = "사진 ID", example = "test_photo_id") @PathVariable String photoId, @RequestBody - PhotoAlbumUpdateRequest request + PhotoUpdateAlbumIdRequest request ); @Operation(summary = "사진 삭제", description = "사진을 삭제합니다.") @DeleteMapping("{photoId}") Mono deletePhoto( + @RequestMemberId + String memberId, + @Parameter(description = "사진 ID", example = "test_photo_id") @PathVariable String photoId diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java b/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java index 735052b..79dad1c 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java @@ -2,28 +2,33 @@ import kr.mafoo.photo.api.PhotoApi; import kr.mafoo.photo.controller.dto.request.PhotoCreateRequest; -import kr.mafoo.photo.controller.dto.request.PhotoAlbumUpdateRequest; +import kr.mafoo.photo.controller.dto.request.PhotoUpdateAlbumIdRequest; import kr.mafoo.photo.controller.dto.response.PhotoResponse; +import kr.mafoo.photo.service.PhotoService; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +@RequiredArgsConstructor @RestController public class PhotoController implements PhotoApi { + private final PhotoService photoService; + @Override - public Flux getAlbumPhotos( + public Flux getPhotos( + String memberId, String albumId ){ - return Flux.just( - new PhotoResponse("test_photo_id_a", "test_album_id_a", "photo_url"), - new PhotoResponse("test_photo_id_b", "test_album_id_a", "photo_url"), - new PhotoResponse("test_photo_id_c", "test_album_id_a", "photo_url") - ); + return photoService + .findAllByAlbumId(albumId, memberId) + .map(PhotoResponse::fromEntity); } @Override public Mono createPhoto( + String memberId, PhotoCreateRequest request ){ return Mono.just( @@ -33,18 +38,21 @@ public Mono createPhoto( @Override public Mono updatePhotoAlbum( + String memberId, String photoId, - PhotoAlbumUpdateRequest request + PhotoUpdateAlbumIdRequest request ){ - return Mono.just( - new PhotoResponse("test_photo_id", "photo_url", "test_album_id") - ); + return photoService + .updatePhotoAlbumId(photoId, request.albumId(), memberId) + .map(PhotoResponse::fromEntity); } @Override public Mono deletePhoto( + String memberId, String photoId ){ - return Mono.empty(); + return photoService + .deletePhotoById(photoId, memberId); } } diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoAlbumUpdateRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java similarity index 86% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoAlbumUpdateRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java index c35cc71..b4bb1b6 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoAlbumUpdateRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "사진 앨범 수정 요청") -public record PhotoAlbumUpdateRequest( +public record PhotoUpdateAlbumIdRequest( @Schema(description = "앨범 ID", example = "test_album_id") String albumId ) { diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java index 298bedf..a70a1fa 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java @@ -1,6 +1,7 @@ package kr.mafoo.photo.controller.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.domain.PhotoEntity; @Schema(description = "사진 응답") public record PhotoResponse( @@ -13,4 +14,13 @@ public record PhotoResponse( @Schema(description = "앨범 ID", example = "test_album_id") String albumId ) { + public static PhotoResponse fromEntity( + PhotoEntity entity + ) { + return new PhotoResponse( + entity.getPhotoId(), + entity.getPhotoUrl(), + entity.getAlbumId() + ); + } } diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java b/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java new file mode 100644 index 0000000..c33fed8 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java @@ -0,0 +1,75 @@ +package kr.mafoo.photo.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.annotation.Transient; +import org.springframework.data.domain.Persistable; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +@Table("photo") +public class PhotoEntity implements Persistable { + @Id + @Column("id") + private String photoId; + + @Column("url") + private String photoUrl; + + @Column("owner_member_id") + private String ownerMemberId; + + @Column("album_id") + private String albumId; + + @CreatedDate + @Column("created_at") + private LocalDateTime createdAt; + + @LastModifiedDate + @Column("updated_at") + private LocalDateTime updatedAt; + + @Transient + private boolean isNew = false; + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + PhotoEntity that = (PhotoEntity) obj; + return photoId.equals(that.photoId); + } + + @Override + public int hashCode() { + return photoId.hashCode(); + } + + @Override + public String getId() { + return photoId; + } + + public PhotoEntity updateAlbumId(String albumId) { + this.albumId = albumId; + return this; + } + + public static PhotoEntity newPhoto(String photoId, String photoUrl, String ownerMemberId) { + PhotoEntity photo = new PhotoEntity(); + photo.photoId = photoId; + photo.photoUrl = photoUrl; + photo.ownerMemberId = ownerMemberId; + photo.isNew = true; + return photo; + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java index 8852dac..5c9d304 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java @@ -7,6 +7,7 @@ @RequiredArgsConstructor public enum ErrorCode { ALBUM_NOT_FOUND("AE0001", "앨범을 찾을 수 없습니다"), + PHOTO_NOT_FOUND("PE0001", "사진을 찾을 수 없습니다"), ; private final String code; diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoNotFoundException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoNotFoundException.java new file mode 100644 index 0000000..3fb32dd --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoNotFoundException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class PhotoNotFoundException extends DomainException { + public PhotoNotFoundException() { + super(ErrorCode.PHOTO_NOT_FOUND); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/repository/PhotoRepository.java b/photo-service/src/main/java/kr/mafoo/photo/repository/PhotoRepository.java new file mode 100644 index 0000000..1af61d4 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/repository/PhotoRepository.java @@ -0,0 +1,9 @@ +package kr.mafoo.photo.repository; + +import kr.mafoo.photo.domain.PhotoEntity; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import reactor.core.publisher.Flux; + +public interface PhotoRepository extends R2dbcRepository { + Flux findAllByAlbumId(String ownerAlbumId); +} \ No newline at end of file diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java new file mode 100644 index 0000000..b9d4939 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java @@ -0,0 +1,71 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.PhotoEntity; +import kr.mafoo.photo.exception.AlbumNotFoundException; +import kr.mafoo.photo.exception.PhotoNotFoundException; +import kr.mafoo.photo.repository.AlbumRepository; +import kr.mafoo.photo.repository.PhotoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Service +public class PhotoService { + private final PhotoRepository photoRepository; + private final AlbumRepository albumRepository; + + public Flux findAllByAlbumId(String albumId, String requestMemberId) { + return albumRepository + .findById(albumId) + .switchIfEmpty(Mono.error(new AlbumNotFoundException())) + .flatMapMany(albumEntity -> { + if(!albumEntity.getOwnerMemberId().equals(requestMemberId)) { + // 내 앨범이 아니면 그냥 없는 앨범 처리 + return Mono.error(new AlbumNotFoundException()); + } else { + return photoRepository.findAllByAlbumId(albumId); + } + }); + } + + public Mono deletePhotoById(String photoId, String requestMemberId) { + return photoRepository + .findById(photoId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())) + .flatMap(photoEntity -> { + if(!photoEntity.getOwnerMemberId().equals(requestMemberId)) { + // 내 사진이 아니면 그냥 없는 사진 처리 + return Mono.error(new PhotoNotFoundException()); + } else { + return photoRepository.deleteById(photoId); + } + }); + } + + public Mono updatePhotoAlbumId(String photoId, String albumId, String requestMemberId) { + return photoRepository + .findById(photoId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())) + .flatMap(photoEntity -> { + if(!photoEntity.getOwnerMemberId().equals(requestMemberId)) { + // 내 사진이 아니면 그냥 없는 사진 처리 + return Mono.error(new PhotoNotFoundException()); + } else { + return albumRepository + .findById(albumId) + .switchIfEmpty(Mono.error(new AlbumNotFoundException())) + .flatMap(albumEntity -> { + if(!albumEntity.getOwnerMemberId().equals(requestMemberId)) { + // 내 앨범이 아니면 그냥 없는 앨범 처리 + return Mono.error(new AlbumNotFoundException()); + } else { + return photoRepository.save(photoEntity.updateAlbumId(albumId)); + } + }); + } + }); + } + +} diff --git a/photo-service/src/main/resources/db/migration/V2__createPhotoTable.sql b/photo-service/src/main/resources/db/migration/V2__createPhotoTable.sql new file mode 100644 index 0000000..2681c0e --- /dev/null +++ b/photo-service/src/main/resources/db/migration/V2__createPhotoTable.sql @@ -0,0 +1,10 @@ +CREATE TABLE photo( + `id` CHAR(26) PRIMARY KEY NOT NULL COMMENT '사진아이디', + `url` VARCHAR(255) NOT NULL COMMENT '사진url', + `owner_member_id` CHAR(26) NOT NULL COMMENT '사진소유자아이디', + `album_id` CHAR(26) NULL COMMENT '사진앨범아이디', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX `photo_idx1` (`owner_member_id`), + INDEX `photo_idx2` (`album_id`) +);