diff --git a/photo-service/src/main/java/kr/mafoo/photo/api/AlbumApi.java b/photo-service/src/main/java/kr/mafoo/photo/api/AlbumApi.java index 3a1213c6..239066b2 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/api/AlbumApi.java +++ b/photo-service/src/main/java/kr/mafoo/photo/api/AlbumApi.java @@ -8,7 +8,8 @@ import kr.mafoo.photo.annotation.ULID; import kr.mafoo.photo.controller.dto.request.AlbumCreateRequest; import kr.mafoo.photo.controller.dto.request.AlbumUpdateDisplayIndexRequest; -import kr.mafoo.photo.controller.dto.request.AlbumUpdateRequest; +import kr.mafoo.photo.controller.dto.request.AlbumUpdateNameAndTypeRequest; +import kr.mafoo.photo.controller.dto.request.AlbumUpdateOwnershipRequest; import kr.mafoo.photo.controller.dto.response.AlbumResponse; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -16,12 +17,12 @@ import reactor.core.publisher.Mono; @Validated -@Tag(name = "앨범 관련 API", description = "앨범 조회, 생성, 수정, 삭제 등 API") +@Tag(name = "앨범 관련 API", description = "앨범 조회, 생성, 변경, 삭제 등 API") @RequestMapping("/v1/albums") public interface AlbumApi { - @Operation(summary = "앨범 n건 조회", description = "앨범 목록을 조회합니다.") + @Operation(summary = "사용자 별 앨범 목록 조회", description = "사용자 별 앨범 목록을 조회합니다.") @GetMapping - Flux getAlbums( + Flux getAlbumListByMember( @RequestMemberId String memberId ); @@ -49,9 +50,9 @@ Mono createAlbum( AlbumCreateRequest request ); - @Operation(summary = "앨범 변경", description = "앨범의 속성을 변경합니다.") + @Operation(summary = "앨범 속성(이름, 종류) 변경", description = "앨범의 속성(이름, 종류)을 변경합니다.") @PutMapping("/{albumId}") - Mono updateAlbum( + Mono updateAlbumNameAndType( @RequestMemberId String memberId, @@ -62,12 +63,28 @@ Mono updateAlbum( @Valid @RequestBody - AlbumUpdateRequest request + AlbumUpdateNameAndTypeRequest request ); - @Operation(summary = "앨범 표기 순서 변경", description = "앨범의 표기 순서를 변경합니다.") - @PatchMapping("/{albumId}/display-index") - Mono updateAlbumDisplayIndex( +// @Operation(summary = "[DEPRECATED] 앨범 표시 순서 변경", description = "앨범의 표시 순서를 변경합니다.") +// @PatchMapping("/{albumId}/display-index") +// Mono updateAlbumDisplayIndex( +// @RequestMemberId +// String memberId, +// +// @ULID +// @Parameter(description = "앨범 ID", example = "test_album_id") +// @PathVariable +// String albumId, +// +// @Valid +// @RequestBody +// AlbumUpdateDisplayIndexRequest request +// ); + + @Operation(summary = "앨범 소유자 변경", description = "앨범의 소유자를 변경합니다.") + @PatchMapping("/{albumId}/ownership") + Mono updateAlbumOwnerShip( @RequestMemberId String memberId, @@ -78,7 +95,7 @@ Mono updateAlbumDisplayIndex( @Valid @RequestBody - AlbumUpdateDisplayIndexRequest request + AlbumUpdateOwnershipRequest request ); @Operation(summary = "앨범 삭제", description = "앨범을 삭제합니다.") 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 843c5cb6..9f344515 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 @@ -21,7 +21,7 @@ public interface PhotoApi { @Operation(summary = "사진 조회", description = "사진 목록을 조회합니다.") @GetMapping - Flux getPhotos( + Flux getPhotoListByAlbum( @RequestMemberId String memberId, @@ -37,35 +37,35 @@ Flux getPhotos( @Operation(summary = "(수정 이전) QR 사진 업로드", description = "QR을 사용해 사진을 업로드합니다.") @PostMapping(value = "") - Mono uploadQrPhotoOriginal( + Mono createPhotoWithQrUrlOriginal( @RequestMemberId String memberId, @Valid @RequestBody - PhotoQrUploadRequest request + PhotoCreateWithQrUrlRequest request ); @Operation(summary = "QR 사진 업로드", description = "QR을 사용해 사진을 업로드합니다.") @PostMapping(value = "/qr") - Mono uploadQrPhoto( + Mono createPhotoWithQrUrl( @RequestMemberId String memberId, @Valid @RequestBody - PhotoQrUploadRequest request + PhotoCreateWithQrUrlRequest request ); @Operation(summary = "파일(url) 사진 n건 업로드", description = "파일(url)을 사용해 사진을 업로드합니다.") @PostMapping(value = "/file-urls") - Flux uploadFileUrlPhoto( + Flux createPhotoBulkWithFileUrls( @RequestMemberId String memberId, @Valid @RequestBody - PhotoFileUrlUploadRequest request + PhotoCreateBulkWithFileUrlsRequest request ); @Operation(summary = "사진 파일로 업로드", description = "사진을 직접 업로드합니다.") @@ -78,9 +78,9 @@ Flux uploadPhoto( Flux request ); - @Operation(summary = "사진 앨범 단건 수정", description = "사진 한 개를 다른 앨범으로 이동시킵니다.") + @Operation(summary = "사진 앨범 설정", description = "사진의 초기 앨범 정보를 설정합니다.") @PatchMapping(value = "/{photoId}/album") - Mono updatePhotoAlbum( + Mono setPhotoAlbum( @RequestMemberId String memberId, @@ -91,7 +91,7 @@ Mono updatePhotoAlbum( @Valid @RequestBody - PhotoUpdateAlbumIdRequest request + PhotoSetAlbumRequest request ); @Operation(summary = "사진 앨범 n건 수정", description = "사진 여러 개를 다른 앨범으로 이동시킵니다.") @@ -102,7 +102,7 @@ Flux updatePhotoBulkAlbum( @Valid @RequestBody - PhotoBulkUpdateAlbumIdRequest request + PhotoUpdateBulkAlbumRequest request ); @Operation(summary = "사진 표시 순서 변경", description = "사진의 표시 순서를 변경합니다.") diff --git a/photo-service/src/main/java/kr/mafoo/photo/api/SharedMemberApi.java b/photo-service/src/main/java/kr/mafoo/photo/api/SharedMemberApi.java new file mode 100644 index 00000000..cff69074 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/api/SharedMemberApi.java @@ -0,0 +1,76 @@ +package kr.mafoo.photo.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import kr.mafoo.photo.annotation.RequestMemberId; +import kr.mafoo.photo.annotation.ULID; +import kr.mafoo.photo.controller.dto.request.SharedMemberCreateRequest; +import kr.mafoo.photo.controller.dto.request.SharedMemberUpdatePermissionRequest; +import kr.mafoo.photo.controller.dto.request.SharedMemberUpdateStatusRequest; +import kr.mafoo.photo.controller.dto.response.SharedMemberResponse; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +@Validated +@Tag(name = "공유 사용자 관련 API", description = "공유 사용자 조회, 생성, 변경, 삭제 등 API") +@RequestMapping("/v1/shared-members") +public interface SharedMemberApi { + + @Operation(summary = "공유 사용자 생성", description = "공유 사용자를 생성합니다.") + @PostMapping + Mono createSharedMember( + @RequestMemberId + String memberId, + + @Valid + @RequestBody + SharedMemberCreateRequest request + ); + + @Operation(summary = "공유 사용자 상태 변경", description = "공유 사용자의 상태를 변경합니다.") + @PatchMapping("/{sharedMemberId}/status") + Mono updateSharedMemberStatus( + @RequestMemberId + String memberId, + + @ULID + @Parameter(description = "공유 사용자 ID", example = "test_shared_member_id") + @PathVariable + String sharedMemberId, + + @Valid + @RequestBody + SharedMemberUpdateStatusRequest request + ); + + @Operation(summary = "공유 사용자 권한 변경", description = "공유 사용자의 권한을 변경합니다.") + @PatchMapping("/{sharedMemberId}/permission") + Mono updateSharedMemberPermission( + @RequestMemberId + String memberId, + + @ULID + @Parameter(description = "공유 사용자 ID", example = "test_shared_member_id") + @PathVariable + String sharedMemberId, + + @Valid + @RequestBody + SharedMemberUpdatePermissionRequest request + ); + + @Operation(summary = "공유 사용자 삭제", description = "공유 사용자를 삭제합니다.") + @DeleteMapping("{sharedMemberId}") + Mono deleteSharedMember( + @RequestMemberId + String memberId, + + @ULID + @Parameter(description = "공유 사용자 ID", example = "test_shared_member_id") + @PathVariable + String sharedMemberId + ); +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/AlbumController.java b/photo-service/src/main/java/kr/mafoo/photo/controller/AlbumController.java index 8e7f2e10..0b5f2986 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/AlbumController.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/AlbumController.java @@ -3,9 +3,9 @@ import kr.mafoo.photo.api.AlbumApi; import kr.mafoo.photo.controller.dto.request.AlbumCreateRequest; import kr.mafoo.photo.controller.dto.request.AlbumUpdateDisplayIndexRequest; -import kr.mafoo.photo.controller.dto.request.AlbumUpdateRequest; +import kr.mafoo.photo.controller.dto.request.AlbumUpdateNameAndTypeRequest; +import kr.mafoo.photo.controller.dto.request.AlbumUpdateOwnershipRequest; import kr.mafoo.photo.controller.dto.response.AlbumResponse; -import kr.mafoo.photo.domain.AlbumType; import kr.mafoo.photo.service.AlbumService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.RestController; @@ -15,14 +15,15 @@ @RequiredArgsConstructor @RestController public class AlbumController implements AlbumApi { + private final AlbumService albumService; @Override - public Flux getAlbums( + public Flux getAlbumListByMember( String memberId ) { return albumService - .findAllByOwnerMemberId(memberId) + .findAlbumListByMemberId(memberId) .map(AlbumResponse::fromEntity); } @@ -32,7 +33,7 @@ public Mono getAlbum( String albumId ) { return albumService - .findByAlbumId(albumId, memberId) + .findAlbumById(albumId, memberId) .map(AlbumResponse::fromEntity); } @@ -41,36 +42,50 @@ public Mono createAlbum( String memberId, AlbumCreateRequest request ){ - AlbumType type = AlbumType.valueOf(request.type()); return albumService - .createNewAlbum(memberId, request.name(), type) + .addAlbum(memberId, request.name(), request.type()) .map(AlbumResponse::fromEntity); } + // tmp. deprecated +// @Override +// public Mono updateAlbumDisplayIndex( +// String memberId, +// String albumId, +// AlbumUpdateDisplayIndexRequest request +// ) { +// return Mono.empty(); +// } + @Override - public Mono updateAlbum(String memberId, String albumId, AlbumUpdateRequest request) { - AlbumType albumType = AlbumType.valueOf(request.type()); + public Mono updateAlbumNameAndType( + String memberId, + String albumId, + AlbumUpdateNameAndTypeRequest request + ) { return albumService - .updateAlbumName(albumId, request.name(), memberId) - .then(albumService.updateAlbumType(albumId, albumType, memberId)) + .modifyAlbumNameAndType(albumId, request.name(), request.type(), memberId) .map(AlbumResponse::fromEntity); } @Override - public Mono updateAlbumDisplayIndex(String memberId, String albumId, AlbumUpdateDisplayIndexRequest request) { + public Mono updateAlbumOwnerShip( + String memberId, + String albumId, + AlbumUpdateOwnershipRequest request + ) { return albumService - .moveAlbumDisplayIndex(albumId, memberId, request.newDisplayIndex()) - .map(AlbumResponse::fromEntity); + .modifyAlbumOwnership(albumId, request.newOwnerMemberId(), memberId) + .map(AlbumResponse::fromEntity); } - @Override public Mono deleteAlbum( String memberId, String albumId ){ return albumService - .deleteAlbumById(albumId, memberId); + .removeAlbum(albumId, memberId); } } 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 7859d638..26d7ce0e 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 @@ -17,43 +17,43 @@ public class PhotoController implements PhotoApi { private final PhotoService photoService; @Override - public Flux getPhotos( + public Flux getPhotoListByAlbum( String memberId, String albumId, String sort ){ return photoService - .findAllByAlbumId(albumId, memberId, sort) + .findPhotoListByAlbumId(albumId, memberId, sort) .map(PhotoResponse::fromEntity); } @Override - public Mono uploadQrPhotoOriginal( + public Mono createPhotoWithQrUrlOriginal( String memberId, - PhotoQrUploadRequest request + PhotoCreateWithQrUrlRequest request ){ return photoService - .createNewPhotoByQrUrl(request.qrUrl(), memberId) + .addPhotoWithQrUrl(request.qrUrl()) .map(PhotoResponse::fromEntity); } @Override - public Mono uploadQrPhoto( + public Mono createPhotoWithQrUrl( String memberId, - PhotoQrUploadRequest request + PhotoCreateWithQrUrlRequest request ){ return photoService - .createNewPhotoByQrUrl(request.qrUrl(), memberId) + .addPhotoWithQrUrl(request.qrUrl()) .map(PhotoResponse::fromEntity); } @Override - public Flux uploadFileUrlPhoto( + public Flux createPhotoBulkWithFileUrls( String memberId, - PhotoFileUrlUploadRequest request + PhotoCreateBulkWithFileUrlsRequest request ){ return photoService - .createNewPhotoFileUrls(request.fileUrls(), request.albumId(), memberId) + .addPhotoBulkWithFileUrls(request.fileUrls(), request.albumId(), memberId) .map(PhotoResponse::fromEntity); } @@ -65,23 +65,23 @@ public Flux uploadPhoto(String memberId, Flux request) } @Override - public Mono updatePhotoAlbum( + public Mono setPhotoAlbum( String memberId, String photoId, - PhotoUpdateAlbumIdRequest request + PhotoSetAlbumRequest request ){ return photoService - .updatePhotoAlbumId(photoId, request.albumId(), memberId) + .initPhotoAlbumId(photoId, request.albumId(), memberId) .map(PhotoResponse::fromEntity); } @Override public Flux updatePhotoBulkAlbum( String memberId, - PhotoBulkUpdateAlbumIdRequest request + PhotoUpdateBulkAlbumRequest request ){ return photoService - .updatePhotoBulkAlbumId(request.photoIds(), request.albumId(), memberId) + .modifyPhotoBulkAlbumId(request.photoIds(), request.albumId(), memberId) .map(PhotoResponse::fromEntity); } @@ -92,7 +92,7 @@ public Mono updatePhotoDisplayIndex( PhotoUpdateDisplayIndexRequest request ) { return photoService - .updatePhotoDisplayIndex(photoId, request.newDisplayIndex(), memberId) + .modifyPhotoDisplayIndex(photoId, request.newDisplayIndex(), memberId) .map(PhotoResponse::fromEntity); } @@ -102,6 +102,6 @@ public Mono deletePhoto( String photoId ){ return photoService - .deletePhotoById(photoId, memberId); + .removePhoto(photoId, memberId); } } diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/SharedMemberController.java b/photo-service/src/main/java/kr/mafoo/photo/controller/SharedMemberController.java new file mode 100644 index 00000000..02ee4df0 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/SharedMemberController.java @@ -0,0 +1,55 @@ +package kr.mafoo.photo.controller; + +import kr.mafoo.photo.api.SharedMemberApi; +import kr.mafoo.photo.controller.dto.request.SharedMemberCreateRequest; +import kr.mafoo.photo.controller.dto.request.SharedMemberUpdatePermissionRequest; +import kr.mafoo.photo.controller.dto.request.SharedMemberUpdateStatusRequest; +import kr.mafoo.photo.controller.dto.response.SharedMemberResponse; +import kr.mafoo.photo.service.SharedMemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@RestController +public class SharedMemberController implements SharedMemberApi { + + private final SharedMemberService sharedMemberService; + + @Override + public Mono createSharedMember( + String memberId, + SharedMemberCreateRequest request + ){ + return sharedMemberService.addSharedMember(request.albumId(), request.permissionLevel(), request.memberId(), memberId) + .map(SharedMemberResponse::fromEntity); + } + + @Override + public Mono updateSharedMemberStatus( + String memberId, + String sharedMemberId, + SharedMemberUpdateStatusRequest request + ){ + return sharedMemberService.modifySharedMemberShareStatus(sharedMemberId, request.shareStatus(), memberId) + .map(SharedMemberResponse::fromEntity); + } + + @Override + public Mono updateSharedMemberPermission( + String memberId, + String sharedMemberId, + SharedMemberUpdatePermissionRequest request + ){ + return sharedMemberService.modifySharedMemberPermissionLevel(sharedMemberId, request.permissionLevel(), memberId) + .map(SharedMemberResponse::fromEntity); + } + + @Override + public Mono deleteSharedMember( + String memberId, + String sharedMemberId + ){ + return sharedMemberService.removeSharedMember(sharedMemberId, memberId); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumCreateRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumCreateRequest.java index bf4cf790..f308893e 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumCreateRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumCreateRequest.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import kr.mafoo.photo.annotation.MatchEnum; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; import org.hibernate.validator.constraints.Length; @Schema(description = "앨범 생성 요청") diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateNameAndTypeRequest.java similarity index 69% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateNameAndTypeRequest.java index d54470d7..23af45fe 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateNameAndTypeRequest.java @@ -3,18 +3,18 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import kr.mafoo.photo.annotation.MatchEnum; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; import org.hibernate.validator.constraints.Length; -@Schema(description = "앨범 수정 요청") -public record AlbumUpdateRequest( +@Schema(description = "앨범 속성 변경 요청") +public record AlbumUpdateNameAndTypeRequest( @NotBlank @Length(min = 1, max = 100) @Schema(description = "앨범 이름", example = "시금치파슷하") String name, @MatchEnum(enumClass = AlbumType.class) - @Schema(description = "앨범 타입") + @Schema(description = "앨범 종류", example = "HEART") String type ) { } diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateOwnershipRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateOwnershipRequest.java new file mode 100644 index 00000000..4c99f010 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/AlbumUpdateOwnershipRequest.java @@ -0,0 +1,12 @@ +package kr.mafoo.photo.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.annotation.ULID; + +@Schema(description = "앨범 소유자 변경 요청") +public record AlbumUpdateOwnershipRequest( + @ULID + @Schema(description = "사용자 ID", example = "test_member_id") + String newOwnerMemberId +) { +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoFileUrlUploadRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateBulkWithFileUrlsRequest.java similarity index 92% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoFileUrlUploadRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateBulkWithFileUrlsRequest.java index 442e8b9a..a7c686b6 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoFileUrlUploadRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateBulkWithFileUrlsRequest.java @@ -5,7 +5,7 @@ import kr.mafoo.photo.annotation.ULID; @Schema(description = "파일(url) 사진 n건 업로드 요청") -public record PhotoFileUrlUploadRequest( +public record PhotoCreateBulkWithFileUrlsRequest( @ArraySchema( schema = @Schema(description = "파일 URL 목록"), arraySchema = @Schema(example = "[\"file_url_1\", \"file_url_2\", \"file_url_3\"]") diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoQrUploadRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateWithQrUrlRequest.java similarity index 87% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoQrUploadRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateWithQrUrlRequest.java index 62322971..080bb7fb 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoQrUploadRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoCreateWithQrUrlRequest.java @@ -4,7 +4,7 @@ import org.hibernate.validator.constraints.URL; @Schema(description = "QR 사진 업로드 요청") -public record PhotoQrUploadRequest( +public record PhotoCreateWithQrUrlRequest( @URL @Schema(description = "QR URL", example = "qr_url") String qrUrl diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoSetAlbumRequest.java similarity index 72% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoSetAlbumRequest.java index 81ab4847..9a4b4825 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateAlbumIdRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoSetAlbumRequest.java @@ -3,8 +3,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import kr.mafoo.photo.annotation.ULID; -@Schema(description = "사진 앨범 수정 요청") -public record PhotoUpdateAlbumIdRequest( +@Schema(description = "사진 앨범 설정 요청") +public record PhotoSetAlbumRequest( @ULID @Schema(description = "앨범 ID", example = "test_album_id") String albumId diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoBulkUpdateAlbumIdRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateBulkAlbumRequest.java similarity index 84% rename from photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoBulkUpdateAlbumIdRequest.java rename to photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateBulkAlbumRequest.java index 258c37c3..a70a17b7 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoBulkUpdateAlbumIdRequest.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/PhotoUpdateBulkAlbumRequest.java @@ -4,8 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import kr.mafoo.photo.annotation.ULID; -@Schema(description = "사진 앨범 수정 요청") -public record PhotoBulkUpdateAlbumIdRequest( +@Schema(description = "사진 n건 앨범 수정 요청") +public record PhotoUpdateBulkAlbumRequest( @ArraySchema( schema = @Schema(description = "사진 ID 목록"), diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberCreateRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberCreateRequest.java new file mode 100644 index 00000000..7e2463a8 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberCreateRequest.java @@ -0,0 +1,22 @@ +package kr.mafoo.photo.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.annotation.MatchEnum; +import kr.mafoo.photo.annotation.ULID; +import kr.mafoo.photo.domain.enums.PermissionLevel; + +@Schema(description = "공유 사용자 생성 요청") +public record SharedMemberCreateRequest( + + @Schema(description = "공유 대상 앨범 ID", example = "test_album_id") + String albumId, + + @ULID + @Schema(description = "공유 대상 사용자 ID", example = "test_member_id") + String memberId, + + @MatchEnum(enumClass = PermissionLevel.class) + @Schema(description = "권한 단계", example = "FULL_ACCESS") + String permissionLevel +) { +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdatePermissionRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdatePermissionRequest.java new file mode 100644 index 00000000..b3ecf650 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdatePermissionRequest.java @@ -0,0 +1,13 @@ +package kr.mafoo.photo.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.annotation.MatchEnum; +import kr.mafoo.photo.domain.enums.PermissionLevel; + +@Schema(description = "공유 사용자 권한 변경 요청") +public record SharedMemberUpdatePermissionRequest( + @MatchEnum(enumClass = PermissionLevel.class) + @Schema(description = "권한 단계", example = "FULL_ACCESS") + String permissionLevel +) { +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdateStatusRequest.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdateStatusRequest.java new file mode 100644 index 00000000..339c2786 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/request/SharedMemberUpdateStatusRequest.java @@ -0,0 +1,13 @@ +package kr.mafoo.photo.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.annotation.MatchEnum; +import kr.mafoo.photo.domain.enums.ShareStatus; + +@Schema(description = "공유 사용자 상태 변경 요청") +public record SharedMemberUpdateStatusRequest( + @MatchEnum(enumClass = ShareStatus.class) + @Schema(description = "공유 상태", example = "PENDING") + String shareStatus +) { +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumRawResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumRawResponse.java index 25882123..000cfbea 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumRawResponse.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumRawResponse.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import kr.mafoo.photo.domain.AlbumEntity; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; import java.time.LocalDateTime; diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumResponse.java index 446c64c8..4688f41c 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumResponse.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/AlbumResponse.java @@ -2,7 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import kr.mafoo.photo.domain.AlbumEntity; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; @Schema(description = "앨범 응답") public record AlbumResponse( @@ -12,7 +12,7 @@ public record AlbumResponse( @Schema(description = "앨범 이름", example = "야뿌들") String name, - @Schema(description = "앨범 종류", example = "TYPE_B") + @Schema(description = "앨범 종류", example = "HEART") AlbumType type, @Schema(description = "앨범 내 사진 수", example = "6") 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 9ac00dbb..0e453853 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,7 +1,7 @@ package kr.mafoo.photo.controller.dto.response; import io.swagger.v3.oas.annotations.media.Schema; -import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.domain.enums.BrandType; import kr.mafoo.photo.domain.PhotoEntity; @Schema(description = "사진 응답") @@ -16,10 +16,7 @@ public record PhotoResponse( BrandType brand, @Schema(description = "앨범 ID", example = "test_album_id") - String albumId, - - @Schema(description = "앨범 생성일") - String createdAt + String albumId ) { public static PhotoResponse fromEntity( PhotoEntity entity @@ -28,8 +25,7 @@ public static PhotoResponse fromEntity( entity.getPhotoId(), entity.getPhotoUrl(), entity.getBrand(), - entity.getAlbumId(), - entity.getCreatedAt().toString() + entity.getAlbumId() ); } } diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PreSignedUrlResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PreSignedUrlResponse.java index bbd24b22..9accefd0 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PreSignedUrlResponse.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PreSignedUrlResponse.java @@ -1,8 +1,6 @@ package kr.mafoo.photo.controller.dto.response; import io.swagger.v3.oas.annotations.media.Schema; -import kr.mafoo.photo.domain.BrandType; -import kr.mafoo.photo.domain.PhotoEntity; @Schema(description = "Pre-signed Url 응답") public record PreSignedUrlResponse( diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberDetailResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberDetailResponse.java new file mode 100644 index 00000000..69b49e9f --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberDetailResponse.java @@ -0,0 +1,44 @@ +package kr.mafoo.photo.controller.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.enums.ShareStatus; +import kr.mafoo.photo.service.dto.SharedMemberDetailDto; + +@Schema(description = "공유 사용자 응답") +public record SharedMemberDetailResponse( + @Schema(description = "공유 사용자 ID", example = "test_shared_member_id") + String sharedMemberId, + + @Schema(description = "공유 상태", example = "PENDING") + ShareStatus shareStatus, + + @Schema(description = "권한 단계", example = "FULL_ACCESS") + PermissionLevel permissionLevel, + + @Schema(description = "공유 대상 앨범 ID", example = "test_album_id") + String albumId, + + @Schema(description = "공유 대상 사용자 ID", example = "test_member_id") + String memberId, + + @Schema(description = "공유 대상 사용자 프로필 사진 URL", example = "test_member_profile_url") + String profileImageUrl, + + @Schema(description = "공유 대상 사용자 이름", example = "test_member_name") + String memberName +) { + public static SharedMemberDetailResponse fromDto( + SharedMemberDetailDto dto + ) { + return new SharedMemberDetailResponse( + dto.sharedMemberId(), + dto.shareStatus(), + dto.permissionLevel(), + dto.albumId(), + dto.memberId(), + dto.profileImageUrl(), + dto.memberName() + ); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberResponse.java new file mode 100644 index 00000000..ec2f7337 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/SharedMemberResponse.java @@ -0,0 +1,36 @@ +package kr.mafoo.photo.controller.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.domain.SharedMemberEntity; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.enums.ShareStatus; + +@Schema(description = "공유 사용자 응답") +public record SharedMemberResponse( + @Schema(description = "공유 사용자 ID", example = "test_shared_member_id") + String sharedMemberId, + + @Schema(description = "공유 상태", example = "PENDING") + ShareStatus shareStatus, + + @Schema(description = "권한 단계", example = "FULL_ACCESS") + PermissionLevel permissionLevel, + + @Schema(description = "공유 대상 앨범 ID", example = "test_album_id") + String albumId, + + @Schema(description = "공유 대상 사용자 ID", example = "test_album_id") + String memberId +) { + public static SharedMemberResponse fromEntity( + SharedMemberEntity entity + ) { + return new SharedMemberResponse( + entity.getSharedMemberId(), + entity.getShareStatus(), + entity.getPermissionLevel(), + entity.getAlbumId(), + entity.getMemberId() + ); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumEntity.java b/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumEntity.java index 312addf0..535587c5 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumEntity.java +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumEntity.java @@ -1,8 +1,8 @@ package kr.mafoo.photo.domain; +import kr.mafoo.photo.domain.enums.AlbumType; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.LastModifiedDate; @@ -14,7 +14,6 @@ import java.time.LocalDateTime; @Getter -@Setter @NoArgsConstructor @Table("album") public class AlbumEntity implements Persistable { @@ -67,6 +66,16 @@ public String getId() { return albumId; } + public AlbumEntity updateOwnerMemberId(String newOwnerMemberId) { + this.ownerMemberId = newOwnerMemberId; + return this; + } + + public AlbumEntity updateDisplayIndex(int newDisplayIndex) { + this.displayIndex = newDisplayIndex; + return this; + } + public AlbumEntity updateName(String newName) { this.name = newName; return this; 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 index 5b446b32..90b4b801 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java @@ -1,5 +1,6 @@ package kr.mafoo.photo.domain; +import kr.mafoo.photo.domain.enums.BrandType; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.annotation.CreatedDate; @@ -35,9 +36,11 @@ public class PhotoEntity implements Persistable { @Column("display_index") private Integer displayIndex; + @CreatedDate @Column("created_at") private LocalDateTime createdAt; + @LastModifiedDate @Column("updated_at") private LocalDateTime updatedAt; @@ -91,8 +94,6 @@ public static PhotoEntity newPhoto(String photoId, String photoUrl, BrandType br photo.albumId = albumId; photo.displayIndex = displayIndex; photo.isNew = true; - photo.createdAt = LocalDateTime.now(); - photo.updatedAt = LocalDateTime.now(); return photo; } } diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/SharedMemberEntity.java b/photo-service/src/main/java/kr/mafoo/photo/domain/SharedMemberEntity.java new file mode 100644 index 00000000..35fc6a00 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/SharedMemberEntity.java @@ -0,0 +1,86 @@ +package kr.mafoo.photo.domain; + +import java.time.LocalDateTime; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.enums.ShareStatus; +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; + +@Getter +@NoArgsConstructor +@Table("shared_member") +public class SharedMemberEntity implements Persistable { + @Id + @Column("id") + private String sharedMemberId; + + @Column("share_status") + private ShareStatus shareStatus; + + @Column("permission_level") + private PermissionLevel permissionLevel; + + @Column("member_id") + private String memberId; + + @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; + + SharedMemberEntity that = (SharedMemberEntity) obj; + return sharedMemberId.equals(that.sharedMemberId); + } + + @Override + public int hashCode() { + return sharedMemberId.hashCode(); + } + + @Override + public String getId() { + return sharedMemberId; + } + + public SharedMemberEntity updateShareStatus(ShareStatus shareStatus) { + this.shareStatus = shareStatus; + return this; + } + + public SharedMemberEntity updatePermissionLevel(PermissionLevel permissionLevel) { + this.permissionLevel = permissionLevel; + return this; + } + + public static SharedMemberEntity newSharedMember(String sharedMemberId, ShareStatus shareStatus, PermissionLevel permissionLevel, String memberId, String albumId) { + SharedMemberEntity sharedMember = new SharedMemberEntity(); + sharedMember.sharedMemberId = sharedMemberId; + sharedMember.shareStatus = shareStatus; + sharedMember.permissionLevel = permissionLevel; + sharedMember.memberId = memberId; + sharedMember.albumId = albumId; + sharedMember.isNew = true; + return sharedMember; + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumType.java b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/AlbumType.java similarity index 74% rename from photo-service/src/main/java/kr/mafoo/photo/domain/AlbumType.java rename to photo-service/src/main/java/kr/mafoo/photo/domain/enums/AlbumType.java index 31f1a91c..d2ecd336 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/domain/AlbumType.java +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/AlbumType.java @@ -1,4 +1,4 @@ -package kr.mafoo.photo.domain; +package kr.mafoo.photo.domain.enums; public enum AlbumType { HEART, diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/BrandType.java similarity index 87% rename from photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java rename to photo-service/src/main/java/kr/mafoo/photo/domain/enums/BrandType.java index af94b1cb..6c7418e2 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/BrandType.java @@ -1,7 +1,9 @@ -package kr.mafoo.photo.domain; +package kr.mafoo.photo.domain.enums; import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor public enum BrandType { LIFE_FOUR_CUTS(Pattern.compile("https://api\\.life4cut\\.net/.*")), PHOTOISM(Pattern.compile("https://qr\\.seobuk\\.kr/.*")), @@ -13,15 +15,11 @@ public enum BrandType { PHOTO_SIGNATURE(Pattern.compile("http://photoqr3\\.kr/.*")), PICDOT(Pattern.compile("https://picdot\\.kr/.*")), MAFOO(Pattern.compile("https://mafoo\\.kr/.*")), - EXTERNAL(Pattern.compile("https://mafoo")) + EXTERNAL(Pattern.compile("https://mafoo")), ; private final Pattern urlPattern; - private BrandType(Pattern urlPattern) { - this.urlPattern = urlPattern; - } - public boolean matches(String qrUrl) { return urlPattern.matcher(qrUrl).matches(); } diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/enums/PermissionLevel.java b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/PermissionLevel.java new file mode 100644 index 00000000..7944a315 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/PermissionLevel.java @@ -0,0 +1,15 @@ +package kr.mafoo.photo.domain.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PermissionLevel { + FULL_ACCESS(3), // 전체 권한 + DOWNLOAD_ACCESS(2), // 다운로드 권한 + VIEW_ACCESS(1), // 뷰 권한 + ; + + private final int tier; +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/enums/ShareStatus.java b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/ShareStatus.java new file mode 100644 index 00000000..685ad92e --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/enums/ShareStatus.java @@ -0,0 +1,8 @@ +package kr.mafoo.photo.domain.enums; + +public enum ShareStatus { + PENDING, + ACCEPTED, + REJECTED +} + diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/AlbumOwnerMismatchException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/AlbumOwnerMismatchException.java new file mode 100644 index 00000000..228f9ed2 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/AlbumOwnerMismatchException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class AlbumOwnerMismatchException extends DomainException { + public AlbumOwnerMismatchException() { + super(ErrorCode.ALBUM_OWNER_MISMATCH); + } +} 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 4dc16e89..2cae0faf 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 @@ -12,11 +12,19 @@ public enum ErrorCode { ALBUM_NOT_FOUND("AE0001", "앨범을 찾을 수 없습니다"), ALBUM_DISPLAY_INDEX_IS_SAME("AE0002", "옮기려는 대상 앨범 인덱스가 같습니다"), + ALBUM_OWNER_MISMATCH("AE0003", "앨범의 소유자가 아닙니다"), + PHOTO_NOT_FOUND("PE0001", "사진을 찾을 수 없습니다"), PHOTO_BRAND_NOT_EXISTS("PE0002", "사진 브랜드가 존재하지 않습니다"), PHOTO_QR_URL_EXPIRED("PE0003", "사진 저장을 위한 QR URL이 만료되었습니다"), PHOTO_DISPLAY_INDEX_IS_SAME("PE0004", "옮기려는 대상 사진 인덱스가 같습니다"), PHOTO_DISPLAY_INDEX_NOT_VALID("PE0005", "옮기려는 대상 사진 인덱스가 유효하지 않습니다"), + PHOTO_OWNER_ALREADY_ASSIGNED("PE0006", "이미 소유자가 존재하는 사진입니다"), + + SHARED_MEMBER_NOT_FOUND("SE0001", "공유 사용자를 찾을 수 없습니다"), + SHARED_MEMBER_DUPLICATED("SE0002", "동일한 공유 사용자가 존재합니다"), + SHARED_MEMBER_STATUS_NOT_ACCEPTED("SE0003", "공유 요청이 수락되지 않았습니다"), + SHARED_MEMBER_PERMISSION_DENIED("SE0004", "공유 사용자의 권한이 충족되지 않습니다"), PRE_SIGNED_URL_EXCEED_MAXIMUM("OE0001", "한 번에 생성할 수 있는 Pre-signed url 최대치를 초과했습니다"), PRE_SIGNED_URL_BANNED_FILE_TYPE("OE0002", "Pre-signed url 발급이 허용되지 않는 파일 형식입니다"), diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoOwnerAlreadyAssignedException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoOwnerAlreadyAssignedException.java new file mode 100644 index 00000000..e464b2b1 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoOwnerAlreadyAssignedException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class PhotoOwnerAlreadyAssignedException extends DomainException { + public PhotoOwnerAlreadyAssignedException() { + super(ErrorCode.PHOTO_OWNER_ALREADY_ASSIGNED); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberDuplicatedException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberDuplicatedException.java new file mode 100644 index 00000000..e046882b --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberDuplicatedException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class SharedMemberDuplicatedException extends DomainException { + public SharedMemberDuplicatedException() { + super(ErrorCode.SHARED_MEMBER_DUPLICATED); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberNotFoundException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberNotFoundException.java new file mode 100644 index 00000000..7f7ef23f --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberNotFoundException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class SharedMemberNotFoundException extends DomainException { + public SharedMemberNotFoundException() { + super(ErrorCode.SHARED_MEMBER_NOT_FOUND); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberPermissionDeniedException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberPermissionDeniedException.java new file mode 100644 index 00000000..4ea2f390 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberPermissionDeniedException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class SharedMemberPermissionDeniedException extends DomainException { + public SharedMemberPermissionDeniedException() { + super(ErrorCode.SHARED_MEMBER_PERMISSION_DENIED); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberStatusNotAcceptedException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberStatusNotAcceptedException.java new file mode 100644 index 00000000..89533cbe --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/SharedMemberStatusNotAcceptedException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class SharedMemberStatusNotAcceptedException extends DomainException { + public SharedMemberStatusNotAcceptedException() { + super(ErrorCode.SHARED_MEMBER_STATUS_NOT_ACCEPTED); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/repository/AlbumRepository.java b/photo-service/src/main/java/kr/mafoo/photo/repository/AlbumRepository.java index 63420cfa..9d3b7add 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/repository/AlbumRepository.java +++ b/photo-service/src/main/java/kr/mafoo/photo/repository/AlbumRepository.java @@ -1,7 +1,7 @@ package kr.mafoo.photo.repository; import kr.mafoo.photo.domain.AlbumEntity; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; import org.springframework.data.domain.Pageable; import org.springframework.data.r2dbc.repository.Modifying; import org.springframework.data.r2dbc.repository.Query; 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 index 127363b2..02b3244d 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/repository/PhotoRepository.java +++ b/photo-service/src/main/java/kr/mafoo/photo/repository/PhotoRepository.java @@ -1,6 +1,6 @@ package kr.mafoo.photo.repository; -import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.domain.enums.BrandType; import kr.mafoo.photo.domain.PhotoEntity; import org.springframework.data.domain.Pageable; import org.springframework.data.r2dbc.repository.Modifying; diff --git a/photo-service/src/main/java/kr/mafoo/photo/repository/SharedMemberRepository.java b/photo-service/src/main/java/kr/mafoo/photo/repository/SharedMemberRepository.java new file mode 100644 index 00000000..70b8277d --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/repository/SharedMemberRepository.java @@ -0,0 +1,13 @@ +package kr.mafoo.photo.repository; + +import kr.mafoo.photo.domain.SharedMemberEntity; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Repository +public interface SharedMemberRepository extends R2dbcRepository { + Flux findAllByAlbumId(String albumId); + Mono findByAlbumIdAndMemberId(String albumId, String memberId); +} \ No newline at end of file diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumAdminService.java b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumAdminService.java index c311dd4b..094037d8 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumAdminService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumAdminService.java @@ -2,7 +2,7 @@ import kr.mafoo.photo.controller.dto.response.PageResponse; import kr.mafoo.photo.domain.AlbumEntity; -import kr.mafoo.photo.domain.AlbumType; +import kr.mafoo.photo.domain.enums.AlbumType; import kr.mafoo.photo.repository.AlbumRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumCommand.java b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumCommand.java new file mode 100644 index 00000000..743b15c7 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumCommand.java @@ -0,0 +1,46 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.AlbumEntity; +import kr.mafoo.photo.domain.enums.AlbumType; +import kr.mafoo.photo.repository.AlbumRepository; +import kr.mafoo.photo.util.IdGenerator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class AlbumCommand { + + private final AlbumRepository albumRepository; + + public Mono addAlbum(String albumName, String albumType, String ownerMemberId) { + return albumRepository.save( + AlbumEntity.newAlbum(IdGenerator.generate(), albumName, AlbumType.valueOf(albumType), ownerMemberId) + ); + } + + public Mono modifyAlbumNameAndType(AlbumEntity album, String newAlbumName, String newAlbumType) { + return albumRepository.save(album + .updateName(newAlbumName) + .updateType(AlbumType.valueOf(newAlbumType)) + ); + } + + public Mono modifyAlbumOwnership(AlbumEntity album, String newOwnerMemberId) { + return albumRepository.save(album.updateOwnerMemberId(newOwnerMemberId)); + } + + public Mono increaseAlbumPhotoCount(AlbumEntity album, int count) { + return albumRepository.save(album.increasePhotoCount(count)); + } + + public Mono decreaseAlbumPhotoCount(AlbumEntity album, int count) { + return albumRepository.save(album.decreasePhotoCount(count)); + } + + public Mono removeAlbum(AlbumEntity album) { + return albumRepository.delete(album); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumPermissionVerifier.java b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumPermissionVerifier.java new file mode 100644 index 00000000..54910612 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumPermissionVerifier.java @@ -0,0 +1,64 @@ +package kr.mafoo.photo.service; + +import static kr.mafoo.photo.domain.enums.ShareStatus.ACCEPTED; + +import kr.mafoo.photo.domain.AlbumEntity; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.enums.ShareStatus; +import kr.mafoo.photo.exception.AlbumOwnerMismatchException; +import kr.mafoo.photo.exception.SharedMemberNotFoundException; +import kr.mafoo.photo.exception.SharedMemberPermissionDeniedException; +import kr.mafoo.photo.exception.SharedMemberStatusNotAcceptedException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class AlbumPermissionVerifier { + + private final AlbumQuery albumQuery; + private final SharedMemberQuery sharedMemberQuery; + + public Mono verifyOwnershipOrAccessPermission(String albumId, String requestMemberId, PermissionLevel permissionLevel) { + return verifyOwnership(albumId, requestMemberId) + .onErrorResume(AlbumOwnerMismatchException.class, ownerEx -> + sharedMemberQuery.findByAlbumIdAndMemberId(albumId, requestMemberId) + .onErrorResume(SharedMemberNotFoundException.class, sharedEx -> + Mono.error(new SharedMemberPermissionDeniedException()) + ) + .flatMap(sharedAlbumMember -> + checkShareStatus(sharedAlbumMember.getShareStatus()) + .then(checkAccessPermission(sharedAlbumMember.getPermissionLevel(), permissionLevel)) + ).then(albumQuery.findById(albumId)) // FIXME : findById 중복 실행 제거 필요 + ); + } + + public Mono verifyOwnership(String albumId, String requestMemberId) { + return albumQuery.findById(albumId) + .flatMap(album -> checkOwnership(album.getOwnerMemberId(), requestMemberId) + .thenReturn(album) + ); + } + + private Mono checkOwnership(String ownerMemberId, String requestMemberId) { + if (!ownerMemberId.equals(requestMemberId)) { + return Mono.error(new AlbumOwnerMismatchException()); + } + return Mono.empty(); + } + + private Mono checkShareStatus(ShareStatus status) { + if (!status.equals(ACCEPTED)) { + return Mono.error(new SharedMemberStatusNotAcceptedException()); + } + return Mono.empty(); + } + + private Mono checkAccessPermission(PermissionLevel currentLevel, PermissionLevel requiredLevel) { + if (currentLevel.getTier() < requiredLevel.getTier()) { + return Mono.error(new SharedMemberPermissionDeniedException()); + } + return Mono.empty(); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumQuery.java b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumQuery.java new file mode 100644 index 00000000..f2b4ac86 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumQuery.java @@ -0,0 +1,20 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.AlbumEntity; +import kr.mafoo.photo.exception.AlbumNotFoundException; +import kr.mafoo.photo.repository.AlbumRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class AlbumQuery { + + private final AlbumRepository albumRepository; + + public Mono findById(String albumId) { + return albumRepository.findById(albumId) + .switchIfEmpty(Mono.error(new AlbumNotFoundException())); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumService.java b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumService.java index b32abacf..e67ae67a 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/AlbumService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/AlbumService.java @@ -1,11 +1,9 @@ package kr.mafoo.photo.service; +import static kr.mafoo.photo.domain.enums.PermissionLevel.FULL_ACCESS; +import static kr.mafoo.photo.domain.enums.PermissionLevel.VIEW_ACCESS; + import kr.mafoo.photo.domain.AlbumEntity; -import kr.mafoo.photo.domain.AlbumType; -import kr.mafoo.photo.exception.AlbumIndexIsSameException; -import kr.mafoo.photo.exception.AlbumNotFoundException; -import kr.mafoo.photo.repository.AlbumRepository; -import kr.mafoo.photo.util.IdGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,91 +13,46 @@ @RequiredArgsConstructor @Service public class AlbumService { - private final AlbumRepository albumRepository; - - @Transactional - public Mono createNewAlbum(String ownerMemberId, String albumName, AlbumType albumType) { - AlbumEntity albumEntity = AlbumEntity.newAlbum(IdGenerator.generate(), albumName, albumType, ownerMemberId); - return albumRepository - .pushDisplayIndex(ownerMemberId) //전부 인덱스 한칸 밀기 - .then(albumRepository.save(albumEntity)); - } - @Transactional - public Mono moveAlbumDisplayIndex(String albumId, String requestMemberId, Integer displayIndex) { - return findByAlbumId(albumId, requestMemberId) - .flatMap(album -> { - Integer currentDisplayIndex = album.getDisplayIndex(); - Mono pushAlbumIndexPublisher; - if(displayIndex < currentDisplayIndex) { - pushAlbumIndexPublisher = albumRepository - .pushDisplayIndexBetween(requestMemberId, displayIndex, currentDisplayIndex -1); - } else if(displayIndex > currentDisplayIndex) { - pushAlbumIndexPublisher = albumRepository - .popDisplayIndexBetween(requestMemberId, currentDisplayIndex + 1, displayIndex); - } else { - pushAlbumIndexPublisher = Mono.error(new AlbumIndexIsSameException()); - } - return pushAlbumIndexPublisher.then(Mono.defer(() -> { - album.setDisplayIndex(displayIndex); - return albumRepository.save(album); - })); - }); - } + private final AlbumQuery albumQuery; + private final AlbumCommand albumCommand; - public Flux findAllByOwnerMemberId(String ownerMemberId) { - return albumRepository.findAllByOwnerMemberIdOrderByDisplayIndex(ownerMemberId); - } + private final AlbumPermissionVerifier albumPermissionVerifier; - public Mono findByAlbumId(String albumId, String requestMemberId) { - return albumRepository - .findById(albumId) - .switchIfEmpty(Mono.error(new AlbumNotFoundException())) - .flatMap(albumEntity -> { - if(!albumEntity.getOwnerMemberId().equals(requestMemberId)) { - // 내 앨범이 아니면 그냥 없는 앨범 처리 - return Mono.error(new AlbumNotFoundException()); - } else { - return Mono.just(albumEntity); - } - }); + @Transactional(readOnly = true) + public Flux findAlbumListByMemberId(String memberId) { + return null; } - @Transactional - public Mono deleteAlbumById(String albumId, String requestMemberId) { - return findByAlbumId(albumId, requestMemberId) - .flatMap(albumEntity -> - albumRepository - .deleteById(albumId) - .then(albumRepository.popDisplayIndexBetween( - requestMemberId, albumEntity.getDisplayIndex(), Integer.MAX_VALUE)) - ); + @Transactional(readOnly = true) + public Mono findAlbumById(String albumId, String memberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, memberId, VIEW_ACCESS) + .then(albumQuery.findById(albumId)); } @Transactional - public Mono updateAlbumName(String albumId, String albumName, String requestMemberId) { - return findByAlbumId(albumId, requestMemberId) - .flatMap(albumEntity -> albumRepository.save(albumEntity.updateName(albumName))); + public Mono addAlbum(String albumName, String albumType, String requestMemberId) { + return albumCommand.addAlbum(albumName, albumType, requestMemberId); } @Transactional - public Mono updateAlbumType(String albumId, AlbumType albumType, String requestMemberId) { - return findByAlbumId(albumId, requestMemberId) - .flatMap(albumEntity -> albumRepository.save(albumEntity.updateType(albumType))); + public Mono modifyAlbumNameAndType(String albumId, String newAlbumName, String newAlbumType, String requestMemberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, FULL_ACCESS) + .flatMap(album -> albumCommand.modifyAlbumNameAndType(album, newAlbumName, newAlbumType)); } @Transactional - public Mono increaseAlbumPhotoCount(String albumId, int count, String requestMemberId) { - return findByAlbumId(albumId, requestMemberId) - .flatMap(albumEntity -> albumRepository.save(albumEntity.increasePhotoCount(count))); + public Mono modifyAlbumOwnership(String albumId, String newOwnerMemberId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnership(albumId, requestMemberId) + .flatMap(album -> albumCommand.modifyAlbumOwnership(album, newOwnerMemberId) + // TODO : 앨범 내부 사진 소유자를 새로운 앨범 소유자로 변경 + ); } @Transactional - public Mono decreaseAlbumPhotoCount(String albumId, int count, String requestMemberId) { - return Mono.justOrEmpty(albumId) - .switchIfEmpty(Mono.empty()) - .flatMap(id -> findByAlbumId(id, requestMemberId)) - .flatMap(albumEntity -> albumRepository.save(albumEntity.decreasePhotoCount(count))); + public Mono removeAlbum(String albumId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnership(albumId, requestMemberId) + .flatMap(albumCommand::removeAlbum); } -} +} \ No newline at end of file diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/Graphics2dService.java b/photo-service/src/main/java/kr/mafoo/photo/service/Graphics2dService.java index bf651f40..822a9788 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/Graphics2dService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/Graphics2dService.java @@ -1,6 +1,6 @@ package kr.mafoo.photo.service; -import kr.mafoo.photo.service.properties.RecapProperties; +import kr.mafoo.photo.util.RecapProperties; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/MemberService.java b/photo-service/src/main/java/kr/mafoo/photo/service/MemberService.java index 4fc6bfc1..19a7096c 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/MemberService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/MemberService.java @@ -1,13 +1,13 @@ package kr.mafoo.photo.service; -import kr.mafoo.photo.exception.MafooUserApiFailed; import kr.mafoo.photo.service.dto.MemberDto; +import kr.mafoo.photo.exception.MafooUserApiFailed; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.ClientResponse; @RequiredArgsConstructor @Service @@ -18,14 +18,35 @@ public class MemberService { private final WebClient client; - public Mono getMemberInfo(String authorizationToken) { + public Mono getMemberInfoByToken(String authorizationToken) { + return client + .get() + .uri(endpoint + "/user/v1/me") + .header("Authorization", "Bearer " + authorizationToken) + .retrieve() + .onStatus(status -> !status.is2xxSuccessful(), (res) -> Mono.error(new MafooUserApiFailed())) + .bodyToMono(MemberDto.class); + } + + public Mono getMemberInfoById(String memberId, String authorizationToken) { return client - .get() - .uri(endpoint + "/user/v1/me") - .header("Authorization", "Bearer " + authorizationToken) - .retrieve() - .onStatus(status -> !status.is2xxSuccessful(), (res) -> Mono.error(new MafooUserApiFailed())) - .bodyToMono(MemberDto.class); + .get() + .uri(endpoint + "/user/v1/members/" + memberId) + .header("Authorization", "Bearer " + authorizationToken) + .retrieve() + .onStatus(status -> !status.is2xxSuccessful(), (res) -> Mono.error(new MafooUserApiFailed())) + .bodyToMono(MemberDto.class); } + + public Flux getMemberListByKeyword(String keyword, String authorizationToken) { + return client + .get() + .uri(endpoint + "/user/v1/members?keyword=" + keyword) + .header("Authorization", "Bearer " + authorizationToken) + .retrieve() + .onStatus(status -> !status.is2xxSuccessful(), (res) -> Mono.error(new MafooUserApiFailed())) + .bodyToFlux(MemberDto.class); + } + } diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java b/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java index 8a77dc3d..f71aa718 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java @@ -8,7 +8,7 @@ import com.amazonaws.services.s3.model.PutObjectRequest; import kr.mafoo.photo.exception.PreSignedUrlBannedFileType; import kr.mafoo.photo.exception.PreSignedUrlExceedMaximum; -import kr.mafoo.photo.service.properties.RecapProperties; +import kr.mafoo.photo.util.RecapProperties; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Value; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoAdminService.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoAdminService.java index 99ac2937..53d6fefd 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoAdminService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoAdminService.java @@ -1,7 +1,7 @@ package kr.mafoo.photo.service; import kr.mafoo.photo.controller.dto.response.PageResponse; -import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.domain.enums.BrandType; import kr.mafoo.photo.domain.PhotoEntity; import kr.mafoo.photo.repository.PhotoRepository; import lombok.RequiredArgsConstructor; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoCommand.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoCommand.java new file mode 100644 index 00000000..51b01858 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoCommand.java @@ -0,0 +1,48 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.PhotoEntity; +import kr.mafoo.photo.domain.enums.BrandType; +import kr.mafoo.photo.repository.PhotoRepository; +import kr.mafoo.photo.util.IdGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Slf4j +@RequiredArgsConstructor +@Service +public class PhotoCommand { + + private final PhotoRepository photoRepository; + + public Mono addPhotoWithoutOwnerAndAlbum(String photoUrl, BrandType type) { + return photoRepository.save( + PhotoEntity.newPhoto(IdGenerator.generate(), photoUrl, type, null, 0, null) + ); + } + + public Mono addPhoto(String fileLink, BrandType type, String albumId, Integer displayIndex, String ownerMemberId) { + return photoRepository.save( + PhotoEntity.newPhoto(IdGenerator.generate(), fileLink, type, albumId, displayIndex, ownerMemberId) + ); + } + + public Mono modifyPhotoAlbumId(PhotoEntity photo, String albumId, Integer newDisplayIndex, String ownerMemberId) { + return photoRepository.save( + photo.updateAlbumId(albumId) + .updateOwnerMemberId(ownerMemberId) + .updateDisplayIndex(newDisplayIndex) + ); + } + + public Mono removePhoto(PhotoEntity photo) { + return popDisplayIndexGreaterThan(photo.getAlbumId(), photo.getDisplayIndex()) + .then(photoRepository.delete(photo)); + } + + public Mono popDisplayIndexGreaterThan(String albumId, int startIndex) { + return photoRepository.popDisplayIndexGreaterThan(albumId, startIndex); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoPermissionVerifier.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoPermissionVerifier.java new file mode 100644 index 00000000..e21a5f34 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoPermissionVerifier.java @@ -0,0 +1,23 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.PhotoEntity; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@RequiredArgsConstructor +public class PhotoPermissionVerifier { + + private final PhotoQuery photoQuery; + private final AlbumPermissionVerifier albumPermissionVerifier; + + public Mono verifyAccessPermission(String photoId, String requestMemberId, PermissionLevel permissionLevel) { + return photoQuery.findByPhotoId(photoId) + .flatMap(photo -> albumPermissionVerifier.verifyOwnershipOrAccessPermission(photo.getAlbumId(), requestMemberId, permissionLevel) + .thenReturn(photo) + ); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoQuery.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoQuery.java new file mode 100644 index 00000000..2d918d2d --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoQuery.java @@ -0,0 +1,39 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.PhotoEntity; +import kr.mafoo.photo.exception.PhotoNotFoundException; +import kr.mafoo.photo.repository.PhotoRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Slf4j +@RequiredArgsConstructor +@Service +public class PhotoQuery { + + private final PhotoRepository photoRepository; + + public Flux findAllByAlbumIdOrderByCreatedAtAsc(String albumId) { + return photoRepository.findAllByAlbumIdOrderByCreatedAtAsc(albumId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())); + } + + public Flux findAllByAlbumIdOrderByCreatedAtDesc(String albumId) { + return photoRepository.findAllByAlbumIdOrderByCreatedAtDesc(albumId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())); + } + + public Flux findAllByAlbumIdOrderByDisplayIndexDesc(String albumId) { + return photoRepository.findAllByAlbumIdOrderByDisplayIndexDesc(albumId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())); + } + + public Mono findByPhotoId(String photoId) { + return photoRepository.findById(photoId) + .switchIfEmpty(Mono.error(new PhotoNotFoundException())); + } + +} 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 index e5f2a676..d7fc0cc4 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java @@ -1,10 +1,12 @@ package kr.mafoo.photo.service; -import kr.mafoo.photo.domain.BrandType; +import static kr.mafoo.photo.domain.enums.PermissionLevel.FULL_ACCESS; +import static kr.mafoo.photo.domain.enums.PermissionLevel.VIEW_ACCESS; + +import kr.mafoo.photo.domain.enums.BrandType; import kr.mafoo.photo.domain.PhotoEntity; import kr.mafoo.photo.exception.PhotoDisplayIndexIsSameException; import kr.mafoo.photo.exception.PhotoDisplayIndexNotValidException; -import kr.mafoo.photo.exception.PhotoNotFoundException; import kr.mafoo.photo.repository.PhotoRepository; import kr.mafoo.photo.util.IdGenerator; import lombok.RequiredArgsConstructor; @@ -21,51 +23,67 @@ import java.util.concurrent.atomic.AtomicInteger; - @Slf4j @RequiredArgsConstructor @Service public class PhotoService { - private final PhotoRepository photoRepository; - private final AlbumService albumService; + private final PhotoQuery photoQuery; + private final PhotoCommand photoCommand; + + private final PhotoPermissionVerifier photoPermissionVerifier; + + private final AlbumQuery albumQuery; + private final AlbumCommand albumCommand; + + private final AlbumPermissionVerifier albumPermissionVerifier; + private final QrService qrService; private final ObjectStorageService objectStorageService; - @Transactional - public Mono createNewPhotoByQrUrl(String qrUrl, String requestMemberId) { - return qrService - .getFileFromQrUrl(qrUrl) - .flatMap(fileDto -> objectStorageService.uploadFile(fileDto.fileByte()) - .flatMap(photoUrl -> createNewPhoto(photoUrl, fileDto.type(), requestMemberId)) - ); - } + // FIXME : 추후 제거 필요 + private final PhotoRepository photoRepository; - @Transactional - public Flux createNewPhotoFileUrls(String[] fileUrls, String albumId, String requestMemberId) { - return albumService.findByAlbumId(albumId, requestMemberId) - .flatMapMany(albumEntity -> { - AtomicInteger displayIndex = new AtomicInteger(albumEntity.getPhotoCount()); - - return Flux.fromArray(fileUrls) - .concatMap(fileUrl -> - createNewPhotoFileUrl(fileUrl, BrandType.EXTERNAL, albumId, displayIndex.getAndIncrement(), requestMemberId) - ); - }); + @Transactional(readOnly = true) + public Flux findPhotoListByAlbumId(String albumId, String requestMemberId, String sort) { + String sortMethod = (sort == null) ? "CUSTOM" : sort.toUpperCase(); + + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, VIEW_ACCESS) + .thenMany( + switch (sortMethod) { + case "ASC" -> photoQuery.findAllByAlbumIdOrderByCreatedAtAsc(albumId); + case "DESC" -> photoQuery.findAllByAlbumIdOrderByCreatedAtDesc(albumId); + case "CUSTOM" -> photoQuery.findAllByAlbumIdOrderByDisplayIndexDesc(albumId); + default -> photoQuery.findAllByAlbumIdOrderByDisplayIndexDesc(albumId); + } + ); } - private Mono createNewPhotoFileUrl(String fileUrl, BrandType type, String albumId, Integer displayIndex, String requestMemberId) { - return objectStorageService.setObjectPublicRead(fileUrl) - .flatMap(fileLink -> { - PhotoEntity photoEntity = PhotoEntity.newPhoto(IdGenerator.generate(), fileLink, type, albumId, displayIndex, requestMemberId); - return albumService.increaseAlbumPhotoCount(albumId, 1, requestMemberId) - .then(photoRepository.save(photoEntity)); - }); + @Transactional + public Mono addPhotoWithQrUrl(String qrUrl) { + return qrService + .getFileFromQrUrl(qrUrl) + .flatMap(fileDto -> objectStorageService.uploadFile(fileDto.fileByte()) + .flatMap(photoUrl -> photoCommand.addPhotoWithoutOwnerAndAlbum(photoUrl, fileDto.type())) + ); } - private Mono createNewPhoto(String photoUrl, BrandType type, String requestMemberId) { - PhotoEntity photoEntity = PhotoEntity.newPhoto(IdGenerator.generate(), photoUrl, type, null, 0, requestMemberId); - return photoRepository.save(photoEntity); + @Transactional + public Flux addPhotoBulkWithFileUrls(String[] fileUrls, String albumId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, FULL_ACCESS) + .flatMapMany(album -> { + AtomicInteger displayIndex = new AtomicInteger(album.getPhotoCount()); + + return Flux.fromArray(fileUrls) + .concatMap(fileUrl -> objectStorageService.setObjectPublicRead(fileUrl) + .flatMap(fileLink -> photoCommand.addPhoto(fileLink, BrandType.EXTERNAL, albumId, displayIndex.getAndIncrement(), album.getOwnerMemberId())) + ) + .collectList() + .flatMapMany(addedPhotos -> + albumCommand.increaseAlbumPhotoCount(album, addedPhotos.size()) + .thenMany(Flux.fromIterable(addedPhotos)) + ); + }); } @Transactional @@ -94,75 +112,46 @@ public Flux uploadPhoto(Flux files, String requestMemberI ).sequential(); } - public Flux findAllByAlbumId(String albumId, String requestMemberId, String sort) { - String sortMethod = (sort == null) ? "CUSTOM" : sort.toUpperCase(); - - return albumService.findByAlbumId(albumId, requestMemberId) - .thenMany( - switch (sortMethod) { - case "ASC" -> photoRepository.findAllByAlbumIdOrderByCreatedAtAsc(albumId); - case "DESC" -> photoRepository.findAllByAlbumIdOrderByCreatedAtDesc(albumId); - default -> photoRepository.findAllByAlbumIdOrderByDisplayIndexDesc(albumId); - } - ); - } - - public Mono findByPhotoId(String photoId, String requestMemberId) { - return photoRepository - .findById(photoId) - .switchIfEmpty(Mono.error(new PhotoNotFoundException())) - .flatMap(photoEntity -> { - if (!photoEntity.hasOwnerMemberId()) { - return photoRepository.save(photoEntity.updateOwnerMemberId(requestMemberId)); - } - else if (!photoEntity.getOwnerMemberId().equals(requestMemberId)) { - // 내 사진이 아니면 그냥 없는 사진 처리 - return Mono.error(new PhotoNotFoundException()); - } else { - return Mono.just(photoEntity); - } - }); - } - @Transactional - public Mono deletePhotoById(String photoId, String requestMemberId) { - return findByPhotoId(photoId, requestMemberId) - .flatMap(photoEntity -> - albumService.decreaseAlbumPhotoCount(photoEntity.getAlbumId(), 1, requestMemberId) - .then(photoRepository.popDisplayIndexGreaterThan(photoEntity.getAlbumId(), photoEntity.getDisplayIndex())) - .then(photoRepository.deleteById(photoId)) - ); + public Mono initPhotoAlbumId(String photoId, String albumId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, FULL_ACCESS) + .flatMap(album -> albumCommand.increaseAlbumPhotoCount(album, 1)) + .flatMap(album -> photoQuery.findByPhotoId(photoId) + .flatMap(photo -> photoCommand.modifyPhotoAlbumId(photo, albumId, album.getPhotoCount()-1, album.getOwnerMemberId())) + ); } @Transactional - public Flux updatePhotoBulkAlbumId(String[] photoIds, String albumId, String requestMemberId) { - return Flux.fromArray(photoIds) - .concatMap(photoId -> this.updatePhotoAlbumId(photoId, albumId, requestMemberId)); - } - - @Transactional - public Mono updatePhotoAlbumId(String photoId, String albumId, String requestMemberId) { - return findByPhotoId(photoId, requestMemberId) - .flatMap(photoEntity -> - albumService.findByAlbumId(albumId, requestMemberId) - .flatMap(albumEntity -> - albumService.decreaseAlbumPhotoCount(photoEntity.getAlbumId(), 1, requestMemberId) - .then(photoRepository.popDisplayIndexGreaterThan(photoEntity.getAlbumId(), photoEntity.getDisplayIndex())) - .then(albumService.increaseAlbumPhotoCount(albumId, 1, requestMemberId)) - .then(photoRepository.save( - photoEntity - .updateAlbumId(albumId) - .updateDisplayIndex(albumEntity.getPhotoCount()) - )) + public Flux modifyPhotoBulkAlbumId(String[] photoIds, String albumId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, FULL_ACCESS) + .flatMapMany(newAlbum -> { + AtomicInteger displayIndex = new AtomicInteger(newAlbum.getPhotoCount()); + + return Flux.fromArray(photoIds) + .concatMap(photoId -> photoQuery.findByPhotoId(photoId) + .flatMap(photo -> albumPermissionVerifier.verifyOwnershipOrAccessPermission(photo.getAlbumId(), requestMemberId, FULL_ACCESS) + .flatMap(oldAlbum -> albumCommand.decreaseAlbumPhotoCount(oldAlbum, 1)) + .then(photoCommand.popDisplayIndexGreaterThan(photo.getAlbumId(), photo.getDisplayIndex()) + .thenReturn(photo) ) - ); + ) + .flatMap(photo -> photoCommand.modifyPhotoAlbumId(photo, albumId, displayIndex.getAndIncrement(), newAlbum.getOwnerMemberId()) + ) + ) + .collectList() + .flatMapMany(addedPhotos -> + albumCommand.increaseAlbumPhotoCount(newAlbum, addedPhotos.size()) + .thenMany(Flux.fromIterable(addedPhotos)) + ); + }); } + // FIXME : 추후 수정 필요 @Transactional - public Mono updatePhotoDisplayIndex(String photoId, Integer newIndex, String requestMemberId) { - return findByPhotoId(photoId, requestMemberId) + public Mono modifyPhotoDisplayIndex(String photoId, Integer newIndex, String requestMemberId) { + return photoPermissionVerifier.verifyAccessPermission(photoId, requestMemberId, FULL_ACCESS) .flatMap(photoEntity -> - albumService.findByAlbumId(photoEntity.getAlbumId(), requestMemberId) + albumPermissionVerifier.verifyOwnershipOrAccessPermission(photoEntity.getAlbumId(), requestMemberId, FULL_ACCESS) .flatMap(albumEntity -> { int targetIndex = albumEntity.getPhotoCount() - newIndex - 1; @@ -187,4 +176,14 @@ public Mono updatePhotoDisplayIndex(String photoId, Integer newInde ); } + @Transactional + public Mono removePhoto(String photoId, String requestMemberId) { + return photoPermissionVerifier.verifyAccessPermission(photoId, requestMemberId, FULL_ACCESS) + .flatMap(photo -> photoCommand.removePhoto(photo) + .then(albumQuery.findById(photo.getAlbumId()) + .flatMap(album -> albumCommand.decreaseAlbumPhotoCount(album, 1)) + ).then() + ); + } + } diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 20235d78..c79d9aec 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -1,6 +1,6 @@ package kr.mafoo.photo.service; -import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.domain.enums.BrandType; import kr.mafoo.photo.exception.PhotoBrandNotExistsException; import kr.mafoo.photo.service.dto.FileDto; import kr.mafoo.photo.service.vendors.*; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/RecapService.java b/photo-service/src/main/java/kr/mafoo/photo/service/RecapService.java index dd2bb1ec..2ba5142d 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/RecapService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/RecapService.java @@ -1,7 +1,9 @@ package kr.mafoo.photo.service; +import static kr.mafoo.photo.domain.enums.PermissionLevel.DOWNLOAD_ACCESS; + import kr.mafoo.photo.domain.PhotoEntity; -import kr.mafoo.photo.service.properties.RecapProperties; +import kr.mafoo.photo.util.RecapProperties; import kr.mafoo.photo.util.IdGenerator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberCommand.java b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberCommand.java new file mode 100644 index 00000000..13bfe550 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberCommand.java @@ -0,0 +1,44 @@ +package kr.mafoo.photo.service; + +import static kr.mafoo.photo.domain.enums.ShareStatus.PENDING; + +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.SharedMemberEntity; +import kr.mafoo.photo.domain.enums.ShareStatus; +import kr.mafoo.photo.repository.SharedMemberRepository; +import kr.mafoo.photo.util.IdGenerator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Service +public class SharedMemberCommand { + + private final SharedMemberRepository sharedMemberRepository; + + public Mono addSharedMember(String albumId, String permissionLevel, String sharingMemberId) { + return sharedMemberRepository.save( + SharedMemberEntity.newSharedMember( + IdGenerator.generate(), PENDING, PermissionLevel.valueOf(permissionLevel), sharingMemberId, albumId + ) + ); + } + + public Mono removeSharedMember(SharedMemberEntity sharedMember) { + return sharedMemberRepository.delete(sharedMember); + } + + public Mono modifySharedMemberShareStatus(SharedMemberEntity sharedMember, String newShareStatus) { + return sharedMemberRepository.save(sharedMember.updateShareStatus( + ShareStatus.valueOf(newShareStatus) + )); + } + + public Mono modifySharedMemberPermissionLevel(SharedMemberEntity sharedMember, String newPermissionLevel) { + return sharedMemberRepository.save(sharedMember.updatePermissionLevel( + PermissionLevel.valueOf(newPermissionLevel) + )); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberQuery.java b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberQuery.java new file mode 100644 index 00000000..4e9e6106 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberQuery.java @@ -0,0 +1,39 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.SharedMemberEntity; +import kr.mafoo.photo.exception.SharedMemberDuplicatedException; +import kr.mafoo.photo.exception.SharedMemberNotFoundException; +import kr.mafoo.photo.repository.SharedMemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Service +public class SharedMemberQuery { + + private final SharedMemberRepository sharedMemberRepository; + + public Flux findAllByAlbumId(String albumId) { + return sharedMemberRepository.findAllByAlbumId(albumId) + .switchIfEmpty(Mono.error(new SharedMemberNotFoundException())); + } + + public Mono findBySharedMemberId(String sharedMemberId) { + return sharedMemberRepository.findById(sharedMemberId) + .switchIfEmpty(Mono.error(new SharedMemberNotFoundException())); + } + + public Mono findByAlbumIdAndMemberId(String albumId, String memberId) { + return sharedMemberRepository.findByAlbumIdAndMemberId(albumId, memberId) + .switchIfEmpty(Mono.error(new SharedMemberNotFoundException())); + } + + public Mono checkDuplicateByAlbumIdAndMemberId(String albumId, String memberId) { + return sharedMemberRepository.findByAlbumIdAndMemberId(albumId, memberId) + .switchIfEmpty(Mono.empty()) + .flatMap(existingMember -> Mono.error(new SharedMemberDuplicatedException())); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberService.java b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberService.java new file mode 100644 index 00000000..e5042970 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/SharedMemberService.java @@ -0,0 +1,72 @@ +package kr.mafoo.photo.service; + +import static kr.mafoo.photo.domain.enums.PermissionLevel.FULL_ACCESS; +import static kr.mafoo.photo.domain.enums.ShareStatus.PENDING; + +import kr.mafoo.photo.domain.SharedMemberEntity; +import kr.mafoo.photo.domain.enums.ShareStatus; +import kr.mafoo.photo.exception.SharedMemberPermissionDeniedException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Service +public class SharedMemberService { + + private final SharedMemberQuery sharedMemberQuery; + private final SharedMemberCommand sharedMemberCommand; + + private final AlbumPermissionVerifier albumPermissionVerifier; + + @Transactional + public Mono addSharedMember(String albumId, String permissionLevel, String sharingMemberId, String requestMemberId) { + return albumPermissionVerifier.verifyOwnershipOrAccessPermission(albumId, requestMemberId, FULL_ACCESS) + .then(sharedMemberQuery.checkDuplicateByAlbumIdAndMemberId(albumId, sharingMemberId) + .then(sharedMemberCommand.addSharedMember(albumId, permissionLevel, sharingMemberId)) + ); + } + + @Transactional + public Mono removeSharedMember(String sharedMemberId, String requestMemberId) { + return sharedMemberQuery.findBySharedMemberId(sharedMemberId) + .flatMap(sharedMember -> { + if (isSharedMemberSelfRequest(sharedMember, requestMemberId) && !isPendingStatus(sharedMember.getShareStatus())) { + return sharedMemberCommand.removeSharedMember(sharedMember); + } else { + return albumPermissionVerifier.verifyOwnership(sharedMember.getAlbumId(), requestMemberId) + .then(sharedMemberCommand.removeSharedMember(sharedMember)); + } + }); + } + + @Transactional + public Mono modifySharedMemberShareStatus(String sharedMemberId, String newShareStatus, String requestMemberId) { + return sharedMemberQuery.findBySharedMemberId(sharedMemberId) + .flatMap(sharedMember -> { + if (isSharedMemberSelfRequest(sharedMember, requestMemberId) && isPendingStatus(sharedMember.getShareStatus())) { + return sharedMemberCommand.modifySharedMemberShareStatus(sharedMember, newShareStatus); + } else { + return Mono.error(new SharedMemberPermissionDeniedException()); + } + }); + } + + @Transactional + public Mono modifySharedMemberPermissionLevel(String sharedMemberId, String newPermissionLevel, String requestMemberId) { + return sharedMemberQuery.findBySharedMemberId(sharedMemberId) + .flatMap(sharedMember -> albumPermissionVerifier.verifyOwnership(sharedMember.getAlbumId(), requestMemberId) + .then(sharedMemberCommand.modifySharedMemberPermissionLevel(sharedMember, newPermissionLevel)) + ); + } + + private boolean isSharedMemberSelfRequest(SharedMemberEntity sharedMember, String requestMemberId) { + return sharedMember.getMemberId().equals(requestMemberId); + } + + private boolean isPendingStatus(ShareStatus status) { + return status.equals(PENDING); + } + +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java b/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java index 5dff696c..d33b2501 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java @@ -1,6 +1,6 @@ package kr.mafoo.photo.service.dto; -import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.domain.enums.BrandType; public record FileDto ( BrandType type, diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/dto/MemberDto.java b/photo-service/src/main/java/kr/mafoo/photo/service/dto/MemberDto.java index 116f5ddb..f7315eff 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/dto/MemberDto.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/dto/MemberDto.java @@ -3,6 +3,7 @@ public record MemberDto( String memberId, String name, - String profileImageUrl + String profileImageUrl, + String serialNumber ) { } diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/dto/SharedMemberDetailDto.java b/photo-service/src/main/java/kr/mafoo/photo/service/dto/SharedMemberDetailDto.java new file mode 100644 index 00000000..72f3882a --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/dto/SharedMemberDetailDto.java @@ -0,0 +1,30 @@ +package kr.mafoo.photo.service.dto; + +import kr.mafoo.photo.domain.SharedMemberEntity; +import kr.mafoo.photo.domain.enums.PermissionLevel; +import kr.mafoo.photo.domain.enums.ShareStatus; + +public record SharedMemberDetailDto( + String sharedMemberId, + ShareStatus shareStatus, + PermissionLevel permissionLevel, + String albumId, + String memberId, + String profileImageUrl, + String memberName +) { + public static SharedMemberDetailDto from( + SharedMemberEntity sharedMemberEntity, + MemberDto memberDto + ) { + return new SharedMemberDetailDto( + sharedMemberEntity.getSharedMemberId(), + sharedMemberEntity.getShareStatus(), + sharedMemberEntity.getPermissionLevel(), + sharedMemberEntity.getAlbumId(), + memberDto.memberId(), + memberDto.profileImageUrl(), + memberDto.name() + ); + } +} diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/properties/RecapProperties.java b/photo-service/src/main/java/kr/mafoo/photo/util/RecapProperties.java similarity index 97% rename from photo-service/src/main/java/kr/mafoo/photo/service/properties/RecapProperties.java rename to photo-service/src/main/java/kr/mafoo/photo/util/RecapProperties.java index 1dbc1d66..f0fa138c 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/properties/RecapProperties.java +++ b/photo-service/src/main/java/kr/mafoo/photo/util/RecapProperties.java @@ -1,4 +1,4 @@ -package kr.mafoo.photo.service.properties; +package kr.mafoo.photo.util; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/photo-service/src/main/resources/db/migration/V9__createSharedMemberTable.sql b/photo-service/src/main/resources/db/migration/V9__createSharedMemberTable.sql new file mode 100644 index 00000000..6851515e --- /dev/null +++ b/photo-service/src/main/resources/db/migration/V9__createSharedMemberTable.sql @@ -0,0 +1,11 @@ +CREATE TABLE shared_member( + `id` CHAR(26) PRIMARY KEY NOT NULL COMMENT '공유사용자아이디', + `share_status` VARCHAR(255) NOT NULL COMMENT '공유상태', + `permission_level` VARCHAR(255) NOT NULL COMMENT '공유단계', + `member_id` CHAR(26) NOT NULL COMMENT '대상사용자아이디', + `album_id` CHAR(26) NOT NULL COMMENT '대상앨범아이디', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX `shared_album_member_idx1` (`member_id`), + INDEX `shared_album_member_idx2` (`album_id`) +); diff --git a/user-service/src/main/java/kr/mafoo/user/api/MemberApi.java b/user-service/src/main/java/kr/mafoo/user/api/MemberApi.java new file mode 100644 index 00000000..2c0538ab --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/api/MemberApi.java @@ -0,0 +1,43 @@ +package kr.mafoo.user.api; + +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.user.annotation.RequestMemberId; +import kr.mafoo.user.controller.dto.response.MemberResponse; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Tag(name = "사용자 관련 API", description = "사용자 정보 조회 API") +@Validated +@RequestMapping("/v1/members") +public interface MemberApi { + @Operation(summary = "사용자 검색", description = "키워드로 사용자를 검색합니다. (이름으로 검색)") + @GetMapping + Flux getMemberListByName( + @RequestMemberId + @Parameter(hidden = true) + String requesterId, + + @Parameter(description = "검색어", example = "사람") + @RequestParam + String keyword + ); + + @Operation(summary = "사용자 단건 조회", description = "사용자 단건 정보를 조회합니다.") + @GetMapping("{memberId}") + Mono getMember( + @RequestMemberId + @Parameter(hidden = true) + String requesterId, + + @Parameter(description = "사용자 ID", example = "test_member_id") + @PathVariable + String memberId + ); +} diff --git a/user-service/src/main/java/kr/mafoo/user/controller/MemberController.java b/user-service/src/main/java/kr/mafoo/user/controller/MemberController.java new file mode 100644 index 00000000..975c8fdb --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/controller/MemberController.java @@ -0,0 +1,33 @@ +package kr.mafoo.user.controller; + +import kr.mafoo.user.api.MemberApi; +import kr.mafoo.user.controller.dto.response.MemberResponse; +import kr.mafoo.user.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@RestController +public class MemberController implements MemberApi { + private final MemberService memberService; + + @Override + public Flux getMemberListByName( + String requesterId, + String keyword + ) { + return memberService.getMemberByKeyword(keyword) + .map(MemberResponse::fromEntity); + } + + @Override + public Mono getMember( + String requesterId, + String memberId + ) { + return memberService.getMemberByMemberId(memberId) + .map(MemberResponse::fromEntity); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java index 3bab5448..298cc88a 100644 --- a/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java +++ b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java @@ -12,13 +12,17 @@ public record MemberResponse( String name, @Schema(description = "프로필 이미지 URL", example = "https://mafoo.kr/profile.jpg") - String profileImageUrl + String profileImageUrl, + + @Schema(description = "식별 번호", example = "0000") + String serialNumber ) { public static MemberResponse fromEntity(MemberEntity memberEntity) { return new MemberResponse( memberEntity.getId(), memberEntity.getName(), - memberEntity.getProfileImageUrl() + memberEntity.getProfileImageUrl(), + String.format("%04d", memberEntity.getSerialNumber()) ); } } diff --git a/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java b/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java index fc1ee2bb..2b826359 100644 --- a/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java +++ b/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java @@ -23,6 +23,9 @@ public class MemberEntity implements Persistable { @Column("name") private String name; + @Column("serial_number") + private Integer serialNumber; + @Column("created_at") private LocalDateTime createdAt; diff --git a/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java b/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java index e8da0184..01962764 100644 --- a/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java +++ b/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java @@ -2,8 +2,10 @@ import kr.mafoo.user.domain.MemberEntity; import org.springframework.data.r2dbc.repository.R2dbcRepository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface MemberRepository extends R2dbcRepository { + Flux findAllByNameContaining(String memberId); Mono deleteMemberById(String memberId); } diff --git a/user-service/src/main/java/kr/mafoo/user/service/MemberService.java b/user-service/src/main/java/kr/mafoo/user/service/MemberService.java index bbf1ea3b..1b30a5e1 100644 --- a/user-service/src/main/java/kr/mafoo/user/service/MemberService.java +++ b/user-service/src/main/java/kr/mafoo/user/service/MemberService.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Slf4j @@ -26,6 +27,12 @@ public Mono quitMemberByMemberId(String memberId) { .then(memberRepository.deleteMemberById(memberId)); } + public Flux getMemberByKeyword(String keyword) { + return memberRepository + .findAllByNameContaining(keyword) + .switchIfEmpty(Mono.error(new MemberNotFoundException())); + } + public Mono getMemberByMemberId(String memberId) { return memberRepository .findById(memberId) diff --git a/user-service/src/main/resources/db/migration/V3__add_serial_number_field.sql b/user-service/src/main/resources/db/migration/V3__add_serial_number_field.sql new file mode 100644 index 00000000..5a466467 --- /dev/null +++ b/user-service/src/main/resources/db/migration/V3__add_serial_number_field.sql @@ -0,0 +1,15 @@ +ALTER TABLE member + ADD serial_number INT UNSIGNED NULL AFTER member_id; + + +SET @row_number = 0; + +UPDATE member +SET serial_number = (@row_number := @row_number + 1) + ORDER BY created_at; + + +SELECT MAX(serial_number) + 1 INTO @next_serial FROM member; + +ALTER TABLE member + MODIFY serial_number INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE;