From 2cf9cf57233bcf551cdea22912f76b7559306f39 Mon Sep 17 00:00:00 2001 From: l2yuPa Date: Sun, 8 Dec 2024 02:53:19 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20swagger=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../letter/controller/LetterBoxController.java | 12 ++++++++---- .../bottler/letter/controller/LetterController.java | 8 +++++--- .../letter/controller/ReplyLetterController.java | 13 ++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/java/postman/bottler/letter/controller/LetterBoxController.java b/src/main/java/postman/bottler/letter/controller/LetterBoxController.java index 580f0165..ef1682f3 100644 --- a/src/main/java/postman/bottler/letter/controller/LetterBoxController.java +++ b/src/main/java/postman/bottler/letter/controller/LetterBoxController.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import postman.bottler.global.response.ApiResponse; -import postman.bottler.letter.dto.request.LetterDeleteRequestDTO; +import postman.bottler.letter.dto.LetterDeleteDTO; import postman.bottler.letter.dto.request.PageRequestDTO; import postman.bottler.letter.dto.response.LetterHeadersResponseDTO; import postman.bottler.letter.dto.response.PageResponseDTO; @@ -39,6 +39,7 @@ public class LetterBoxController { @Operation( summary = "보관된 모든 편지 조회", description = "페이지네이션을 사용하여 보관된 모든 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다." + + "\nPage Default: page(1) size(9) sort(createAt)" ) @GetMapping public ApiResponse> getAllLetters( @@ -54,7 +55,8 @@ public ApiResponse> getAllLetters( @Operation( summary = "보낸 편지 조회", - description = "페이지네이션을 사용하여 보관된 보낸 편지의 헤더 정보를 조회합니다." + description = "페이지네이션을 사용하여 보관된 보낸 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다." + + "\nPage Default: page(1) size(9) sort(createAt)" ) @GetMapping("/sent") public ApiResponse> getSentLetters( @@ -71,6 +73,7 @@ public ApiResponse> getSentLetters( @Operation( summary = "받은 편지 조회", description = "페이지네이션을 사용하여 보관된 받은 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다." + + "\nPage Default: page(1) size(9) sort(createAt)" ) @GetMapping("/received") public ApiResponse> getReceivedLetters( @@ -90,9 +93,10 @@ public ApiResponse> getReceivedLetters ) @DeleteMapping public ApiResponse deleteSavedLetter( - @RequestBody @Valid List letterDeleteRequestDTOS + @RequestBody @Valid List letterDeleteDTOS, + @AuthenticationPrincipal CustomUserDetails userDetails ) { - deleteManagerService.deleteLetters(letterDeleteRequestDTOS); + deleteManagerService.deleteLetters(letterDeleteDTOS, userDetails.getUserId()); return ApiResponse.onSuccess("편지 보관을 취소했습니다."); } diff --git a/src/main/java/postman/bottler/letter/controller/LetterController.java b/src/main/java/postman/bottler/letter/controller/LetterController.java index 52acba86..7acf0ff8 100644 --- a/src/main/java/postman/bottler/letter/controller/LetterController.java +++ b/src/main/java/postman/bottler/letter/controller/LetterController.java @@ -18,6 +18,7 @@ import postman.bottler.keyword.service.LetterKeywordService; import postman.bottler.keyword.service.RedisLetterService; import postman.bottler.letter.domain.Letter; +import postman.bottler.letter.dto.LetterDeleteDTO; import postman.bottler.letter.dto.request.LetterDeleteRequestDTO; import postman.bottler.letter.dto.request.LetterRequestDTO; import postman.bottler.letter.dto.response.LetterDetailResponseDTO; @@ -57,13 +58,14 @@ public ApiResponse createLetter( @Operation( summary = "키워드 편지 삭제", - description = "키워드 편지ID, 편지타입(LETTER, REPLY_LETTER), 송수신 타입(SEND, RECEIVE)을 기반으로 키워드 편지를 삭제합니다." + description = "키워드 편지ID, BoxType 송수신(SEND, RECEIVE)을 기반으로 키워드 편지를 삭제합니다." ) @DeleteMapping public ApiResponse deleteLetter( - @RequestBody @Valid LetterDeleteRequestDTO letterDeleteRequestDTO + @RequestBody @Valid LetterDeleteRequestDTO letterDeleteRequestDTO, + @AuthenticationPrincipal CustomUserDetails userDetails ) { - deleteManagerService.deleteLetters(List.of(letterDeleteRequestDTO)); + deleteManagerService.deleteLetter(LetterDeleteDTO.fromLetter(letterDeleteRequestDTO), userDetails.getUserId()); return ApiResponse.onSuccess("키워드 편지를 삭제했습니다."); } diff --git a/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java b/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java index e9020e19..88e67b65 100644 --- a/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java +++ b/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java @@ -3,7 +3,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -17,8 +16,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import postman.bottler.global.response.ApiResponse; -import postman.bottler.letter.dto.request.LetterDeleteRequestDTO; +import postman.bottler.letter.dto.LetterDeleteDTO; import postman.bottler.letter.dto.request.PageRequestDTO; +import postman.bottler.letter.dto.request.ReplyLetterDeleteRequestDTO; import postman.bottler.letter.dto.request.ReplyLetterRequestDTO; import postman.bottler.letter.dto.response.PageResponseDTO; import postman.bottler.letter.dto.response.ReplyLetterHeadersResponseDTO; @@ -61,6 +61,7 @@ public ApiResponse createReply( @Operation( summary = "특정 키워드 편지에 대한 답장 목록 조회", description = "지정된 편지 ID에 대한 답장들의 제목, 라벨이미지, 작성날짜를 페이지네이션 형태로 반환합니다." + + "\nPage Default: page(1) size(9) sort(createAt)" ) @GetMapping("/{letterId}") public ApiResponse> getReplyForLetter( @@ -89,13 +90,15 @@ public ApiResponse getReplyLetter( @Operation( summary = "답장 편지 삭제", - description = "답장 편지ID, 편지타입(LETTER, REPLY_LETTER, 송수신 타입(SEND, RECEIVE)을 기반으로 답장 편지를 삭제합니다." + description = "답장 편지ID, 송수신 타입(SEND, RECEIVE)을 기반으로 답장 편지를 삭제합니다." ) @DeleteMapping public ApiResponse deleteReplyLetter( - @RequestBody @Valid LetterDeleteRequestDTO letterDeleteRequestDTO + @RequestBody @Valid ReplyLetterDeleteRequestDTO replyLetterDeleteRequestDTO, + @AuthenticationPrincipal CustomUserDetails userDetails ) { - deleteManagerService.deleteLetters(List.of(letterDeleteRequestDTO)); + deleteManagerService.deleteLetter(LetterDeleteDTO.fromReplyLetter(replyLetterDeleteRequestDTO), + userDetails.getUserId()); return ApiResponse.onSuccess("success"); } From ed43dfd117b0bb8800b576571038d06043830f04 Mon Sep 17 00:00:00 2001 From: l2yuPa Date: Sun, 8 Dec 2024 02:55:01 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=EB=B0=9B=EC=9D=80=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EC=82=AD=EC=A0=9C=EC=8B=9C=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EB=B0=9B=EC=9D=80=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80=EA=B0=80=20=EC=82=AD=EC=A0=9C=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/KeywordResponseDTO.java | 3 -- .../keyword/service/RedisLetterService.java | 2 +- .../dto/request/LetterDeleteRequestDTO.java | 9 ----- .../infra/LetterBoxQueryRepository.java | 13 +++++++ .../letter/infra/LetterBoxRepositoryImpl.java | 5 +++ .../letter/service/DeleteManagerService.java | 37 ++++++++++++++----- .../letter/service/LetterBoxRepository.java | 2 + .../letter/service/LetterBoxService.java | 6 +++ .../letter/service/ReplyLetterService.java | 4 +- 9 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/main/java/postman/bottler/keyword/dto/response/KeywordResponseDTO.java b/src/main/java/postman/bottler/keyword/dto/response/KeywordResponseDTO.java index 1e0b852a..6546e47f 100644 --- a/src/main/java/postman/bottler/keyword/dto/response/KeywordResponseDTO.java +++ b/src/main/java/postman/bottler/keyword/dto/response/KeywordResponseDTO.java @@ -8,16 +8,13 @@ public record KeywordResponseDTO( List categories ) { - // 정적 팩토리 메서드로 변환 로직 처리 public static KeywordResponseDTO from(List keywordList) { - // 카테고리별로 키워드 그룹화 Map> groupedByCategory = keywordList.stream() .collect(Collectors.groupingBy( Keyword::getCategory, Collectors.mapping(Keyword::getKeyword, Collectors.toList()) )); - // 카테고리 DTO 리스트 생성 List categories = groupedByCategory.entrySet().stream() .map(entry -> new CategoryKeywordsDTO(entry.getKey(), entry.getValue())) .toList(); diff --git a/src/main/java/postman/bottler/keyword/service/RedisLetterService.java b/src/main/java/postman/bottler/keyword/service/RedisLetterService.java index dca08918..3b8e0a57 100644 --- a/src/main/java/postman/bottler/keyword/service/RedisLetterService.java +++ b/src/main/java/postman/bottler/keyword/service/RedisLetterService.java @@ -41,7 +41,7 @@ public RecommendNotificationRequestDTO updateRecommendationsFromTemp(Long userId Long recommendId = findFirstValidLetter(tempRecommendations); updateActiveRecommendations(recommendId, activeRecommendations, activeKey); saveLetterToBox(userId, recommendId); - + redisTemplate.delete(tempKey); return RecommendNotificationRequestDTO.of(userId, recommendId); diff --git a/src/main/java/postman/bottler/letter/dto/request/LetterDeleteRequestDTO.java b/src/main/java/postman/bottler/letter/dto/request/LetterDeleteRequestDTO.java index 5ca100d0..17fbb9c8 100644 --- a/src/main/java/postman/bottler/letter/dto/request/LetterDeleteRequestDTO.java +++ b/src/main/java/postman/bottler/letter/dto/request/LetterDeleteRequestDTO.java @@ -2,18 +2,9 @@ import jakarta.validation.constraints.NotNull; import postman.bottler.letter.domain.BoxType; -import postman.bottler.letter.domain.LetterType; public record LetterDeleteRequestDTO( @NotNull(message = "Letter ID는 필수입니다.") Long letterId, - @NotNull(message = "Letter Type은 필수입니다.") LetterType letterType, @NotNull(message = "Box Type은 필수입니다.") BoxType boxType ) { - public static LetterDeleteRequestDTO of(Long letterId, LetterType letterType, BoxType boxType) { - return new LetterDeleteRequestDTO( - letterId, - letterType, - boxType - ); - } } diff --git a/src/main/java/postman/bottler/letter/infra/LetterBoxQueryRepository.java b/src/main/java/postman/bottler/letter/infra/LetterBoxQueryRepository.java index d8c38644..f20cd5d8 100644 --- a/src/main/java/postman/bottler/letter/infra/LetterBoxQueryRepository.java +++ b/src/main/java/postman/bottler/letter/infra/LetterBoxQueryRepository.java @@ -86,4 +86,17 @@ public void deleteByCondition(List letterIds, LetterType letterType, BoxTy ) .execute(); } + + public void deleteByConditionAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId) { + QLetterBoxEntity letterBox = QLetterBoxEntity.letterBoxEntity; + + queryFactory.delete(letterBox) + .where( + letterIds != null ? letterBox.letterId.in(letterIds) : null, + letterType != LetterType.UNKNOWN ? letterBox.letterType.eq(letterType) : null, + boxType != BoxType.UNKNOWN ? letterBox.boxType.eq(boxType) : null, + letterBox.userId.eq(userId) + ) + .execute(); + } } diff --git a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java index b8d58b00..b8aa6f6e 100644 --- a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java +++ b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java @@ -52,6 +52,11 @@ public void deleteByCondition(List letterIds, LetterType letterType, BoxTy letterBoxQueryRepository.deleteByCondition(letterIds, letterType, boxType); } + @Override + public void deleteByConditionAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId) { + letterBoxQueryRepository.deleteByConditionAndUserId(letterIds, letterType, boxType, userId); + } + @Override public List getReceivedLettersById(Long userId) { return letterBoxQueryRepository.getReceivedLettersById(userId); diff --git a/src/main/java/postman/bottler/letter/service/DeleteManagerService.java b/src/main/java/postman/bottler/letter/service/DeleteManagerService.java index a949fcdf..fae9623d 100644 --- a/src/main/java/postman/bottler/letter/service/DeleteManagerService.java +++ b/src/main/java/postman/bottler/letter/service/DeleteManagerService.java @@ -10,7 +10,7 @@ import postman.bottler.keyword.service.LetterKeywordService; import postman.bottler.letter.domain.BoxType; import postman.bottler.letter.domain.LetterType; -import postman.bottler.letter.dto.request.LetterDeleteRequestDTO; +import postman.bottler.letter.dto.LetterDeleteDTO; @Slf4j @Service @@ -22,27 +22,42 @@ public class DeleteManagerService { private final LetterKeywordService letterKeywordService; @Transactional - public void deleteLetters(List letterDeleteRequestDTOs) { - Map>> groupedByTypeAndBox = letterDeleteRequestDTOs.stream() + public void deleteLetter(LetterDeleteDTO letterDeleteDTO, Long userId) { + Map>> groupedByTypeAndBox = Map.of( + letterDeleteDTO.letterType(), + Map.of( + letterDeleteDTO.boxType(), + List.of(letterDeleteDTO.letterId()) + ) + ); + processGroupedLetters(groupedByTypeAndBox, userId); + } + + @Transactional + public void deleteLetters(List letterDeleteDTOS, Long userId) { + Map>> groupedByTypeAndBox = letterDeleteDTOS.stream() .collect(Collectors.groupingBy( - LetterDeleteRequestDTO::letterType, + LetterDeleteDTO::letterType, Collectors.groupingBy( - LetterDeleteRequestDTO::boxType, - Collectors.mapping(LetterDeleteRequestDTO::letterId, Collectors.toList()) + LetterDeleteDTO::boxType, + Collectors.mapping(LetterDeleteDTO::letterId, Collectors.toList()) ) )); + processGroupedLetters(groupedByTypeAndBox, userId); + } + private void processGroupedLetters(Map>> groupedByTypeAndBox, Long userId) { if (groupedByTypeAndBox.containsKey(LetterType.LETTER)) { Map> letterBoxMap = groupedByTypeAndBox.get(LetterType.LETTER); if (letterBoxMap.containsKey(BoxType.SEND)) { List letterIds = letterBoxMap.get(BoxType.SEND); - letterService.deleteLetters(letterIds); // LetterService에서 삭제 + letterService.deleteLetters(letterIds); letterKeywordService.markKeywordsAsDeleted(letterIds); letterBoxService.deleteByIdsAndType(letterIds, LetterType.LETTER, BoxType.UNKNOWN); } if (letterBoxMap.containsKey(BoxType.RECEIVE)) { List letterIds = letterBoxMap.get(BoxType.RECEIVE); - letterBoxService.deleteByIdsAndType(letterIds, LetterType.LETTER, BoxType.RECEIVE); + letterBoxService.deleteByIdsAndTypeAndUserId(letterIds, LetterType.LETTER, BoxType.RECEIVE, userId); } } @@ -50,13 +65,15 @@ public void deleteLetters(List letterDeleteRequestDTOs) Map> replyLetterBoxMap = groupedByTypeAndBox.get(LetterType.REPLY_LETTER); if (replyLetterBoxMap.containsKey(BoxType.SEND)) { List replyLetterIds = replyLetterBoxMap.get(BoxType.SEND); - replyLetterService.deleteReplyLetters(replyLetterIds); // ReplyLetterService에서 삭제 + replyLetterService.deleteReplyLetters(replyLetterIds); letterBoxService.deleteByIdsAndType(replyLetterIds, LetterType.REPLY_LETTER, BoxType.UNKNOWN); } if (replyLetterBoxMap.containsKey(BoxType.RECEIVE)) { List replyLetterIds = replyLetterBoxMap.get(BoxType.RECEIVE); - letterBoxService.deleteByIdsAndType(replyLetterIds, LetterType.REPLY_LETTER, BoxType.RECEIVE); + letterBoxService.deleteByIdsAndTypeAndUserId(replyLetterIds, LetterType.REPLY_LETTER, BoxType.RECEIVE, + userId); } } } + } diff --git a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java index 20f6002d..08916f09 100644 --- a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java +++ b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java @@ -19,6 +19,8 @@ public interface LetterBoxRepository { void deleteByCondition(List letterIds, LetterType letterType, BoxType boxType); + void deleteByConditionAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId); + List getReceivedLettersById(Long userId); void saveRecommendedLetters(List letterBoxes); diff --git a/src/main/java/postman/bottler/letter/service/LetterBoxService.java b/src/main/java/postman/bottler/letter/service/LetterBoxService.java index b5967775..eaa6ac40 100644 --- a/src/main/java/postman/bottler/letter/service/LetterBoxService.java +++ b/src/main/java/postman/bottler/letter/service/LetterBoxService.java @@ -44,6 +44,12 @@ public void deleteByIdsAndType(List letterIds, LetterType letterType, BoxT letterBoxRepository.deleteByCondition(letterIds, letterType, boxType); } + @Transactional + public void deleteByIdsAndTypeAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId) { + letterBoxRepository.deleteByConditionAndUserId(letterIds, letterType, boxType, userId); + } + + @Transactional(readOnly = true) public List getLettersByUserId(Long userId) { return letterBoxRepository.getReceivedLettersById(userId); diff --git a/src/main/java/postman/bottler/letter/service/ReplyLetterService.java b/src/main/java/postman/bottler/letter/service/ReplyLetterService.java index 6beb3c8a..89f82fa4 100644 --- a/src/main/java/postman/bottler/letter/service/ReplyLetterService.java +++ b/src/main/java/postman/bottler/letter/service/ReplyLetterService.java @@ -81,16 +81,16 @@ public ReplyLetterResponseDTO getReplyLetterDetail(Long replyLetterId) { @Transactional public void deleteReplyLetters(List letterIds) { - replyLetterRepository.deleteByIds(letterIds); for (Long letterId : letterIds) { deleteRecentReply(letterId); } + replyLetterRepository.deleteByIds(letterIds); } @Transactional public Long blockLetter(Long replyLetterId) { - replyLetterRepository.blockReplyLetterById(replyLetterId); ReplyLetter replyLetter = findReplyLetter(replyLetterId); + replyLetterRepository.blockReplyLetterById(replyLetterId); return replyLetter.getSenderId(); } From d65093accef150972ee597692f3366f118ee31b6 Mon Sep 17 00:00:00 2001 From: l2yuPa Date: Sun, 8 Dec 2024 02:55:24 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20DTO=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=B6=84=ED=95=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottler/letter/dto/LetterDeleteDTO.java | 29 +++++++++++++++++++ .../request/ReplyLetterDeleteRequestDTO.java | 16 ++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/postman/bottler/letter/dto/LetterDeleteDTO.java create mode 100644 src/main/java/postman/bottler/letter/dto/request/ReplyLetterDeleteRequestDTO.java diff --git a/src/main/java/postman/bottler/letter/dto/LetterDeleteDTO.java b/src/main/java/postman/bottler/letter/dto/LetterDeleteDTO.java new file mode 100644 index 00000000..5bc4c33e --- /dev/null +++ b/src/main/java/postman/bottler/letter/dto/LetterDeleteDTO.java @@ -0,0 +1,29 @@ +package postman.bottler.letter.dto; + +import jakarta.validation.constraints.NotNull; +import postman.bottler.letter.domain.BoxType; +import postman.bottler.letter.domain.LetterType; +import postman.bottler.letter.dto.request.LetterDeleteRequestDTO; +import postman.bottler.letter.dto.request.ReplyLetterDeleteRequestDTO; + +public record LetterDeleteDTO( + @NotNull(message = "Letter ID는 필수입니다.") Long letterId, + @NotNull(message = "Letter Type은 필수입니다.") LetterType letterType, + @NotNull(message = "Box Type은 필수입니다.") BoxType boxType +) { + public static LetterDeleteDTO fromLetter(LetterDeleteRequestDTO letterDeleteRequestDTO) { + return new LetterDeleteDTO( + letterDeleteRequestDTO.letterId(), + LetterType.LETTER, + letterDeleteRequestDTO.boxType() + ); + } + + public static LetterDeleteDTO fromReplyLetter(ReplyLetterDeleteRequestDTO replyLetterDeleteRequestDTO) { + return new LetterDeleteDTO( + replyLetterDeleteRequestDTO.letterId(), + LetterType.REPLY_LETTER, + replyLetterDeleteRequestDTO.boxType() + ); + } +} diff --git a/src/main/java/postman/bottler/letter/dto/request/ReplyLetterDeleteRequestDTO.java b/src/main/java/postman/bottler/letter/dto/request/ReplyLetterDeleteRequestDTO.java new file mode 100644 index 00000000..22ccd0a3 --- /dev/null +++ b/src/main/java/postman/bottler/letter/dto/request/ReplyLetterDeleteRequestDTO.java @@ -0,0 +1,16 @@ +package postman.bottler.letter.dto.request; + +import jakarta.validation.constraints.NotNull; +import postman.bottler.letter.domain.BoxType; + +public record ReplyLetterDeleteRequestDTO( + @NotNull(message = "Letter ID는 필수입니다.") Long letterId, + @NotNull(message = "Box Type은 필수입니다.") BoxType boxType +) { + public static ReplyLetterDeleteRequestDTO of(Long letterId, BoxType boxType) { + return new ReplyLetterDeleteRequestDTO( + letterId, + boxType + ); + } +} From 976049f8630368adde75be1157655de39d5b20be Mon Sep 17 00:00:00 2001 From: l2yuPa Date: Sun, 8 Dec 2024 02:57:23 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottler/letter/infra/LetterBoxRepositoryImpl.java | 5 ----- .../postman/bottler/letter/service/LetterBoxRepository.java | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java index b8aa6f6e..48d7e4a9 100644 --- a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java +++ b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java @@ -62,11 +62,6 @@ public List getReceivedLettersById(Long userId) { return letterBoxQueryRepository.getReceivedLettersById(userId); } - @Override - public void saveRecommendedLetters(List letterBoxes) { - letterBoxJdbcRepository.saveAll(letterBoxes); - } - private List fetchLetters(Long userId, BoxType boxType, Pageable pageable) { return letterBoxQueryRepository.fetchLetters(userId, boxType, pageable); } diff --git a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java index 08916f09..7ffbf474 100644 --- a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java +++ b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java @@ -22,6 +22,4 @@ public interface LetterBoxRepository { void deleteByConditionAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId); List getReceivedLettersById(Long userId); - - void saveRecommendedLetters(List letterBoxes); } From f81e221fdfb7ad1aa6e6480c05d080c15651f5d3 Mon Sep 17 00:00:00 2001 From: l2yuPa Date: Sun, 8 Dec 2024 04:02:15 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20getMapping=EC=9D=98=20PathVariable?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=A0=91=EC=86=8D?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=EC=84=B1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/response/code/ErrorStatus.java | 2 ++ .../letter/controller/LetterController.java | 3 +++ .../controller/ReplyLetterController.java | 7 ++++++- .../LetterAuthorMismatchException.java | 7 +++++++ .../exception/LetterExceptionHandler.java | 17 +++++++++++++++++ .../UnauthorizedLetterAccessException.java | 7 +++++++ .../letter/infra/LetterBoxJdbcRepository.java | 7 +++++++ .../letter/infra/LetterBoxRepositoryImpl.java | 5 +++++ .../letter/service/DeleteManagerService.java | 4 ++-- .../letter/service/LetterBoxRepository.java | 2 ++ .../letter/service/LetterBoxService.java | 8 ++++++++ .../bottler/letter/service/LetterService.java | 8 +++++++- .../letter/service/ReplyLetterService.java | 12 ++++++++---- 13 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 src/main/java/postman/bottler/letter/exception/LetterAuthorMismatchException.java create mode 100644 src/main/java/postman/bottler/letter/exception/UnauthorizedLetterAccessException.java diff --git a/src/main/java/postman/bottler/global/response/code/ErrorStatus.java b/src/main/java/postman/bottler/global/response/code/ErrorStatus.java index 4e2d26c8..f6d63ca0 100644 --- a/src/main/java/postman/bottler/global/response/code/ErrorStatus.java +++ b/src/main/java/postman/bottler/global/response/code/ErrorStatus.java @@ -30,6 +30,8 @@ public enum ErrorStatus { REPLY_LETTER_VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "REPLY_LETTER4001"), INVALID_LETTER_TYPE(HttpStatus.BAD_REQUEST, "LETTER_TYPE4000"), DUPLICATE_REPLY_LETTER(HttpStatus.BAD_REQUEST, "REPLY_LETTER4002"), + LETTER_AUTHOR_MISMATCH(HttpStatus.BAD_REQUEST, "LETTER4004"), + UNAUTHORIZED_LETTER_ACCESS(HttpStatus.UNAUTHORIZED, "LETTER4010"), // 추천 에러 TEMP_RECOMMENDATIONS_NOT_FOUND(HttpStatus.NOT_FOUND, "TEMP_RECOMMENDATIONS_4040"), diff --git a/src/main/java/postman/bottler/letter/controller/LetterController.java b/src/main/java/postman/bottler/letter/controller/LetterController.java index 7acf0ff8..7873d175 100644 --- a/src/main/java/postman/bottler/letter/controller/LetterController.java +++ b/src/main/java/postman/bottler/letter/controller/LetterController.java @@ -25,6 +25,7 @@ import postman.bottler.letter.dto.response.LetterRecommendHeadersResponseDTO; import postman.bottler.letter.exception.InvalidLetterRequestException; import postman.bottler.letter.service.DeleteManagerService; +import postman.bottler.letter.service.LetterBoxService; import postman.bottler.letter.service.LetterService; import postman.bottler.letter.utiil.ValidationUtil; import postman.bottler.user.auth.CustomUserDetails; @@ -40,6 +41,7 @@ public class LetterController { private final LetterKeywordService letterKeywordService; private final ValidationUtil validationUtil; private final RedisLetterService redisLetterService; + private final LetterBoxService letterBoxService; @Operation( summary = "키워드 편지 생성", @@ -77,6 +79,7 @@ public ApiResponse deleteLetter( public ApiResponse getLetter( @PathVariable Long letterId, @AuthenticationPrincipal CustomUserDetails userDetails ) { + letterBoxService.validateLetterInUserBox(letterId, userDetails.getUserId()); List keywords = letterKeywordService.getKeywords(letterId); LetterDetailResponseDTO result = letterService.getLetterDetail(letterId, keywords, userDetails.getUserId()); return ApiResponse.onSuccess(result); diff --git a/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java b/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java index 88e67b65..262716a6 100644 --- a/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java +++ b/src/main/java/postman/bottler/letter/controller/ReplyLetterController.java @@ -26,6 +26,7 @@ import postman.bottler.letter.exception.InvalidPageRequestException; import postman.bottler.letter.exception.InvalidReplyLetterRequestException; import postman.bottler.letter.service.DeleteManagerService; +import postman.bottler.letter.service.LetterBoxService; import postman.bottler.letter.service.ReplyLetterService; import postman.bottler.letter.utiil.ValidationUtil; import postman.bottler.user.auth.CustomUserDetails; @@ -40,6 +41,7 @@ public class ReplyLetterController { private final ReplyLetterService letterReplyService; private final DeleteManagerService deleteManagerService; private final ValidationUtil validationUtil; + private final LetterBoxService letterBoxService; @Operation( summary = "키워드 편지 생성", @@ -70,6 +72,7 @@ public ApiResponse> getReplyForLe BindingResult bindingResult, @AuthenticationPrincipal CustomUserDetails userDetails ) { + letterBoxService.validateLetterInUserBox(letterId, userDetails.getUserId()); validatePageRequest(bindingResult); Page result = letterReplyService.getReplyLetterHeadersById(letterId, pageRequestDTO, userDetails.getUserId()); @@ -82,8 +85,10 @@ public ApiResponse> getReplyForLe ) @GetMapping("/detail/{replyLetterId}") public ApiResponse getReplyLetter( - @PathVariable Long replyLetterId + @PathVariable Long replyLetterId, + @AuthenticationPrincipal CustomUserDetails userDetails ) { + letterBoxService.validateLetterInUserBox(replyLetterId, userDetails.getUserId()); ReplyLetterResponseDTO result = letterReplyService.getReplyLetterDetail(replyLetterId); return ApiResponse.onSuccess(result); } diff --git a/src/main/java/postman/bottler/letter/exception/LetterAuthorMismatchException.java b/src/main/java/postman/bottler/letter/exception/LetterAuthorMismatchException.java new file mode 100644 index 00000000..25f59d2c --- /dev/null +++ b/src/main/java/postman/bottler/letter/exception/LetterAuthorMismatchException.java @@ -0,0 +1,7 @@ +package postman.bottler.letter.exception; + +public class LetterAuthorMismatchException extends RuntimeException { + public LetterAuthorMismatchException(String message) { + super(message); + } +} diff --git a/src/main/java/postman/bottler/letter/exception/LetterExceptionHandler.java b/src/main/java/postman/bottler/letter/exception/LetterExceptionHandler.java index fa5cf895..f7cf31db 100644 --- a/src/main/java/postman/bottler/letter/exception/LetterExceptionHandler.java +++ b/src/main/java/postman/bottler/letter/exception/LetterExceptionHandler.java @@ -5,6 +5,7 @@ import static postman.bottler.global.response.code.ErrorStatus.INVALID_SORT_FIELD; import static postman.bottler.global.response.code.ErrorStatus.LETTER_ACCESS_DENIED; import static postman.bottler.global.response.code.ErrorStatus.LETTER_ALREADY_SAVED; +import static postman.bottler.global.response.code.ErrorStatus.LETTER_AUTHOR_MISMATCH; import static postman.bottler.global.response.code.ErrorStatus.LETTER_DELETE_VALIDATION_ERROR; import static postman.bottler.global.response.code.ErrorStatus.LETTER_NOT_FOUND; import static postman.bottler.global.response.code.ErrorStatus.LETTER_UNKNOWN_VALIDATION_ERROR; @@ -12,6 +13,7 @@ import static postman.bottler.global.response.code.ErrorStatus.PAGINATION_VALIDATION_ERROR; import static postman.bottler.global.response.code.ErrorStatus.REPLY_LETTER_VALIDATION_ERROR; import static postman.bottler.global.response.code.ErrorStatus.TEMP_RECOMMENDATIONS_NOT_FOUND; +import static postman.bottler.global.response.code.ErrorStatus.UNAUTHORIZED_LETTER_ACCESS; import java.util.Map; import lombok.extern.slf4j.Slf4j; @@ -114,4 +116,19 @@ public ResponseEntity> handleInvalidLetterTypeException( .status(TEMP_RECOMMENDATIONS_NOT_FOUND.getHttpStatus()) .body(ApiResponse.onFailure(TEMP_RECOMMENDATIONS_NOT_FOUND.getCode(), e.getMessage(), null)); } + + @ExceptionHandler(LetterAuthorMismatchException.class) + public ResponseEntity> handleLetterAuthorMismatchException(LetterAuthorMismatchException e) { + return ResponseEntity + .status(LETTER_AUTHOR_MISMATCH.getHttpStatus()) + .body(ApiResponse.onFailure(LETTER_AUTHOR_MISMATCH.getCode(), e.getMessage(), null)); + } + + @ExceptionHandler(UnauthorizedLetterAccessException.class) + public ResponseEntity> handleUnauthorizedLetterAccessException( + UnauthorizedLetterAccessException e) { + return ResponseEntity + .status(UNAUTHORIZED_LETTER_ACCESS.getHttpStatus()) + .body(ApiResponse.onFailure(UNAUTHORIZED_LETTER_ACCESS.getCode(), e.getMessage(), null)); + } } diff --git a/src/main/java/postman/bottler/letter/exception/UnauthorizedLetterAccessException.java b/src/main/java/postman/bottler/letter/exception/UnauthorizedLetterAccessException.java new file mode 100644 index 00000000..5bfef366 --- /dev/null +++ b/src/main/java/postman/bottler/letter/exception/UnauthorizedLetterAccessException.java @@ -0,0 +1,7 @@ +package postman.bottler.letter.exception; + +public class UnauthorizedLetterAccessException extends RuntimeException { + public UnauthorizedLetterAccessException(String message) { + super(message); + } +} diff --git a/src/main/java/postman/bottler/letter/infra/LetterBoxJdbcRepository.java b/src/main/java/postman/bottler/letter/infra/LetterBoxJdbcRepository.java index 44ff58f2..d7b0a778 100644 --- a/src/main/java/postman/bottler/letter/infra/LetterBoxJdbcRepository.java +++ b/src/main/java/postman/bottler/letter/infra/LetterBoxJdbcRepository.java @@ -42,4 +42,11 @@ public void saveAll(List letterBoxes) { jdbcTemplate.batchUpdate(sql, params); } + + public boolean existsByUserIdAndLetterId(Long letterId, Long userId) { + String sql = "SELECT COUNT(*) FROM letter_box WHERE user_id = ? AND letter_id = ?"; + + Integer count = jdbcTemplate.queryForObject(sql, Integer.class, userId, letterId); + return count > 0; + } } diff --git a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java index 48d7e4a9..1a9af7e9 100644 --- a/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java +++ b/src/main/java/postman/bottler/letter/infra/LetterBoxRepositoryImpl.java @@ -62,6 +62,11 @@ public List getReceivedLettersById(Long userId) { return letterBoxQueryRepository.getReceivedLettersById(userId); } + @Override + public boolean existsByLetterIdAndUserId(Long letterId, Long userId) { + return letterBoxJdbcRepository.existsByUserIdAndLetterId(letterId, userId); + } + private List fetchLetters(Long userId, BoxType boxType, Pageable pageable) { return letterBoxQueryRepository.fetchLetters(userId, boxType, pageable); } diff --git a/src/main/java/postman/bottler/letter/service/DeleteManagerService.java b/src/main/java/postman/bottler/letter/service/DeleteManagerService.java index fae9623d..0f4fb019 100644 --- a/src/main/java/postman/bottler/letter/service/DeleteManagerService.java +++ b/src/main/java/postman/bottler/letter/service/DeleteManagerService.java @@ -51,7 +51,7 @@ private void processGroupedLetters(Map>> gro Map> letterBoxMap = groupedByTypeAndBox.get(LetterType.LETTER); if (letterBoxMap.containsKey(BoxType.SEND)) { List letterIds = letterBoxMap.get(BoxType.SEND); - letterService.deleteLetters(letterIds); + letterService.deleteLetters(letterIds, userId); letterKeywordService.markKeywordsAsDeleted(letterIds); letterBoxService.deleteByIdsAndType(letterIds, LetterType.LETTER, BoxType.UNKNOWN); } @@ -65,7 +65,7 @@ private void processGroupedLetters(Map>> gro Map> replyLetterBoxMap = groupedByTypeAndBox.get(LetterType.REPLY_LETTER); if (replyLetterBoxMap.containsKey(BoxType.SEND)) { List replyLetterIds = replyLetterBoxMap.get(BoxType.SEND); - replyLetterService.deleteReplyLetters(replyLetterIds); + replyLetterService.deleteReplyLetters(replyLetterIds, userId); letterBoxService.deleteByIdsAndType(replyLetterIds, LetterType.REPLY_LETTER, BoxType.UNKNOWN); } if (replyLetterBoxMap.containsKey(BoxType.RECEIVE)) { diff --git a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java index 7ffbf474..b0e1eb86 100644 --- a/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java +++ b/src/main/java/postman/bottler/letter/service/LetterBoxRepository.java @@ -22,4 +22,6 @@ public interface LetterBoxRepository { void deleteByConditionAndUserId(List letterIds, LetterType letterType, BoxType boxType, Long userId); List getReceivedLettersById(Long userId); + + boolean existsByLetterIdAndUserId(Long letterId, Long userId); } diff --git a/src/main/java/postman/bottler/letter/service/LetterBoxService.java b/src/main/java/postman/bottler/letter/service/LetterBoxService.java index eaa6ac40..bc5b7a70 100644 --- a/src/main/java/postman/bottler/letter/service/LetterBoxService.java +++ b/src/main/java/postman/bottler/letter/service/LetterBoxService.java @@ -11,6 +11,7 @@ import postman.bottler.letter.dto.LetterBoxDTO; import postman.bottler.letter.dto.request.PageRequestDTO; import postman.bottler.letter.dto.response.LetterHeadersResponseDTO; +import postman.bottler.letter.exception.UnauthorizedLetterAccessException; @Slf4j @Service @@ -55,4 +56,11 @@ public List getLettersByUserId(Long userId) { return letterBoxRepository.getReceivedLettersById(userId); } + @Transactional(readOnly = true) + public void validateLetterInUserBox(Long letterId, Long userId) { + boolean isLetterInUserBox = letterBoxRepository.existsByLetterIdAndUserId(letterId, userId); + if (!isLetterInUserBox) { + throw new UnauthorizedLetterAccessException("사용자가 해당 편지에 접근할 권한이 없습니다."); + } + } } diff --git a/src/main/java/postman/bottler/letter/service/LetterService.java b/src/main/java/postman/bottler/letter/service/LetterService.java index 3fb6268c..77d1bdcc 100644 --- a/src/main/java/postman/bottler/letter/service/LetterService.java +++ b/src/main/java/postman/bottler/letter/service/LetterService.java @@ -13,6 +13,7 @@ import postman.bottler.letter.dto.request.LetterRequestDTO; import postman.bottler.letter.dto.response.LetterDetailResponseDTO; import postman.bottler.letter.dto.response.LetterRecommendHeadersResponseDTO; +import postman.bottler.letter.exception.LetterAuthorMismatchException; import postman.bottler.letter.exception.LetterNotFoundException; import postman.bottler.user.service.UserService; @@ -35,7 +36,12 @@ public Letter createLetter(LetterRequestDTO letterRequestDTO, Long userId) { } @Transactional - public void deleteLetters(List letterIds) { + public void deleteLetters(List letterIds, Long userId) { + letterIds.forEach(letterId -> { + if (!findLetter(letterId).getUserId().equals(userId)) { + throw new LetterAuthorMismatchException("요청자와 작성자가 일치하지 않습니다."); + } + }); letterRepository.deleteByIds(letterIds); } diff --git a/src/main/java/postman/bottler/letter/service/ReplyLetterService.java b/src/main/java/postman/bottler/letter/service/ReplyLetterService.java index 89f82fa4..1e1f614e 100644 --- a/src/main/java/postman/bottler/letter/service/ReplyLetterService.java +++ b/src/main/java/postman/bottler/letter/service/ReplyLetterService.java @@ -17,6 +17,7 @@ import postman.bottler.letter.dto.response.ReplyLetterHeadersResponseDTO; import postman.bottler.letter.dto.response.ReplyLetterResponseDTO; import postman.bottler.letter.exception.DuplicateReplyLetterException; +import postman.bottler.letter.exception.LetterAuthorMismatchException; import postman.bottler.letter.exception.LetterNotFoundException; import postman.bottler.reply.dto.ReplyType; @@ -80,10 +81,13 @@ public ReplyLetterResponseDTO getReplyLetterDetail(Long replyLetterId) { } @Transactional - public void deleteReplyLetters(List letterIds) { - for (Long letterId : letterIds) { - deleteRecentReply(letterId); - } + public void deleteReplyLetters(List letterIds, Long userId) { + letterIds.forEach(letterId -> { + if (!findReplyLetter(letterId).getSenderId().equals(userId)) { + throw new LetterAuthorMismatchException("요청자와 작성자가 일치하지 않습니다."); + } + }); + letterIds.forEach(this::deleteRecentReply); replyLetterRepository.deleteByIds(letterIds); }