From 3a2d8c2d73655cf226e6a6412ff5f9188ea7e86b Mon Sep 17 00:00:00 2001 From: yongseok Date: Tue, 14 Nov 2023 22:52:53 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20album=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumController.java | 14 ++++++++++ .../controller/dto/CreateAlbumRequest.java | 10 +++++++ .../controller/dto/CreateAlbumResponse.java | 3 +++ .../onebyte/life4cut/album/domain/Album.java | 6 +++++ .../life4cut/album/domain/UserAlbum.java | 21 +++++++++++++++ .../album/repository/AlbumRepository.java | 3 ++- .../album/repository/AlbumRepositoryImpl.java | 10 +++++++ .../album/repository/UserAlbumRepository.java | 2 ++ .../repository/UserAlbumRepositoryImpl.java | 9 +++++++ .../life4cut/album/service/AlbumService.java | 27 +++++++++++++++++++ 10 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java create mode 100644 src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumResponse.java diff --git a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java index 057b516..daa1fec 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java @@ -1,5 +1,7 @@ package com.onebyte.life4cut.album.controller; +import com.onebyte.life4cut.album.controller.dto.CreateAlbumRequest; +import com.onebyte.life4cut.album.controller.dto.CreateAlbumResponse; import com.onebyte.life4cut.album.controller.dto.CreatePictureRequest; import com.onebyte.life4cut.album.controller.dto.CreatePictureResponse; import com.onebyte.life4cut.album.controller.dto.GetMyRoleInAlbumResponse; @@ -39,6 +41,18 @@ public class AlbumController { private final PictureTagService pictureTagService; private final AlbumService albumService; + @PostMapping("") + public ApiResponse createAlbum( + @Valid CreateAlbumRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) { + Long albumId = + albumService.createAlbum( + request.name(), + userDetails.getUserId(), + request.memberUserIds(), + request.guestUserIds()); + return ApiResponse.OK(new CreateAlbumResponse(albumId)); + } + @PostMapping("/{albumId}/pictures") public ApiResponse uploadPicture( @Min(1) @PathVariable("albumId") Long albumId, diff --git a/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java new file mode 100644 index 0000000..bef69e4 --- /dev/null +++ b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java @@ -0,0 +1,10 @@ +package com.onebyte.life4cut.album.controller.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import java.util.List; + +public record CreateAlbumRequest( + @NotBlank(message = "앨범의 이름을 입력해주세요") String name, + List<@Min(value = 1, message = "올바른 memberUserIds를 입력해주세요") Long> memberUserIds, + List<@Min(value = 1, message = "올바른 guestUserIds를 입력해주세요") Long> guestUserIds) {} diff --git a/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumResponse.java b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumResponse.java new file mode 100644 index 0000000..f01268d --- /dev/null +++ b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumResponse.java @@ -0,0 +1,3 @@ +package com.onebyte.life4cut.album.controller.dto; + +public record CreateAlbumResponse(Long id) {} diff --git a/src/main/java/com/onebyte/life4cut/album/domain/Album.java b/src/main/java/com/onebyte/life4cut/album/domain/Album.java index fea3b28..cbe3e57 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/Album.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/Album.java @@ -15,4 +15,10 @@ public class Album extends BaseEntity { private String name; @Nullable @Column private LocalDateTime deletedAt; + + public static Album create(@Nonnull String name) { + Album album = new Album(); + album.name = name; + return album; + } } diff --git a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java index 70f3775..6da5798 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java @@ -36,4 +36,25 @@ public class UserAlbum extends BaseEntity { public boolean isGuest() { return role == UserAlbumRole.GUEST; } + + public static UserAlbum createHost(@Nonnull Long albumId, @Nonnull Long userId) { + return createUserAlbum(albumId, userId, UserAlbumRole.HOST); + } + + public static UserAlbum createMember(@Nonnull Long albumId, @Nonnull Long userId) { + return createUserAlbum(albumId, userId, UserAlbumRole.MEMBER); + } + + public static UserAlbum createGuest(@Nonnull Long albumId, @Nonnull Long userId) { + return createUserAlbum(albumId, userId, UserAlbumRole.GUEST); + } + + private static UserAlbum createUserAlbum( + @Nonnull Long albumId, @Nonnull Long userId, @Nonnull UserAlbumRole role) { + UserAlbum userAlbum = new UserAlbum(); + userAlbum.albumId = albumId; + userAlbum.userId = userId; + userAlbum.role = role; + return userAlbum; + } } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java index 9f0fb73..3c81509 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java @@ -4,6 +4,7 @@ import java.util.Optional; public interface AlbumRepository { - Optional findById(Long id); + + Album save(Album album); } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java index 0e74d42b..6f7d227 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java @@ -4,6 +4,9 @@ import com.onebyte.life4cut.album.domain.Album; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; import java.util.Optional; import org.springframework.stereotype.Repository; @@ -11,6 +14,7 @@ public class AlbumRepositoryImpl implements AlbumRepository { private final JPAQueryFactory jpaQueryFactory; + @PersistenceContext private EntityManager entityManager; public AlbumRepositoryImpl(JPAQueryFactory jpaQueryFactory) { this.jpaQueryFactory = jpaQueryFactory; @@ -23,4 +27,10 @@ public Optional findById(Long id) { .where(album.id.eq(id), album.deletedAt.isNull()) .fetchOne()); } + + @Transactional + public Album save(Album album) { + entityManager.persist(album); + return album; + } } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java index 1cc800b..d2d285d 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java @@ -5,4 +5,6 @@ public interface UserAlbumRepository { Optional findByUserIdAndAlbumId(Long userId, Long albumId); + + UserAlbum save(UserAlbum userAlbum); } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java index 3efabe8..1dbffa6 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java @@ -4,6 +4,8 @@ import com.onebyte.life4cut.album.domain.UserAlbum; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import java.util.Optional; import org.springframework.stereotype.Repository; @@ -11,6 +13,7 @@ public class UserAlbumRepositoryImpl implements UserAlbumRepository { private final JPAQueryFactory jpaQueryFactory; + @PersistenceContext private EntityManager entityManager; public UserAlbumRepositoryImpl(JPAQueryFactory jpaQueryFactory) { this.jpaQueryFactory = jpaQueryFactory; @@ -26,4 +29,10 @@ public Optional findByUserIdAndAlbumId(Long userId, Long albumId) { userAlbum.deletedAt.isNull()) .fetchOne()); } + + @Override + public UserAlbum save(UserAlbum userAlbum) { + entityManager.persist(userAlbum); + return userAlbum; + } } diff --git a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java index 5e045df..a872058 100644 --- a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java +++ b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java @@ -1,11 +1,14 @@ package com.onebyte.life4cut.album.service; +import com.onebyte.life4cut.album.domain.Album; import com.onebyte.life4cut.album.domain.UserAlbum; import com.onebyte.life4cut.album.domain.vo.UserAlbumRole; import com.onebyte.life4cut.album.exception.AlbumNotFoundException; import com.onebyte.life4cut.album.exception.UserAlbumRolePermissionException; import com.onebyte.life4cut.album.repository.AlbumRepository; import com.onebyte.life4cut.album.repository.UserAlbumRepository; +import jakarta.annotation.Nonnull; +import java.util.List; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -28,4 +31,28 @@ public UserAlbumRole getRoleInAlbum(@NonNull Long albumId, @NonNull Long userId) return userAlbum.getRole(); } + + public Long createAlbum( + @Nonnull String name, Long userId, List memberUserIds, List guestUserIds) { + + Album album = Album.create(name); + albumRepository.save(album); + Long albumId = album.getId(); + UserAlbum hostUserAlbum = UserAlbum.createHost(albumId, userId); + userAlbumRepository.save(hostUserAlbum); + if (memberUserIds != null) { + for (Long memberId : memberUserIds) { + UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId); + userAlbumRepository.save(memberUserAlbum); + } + } + if (guestUserIds != null) { + for (Long guestId : guestUserIds) { + UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId); + userAlbumRepository.save(guestUserAlbum); + } + } + + return albumId; + } } From d9f56858ccfc713ccc1723942a431724056f4c35 Mon Sep 17 00:00:00 2001 From: yongseok Date: Wed, 15 Nov 2023 07:59:57 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20album=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumController.java | 9 +++++++++ .../onebyte/life4cut/album/domain/Album.java | 3 +++ .../life4cut/album/domain/UserAlbum.java | 3 +++ .../album/repository/AlbumRepository.java | 2 ++ .../album/repository/AlbumRepositoryImpl.java | 10 ++++++++++ .../album/repository/UserAlbumRepository.java | 4 ++++ .../repository/UserAlbumRepositoryImpl.java | 19 +++++++++++++++++++ .../life4cut/album/service/AlbumService.java | 16 ++++++++++++++++ 8 files changed, 66 insertions(+) diff --git a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java index daa1fec..ec2bf4d 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java @@ -23,6 +23,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -53,6 +54,14 @@ public ApiResponse createAlbum( return ApiResponse.OK(new CreateAlbumResponse(albumId)); } + @DeleteMapping("/{albumId}") + public ApiResponse deleteAlbum( + @Min(1) @PathVariable("albumId") Long albumId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + albumService.deleteAlbum(albumId, userDetails.getUserId()); + return ApiResponse.OK(); + } + @PostMapping("/{albumId}/pictures") public ApiResponse uploadPicture( @Min(1) @PathVariable("albumId") Long albumId, diff --git a/src/main/java/com/onebyte/life4cut/album/domain/Album.java b/src/main/java/com/onebyte/life4cut/album/domain/Album.java index cbe3e57..2e2ea79 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/Album.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/Album.java @@ -21,4 +21,7 @@ public static Album create(@Nonnull String name) { album.name = name; return album; } + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java index 6da5798..d911583 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java @@ -57,4 +57,7 @@ private static UserAlbum createUserAlbum( userAlbum.role = role; return userAlbum; } + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java index 3c81509..4db932f 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepository.java @@ -7,4 +7,6 @@ public interface AlbumRepository { Optional findById(Long id); Album save(Album album); + + void deleteById(Long id); } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java index 6f7d227..6e0f0f4 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/AlbumRepositoryImpl.java @@ -33,4 +33,14 @@ public Album save(Album album) { entityManager.persist(album); return album; } + + @Override + public void deleteById(Long id) { + Album album = entityManager.find(Album.class, id); + + if (album != null) { + album.softDelete(); + entityManager.merge(album); + } + } } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java index d2d285d..bb91808 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepository.java @@ -7,4 +7,8 @@ public interface UserAlbumRepository { Optional findByUserIdAndAlbumId(Long userId, Long albumId); UserAlbum save(UserAlbum userAlbum); + + void delete(UserAlbum userAlbum); + + void deleteByAlbumId(Long albumId); } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java index 1dbffa6..2eb7b95 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java @@ -6,6 +6,9 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; + +import java.time.LocalDateTime; import java.util.Optional; import org.springframework.stereotype.Repository; @@ -35,4 +38,20 @@ public UserAlbum save(UserAlbum userAlbum) { entityManager.persist(userAlbum); return userAlbum; } + + @Transactional + @Override + public void delete(UserAlbum userAlbum) { + userAlbum.softDelete(); + entityManager.merge(userAlbum); } + + @Transactional + @Override + public void deleteByAlbumId(Long albumId) { + jpaQueryFactory + .update(userAlbum) + .set(userAlbum.deletedAt, LocalDateTime.now()) + .where(userAlbum.albumId.eq(albumId)) + .execute(); + } } diff --git a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java index a872058..f4bd61d 100644 --- a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java +++ b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java @@ -55,4 +55,20 @@ public Long createAlbum( return albumId; } + + public void deleteAlbum(Long albumId, @NonNull Long userId) { + albumRepository.findById(albumId).orElseThrow(AlbumNotFoundException::new); + + UserAlbum userAlbum = + userAlbumRepository + .findByUserIdAndAlbumId(userId, albumId) + .orElseThrow(UserAlbumRolePermissionException::new); + + if (userAlbum.getRole() == UserAlbumRole.HOST) { + albumRepository.deleteById(albumId); + userAlbumRepository.deleteByAlbumId(albumId); + } else { + userAlbumRepository.delete(userAlbum); + } + } } From 8a43f61cb270fe6376b2d8f8935357f434a159f1 Mon Sep 17 00:00:00 2001 From: yongseok Date: Wed, 15 Nov 2023 13:25:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=EB=B3=84=20?= =?UTF-8?q?=EC=95=A8=EB=B2=94=20=EA=B6=8C=ED=95=9C=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../life4cut/album/service/AlbumService.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java index f4bd61d..8360b1b 100644 --- a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java +++ b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java @@ -8,7 +8,9 @@ import com.onebyte.life4cut.album.repository.AlbumRepository; import com.onebyte.life4cut.album.repository.UserAlbumRepository; import jakarta.annotation.Nonnull; +import java.util.HashSet; import java.util.List; +import java.util.Set; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -34,22 +36,30 @@ public UserAlbumRole getRoleInAlbum(@NonNull Long albumId, @NonNull Long userId) public Long createAlbum( @Nonnull String name, Long userId, List memberUserIds, List guestUserIds) { + Set userIds = new HashSet<>(); Album album = Album.create(name); albumRepository.save(album); Long albumId = album.getId(); UserAlbum hostUserAlbum = UserAlbum.createHost(albumId, userId); + userAlbumRepository.save(hostUserAlbum); + userIds.add(userId); + if (memberUserIds != null) { for (Long memberId : memberUserIds) { - UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId); - userAlbumRepository.save(memberUserAlbum); + if (userIds.add(memberId)) { + UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId); + userAlbumRepository.save(memberUserAlbum); + } } } if (guestUserIds != null) { for (Long guestId : guestUserIds) { - UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId); - userAlbumRepository.save(guestUserAlbum); + if (userIds.add(guestId)) { + UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId); + userAlbumRepository.save(guestUserAlbum); + } } } From 1de70c988a8b7bed08a1cdc5f8a8729b239a90d4 Mon Sep 17 00:00:00 2001 From: yongseok Date: Wed, 15 Nov 2023 21:52:25 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20album=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumController.java | 15 +++++++ .../controller/dto/UpdateAlbumRequest.java | 11 +++++ .../onebyte/life4cut/album/domain/Album.java | 3 ++ .../life4cut/album/domain/UserAlbum.java | 5 +++ .../repository/UserAlbumRepositoryImpl.java | 12 ++--- .../life4cut/album/service/AlbumService.java | 45 ++++++++++++++++++- 6 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java diff --git a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java index ec2bf4d..ac64285 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java @@ -8,6 +8,7 @@ import com.onebyte.life4cut.album.controller.dto.GetPicturesInSlotResponse; import com.onebyte.life4cut.album.controller.dto.SearchTagsRequest; import com.onebyte.life4cut.album.controller.dto.SearchTagsResponse; +import com.onebyte.life4cut.album.controller.dto.UpdateAlbumRequest; import com.onebyte.life4cut.album.controller.dto.UpdatePictureRequest; import com.onebyte.life4cut.album.domain.vo.UserAlbumRole; import com.onebyte.life4cut.album.service.AlbumService; @@ -62,6 +63,20 @@ public ApiResponse deleteAlbum( return ApiResponse.OK(); } + @PatchMapping("/{albumId}") + public ApiResponse updateAlbum( + @Min(1) @PathVariable("albumId") Long albumId, + @Valid UpdateAlbumRequest request, + @AuthenticationPrincipal CustomUserDetails userDetails) { + albumService.updateAlbum( + albumId, + request.name(), + userDetails.getUserId(), + request.memberUserIds(), + request.guestUserIds()); + return ApiResponse.OK(); + } + @PostMapping("/{albumId}/pictures") public ApiResponse uploadPicture( @Min(1) @PathVariable("albumId") Long albumId, diff --git a/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java b/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java new file mode 100644 index 0000000..65e26f6 --- /dev/null +++ b/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java @@ -0,0 +1,11 @@ +package com.onebyte.life4cut.album.controller.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +import java.util.List; + +public record UpdateAlbumRequest( + @NotBlank(message = "앨범의 이름을 입력해주세요") String name, + List<@Min(value = 1, message = "올바른 memberUserIds를 입력해주세요") Long> memberUserIds, + List<@Min(value = 1, message = "올바른 guestUserIds를 입력해주세요") Long> guestUserIds) {} diff --git a/src/main/java/com/onebyte/life4cut/album/domain/Album.java b/src/main/java/com/onebyte/life4cut/album/domain/Album.java index 2e2ea79..7ce7cd0 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/Album.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/Album.java @@ -6,10 +6,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import java.time.LocalDateTime; +import lombok.Setter; @Entity public class Album extends BaseEntity { + @Setter @Nonnull @Column(nullable = false) private String name; @@ -21,6 +23,7 @@ public static Album create(@Nonnull String name) { album.name = name; return album; } + public void softDelete() { this.deletedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java index d911583..3305c3e 100644 --- a/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java +++ b/src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java @@ -37,6 +37,10 @@ public boolean isGuest() { return role == UserAlbumRole.GUEST; } + public boolean isHost() { + return role == UserAlbumRole.HOST; + } + public static UserAlbum createHost(@Nonnull Long albumId, @Nonnull Long userId) { return createUserAlbum(albumId, userId, UserAlbumRole.HOST); } @@ -57,6 +61,7 @@ private static UserAlbum createUserAlbum( userAlbum.role = role; return userAlbum; } + public void softDelete() { this.deletedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java index 2eb7b95..a1c57c2 100644 --- a/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java +++ b/src/main/java/com/onebyte/life4cut/album/repository/UserAlbumRepositoryImpl.java @@ -7,7 +7,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; - import java.time.LocalDateTime; import java.util.Optional; import org.springframework.stereotype.Repository; @@ -43,15 +42,16 @@ public UserAlbum save(UserAlbum userAlbum) { @Override public void delete(UserAlbum userAlbum) { userAlbum.softDelete(); - entityManager.merge(userAlbum); } + entityManager.merge(userAlbum); + } @Transactional @Override public void deleteByAlbumId(Long albumId) { jpaQueryFactory - .update(userAlbum) - .set(userAlbum.deletedAt, LocalDateTime.now()) - .where(userAlbum.albumId.eq(albumId)) - .execute(); + .update(userAlbum) + .set(userAlbum.deletedAt, LocalDateTime.now()) + .where(userAlbum.albumId.eq(albumId)) + .execute(); } } diff --git a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java index 8360b1b..ef72d03 100644 --- a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java +++ b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java @@ -74,11 +74,54 @@ public void deleteAlbum(Long albumId, @NonNull Long userId) { .findByUserIdAndAlbumId(userId, albumId) .orElseThrow(UserAlbumRolePermissionException::new); - if (userAlbum.getRole() == UserAlbumRole.HOST) { + if (userAlbum.isHost()) { albumRepository.deleteById(albumId); userAlbumRepository.deleteByAlbumId(albumId); } else { userAlbumRepository.delete(userAlbum); } } + + public void updateAlbum( + Long albumId, String name, Long userId, List memberUserIds, List guestUserIds) { + Album album = albumRepository.findById(albumId).orElseThrow(() -> new AlbumNotFoundException()); + UserAlbum userAlbum = + userAlbumRepository + .findByUserIdAndAlbumId(userId, albumId) + .orElseThrow(UserAlbumRolePermissionException::new); + + if (!userAlbum.isHost()) { + throw new UserAlbumRolePermissionException(); + } + userAlbumRepository.deleteByAlbumId(albumId); + if (name != null) { + album.setName(name); + } + albumRepository.save(album); + + Set userIds = new HashSet<>(); + + UserAlbum hostUserAlbum = UserAlbum.createHost(albumId, userId); + + userAlbumRepository.save(hostUserAlbum); + userIds.add(userId); + + if (memberUserIds != null) { + for (Long memberId : memberUserIds) { + if (userIds.add(memberId)) { + UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId); + userAlbumRepository.save(memberUserAlbum); + } + } + } + if (guestUserIds != null) { + for (Long guestId : guestUserIds) { + if (userIds.add(guestId)) { + UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId); + userAlbumRepository.save(guestUserAlbum); + } + } + } + } + } From 16cf2965699ada46ba882831a7d78e440625a9e2 Mon Sep 17 00:00:00 2001 From: yongseok Date: Sat, 18 Nov 2023 19:28:40 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20album=20api=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../life4cut/album/controller/AlbumController.java | 8 +++++--- .../life4cut/album/controller/dto/CreateAlbumRequest.java | 4 ++-- .../life4cut/album/controller/dto/UpdateAlbumRequest.java | 1 - .../com/onebyte/life4cut/album/service/AlbumService.java | 6 ++++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java index ac64285..f4f1c46 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/AlbumController.java @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; @@ -45,7 +46,8 @@ public class AlbumController { @PostMapping("") public ApiResponse createAlbum( - @Valid CreateAlbumRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) { + @Valid @RequestBody CreateAlbumRequest request, + @AuthenticationPrincipal CustomUserDetails userDetails) { Long albumId = albumService.createAlbum( request.name(), @@ -56,7 +58,7 @@ public ApiResponse createAlbum( } @DeleteMapping("/{albumId}") - public ApiResponse deleteAlbum( + public ApiResponse deleteAlbum( @Min(1) @PathVariable("albumId") Long albumId, @AuthenticationPrincipal CustomUserDetails userDetails) { albumService.deleteAlbum(albumId, userDetails.getUserId()); @@ -66,7 +68,7 @@ public ApiResponse deleteAlbum( @PatchMapping("/{albumId}") public ApiResponse updateAlbum( @Min(1) @PathVariable("albumId") Long albumId, - @Valid UpdateAlbumRequest request, + @Valid @RequestBody UpdateAlbumRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) { albumService.updateAlbum( albumId, diff --git a/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java index bef69e4..85f6682 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/dto/CreateAlbumRequest.java @@ -1,10 +1,10 @@ package com.onebyte.life4cut.album.controller.dto; import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.util.List; public record CreateAlbumRequest( - @NotBlank(message = "앨범의 이름을 입력해주세요") String name, + @NotNull(message = "앨범의 이름을 입력해주세요") String name, List<@Min(value = 1, message = "올바른 memberUserIds를 입력해주세요") Long> memberUserIds, List<@Min(value = 1, message = "올바른 guestUserIds를 입력해주세요") Long> guestUserIds) {} diff --git a/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java b/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java index 65e26f6..f7c32be 100644 --- a/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java +++ b/src/main/java/com/onebyte/life4cut/album/controller/dto/UpdateAlbumRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; - import java.util.List; public record UpdateAlbumRequest( diff --git a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java index ef72d03..a47f6c9 100644 --- a/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java +++ b/src/main/java/com/onebyte/life4cut/album/service/AlbumService.java @@ -35,7 +35,10 @@ public UserAlbumRole getRoleInAlbum(@NonNull Long albumId, @NonNull Long userId) } public Long createAlbum( - @Nonnull String name, Long userId, List memberUserIds, List guestUserIds) { + @Nonnull String name, + @Nonnull Long userId, + List memberUserIds, + List guestUserIds) { Set userIds = new HashSet<>(); Album album = Album.create(name); @@ -123,5 +126,4 @@ public void updateAlbum( } } } - } From 6e0cb8df1da8551439c0407baf4ef3a85b73ad92 Mon Sep 17 00:00:00 2001 From: yongseok Date: Sat, 18 Nov 2023 19:29:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20album=20api=20test=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumControllerTest.java | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/src/test/java/com/onebyte/life4cut/album/controller/AlbumControllerTest.java b/src/test/java/com/onebyte/life4cut/album/controller/AlbumControllerTest.java index c8980f6..8f1e978 100644 --- a/src/test/java/com/onebyte/life4cut/album/controller/AlbumControllerTest.java +++ b/src/test/java/com/onebyte/life4cut/album/controller/AlbumControllerTest.java @@ -15,6 +15,7 @@ import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody; import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartFields; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -23,7 +24,9 @@ import com.epages.restdocs.apispec.ResourceSnippetParameters; import com.epages.restdocs.apispec.Schema; import com.epages.restdocs.apispec.SimpleType; +import com.onebyte.life4cut.album.controller.dto.CreateAlbumRequest; import com.onebyte.life4cut.album.controller.dto.CreatePictureRequest; +import com.onebyte.life4cut.album.controller.dto.UpdateAlbumRequest; import com.onebyte.life4cut.album.controller.dto.UpdatePictureRequest; import com.onebyte.life4cut.album.domain.vo.UserAlbumRole; import com.onebyte.life4cut.album.service.AlbumService; @@ -50,6 +53,7 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.generate.RestDocumentationGenerator; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.restdocs.snippet.Attributes; import org.springframework.test.web.servlet.ResultActions; @@ -397,4 +401,150 @@ void getMyRoleInAlbum() throws Exception { .build()))); } } + + @Nested + @WithCustomMockUser + class CreateAlbum { + + @Test + @DisplayName("앨범을 생성한다") + void createAlbum() throws Exception { + // given + String albumName = "앨범이름"; + List memberUserIds = List.of(1L, 2L); + List guestUserIds = List.of(3L, 4L); + + CreateAlbumRequest request = new CreateAlbumRequest(albumName, memberUserIds, guestUserIds); + + when(albumService.createAlbum(any(), any(), any(), any())).thenReturn(123L); + + // when + ResultActions result = + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/albums") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.data.id").value(123)) + .andDo( + document( + "{class_name}/{method_name}", + resource( + ResourceSnippetParameters.builder() + .tag(API_TAG) + .description("앨범을 생성한다") + .summary("앨범을 생성한다") + .responseFields( + fieldWithPath("message").type(STRING).description("응답 메시지"), + fieldWithPath("data.id").type(NUMBER).description("앨범 아이디")) + .requestSchema(Schema.schema("CreateAlbumRequest")) + .responseSchema(Schema.schema("CreateAlbumResponse")) + .build()))); + } + } + + @Nested + @WithCustomMockUser + class DeleteAlbum { + + @Test + @DisplayName("앨범을 삭제한다") + void deleteAlbum() throws Exception { + // given + Long albumId = 1L; + + doNothing().when(albumService).deleteAlbum(any(), any()); + + // when + ResultActions result = + mockMvc.perform( + RestDocumentationRequestBuilders.delete("/api/v1/albums/{albumId}", albumId)); + + // then + result + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("OK")) + .andDo( + document( + "{class_name}/{method_name}", + resource( + ResourceSnippetParameters.builder() + .tag(API_TAG) + .description("앨범을 삭제한다") + .summary("앨범을 삭제한다") + .pathParameters( + parameterWithName("albumId") + .description("앨범 아이디") + .type(SimpleType.NUMBER)) + .responseFields( + fieldWithPath("message").type(STRING).description("응답 메시지"), + fieldWithPath("data").optional().description("빈 객체")) + .requestSchema(Schema.schema("DeleteAlbumRequest")) + .responseSchema(Schema.schema("DeleteAlbumResponse")) + .build()))); + } + } + + @Nested + @WithCustomMockUser + class UpdateAlbumTest { + + @Test + @DisplayName("앨범을 업데이트한다") + void updateAlbum() throws Exception { + // Given + Long albumId = 1L; + String albumName = "새로운앨범이름"; + List memberUserIds = List.of(5L, 6L); + List guestUserIds = List.of(7L, 8L); + + UpdateAlbumRequest request = new UpdateAlbumRequest(albumName, memberUserIds, guestUserIds); + + doNothing().when(albumService).updateAlbum(any(), any(), any(), any(), any()); + + // When + ResultActions result = + mockMvc.perform( + RestDocumentationRequestBuilders.patch("/api/v1/albums/{albumId}", albumId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // Then + result + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("OK")) + .andDo( + document( + "{class_name}/{method_name}", + resource( + ResourceSnippetParameters.builder() + .tag(API_TAG) + .description("앨범을 업데이트한다") + .summary("앨범을 업데이트한다") + .pathParameters( + parameterWithName("albumId") + .description("앨범 아이디") + .type(SimpleType.NUMBER)) + .requestSchema(Schema.schema("UpdateAlbumRequest")) + .responseSchema(Schema.schema("EmptyResponse")) + .build()), + requestFields( + fieldWithPath("name").type(STRING).description("앨범 이름").optional(), + fieldWithPath("memberUserIds[]") + .type(JsonFieldType.ARRAY) + .description("멤버 사용자 아이디 목록") + .attributes(Attributes.key("itemType").value(JsonFieldType.NUMBER)) + .optional(), + fieldWithPath("guestUserIds[]") + .type(JsonFieldType.ARRAY) + .description("게스트 사용자 아이디 목록") + .attributes(Attributes.key("itemType").value(JsonFieldType.NUMBER)) + .optional()))); + } + } + // }