Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI-CD/ 키워드 편지 수정 #169

Merged
merged 6 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
public record KeywordResponseDTO(
List<CategoryKeywordsDTO> categories
) {
// 정적 팩토리 메서드로 변환 로직 처리
public static KeywordResponseDTO from(List<Keyword> keywordList) {
// 카테고리별로 키워드 그룹화
Map<String, List<String>> groupedByCategory = keywordList.stream()
.collect(Collectors.groupingBy(
Keyword::getCategory,
Collectors.mapping(Keyword::getKeyword, Collectors.toList())
));

// 카테고리 DTO 리스트 생성
List<CategoryKeywordsDTO> categories = groupedByCategory.entrySet().stream()
.map(entry -> new CategoryKeywordsDTO(entry.getKey(), entry.getValue()))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,6 +39,7 @@ public class LetterBoxController {
@Operation(
summary = "보관된 모든 편지 조회",
description = "페이지네이션을 사용하여 보관된 모든 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다."
+ "\nPage Default: page(1) size(9) sort(createAt)"
)
@GetMapping
public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getAllLetters(
Expand All @@ -54,7 +55,8 @@ public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getAllLetters(

@Operation(
summary = "보낸 편지 조회",
description = "페이지네이션을 사용하여 보관된 보낸 편지의 헤더 정보를 조회합니다."
description = "페이지네이션을 사용하여 보관된 보낸 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다."
+ "\nPage Default: page(1) size(9) sort(createAt)"
)
@GetMapping("/sent")
public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getSentLetters(
Expand All @@ -71,6 +73,7 @@ public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getSentLetters(
@Operation(
summary = "받은 편지 조회",
description = "페이지네이션을 사용하여 보관된 받은 편지의 제목, 라벨이미지, 작성날짜 정보를 조회합니다."
+ "\nPage Default: page(1) size(9) sort(createAt)"
)
@GetMapping("/received")
public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getReceivedLetters(
Expand All @@ -90,9 +93,10 @@ public ApiResponse<PageResponseDTO<LetterHeadersResponseDTO>> getReceivedLetters
)
@DeleteMapping
public ApiResponse<String> deleteSavedLetter(
@RequestBody @Valid List<LetterDeleteRequestDTO> letterDeleteRequestDTOS
@RequestBody @Valid List<LetterDeleteDTO> letterDeleteDTOS,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
deleteManagerService.deleteLetters(letterDeleteRequestDTOS);
deleteManagerService.deleteLetters(letterDeleteDTOS, userDetails.getUserId());
return ApiResponse.onSuccess("편지 보관을 취소했습니다.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
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;
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;
Expand All @@ -39,6 +41,7 @@ public class LetterController {
private final LetterKeywordService letterKeywordService;
private final ValidationUtil validationUtil;
private final RedisLetterService redisLetterService;
private final LetterBoxService letterBoxService;

@Operation(
summary = "키워드 편지 생성",
Expand All @@ -57,13 +60,14 @@ public ApiResponse<String> createLetter(

@Operation(
summary = "키워드 편지 삭제",
description = "키워드 편지ID, 편지타입(LETTER, REPLY_LETTER), 송수신 타입(SEND, RECEIVE)을 기반으로 키워드 편지를 삭제합니다."
description = "키워드 편지ID, BoxType 송수신(SEND, RECEIVE)을 기반으로 키워드 편지를 삭제합니다."
)
@DeleteMapping
public ApiResponse<String> 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("키워드 편지를 삭제했습니다.");
}

Expand All @@ -75,6 +79,7 @@ public ApiResponse<String> deleteLetter(
public ApiResponse<LetterDetailResponseDTO> getLetter(
@PathVariable Long letterId, @AuthenticationPrincipal CustomUserDetails userDetails
) {
letterBoxService.validateLetterInUserBox(letterId, userDetails.getUserId());
List<String> keywords = letterKeywordService.getKeywords(letterId);
LetterDetailResponseDTO result = letterService.getLetterDetail(letterId, keywords, userDetails.getUserId());
return ApiResponse.onSuccess(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,15 +16,17 @@
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;
import postman.bottler.letter.dto.response.ReplyLetterResponseDTO;
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;
Expand All @@ -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 = "키워드 편지 생성",
Expand All @@ -61,6 +63,7 @@ public ApiResponse<ReplyLetterResponseDTO> createReply(
@Operation(
summary = "특정 키워드 편지에 대한 답장 목록 조회",
description = "지정된 편지 ID에 대한 답장들의 제목, 라벨이미지, 작성날짜를 페이지네이션 형태로 반환합니다."
+ "\nPage Default: page(1) size(9) sort(createAt)"
)
@GetMapping("/{letterId}")
public ApiResponse<PageResponseDTO<ReplyLetterHeadersResponseDTO>> getReplyForLetter(
Expand All @@ -69,6 +72,7 @@ public ApiResponse<PageResponseDTO<ReplyLetterHeadersResponseDTO>> getReplyForLe
BindingResult bindingResult,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
letterBoxService.validateLetterInUserBox(letterId, userDetails.getUserId());
validatePageRequest(bindingResult);
Page<ReplyLetterHeadersResponseDTO> result =
letterReplyService.getReplyLetterHeadersById(letterId, pageRequestDTO, userDetails.getUserId());
Expand All @@ -81,21 +85,25 @@ public ApiResponse<PageResponseDTO<ReplyLetterHeadersResponseDTO>> getReplyForLe
)
@GetMapping("/detail/{replyLetterId}")
public ApiResponse<ReplyLetterResponseDTO> getReplyLetter(
@PathVariable Long replyLetterId
@PathVariable Long replyLetterId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
letterBoxService.validateLetterInUserBox(replyLetterId, userDetails.getUserId());
ReplyLetterResponseDTO result = letterReplyService.getReplyLetterDetail(replyLetterId);
return ApiResponse.onSuccess(result);
}

@Operation(
summary = "답장 편지 삭제",
description = "답장 편지ID, 편지타입(LETTER, REPLY_LETTER, 송수신 타입(SEND, RECEIVE)을 기반으로 답장 편지를 삭제합니다."
description = "답장 편지ID, 송수신 타입(SEND, RECEIVE)을 기반으로 답장 편지를 삭제합니다."
)
@DeleteMapping
public ApiResponse<String> 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");
}

Expand Down
29 changes: 29 additions & 0 deletions src/main/java/postman/bottler/letter/dto/LetterDeleteDTO.java
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package postman.bottler.letter.exception;

public class LetterAuthorMismatchException extends RuntimeException {
public LetterAuthorMismatchException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
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;
import static postman.bottler.global.response.code.ErrorStatus.LETTER_VALIDATION_ERROR;
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;
Expand Down Expand Up @@ -114,4 +116,19 @@ public ResponseEntity<ApiResponse<String>> handleInvalidLetterTypeException(
.status(TEMP_RECOMMENDATIONS_NOT_FOUND.getHttpStatus())
.body(ApiResponse.onFailure(TEMP_RECOMMENDATIONS_NOT_FOUND.getCode(), e.getMessage(), null));
}

@ExceptionHandler(LetterAuthorMismatchException.class)
public ResponseEntity<ApiResponse<String>> 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<ApiResponse<String>> handleUnauthorizedLetterAccessException(
UnauthorizedLetterAccessException e) {
return ResponseEntity
.status(UNAUTHORIZED_LETTER_ACCESS.getHttpStatus())
.body(ApiResponse.onFailure(UNAUTHORIZED_LETTER_ACCESS.getCode(), e.getMessage(), null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package postman.bottler.letter.exception;

public class UnauthorizedLetterAccessException extends RuntimeException {
public UnauthorizedLetterAccessException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ public void saveAll(List<LetterBox> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,17 @@ public void deleteByCondition(List<Long> letterIds, LetterType letterType, BoxTy
)
.execute();
}

public void deleteByConditionAndUserId(List<Long> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ public void deleteByCondition(List<Long> letterIds, LetterType letterType, BoxTy
letterBoxQueryRepository.deleteByCondition(letterIds, letterType, boxType);
}

@Override
public void deleteByConditionAndUserId(List<Long> letterIds, LetterType letterType, BoxType boxType, Long userId) {
letterBoxQueryRepository.deleteByConditionAndUserId(letterIds, letterType, boxType, userId);
}

@Override
public List<Long> getReceivedLettersById(Long userId) {
return letterBoxQueryRepository.getReceivedLettersById(userId);
}

@Override
public void saveRecommendedLetters(List<LetterBox> letterBoxes) {
letterBoxJdbcRepository.saveAll(letterBoxes);
public boolean existsByLetterIdAndUserId(Long letterId, Long userId) {
return letterBoxJdbcRepository.existsByUserIdAndLetterId(letterId, userId);
}

private List<LetterHeadersResponseDTO> fetchLetters(Long userId, BoxType boxType, Pageable pageable) {
Expand Down
Loading
Loading