From 1311c88a369d0a426aef02db97f962a6bdde08ba Mon Sep 17 00:00:00 2001 From: juhoon Date: Thu, 11 Dec 2025 17:19:27 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat(api):=20jwtToken=20role=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20subject=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../address/controller/AddressController.java | 4 +- .../controller/CommentController.java | 2 +- .../CommunityCommentController.java | 6 +- .../service/CommunityCommentService.java | 16 +- .../controller/CommunityController.java | 12 +- .../service/CommunityLikeService.java | 8 +- .../service/CommunityService.java | 16 +- .../controller/InquiryController.java | 14 +- .../inquires/service/InquiryService.java | 28 ++-- ...indergartenInternshipReviewController.java | 8 +- .../KindergartenInternshipReviewService.java | 16 +- .../KindergartenWorkHistoryController.java | 6 +- .../KindergartenWorkHistoryService.java | 4 +- .../KindergartenWorkReviewController.java | 8 +- .../KindergartenWorkReviewService.java | 17 +- .../PushNotificationController.java | 36 ++-- .../service/PushNotificationService.java | 23 ++- .../reports/controller/ReportController.java | 4 +- .../domain/reports/service/ReportService.java | 8 +- .../user/controller/AdminUserController.java | 4 +- .../user/controller/UserApiController.java | 38 +++-- .../domain/user/dto/JwtUserInfoDto.java | 12 ++ .../user/repository/UserRepository.java | 6 +- .../domain/user/service/UserService.java | 157 +++++++----------- .../controller/UserBlockController.java | 6 +- .../userBlock/service/UserBlockService.java | 17 +- .../UserFavoriteKindergartenController.java | 6 +- .../UserFavoriteKindergartenService.java | 12 +- .../global/config/SecurityConfig.java | 7 +- .../global/facade/AddressFacade.java | 8 +- .../global/facade/CommunityFacade.java | 4 +- .../global/facade/KindergartenFacade.java | 28 ++-- .../facade/KindergartenWorkHistoryFacade.java | 8 +- .../global/facade/UserFacade.java | 60 ++++--- .../kindergarten/global/jwt/JwtFilter.java | 32 ++-- .../kindergarten/global/jwt/JwtProvider.java | 55 ++++-- 36 files changed, 365 insertions(+), 331 deletions(-) create mode 100644 src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java b/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java index 2408016..b594d6d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java @@ -26,7 +26,7 @@ public class AddressController { public void kindergartenRegionBatch( @AuthenticationPrincipal UserDetails userDetails ) { - addressFacade.regionBatch(userDetails.getUsername()); + addressFacade.regionBatch(Long.valueOf(userDetails.getUsername())); } @PostMapping("/batch/sub-region") @@ -34,7 +34,7 @@ public void kindergartenRegionBatch( public void kindergartenSubRegionBatch( @AuthenticationPrincipal UserDetails userDetails ) { - addressFacade.subRegionBatch(userDetails.getUsername()); + addressFacade.subRegionBatch(Long.valueOf(userDetails.getUsername())); } @GetMapping diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java index f06dff8..aa191b3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java @@ -23,7 +23,7 @@ public ResponseDto deleteComment( @PathVariable Long commentId, @AuthenticationPrincipal UserDetails userDetails ) { - commentService.deleteComment(commentId, userDetails.getUsername()); + commentService.deleteComment(commentId, Long.valueOf(userDetails.getUsername())); return ResponseDto.success("댓글이 삭제되었습니다."); } } \ No newline at end of file diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java index a324748..76ddf27 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java @@ -34,7 +34,7 @@ public ResponseDto createComment( @Valid @RequestBody CreateCommentRequestDTO dto, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(commentService.createComment(postId, dto, userDetails.getUsername())); + return ResponseDto.success(commentService.createComment(postId, dto, Long.valueOf(userDetails.getUsername()))); } @GetMapping @@ -62,7 +62,7 @@ public PageResponseDTO getAllComments( @PageableDefault(size = 30, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal UserDetails userDetails ) { - String email = userDetails != null ? userDetails.getUsername() : null; - return new PageResponseDTO<>(commentService.getAllCommentsWithReplies(postId, pageable, email)); + Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; + return new PageResponseDTO<>(commentService.getAllCommentsWithReplies(postId, pageable, userId)); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java index 0c852ed..de173a4 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java @@ -39,10 +39,10 @@ public class CommunityCommentService { /// 댓글 작성 (원댓글 또는 대댓글) @Transactional - public CommentResponseDTO createComment(Long postId, CreateCommentRequestDTO dto, String email) { + public CommentResponseDTO createComment(Long postId, CreateCommentRequestDTO dto, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 게시글 조회 (작성자 정보를 포함) CommunityPost post = postRepository.findByIdWithUser(postId) @@ -113,12 +113,12 @@ public List getReplies(Long commentId) { } /// 게시글의 모든 댓글과 대댓글 목록 조회 (계층 구조로 정렬) - public Page getAllCommentsWithReplies(Long postId, Pageable pageable, String email) { + public Page getAllCommentsWithReplies(Long postId, Pageable pageable, Long userId) { // 차단된 사용자 ID 목록 조회 List blockedUserIds = Collections.emptyList(); - if (email != null) { - User user = userService.getUserByEmail(email); + if (userId != null) { + User user = userService.getUserById(userId); blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); if (blockedUserIds == null) { blockedUserIds = Collections.emptyList(); @@ -144,16 +144,16 @@ public PageCommunityCommentsResponseDTO getWroteMyCommunityComments(Long userId, /// 댓글 삭제 (소프트 삭제) @Transactional - public void deleteComment(Long commentId, String email) { + public void deleteComment(Long commentId, Long userId) { // 댓글 조회 (작성자 정보 포함) CommunityComment comment = commentRepository.findByIdWithUser(commentId) .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_COMMENT)); // 현재 사용자 조회 - User currentUser = userService.getUserByEmail(email); + User currentUser = userService.getUserById(userId); // 작성자 또는 관리자 권한 확인 - if (!comment.getUser().getEmail().equals(email) && !currentUser.getRole().equals(UserRole.ADMIN)) { + if (!comment.getUser().getId().equals(userId) && !currentUser.getRole().equals(UserRole.ADMIN)) { throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java index 30dc22d..b9add42 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java @@ -38,7 +38,7 @@ public ResponseDto createPost( @Valid @RequestBody CreateCommunityPostRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(communityService.createPost(request, userDetails.getUsername())); + return ResponseDto.success(communityService.createPost(request, Long.valueOf(userDetails.getUsername()))); } @DeleteMapping("/{id}") @@ -47,7 +47,7 @@ public ResponseDto deletePost( @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails ) { - communityFacade.deletePost(id, userDetails.getUsername()); + communityFacade.deletePost(id, Long.valueOf(userDetails.getUsername())); return ResponseDto.success("게시글이 삭제되었습니다."); } @@ -58,8 +58,8 @@ public PageResponseDTO getPosts( @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal UserDetails userDetails ) { - String email = userDetails != null ? userDetails.getUsername() : null; - return new PageResponseDTO<>(communityService.getPosts(searchDTO, pageable, email)); + Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; + return new PageResponseDTO<>(communityService.getPosts(searchDTO, pageable, userId)); } @GetMapping("/{id}") @@ -82,7 +82,7 @@ public ResponseDto toggleLike( @PathVariable Long postId, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(communityLikeService.toggleLike(postId, userDetails.getUsername())); + return ResponseDto.success(communityLikeService.toggleLike(postId, Long.valueOf(userDetails.getUsername()))); } @GetMapping("/{postId}/like") @@ -91,6 +91,6 @@ public ResponseDto getLikeStatus( @PathVariable Long postId, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(communityLikeService.getLikeInfo(postId, userDetails.getUsername())); + return ResponseDto.success(communityLikeService.getLikeInfo(postId, Long.valueOf(userDetails.getUsername()))); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java index 88ec487..e3357e6 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java @@ -25,10 +25,10 @@ public class CommunityLikeService { private final NotificationTemplateService notificationTemplateService; /// 게시글 좋아요 상태 조회 및 좋아요 수 조회 - public CommunityLikeResponseDTO getLikeInfo(Long postId, String email) { + public CommunityLikeResponseDTO getLikeInfo(Long postId, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 한 번의 쿼리로 좋아요 상태와 개수를 함께 조회 return communityLikeRepository.findLikeInfo(postId, user) @@ -37,9 +37,9 @@ public CommunityLikeResponseDTO getLikeInfo(Long postId, String email) { /// 게시글 좋아요/좋아요 취소 토글 @Transactional - public CommunityLikeResponseDTO toggleLike(Long postId, String email) { + public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 게시글 존재 여부와 좋아요 여부를 한 번에 확인 return communityLikeRepository.findByUserAndPostId(user, postId) diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java index bbfeadb..429ed93 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java @@ -39,10 +39,10 @@ public class CommunityService { /// 게시글 생성 @Transactional - public CommunityPostResponseDTO createPost(CreateCommunityPostRequestDTO request, String email) { + public CommunityPostResponseDTO createPost(CreateCommunityPostRequestDTO request, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 커뮤니티 카테고리 조회 또는 생성 CommunityCategory communityCategory = communityCategoryRepository.findByCategoryName(request.getCommunityCategoryName()) @@ -65,12 +65,12 @@ public CommunityPostResponseDTO createPost(CreateCommunityPostRequestDTO request } /// 게시글 목록 조회 - public Page getPosts(CommunitySearchDTO searchDTO, Pageable pageable, String email) { + public Page getPosts(CommunitySearchDTO searchDTO, Pageable pageable, Long userId) { // 차단된 사용자 ID 목록 가져오기 List blockedUserIds = Collections.emptyList(); - if (email != null) { - User user = userService.getUserByEmail(email); + if (userId != null) { + User user = userService.getUserById(userId); blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); } @@ -109,17 +109,17 @@ public void refreshTopPostsCache() {} /// 게시글 삭제 (소프트 삭제) @Transactional - public void deletePost(Long postId, String email) { + public void deletePost(Long postId, Long userId) { // 게시글 조회 (작성자 정보 포함) CommunityPost post = communityRepository.findByIdWithUser(postId) .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); // 현재 사용자 조회 - User currentUser = userService.getUserByEmail(email); + User currentUser = userService.getUserById(userId); // 작성자 또는 관리자 권한 확인 - if (!post.getUser().getEmail().equals(email) && !currentUser.getRole().equals(UserRole.ADMIN)) { + if (!post.getUser().getId().equals(userId) && !currentUser.getRole().equals(UserRole.ADMIN)) { throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java index f930d35..02febc1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java @@ -31,7 +31,7 @@ public ResponseDto createInquiry( @Valid @RequestBody CreateInquiryRequestDTO dto, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(inquiryService.createInquiry(dto, userDetails.getUsername())); + return ResponseDto.success(inquiryService.createInquiry(dto, Long.valueOf(userDetails.getUsername()))); } @GetMapping("/{id}") @@ -40,7 +40,7 @@ public ResponseDto getInquiry( @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(inquiryService.getInquiry(id, userDetails.getUsername())); + return ResponseDto.success(inquiryService.getInquiry(id, Long.valueOf(userDetails.getUsername()))); } @GetMapping("/my") @@ -49,7 +49,7 @@ public PageResponseDTO getMyInquiries( @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal UserDetails userDetails ) { - return new PageResponseDTO<>(inquiryService.getUserInquiries(userDetails.getUsername(), pageable)); + return new PageResponseDTO<>(inquiryService.getUserInquiries(Long.valueOf(userDetails.getUsername()), pageable)); } @GetMapping("/all") @@ -58,7 +58,7 @@ public PageResponseDTO getAllInquiries( @PageableDefault Pageable pageable, @AuthenticationPrincipal UserDetails userDetails ) { - return new PageResponseDTO<>(inquiryService.getAllInquiries(userDetails.getUsername(), pageable)); + return new PageResponseDTO<>(inquiryService.getAllInquiries(Long.valueOf(userDetails.getUsername()), pageable)); } @GetMapping("/status/{status}") @@ -68,7 +68,7 @@ public PageResponseDTO getInquiriesByStatus( @PageableDefault Pageable pageable, @AuthenticationPrincipal UserDetails userDetails ) { - return new PageResponseDTO<>(inquiryService.getInquiriesByStatus(status, userDetails.getUsername(), pageable)); + return new PageResponseDTO<>(inquiryService.getInquiriesByStatus(status, Long.valueOf(userDetails.getUsername()), pageable)); } @PostMapping("/{id}/answer") @@ -78,7 +78,7 @@ public ResponseDto answerInquiry( @Valid @RequestBody AnswerInquiryRequestDTO dto, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(inquiryService.answerInquiry(id, dto, userDetails.getUsername())); + return ResponseDto.success(inquiryService.answerInquiry(id, dto, Long.valueOf(userDetails.getUsername()))); } @PostMapping("/{id}/close") @@ -87,6 +87,6 @@ public ResponseDto closeInquiry( @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(inquiryService.closeInquiry(id, userDetails.getUsername())); + return ResponseDto.success(inquiryService.closeInquiry(id, Long.valueOf(userDetails.getUsername()))); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java index 24ede36..33644f5 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java @@ -29,10 +29,10 @@ public class InquiryService { /// 문의 생성 @Transactional - public InquiryResponseDTO createInquiry(CreateInquiryRequestDTO dto, String email) { + public InquiryResponseDTO createInquiry(CreateInquiryRequestDTO dto, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 문의 생성 Inquiry inquiry = Inquiry.builder() @@ -46,10 +46,10 @@ public InquiryResponseDTO createInquiry(CreateInquiryRequestDTO dto, String emai } /// 문의 조회 (단일) - public InquiryResponseDTO getInquiry(Long id, String email) { + public InquiryResponseDTO getInquiry(Long id, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 문의 조회 Inquiry inquiry = inquiryRepository.findByIdWithUser(id) @@ -64,20 +64,20 @@ public InquiryResponseDTO getInquiry(Long id, String email) { } /// 내 문의 목록 조회 - public Page getUserInquiries(String email, Pageable pageable) { + public Page getUserInquiries(Long userId, Pageable pageable) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 문의 목록 조회 return inquiryRepository.findDtosByUser(user, pageable); } /// 모든 문의 목록 조회 (관리자 전용) - public Page getAllInquiries(String email, Pageable pageable) { + public Page getAllInquiries(Long userId, Pageable pageable) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 관리자 권한 체크 if (!user.getRole().equals(UserRole.ADMIN)) { @@ -88,10 +88,10 @@ public Page getAllInquiries(String email, Pageable pageable) } /// 상태별 문의 목록 조회 (관리자 전용) - public Page getInquiriesByStatus(InquiryStatus status, String email, Pageable pageable) { + public Page getInquiriesByStatus(InquiryStatus status, Long userId, Pageable pageable) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 관리자 권한 체크 if (!user.getRole().equals(UserRole.ADMIN)) { @@ -103,10 +103,10 @@ public Page getInquiriesByStatus(InquiryStatus status, Strin /// 문의 답변 (관리자 전용) @Transactional - public InquiryResponseDTO answerInquiry(Long id, AnswerInquiryRequestDTO dto, String email) { + public InquiryResponseDTO answerInquiry(Long id, AnswerInquiryRequestDTO dto, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 관리자 권한 체크 if (!user.getRole().equals(UserRole.ADMIN)) { @@ -132,10 +132,10 @@ public InquiryResponseDTO answerInquiry(Long id, AnswerInquiryRequestDTO dto, St /// 문의 마감 (관리자 전용) @Transactional - public InquiryResponseDTO closeInquiry(Long id, String email) { + public InquiryResponseDTO closeInquiry(Long id, Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 관리자 권한 체크 if (!user.getRole().equals(UserRole.ADMIN)) { diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java index 32b95bd..b53c316 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java @@ -28,7 +28,7 @@ public void createInternshipReview( @RequestBody CreateInternshipReviewRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.createInternshipReview(request, userDetails.getUsername()); + kindergartenFacade.createInternshipReview(request, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "실습리뷰-02 리뷰 수정", description = "리뷰 수정") @@ -37,7 +37,7 @@ public void modifyInternshipReview( @RequestBody ModifyInternshipReviewRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.modifyInternshipReview(request, userDetails.getUsername()); + kindergartenFacade.modifyInternshipReview(request, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "실습리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") @@ -46,7 +46,7 @@ public void likeInternshipReview( @PathVariable("internshipReviewId") long id, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenInternshipReviewService.likeInternshipReview(id, userDetails.getUsername()); + kindergartenInternshipReviewService.likeInternshipReview(id, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "실습리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") @@ -68,7 +68,7 @@ public ResponseDto deleteInternshipReview( @PathVariable("internshipReviewId") Long id, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.deleteInternshipReview(id, userDetails.getUsername()); + kindergartenFacade.deleteInternshipReview(id, Long.valueOf(userDetails.getUsername())); return ResponseDto.success("실습 리뷰가 삭제되었습니다."); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java index 1427021..ea72802 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java @@ -35,8 +35,8 @@ public class KindergartenInternshipReviewService { private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; private final KindergartenInternshipReviewLikeHistoryRepository kindergartenInternshipReviewLikeHistoryRepository; - public Kindergarten createInternshipReview(CreateInternshipReviewRequestDTO request, String email) { - User user = userService.getUserByEmail(email); + public Kindergarten createInternshipReview(CreateInternshipReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); @@ -66,8 +66,8 @@ public Kindergarten createInternshipReview(CreateInternshipReviewRequestDTO requ return kindergarten; } - public Kindergarten modifyInternshipReview(ModifyInternshipReviewRequestDTO request, String email) { - User user = userService.getUserByEmail(email); + public Kindergarten modifyInternshipReview(ModifyInternshipReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); KindergartenInternshipReview review = kindergartenInternshipReviewRepository @@ -89,8 +89,8 @@ public Kindergarten modifyInternshipReview(ModifyInternshipReviewRequestDTO requ } @Transactional - public void likeInternshipReview(long reviewId, String email) { - User user = userService.getUserByEmail(email); + public void likeInternshipReview(long reviewId, Long userId) { + User user = userService.getUserById(userId); KindergartenInternshipReview review = kindergartenInternshipReviewRepository.findById(reviewId) .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); @@ -163,8 +163,8 @@ public InternshipReviewPagedResponseDTO getReviews(Long kindergartenId, int page } /// 내가 작성한 실습 리뷰 조회 - public InternshipReviewPagedResponseDTO getMyReviews(String email, int page, int size) { - User user = userService.getUserByEmail(email); + public InternshipReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { + User user = userService.getUserById(userId); Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); Page reviewPage = kindergartenInternshipReviewRepository.findMyReviews( diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java index c732e02..ead001b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java @@ -27,13 +27,13 @@ public class KindergartenWorkHistoryController { public ResponseDto addCertification( @AuthenticationPrincipal UserDetails userDetails, @RequestBody KindergartenWorkHistoryRequest request) { - return ResponseDto.success(kindergartenWorkHistoryFacade.addCertification(userDetails.getUsername(), request)); + return ResponseDto.success(kindergartenWorkHistoryFacade.addCertification(Long.valueOf(userDetails.getUsername()), request)); } @GetMapping @Operation(summary = "유치원 근무 이력 조회", description = "유치원 근무 이력을 조회합니다.") public ResponseDto> getCertification(@AuthenticationPrincipal UserDetails userDetails) { - return ResponseDto.success(workHistoryService.getCertification(userDetails.getUsername())); + return ResponseDto.success(workHistoryService.getCertification(Long.valueOf(userDetails.getUsername()))); } @DeleteMapping("/{certificationId}") @@ -41,7 +41,7 @@ public ResponseDto> getCertification(@Auth public ResponseDto deleteCertification( @AuthenticationPrincipal UserDetails userDetails, @PathVariable Long certificationId) { - kindergartenWorkHistoryFacade.deleteCertification(userDetails.getUsername(), certificationId); + kindergartenWorkHistoryFacade.deleteCertification(Long.valueOf(userDetails.getUsername()), certificationId); return ResponseDto.success(null); } } \ No newline at end of file diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java index 0b508ae..93f6bac 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java @@ -37,10 +37,10 @@ public KindergartenWorkHistoryResponse addCertification(User user, Kindergarten } /// 유치원 근무 이력 조회 - public List getCertification(String email) { + public List getCertification(Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 유치원 근무 이력 조회 return workHistoryRepository.findDtosByUser(user); diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java index 1ed45ac..9da8070 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java @@ -28,7 +28,7 @@ public void createWorkReview( @RequestBody CreateWorkReviewRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.createWorkReview(request, userDetails.getUsername()); + kindergartenFacade.createWorkReview(request, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "근무리뷰-02 리뷰 수정", description = "리뷰 수정") @@ -37,7 +37,7 @@ public void modifyWorkReview( @RequestBody ModifyWorkReviewRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.modifyWorkReview(request, userDetails.getUsername()); + kindergartenFacade.modifyWorkReview(request, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "근무리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") @@ -46,7 +46,7 @@ public void likeWorkReview( @PathVariable("workReviewId") long id, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenWorkReviewService.likeWorkReview(id, userDetails.getUsername()); + kindergartenWorkReviewService.likeWorkReview(id, Long.valueOf(userDetails.getUsername())); } @Operation(summary = "근무리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") @@ -68,7 +68,7 @@ public ResponseDto deleteWorkReview( @PathVariable("workReviewId") Long id, @AuthenticationPrincipal UserDetails userDetails ) { - kindergartenFacade.deleteWorkReview(id, userDetails.getUsername()); + kindergartenFacade.deleteWorkReview(id, Long.valueOf(userDetails.getUsername())); return ResponseDto.success("근무 리뷰가 삭제되었습니다."); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java index 0ab5d6a..dc4c6cd 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java @@ -39,8 +39,8 @@ public class KindergartenWorkReviewService { private final KindergartenWorkReviewRepository kindergartenWorkReviewRepository; @Transactional - public Kindergarten createWorkReview(CreateWorkReviewRequestDTO request, String email) { - User user = userService.getUserByEmail(email); + public Kindergarten createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); boolean exists = workReviewRepository.existsByUserAndKindergarten(user, kindergarten); @@ -74,8 +74,8 @@ public Kindergarten createWorkReview(CreateWorkReviewRequestDTO request, String } @Transactional - public Kindergarten modifyWorkReview(ModifyWorkReviewRequestDTO request, String email) { - User user = userService.getUserByEmail(email); + public Kindergarten modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); KindergartenWorkReview review = workReviewRepository.findById(request.getWorkReviewId()) @@ -94,8 +94,8 @@ public Kindergarten modifyWorkReview(ModifyWorkReviewRequestDTO request, String } @Transactional - public void likeWorkReview(long reviewId, String email) { - User user = userService.getUserByEmail(email); + public void likeWorkReview(long reviewId, Long userId) { + User user = userService.getUserById(userId); KindergartenWorkReview review = workReviewRepository.findById(reviewId) .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); @@ -191,12 +191,11 @@ public WorkReviewPagedResponseDTO getReviews(Long kindergartenId, int page, int } /// 내가 작성한 근무 리뷰 조회 - public WorkReviewPagedResponseDTO getMyReviews(String email, int page, int size) { - User user = userService.getUserByEmail(email); + public WorkReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); Page reviewPage = workReviewRepository.findMyReviews( - user.getId(), + userId, ReviewStatus.ACCEPTED, pageable ); diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java index 5ac2bb8..e8cde89 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java @@ -23,50 +23,64 @@ public class PushNotificationController { @PostMapping("/save") @Operation(summary = "푸시 알림 저장(for 배치)", description = "알림을 전송합니다.") - public ResponseDto sendNotification(@RequestBody PushNotificationRequestDTO requestDTO) { + public ResponseDto sendNotification( + @RequestBody PushNotificationRequestDTO requestDTO + ) { pushNotificationService.savePushNotification(requestDTO); return ResponseDto.success("알림이 성공적으로 전송되었습니다."); } @GetMapping("/user/{userId}") @Operation(summary = "사용자의 모든 알림 조회", description = "사용자의 모든 알림을 조회합니다.") - public ResponseDto> getUserNotifications(@PathVariable Long userId) { + public ResponseDto> getUserNotifications( + @PathVariable Long userId + ) { List notifications = pushNotificationService.getUserNotifications(userId); return ResponseDto.success(notifications); } @GetMapping("/my") @Operation(summary = "현재 로그인한 사용자의 알림 조회", description = "현재 로그인한 사용자의 알림을 조회합니다.") - public ResponseDto> getMyNotifications(@AuthenticationPrincipal UserDetails userDetails) { - List notifications = pushNotificationService.getUserNotificationByUserDetails(userDetails); + public ResponseDto> getMyNotifications( + @AuthenticationPrincipal UserDetails userDetails + ) { + List notifications = pushNotificationService.getUserNotificationByUserDetails(Long.valueOf(userDetails.getUsername())); return ResponseDto.success(notifications); } @GetMapping("/my/unread") @Operation(summary = "현재 로그인한 사용자의 읽지 않은 알림 조회", description = "현재 로그인한 사용자의 읽지 않은 알림을 조회합니다.") - public ResponseDto> getMyUnreadNotifications(@AuthenticationPrincipal UserDetails userDetails) { - List notifications = pushNotificationService.getUnreadNotificationsByUserDetails(userDetails); + public ResponseDto> getMyUnreadNotifications( + @AuthenticationPrincipal UserDetails userDetails + ) { + List notifications = pushNotificationService.getUnreadNotificationsByUserDetails(Long.valueOf(userDetails.getUsername())); return ResponseDto.success(notifications); } @GetMapping("/my/unread/count") @Operation(summary = "현재 로그인한 사용자의 읽지 않은 알림 개수 조회", description = "현재 로그인한 사용자의 읽지 않은 알림 개수를 조회합니다.") - public ResponseDto getUnreadCount(@AuthenticationPrincipal UserDetails userDetails) { - long count = pushNotificationService.countUnreadNotifications(userDetails); + public ResponseDto getUnreadCount( + @AuthenticationPrincipal UserDetails userDetails + ) { + long count = pushNotificationService.countUnreadNotifications(Long.valueOf(userDetails.getUsername())); return ResponseDto.success(count); } @PatchMapping("/{notificationId}/read") @Operation(summary = "알림 읽음 표시", description = "알림을 읽음 처리합니다.") - public ResponseDto markAsRead(@PathVariable Long notificationId) { + public ResponseDto markAsRead( + @PathVariable Long notificationId + ) { pushNotificationService.markAsRead(notificationId); return ResponseDto.success("알림이 읽음 처리되었습니다."); } @PatchMapping("/my/read-all") @Operation(summary = "모든 알림 읽음 표시", description = "모든 알림을 읽음 처리합니다.") - public ResponseDto markAllAsRead(@AuthenticationPrincipal UserDetails userDetails) { - pushNotificationService.markAllAsRead(userDetails); + public ResponseDto markAllAsRead( + @AuthenticationPrincipal UserDetails userDetails + ) { + pushNotificationService.markAllAsRead(Long.valueOf(userDetails.getUsername())); return ResponseDto.success("모든 알림이 읽음 처리되었습니다."); } } \ No newline at end of file diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java index 871ffe8..04dda9f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java @@ -8,11 +8,11 @@ import com.onebyone.kindergarten.domain.pushNotification.repository.PushNotificationRepository; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.repository.UserRepository; +import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -41,6 +41,7 @@ public class PushNotificationService { private final FirebaseMessaging firebaseMessaging; private final PushNotificationRepository pushNotificationRepository; private final UserRepository userRepository; + private final UserService userService; /// 알림 전송 여부 확인 ( 내부 메서드 ) private boolean shouldSendNotification(User user, NotificationType type) { @@ -349,11 +350,10 @@ public List getUserNotifications(Long userId) { /// 현재 로그인한 사용자의 알림 조회 @Transactional(readOnly = true) - public List getUserNotificationByUserDetails(UserDetails userDetails) { + public List getUserNotificationByUserDetails(Long userId) { // 사용자 조회 - User user = userRepository.findByEmailAndDeletedAtIsNull(userDetails.getUsername()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + User user = userService.getUserById(userId); // 모든 알림 조회 return pushNotificationRepository.findByUserOrderByCreatedAtDesc(user) @@ -365,11 +365,10 @@ public List getUserNotificationByUserDetails(UserDe /// 사용자의 읽지 않은 알림 조회 @Transactional(readOnly = true) - public List getUnreadNotificationsByUserDetails(UserDetails userDetails) { + public List getUnreadNotificationsByUserDetails(Long userId) { // 사용자 조회 - User user = userRepository.findByEmailAndDeletedAtIsNull(userDetails.getUsername()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + User user = userService.getUserById(userId); // 읽지 않은 알림 조회 return pushNotificationRepository.findByUserAndIsReadFalseOrderByCreatedAtDesc(user) @@ -393,11 +392,10 @@ public void markAsRead(Long notificationId) { /// 사용자의 모든 알림 읽음 표시 @Transactional - public void markAllAsRead(UserDetails userDetails) { + public void markAllAsRead(Long userId) { // 사용자 조회 - User user = userRepository.findByEmailAndDeletedAtIsNull(userDetails.getUsername()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + User user = userService.getUserById(userId); // 모든 알림 읽음 처리 pushNotificationRepository.markAllAsRead(user, LocalDateTime.now()); @@ -405,11 +403,10 @@ public void markAllAsRead(UserDetails userDetails) { /// 읽지 않은 알림 개수 조회 @Transactional(readOnly = true) - public Long countUnreadNotifications(UserDetails userDetails) { + public Long countUnreadNotifications(Long userId) { // 사용자 조회 - User user = userRepository.findByEmailAndDeletedAtIsNull(userDetails.getUsername()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + User user = userService.getUserById(userId); // 읽지 않은 알림 개수 조회 return pushNotificationRepository.countByUserAndIsReadFalse(user); diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java index 350b53d..eb9fab4 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java @@ -30,7 +30,7 @@ public ResponseDto createReport( @Valid @RequestBody CreateReportRequestDTO dto, @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(reportService.createReport(dto, userDetails.getUsername())); + return ResponseDto.success(reportService.createReport(dto, Long.valueOf(userDetails.getUsername()))); } @GetMapping("/my") @@ -39,7 +39,7 @@ public PageResponseDTO getMyReports( @AuthenticationPrincipal UserDetails userDetails, @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { - return new PageResponseDTO<>(reportService.getMyReports(userDetails.getUsername(), pageable)); + return new PageResponseDTO<>(reportService.getMyReports(Long.valueOf(userDetails.getUsername()), pageable)); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java b/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java index 517111f..990c1a5 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java @@ -40,10 +40,10 @@ public class ReportService { /// 신고 생성 (사용자) @Transactional - public ReportResponseDTO createReport(CreateReportRequestDTO dto, String email) { + public ReportResponseDTO createReport(CreateReportRequestDTO dto, Long userId) { // 사용자 조회 - User reporter = userService.getUserByEmail(email); + User reporter = userService.getUserById(userId); // 신고 대상 존재 여부 확인을 위한 메서드 추가 validateReportTarget(dto.getTargetType(), dto.getTargetId()); @@ -62,10 +62,10 @@ public ReportResponseDTO createReport(CreateReportRequestDTO dto, String email) } /// 내 신고 목록 조회 (사용자) - public Page getMyReports(String email, Pageable pageable) { + public Page getMyReports(Long userId, Pageable pageable) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 신고 목록 조회 return reportRepository.findDtosByReporter(user, pageable); diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java b/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java index 8a3712c..461cba8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java @@ -50,7 +50,7 @@ public PageResponseDTO searchUsers( public ResponseDto getUserDetail( @PathVariable Long userId ) { - AdminUserResponseDTO user = userService.getUserById(userId); + AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); return ResponseDto.success(user); } @@ -61,7 +61,7 @@ public ResponseDto updateUserStatus( @Valid @RequestBody UpdateUserStatusRequestDTO request, @AuthenticationPrincipal UserDetails userDetails ) { - userService.updateUserStatus(userId, request, userDetails.getUsername()); + userService.updateUserStatus(userId, request); return ResponseDto.success("유저 상태가 변경되었습니다."); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java index f189768..4b98874 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java @@ -1,6 +1,7 @@ package com.onebyone.kindergarten.domain.user.controller; import com.onebyone.kindergarten.domain.communityComments.dto.response.PageCommunityCommentsResponseDTO; +import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.global.facade.UserFacade; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewPagedResponseDTO; @@ -17,6 +18,7 @@ import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; import com.onebyone.kindergarten.global.jwt.JwtProvider; +import io.jsonwebtoken.Claims; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -33,7 +35,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -@Tag(name = "User API", description = "유저 API") +@Tag(name = "유저 API", description = "유저 API") @RestController @RequestMapping("/users") @RequiredArgsConstructor @@ -45,7 +47,7 @@ public class UserApiController { @Operation(summary = "유저-01 회원가입", description = "계정 생성합니다.") @PostMapping("/sign-up") public SignUpResponseDTO signUp( - @RequestBody @Valid final SignUpRequestDTO request) { + @RequestBody SignUpRequestDTO request) { return userFacade.signUp(request); } @@ -61,7 +63,7 @@ public SignInResponseDTO signIn( public void changePassword( @AuthenticationPrincipal UserDetails userDetails, @RequestBody ModifyUserPasswordRequestDTO request) { - userService.changePassword(userDetails.getUsername(), request); + userService.changePassword(Long.valueOf(userDetails.getUsername()), request); } @Operation(summary = "유저-04 닉네임 변경", description = "닉네임 변경입니다.") @@ -69,21 +71,21 @@ public void changePassword( public void changeNickname( @AuthenticationPrincipal UserDetails userDetails, @RequestBody ModifyUserNicknameRequestDTO request) { - userService.changeNickname(userDetails.getUsername(), request); + userService.changeNickname(Long.valueOf(userDetails.getUsername()), request); } @Operation(summary = "유저-05 회원탈퇴", description = "회원 탈퇴입니다.") @PostMapping("/withdraw") public void withdraw( @AuthenticationPrincipal UserDetails userDetails) { - userService.withdraw(userDetails.getUsername()); + userService.withdraw(Long.valueOf(userDetails.getUsername())); } @Operation(summary = "유저-06 유저정보", description = "유저 조회입니다.") @GetMapping public GetUserResponseDTO getUser( @AuthenticationPrincipal UserDetails userDetails) { - return new GetUserResponseDTO(userService.getUser(userDetails.getUsername())); + return new GetUserResponseDTO(userService.getUserToDTO(Long.valueOf(userDetails.getUsername()))); } @Operation(summary = "유저-07 토큰 재발급", description = "토큰 재발급입니다.") @@ -98,10 +100,10 @@ public ReIssueResponseDTO reissue( String refreshToken = authHeader.substring(7); - String email = jwtProvider.getEmailFromRefreshToken(refreshToken); + Claims claim = jwtProvider.getClaimFromRefreshToken(refreshToken); - String newAccessToken = jwtProvider.generateAccessToken(email); - String newRefreshToken = jwtProvider.generateRefreshToken(email); + String newAccessToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), (UserRole) claim.get("role")); + String newRefreshToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), (UserRole) claim.get("role")); return ReIssueResponseDTO.builder() .accessToken(newAccessToken) @@ -165,7 +167,7 @@ public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { - return userFacade.getWroteMyCommunityComments(userDetails.getUsername(), page, size); + return userFacade.getWroteMyCommunityComments(Long.valueOf(userDetails.getUsername()), page, size); } @Operation(summary = "유저-11 홈 바로가기 정보 업데이트", description = "사용자의 홈 바로가기 정보를 업데이트합니다.") @@ -173,7 +175,7 @@ public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( public UpdateHomeShortcutsResponseDTO updateHomeShortcut( @AuthenticationPrincipal UserDetails userDetails, @Valid @RequestBody HomeShortcutsDto request) { - userService.updateHomeShortcut(userDetails.getUsername(), request); + userService.updateHomeShortcut(Long.valueOf(userDetails.getUsername()), request); return UpdateHomeShortcutsResponseDTO.success(); } @@ -204,7 +206,7 @@ public ResponseEntity checkEmailCertification( public ResponseEntity updateUserRole( @AuthenticationPrincipal UserDetails userDetails, @RequestBody UpdateUserRoleRequestDTO request) { - userService.updateUserRole(userDetails.getUsername(), request); + userService.updateUserRole(Long.valueOf(userDetails.getUsername()), request); return ResponseEntity.ok("권한이 변경되었습니다."); } @@ -228,7 +230,7 @@ public InternshipReviewPagedResponseDTO getMyInternshipReviews( @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { - return userFacade.getMyInternshipReviews(userDetails.getUsername(), page, size); + return userFacade.getMyInternshipReviews(Long.valueOf(userDetails.getUsername()), page, size); } @Operation(summary = "유저-017 내가 작성한 근무 리뷰 조회", description = "내가 작성한 근무 리뷰를 조회합니다.") @@ -237,13 +239,15 @@ public WorkReviewPagedResponseDTO getMyWorkReviews( @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { - return userFacade.getMyWorkReviews(userDetails.getUsername(), page, size); + return userFacade.getMyWorkReviews(Long.valueOf(userDetails.getUsername()), page, size); } @Operation(summary = "유저-018 알림 설정 조회", description = "사용자의 알림 설정을 조회합니다.") @GetMapping("/notification-settings") - public ResponseDto getNotificationSettings(@AuthenticationPrincipal UserDetails userDetails) { - NotificationSettingsDTO settings = userService.getNotificationSettings(userDetails.getUsername()); + public ResponseDto getNotificationSettings( + @AuthenticationPrincipal UserDetails userDetails + ) { + NotificationSettingsDTO settings = userService.getNotificationSettings(Long.valueOf(userDetails.getUsername())); return ResponseDto.success(settings); } @@ -252,7 +256,7 @@ public ResponseDto getNotificationSettings(@Authenticat public ResponseDto updateNotificationSettings( @AuthenticationPrincipal UserDetails userDetails, @RequestBody NotificationSettingsDTO request) { - NotificationSettingsDTO updatedSettings = userService.updateNotificationSettings(userDetails.getUsername(), request); + NotificationSettingsDTO updatedSettings = userService.updateNotificationSettings(Long.valueOf(userDetails.getUsername()), request); return ResponseDto.success(updatedSettings); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java new file mode 100644 index 0000000..0142d38 --- /dev/null +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java @@ -0,0 +1,12 @@ +package com.onebyone.kindergarten.domain.user.dto; + +import com.onebyone.kindergarten.domain.user.enums.UserRole; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class JwtUserInfoDto { + private Long userId; + private UserRole role; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java b/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java index 31ca2d9..856e0eb 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java @@ -17,8 +17,8 @@ public interface UserRepository extends JpaRepository { Optional findByEmailAndDeletedAtIsNull(String email); - @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.email = :email AND u.deletedAt IS NULL") - Optional findUserWithKindergarten(@Param("email") String email); + @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.id = :userId AND u.deletedAt IS NULL") + Optional findIdWithKindergarten(@Param("userId") Long userId); Optional findByEmailAndDeletedAtIsNotNull(String email); @@ -73,4 +73,6 @@ Page findUsersWithFilters( /// 모든 활성 사용자 조회 @Query("SELECT u FROM user u WHERE u.deletedAt IS NULL") List findAllActiveUsers(); + + Optional findByIdAndDeletedAtIsNull(Long userId); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java index 0d0e247..9395e63 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java @@ -37,20 +37,23 @@ public class UserService { private final EmailCertificationRepository emailCertificationRepository; @Transactional - public String signUp(SignUpRequestDTO request) { + public JwtUserInfoDto signUp(SignUpRequestDTO request) { if (isExistedEmail(request.getEmail())) { throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); } - EmailCertification emailCertification = emailCertificationRepository.findByEmail(request.getEmail()); - if (emailCertification == null || !emailCertification.isCertificated()) { - throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); - } +// EmailCertification emailCertification = emailCertificationRepository.findByEmail(request.getEmail()); +// if (emailCertification == null || !emailCertification.isCertificated()) { +// throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); +// } String encodedPassword = encodePassword(request.getPassword()); User user = userRepository.save(request.toEntity(encodedPassword)); - return user.getEmail(); + return new JwtUserInfoDto( + user.getId(), + user.getRole() + ); } @Transactional(readOnly = true) @@ -63,7 +66,7 @@ private String encodePassword(String password) { } @Transactional - public String signIn(SignInRequestDTO request) { + public JwtUserInfoDto signIn(SignInRequestDTO request) { // 먼저 활성 사용자 확인 Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(request.getEmail()); if (activeUser.isPresent()) { @@ -75,8 +78,11 @@ public String signIn(SignInRequestDTO request) { if (request.getFcmToken() != null) { user.updateFcmToken(request.getFcmToken()); } - - return user.getEmail(); + + return new JwtUserInfoDto( + user.getId(), + user.getRole() + ); } // 탈퇴된 사용자 확인 및 복구 @@ -93,16 +99,19 @@ public String signIn(SignInRequestDTO request) { if (request.getFcmToken() != null) { user.updateFcmToken(request.getFcmToken()); } - - return user.getEmail(); + + return new JwtUserInfoDto( + user.getId(), + user.getRole() + ); } throw new BusinessException(ErrorCodes.NOT_FOUND_EMAIL); } @Transactional - public void changeNickname(String email, ModifyUserNicknameRequestDTO request) { - User user = findUser(email); + public void changeNickname(Long userId, ModifyUserNicknameRequestDTO request) { + User user = getUserById(userId); // 현재 닉네임과 동일한지 확인 if (user.getNickname().equals(request.getNewNickname())) { @@ -113,8 +122,8 @@ public void changeNickname(String email, ModifyUserNicknameRequestDTO request) { } @Transactional - public void changePassword(String email, ModifyUserPasswordRequestDTO request) { - User user = findUser(email); + public void changePassword(Long userId, ModifyUserPasswordRequestDTO request) { + User user = getUserById(userId); if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); @@ -123,14 +132,9 @@ public void changePassword(String email, ModifyUserPasswordRequestDTO request) { user.changePassword(passwordEncoder.encode(request.getNewPassword())); } - private User findUser(String email) { - return userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - } - @Transactional - public void withdraw(String email) { - User user = findUser(email); + public void withdraw(Long userId) { + User user = getUserById(userId); user.withdraw(); } @@ -149,13 +153,18 @@ public void removeCareer(User user, LocalDate startDate, LocalDate endDate) { user.updateCareer(String.valueOf(careerMonths)); } - public UserDTO getUser(String email) { - return UserDTO.from(userRepository.findUserWithKindergarten(email) + public User getUserById(Long userId) { + return userRepository.findByIdAndDeletedAtIsNull(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + } + + public UserDTO getUserToDTO(Long userId) { + return UserDTO.from(userRepository.findIdWithKindergarten(userId) .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL))); } @Transactional - public String signUpByKakao(KakaoUserResponse userResponse) { + public User signUpByKakao(KakaoUserResponse userResponse) { String email = userResponse.getKakao_account().getEmail(); String nickname; @@ -176,7 +185,7 @@ public String signUpByKakao(KakaoUserResponse userResponse) { // 활성 사용자 확인 Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); if (activeUser.isPresent()) { - return email; + return activeUser.get(); } // 탈퇴된 사용자 확인 및 복구 @@ -189,8 +198,8 @@ public String signUpByKakao(KakaoUserResponse userResponse) { if (userResponse.getKakao_account().getProfile() != null) { user.updateProfileImageUrl(userResponse.getKakao_account().getProfile().getProfile_image_url()); } - - return user.getEmail(); + + return user; } // 새로운 사용자 생성 @@ -205,31 +214,17 @@ public String signUpByKakao(KakaoUserResponse userResponse) { userRepository.save(user); - return user.getEmail(); + return user; } @Transactional - public String signUpByKakao(KakaoUserResponse userResponse, String fcmToken) { - String email = signUpByKakao(userResponse); - - // FCM 토큰이 있으면 업데이트 - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - user.updateFcmToken(fcmToken); - } - - return email; - } - - @Transactional - public String signUpByNaver(NaverUserResponse userResponse) { + public User signUpByNaver(NaverUserResponse userResponse) { String email = userResponse.getResponse().getEmail(); // 활성 사용자 확인 Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); if (activeUser.isPresent()) { - return email; + return activeUser.get(); } // 탈퇴된 사용자 확인 및 복구 @@ -243,7 +238,7 @@ public String signUpByNaver(NaverUserResponse userResponse) { user.updateProfileImageUrl(userResponse.getResponse().getProfile_image()); } - return user.getEmail(); + return user; } // 새로운 사용자 생성 @@ -271,25 +266,11 @@ public String signUpByNaver(NaverUserResponse userResponse) { userRepository.save(user); - return user.getEmail(); + return user; } @Transactional - public String signUpByNaver(NaverUserResponse userResponse, String fcmToken) { - String email = signUpByNaver(userResponse); - - // FCM 토큰이 있으면 업데이트 - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - user.updateFcmToken(fcmToken); - } - - return email; - } - - @Transactional - public String signUpByApple(AppleUserResponse userResponse) { + public User signUpByApple(AppleUserResponse userResponse) { String appleUserId = userResponse.getSub(); String providedEmail = userResponse.getEmail(); @@ -311,7 +292,7 @@ public String signUpByApple(AppleUserResponse userResponse) { // 활성 사용자 확인 Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(systemEmail); if (activeUser.isPresent()) { - return systemEmail; + return activeUser.get(); } // 탈퇴된 사용자 확인 및 복구 @@ -319,7 +300,7 @@ public String signUpByApple(AppleUserResponse userResponse) { if (deletedUser.isPresent()) { User user = deletedUser.get(); user.restore(); - return user.getEmail(); + return user; } // 새로운 사용자 생성 @@ -338,26 +319,12 @@ public String signUpByApple(AppleUserResponse userResponse) { userRepository.save(user); - return user.getEmail(); - } - - @Transactional - public String signUpByApple(AppleUserResponse userResponse, String fcmToken) { - String email = signUpByApple(userResponse); - - // FCM 토큰이 있으면 업데이트 - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - user.updateFcmToken(fcmToken); - } - - return email; + return user; } @Transactional - public void updateHomeShortcut(String email, HomeShortcutsDto homeShortcutsDto) { - User user = findUser(email); + public void updateHomeShortcut(Long userId, HomeShortcutsDto homeShortcutsDto) { + User user = getUserById(userId); user.updateHomeShortcut(homeShortcutsDto.toJson()); } @@ -406,13 +373,13 @@ public void checkEmailCertification(CheckEmailCertificationRequestDTO request) { } @Transactional - public void updateUserRole(String email, UpdateUserRoleRequestDTO request) { - User user = findUser(email); + public void updateUserRole(Long userId, UpdateUserRoleRequestDTO request) { + User user = getUserById(userId); user.updateUserRole(request.getRole()); } public void updateTemporaryPassword(String email, String number) { - User user = findUser(email); + User user = getUserByEmail(email); user.changePassword(passwordEncoder.encode(number)); } @@ -430,9 +397,8 @@ public void checkEmailCertificationByTemporaryPassword(String email, String code } @Transactional(readOnly = true) - public NotificationSettingsDTO getNotificationSettings(String email) { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + public NotificationSettingsDTO getNotificationSettings(Long userId) { + User user = getUserById(userId); return NotificationSettingsDTO.builder() .allNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) @@ -442,9 +408,8 @@ public NotificationSettingsDTO getNotificationSettings(String email) { } @Transactional - public NotificationSettingsDTO updateNotificationSettings(String email, NotificationSettingsDTO request) { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + public NotificationSettingsDTO updateNotificationSettings(Long userId, NotificationSettingsDTO request) { + User user = getUserById(userId); Set enabledSettings = new HashSet<>(); if (request.isAllNotificationsEnabled()) { @@ -463,8 +428,8 @@ public NotificationSettingsDTO updateNotificationSettings(String email, Notifica } @Transactional - public void markUserAsReviewWriter(String email) { - User user = getUserByEmail(email); + public void markUserAsReviewWriter(Long userId) { + User user = getUserById(userId); if (!user.hasWrittenReview()) { user.markAsReviewWriter(); } @@ -495,17 +460,17 @@ public Page searchUsers(UserSearchDTO searchDTO, Pageable } @Transactional(readOnly = true) - public AdminUserResponseDTO getUserById(Long userId) { + public AdminUserResponseDTO getUserToAdminDTO(Long userId) { User user = userRepository.findByIdWithKindergarten(userId) - .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다. ID: " + userId)); + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); return AdminUserResponseDTO.from(user); } /// 관리자용 - 유저 상태 변경 @Transactional - public void updateUserStatus(Long userId, UpdateUserStatusRequestDTO request, String adminEmail) { + public void updateUserStatus(Long userId, UpdateUserStatusRequestDTO request) { // 관리자 권한 확인 - User admin = getUserByEmail(adminEmail); + User admin = getUserById(userId); if (!admin.getRole().equals(UserRole.ADMIN)) { throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java index 469afcd..99f397a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java @@ -26,7 +26,7 @@ public ResponseDto blockUser( @AuthenticationPrincipal UserDetails userDetails, @RequestBody UserBlockRequestDto request ) { - userBlockService.blockUser(userDetails, request.getTargetUserEmail()); + userBlockService.blockUser(Long.valueOf(userDetails.getUsername()), request.getTargetUserEmail()); return ResponseDto.success(null); } @@ -36,7 +36,7 @@ public ResponseDto unblockUser( @AuthenticationPrincipal UserDetails userDetails, @PathVariable String targetUserEmail ) { - userBlockService.unblockUser(userDetails, targetUserEmail); + userBlockService.unblockUser(Long.valueOf(userDetails.getUsername()), targetUserEmail); return ResponseDto.success(null); } @@ -45,7 +45,7 @@ public ResponseDto unblockUser( public ResponseDto> getBlockedUsers( @AuthenticationPrincipal UserDetails userDetails ) { - return ResponseDto.success(userBlockService.getBlockedUsers(userDetails)); + return ResponseDto.success(userBlockService.getBlockedUsers(Long.valueOf(userDetails.getUsername()))); } } \ No newline at end of file diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java index f63d64e..ac2cc7a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java @@ -9,7 +9,6 @@ import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,9 +20,8 @@ public class UserBlockService { private final UserService userService; @Transactional - public void blockUser(UserDetails userDetails, String targetUserEmail) { - String email = userDetails.getUsername(); - User user = userService.getUserByEmail(email); + public void blockUser(Long userId, String targetUserEmail) { + User user = userService.getUserById(userId); User targetUser = userService.getUserByEmail(targetUserEmail); validateBlockRequest(user.getId(), targetUser.getId()); @@ -37,9 +35,9 @@ public void blockUser(UserDetails userDetails, String targetUserEmail) { } @Transactional - public void unblockUser(UserDetails userDetails, String targetUserEmail) { - String email = userDetails.getUsername(); - User user = userService.getUserByEmail(email); + public void unblockUser(Long userId, String targetUserEmail) { + User user = userService.getUserById(userId); + User targetUser = userService.getUserByEmail(targetUserEmail); userBlockRepository.deleteByUserIdAndBlockedUserId(user.getId(), targetUser.getId()); } @@ -53,9 +51,8 @@ private void validateBlockRequest(Long userId, Long targetUserId) { } } - public List getBlockedUsers(UserDetails userDetails) { - String email = userDetails.getUsername(); - User user = userService.getUserByEmail(email); + public List getBlockedUsers(Long userId) { + User user = userService.getUserById(userId); return userBlockRepository.findBlockedUsersByUserId(user.getId()); } } \ No newline at end of file diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java index 9573d73..ca893d3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java @@ -34,7 +34,7 @@ public ResponseDto toggleFavorite( @AuthenticationPrincipal UserDetails userDetails ) { return ResponseDto.success( - favoriteService.toggleFavorite(userDetails.getUsername(), request.getKindergartenId()) + favoriteService.toggleFavorite(Long.valueOf(userDetails.getUsername()), request.getKindergartenId()) ); } @@ -44,7 +44,7 @@ public ResponseDto> getMyFavorites( @AuthenticationPrincipal UserDetails userDetails ) { return ResponseDto.success( - favoriteService.getMyFavorites(userDetails.getUsername()) + favoriteService.getMyFavorites(Long.valueOf(userDetails.getUsername())) ); } @@ -55,7 +55,7 @@ public ResponseDto getFavoriteStatus( @AuthenticationPrincipal UserDetails userDetails ) { return ResponseDto.success( - favoriteService.isFavorite(userDetails.getUsername(), kindergartenId) + favoriteService.isFavorite(Long.valueOf(userDetails.getUsername()), kindergartenId) ); } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java index 25a39c7..c049c41 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java @@ -25,10 +25,10 @@ public class UserFavoriteKindergartenService { /// 유치원 즐겨찾기 토글 @Transactional - public FavoriteToggleResponseDTO toggleFavorite(String email, Long kindergartenId) { + public FavoriteToggleResponseDTO toggleFavorite(Long userId, Long kindergartenId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 즐겨찾기 존재 여부 확인 및 삭제 시도 boolean existed = favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); @@ -49,10 +49,10 @@ public FavoriteToggleResponseDTO toggleFavorite(String email, Long kindergartenI } /// 즐겨찾기 목록 조회 - public List getMyFavorites(String email) { + public List getMyFavorites(Long userId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 즐겨찾기 목록 조회 List favorites = favoriteRepository.findByUser(user); @@ -62,10 +62,10 @@ public List getMyFavorites(String email) { } /// 즐겨찾기 상태 확인 - public boolean isFavorite(String email, Long kindergartenId) { + public boolean isFavorite(Long userId, Long kindergartenId) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 유치원 존재 여부 확인 return favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); diff --git a/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java index 40aa5d6..a435b02 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java @@ -37,6 +37,11 @@ public class SecurityConfig { "/notice/**" )); + private final List adminOriginList = new ArrayList<>(Arrays.asList( + "/admin/**", + "/address/batch/**" + )); + @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); @@ -55,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers(permitOriginList.toArray(new String[0])) .permitAll() - .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 전용 API + .requestMatchers(adminOriginList.toArray(new String[0])).hasRole("ADMIN") // 관리자 전용 API .anyRequest().authenticated() // 나머지 요청은 인증된 사용자만 접근 가능 ) .exceptionHandling(ex -> ex diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java index 79e3bac..c6f6dd4 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java @@ -26,8 +26,8 @@ public class AddressFacade { private final Job subRegionJob; private final Job regionJob; - public void regionBatch(String username){ - UserDTO user = userService.getUser(username); + public void regionBatch(Long userId){ + UserDTO user = userService.getUserToDTO(userId); if (!UserRole.ADMIN.name().equals(user.getRole())) { throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); @@ -45,8 +45,8 @@ public void regionBatch(String username){ } } - public void subRegionBatch(String username){ - UserDTO user = userService.getUser(username); + public void subRegionBatch(Long userId){ + UserDTO user = userService.getUserToDTO(userId); if (!UserRole.ADMIN.name().equals(user.getRole())) { throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java index 347fda0..efd0225 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java @@ -11,8 +11,8 @@ public class CommunityFacade { private final CommunityService communityService; @Transactional - public void deletePost(Long id, String username) { - communityService.deletePost(id, username); + public void deletePost(Long id, Long userId) { + communityService.deletePost(id, userId); communityService.refreshTopPostsCache(); } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java index 8bc7809..b3372f9 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java @@ -27,42 +27,42 @@ public class KindergartenFacade { @Transactional - public void createInternshipReview(CreateInternshipReviewRequestDTO request, String email) { - Kindergarten kindergarten = kindergartenInternshipReviewService.createInternshipReview(request, email); + public void createInternshipReview(CreateInternshipReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenInternshipReviewService.createInternshipReview(request, userId); kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); /// 사용자 리뷰 작성 플래그 업데이트 - userService.markUserAsReviewWriter(email); + userService.markUserAsReviewWriter(userId); } @Transactional - public void modifyInternshipReview(ModifyInternshipReviewRequestDTO request, String email) { - Kindergarten kindergarten = kindergartenInternshipReviewService.modifyInternshipReview(request, email); + public void modifyInternshipReview(ModifyInternshipReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenInternshipReviewService.modifyInternshipReview(request, userId); kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); } @Transactional - public void createWorkReview(CreateWorkReviewRequestDTO request, String email) { - Kindergarten kindergarten = kindergartenWorkReviewService.createWorkReview(request, email); + public void createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenWorkReviewService.createWorkReview(request, userId); kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); /// 사용자 리뷰 작성 플래그 업데이트 - userService.markUserAsReviewWriter(email); + userService.markUserAsReviewWriter(userId); } @Transactional - public void modifyWorkReview(ModifyWorkReviewRequestDTO request, String email) { - Kindergarten kindergarten = kindergartenWorkReviewService.modifyWorkReview(request, email); + public void modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenWorkReviewService.modifyWorkReview(request, userId); kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); } @Transactional - public void deleteWorkReview(Long reviewId, String username) { - User currentUser = userService.getUserByEmail(username); + public void deleteWorkReview(Long reviewId, Long userId) { + User currentUser = userService.getUserById(userId); kindergartenWorkReviewService.deleteWorkReview(reviewId, currentUser.getId(), currentUser.getRole()); int workReviewCount = kindergartenWorkReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); int internshipReviewCount = kindergartenInternshipReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); @@ -72,8 +72,8 @@ public void deleteWorkReview(Long reviewId, String username) { } @Transactional - public void deleteInternshipReview(Long reviewId, String username) { - User currentUser = userService.getUserByEmail(username); + public void deleteInternshipReview(Long reviewId, Long userId) { + User currentUser = userService.getUserById(userId); kindergartenInternshipReviewService.deleteWorkReview(reviewId, currentUser.getId(), currentUser.getRole()); int workReviewCount = kindergartenWorkReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); int internshipReviewCount = kindergartenInternshipReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java index af851c2..684c7f3 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java @@ -22,9 +22,9 @@ public class KindergartenWorkHistoryFacade { private final KindergartenWorkHistoryService kindergartenWorkHistoryService; @Transactional - public KindergartenWorkHistoryResponse addCertification(String email, KindergartenWorkHistoryRequest request) { + public KindergartenWorkHistoryResponse addCertification(Long userId, KindergartenWorkHistoryRequest request) { // 사용자 조회 - User user = userService.getUserByEmail(email); + User user = userService.getUserById(userId); // 유치원 이름으로 유치원 조회 Kindergarten kindergarten = kindergartenService.getKindergartenByName(request.getKindergartenName()); @@ -34,8 +34,8 @@ public KindergartenWorkHistoryResponse addCertification(String email, Kindergart } @Transactional - public void deleteCertification(String email, Long certificationId) { - User user = userService.getUserByEmail(email); + public void deleteCertification(Long userId, Long certificationId) { + User user = userService.getUserById(userId); KindergartenWorkHistory workHistory = kindergartenWorkHistoryService.getKindergartenWorkHistory(certificationId); // 유치원 근무 이력 소유자 확인 diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java index 8b64b30..110858b 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java @@ -2,8 +2,10 @@ import com.onebyone.kindergarten.domain.communityComments.dto.response.PageCommunityCommentsResponseDTO; import com.onebyone.kindergarten.domain.communityComments.service.CommunityCommentService; +import com.onebyone.kindergarten.domain.user.dto.JwtUserInfoDto; import com.onebyone.kindergarten.domain.user.dto.request.EmailCertificationRequestDTO; import com.onebyone.kindergarten.domain.user.dto.request.UpdateTemporaryPasswordRequestDTO; +import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.EmailCertificationType; import com.onebyone.kindergarten.global.feignClient.KakaoApiClient; import com.onebyone.kindergarten.global.feignClient.KakaoAuthClient; @@ -55,10 +57,10 @@ public class UserFacade { private String naverClientSecret; public SignUpResponseDTO signUp(SignUpRequestDTO request) { - String email = userService.signUp(request); + JwtUserInfoDto dto = userService.signUp(request); - String accessToken = jwtProvider.generateAccessToken(email); - String refreshToken = jwtProvider.generateRefreshToken(email); + String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); return SignUpResponseDTO.builder() .accessToken(accessToken) @@ -67,10 +69,10 @@ public SignUpResponseDTO signUp(SignUpRequestDTO request) { } public SignInResponseDTO signIn(SignInRequestDTO request) { - String email = userService.signIn(request); + JwtUserInfoDto dto = userService.signIn(request); - String accessToken = jwtProvider.generateAccessToken(email); - String refreshToken = jwtProvider.generateRefreshToken(email); + String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); return SignInResponseDTO.builder() .accessToken(accessToken) @@ -78,6 +80,7 @@ public SignInResponseDTO signIn(SignInRequestDTO request) { .build(); } + @Transactional public SignInResponseDTO kakaoLogin(String code, String fcmToken) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); @@ -96,10 +99,15 @@ public SignInResponseDTO kakaoLogin(String code, String fcmToken) { String kakaoAccessToken = tokenResponse.getAccess_token(); KakaoUserResponse userResponse = kakaoApiClient.getUserInfo("Bearer " + kakaoAccessToken); - String email = userService.signUpByKakao(userResponse, fcmToken); - String accessToken = jwtProvider.generateAccessToken(email); - String refreshToken = jwtProvider.generateRefreshToken(email); + User user = userService.signUpByKakao(userResponse); + + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); + } + + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); return SignInResponseDTO.builder() .accessToken(accessToken) @@ -107,6 +115,7 @@ public SignInResponseDTO kakaoLogin(String code, String fcmToken) { .build(); } + @Transactional public SignInResponseDTO naverLogin(String code, String state, String fcmToken) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); @@ -125,10 +134,14 @@ public SignInResponseDTO naverLogin(String code, String state, String fcmToken) NaverTokenResponse response = naverAuthClient.getAccessToken(params); NaverUserResponse userResponse = naverApiClient.getUserInfo("Bearer " + response.getAccess_token()); - String email = userService.signUpByNaver(userResponse, fcmToken); + User user = userService.signUpByNaver(userResponse); - String accessToken = jwtProvider.generateAccessToken(email); - String refreshToken = jwtProvider.generateRefreshToken(email); + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); + } + + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); return SignInResponseDTO.builder() .accessToken(accessToken) @@ -147,10 +160,14 @@ public SignInResponseDTO appleLogin(String idToken, String fcmToken) { // Apple ID Token 검증 및 사용자 정보 추출 AppleUserResponse userResponse = appleAuthService.verifyIdToken(idToken); - String email = userService.signUpByApple(userResponse, fcmToken); + User user = userService.signUpByApple(userResponse); + + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); + } - String accessToken = jwtProvider.generateAccessToken(email); - String refreshToken = jwtProvider.generateRefreshToken(email); + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); return SignInResponseDTO.builder() .accessToken(accessToken) @@ -158,19 +175,20 @@ public SignInResponseDTO appleLogin(String idToken, String fcmToken) { .build(); } - public PageCommunityCommentsResponseDTO getWroteMyCommunityComments(String username, int page, int size) { - UserDTO user = userService.getUser(username); + public PageCommunityCommentsResponseDTO getWroteMyCommunityComments(Long userId, int page, int size) { + UserDTO user = userService.getUserToDTO(userId); return communityCommentService.getWroteMyCommunityComments(user.getUserId(), page, size); } /// 내가 작성한 실습 리뷰 조회 - public InternshipReviewPagedResponseDTO getMyInternshipReviews(String username, int page, int size) { - return kindergartenInternshipReviewService.getMyReviews(username, page, size); + public InternshipReviewPagedResponseDTO getMyInternshipReviews(Long userId, int page, int size) { + return kindergartenInternshipReviewService.getMyReviews(userId, page, size); } /// 내가 작성한 근무 리뷰 조회 - public WorkReviewPagedResponseDTO getMyWorkReviews(String username, int page, int size) { - return kindergartenWorkReviewService.getMyReviews(username, page, size); + public WorkReviewPagedResponseDTO getMyWorkReviews(Long userId, int page, int size) { + AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); + return kindergartenWorkReviewService.getMyReviews(user.getId(), page, size); } @Transactional diff --git a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java index ae0503b..bba517e 100644 --- a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java +++ b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java @@ -35,29 +35,27 @@ protected void doFilterInternal( return; } - String requestURI = request.getRequestURI(); - - if (requestURI.startsWith("/users/reissue")) { - filterChain.doFilter(request, response); - return; - } - - if (requestURI.startsWith("/users/kakao/callback") || - requestURI.startsWith("/users/naver/callback") || - requestURI.startsWith("/users/apple/callback")) { - filterChain.doFilter(request, response); - return; - } - String jwt = resolveToken(request); if (StringUtils.hasText(jwt) ) { Map map = jwtProvider.validateTokenWithError(jwt); if ((boolean) map.get("isValid")) { - Authentication authentication = jwtProvider.getAuthentication(jwt); - SecurityContextHolder.getContext().setAuthentication(authentication); - filterChain.doFilter(request, response); + Map authenticationMap = jwtProvider.getAuthentication(jwt); + + if ((boolean) authenticationMap.get("isValid")) { + Authentication authentication = (Authentication) authenticationMap.get("authentication"); + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } else { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + + ErrorResponse errorResponse = ErrorResponse.buildError((ErrorCodes) authenticationMap.get("errorCode")); + response.getWriter().write( + "{\"code\": \"" + errorResponse.getCode() + "\", \"message\": \"" + errorResponse.getMessage() + "\"}" + ); + } } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json;charset=UTF-8"); diff --git a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java index c18c4e2..442343d 100644 --- a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java +++ b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java @@ -1,5 +1,6 @@ package com.onebyone.kindergarten.global.jwt; +import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.domain.user.service.CustomUserDetailService; import com.onebyone.kindergarten.global.exception.*; import io.jsonwebtoken.*; @@ -9,14 +10,15 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.security.Key; -import java.util.Date; -import java.util.Map; -import java.util.HashMap; +import java.util.*; @Slf4j @Component @@ -29,13 +31,15 @@ public class JwtProvider { private final Long accessTokenValidationMs = 30 * 60 * 1000L; private final Long refreshTokenValidationMs = 15 * 24 * 60 * 60 * 1000L; - public String generateAccessToken(String email) { + public String generateAccessToken(Long userId, UserRole role) { Claims claims = Jwts.claims() - .setSubject(email) + .setSubject(String.valueOf(userId)) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + accessTokenValidationMs)); + claims.put("role", String.valueOf(role)); + return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .setClaims(claims) @@ -44,13 +48,15 @@ public String generateAccessToken(String email) { } // RefreshToken 생성 - public String generateRefreshToken(String email) { + public String generateRefreshToken(Long userId, UserRole role) { Claims claims = Jwts.claims() - .setSubject(email) + .setSubject(String.valueOf(userId)) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + refreshTokenValidationMs)); + claims.put("role", String.valueOf(role)); + return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .setClaims(claims) @@ -95,6 +101,9 @@ public Map validateTokenWithError(String token) { } catch (IllegalArgumentException e) { result.put("isValid", false); result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); + } catch (Exception e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INTERNAL_SERVER_ERROR); } return result; @@ -114,25 +123,39 @@ private Claims getClaims(String token) { } // JWT Claims으로 User 객체를 생성하여 Authentication 객체를 반환 - public Authentication getAuthentication(String token) { + public Map getAuthentication(String token) { + Map result = new HashMap<>(); // JWT에서 Claims 가져오기 Claims claims = getClaims(token); - String email = claims.getSubject(); + String userId = claims.getSubject(); + String role = claims.get("role", String.class); - if (email == null) { - throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); - } + if (userId == null || role == null) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); + return result; } + + List grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role)); - UserDetails userDetails = customUserDetailService.loadUserByUsername(email); - return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + UserDetails userDetails = User + .withUsername(userId) + .password("") + .authorities(grantedAuthorities) + .build(); + + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + + result.put("isValid", true); + result.put("authentication", authentication); + return result; } - public String getEmailFromRefreshToken(String refreshToken) { + public Claims getClaimFromRefreshToken(String refreshToken) { if (!validateToken(refreshToken)) { throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); } - return getClaims(refreshToken).getSubject(); // email + return getClaims(refreshToken); } } \ No newline at end of file From fa8200b7d7cbb578c6273b89a4037c74c2fe9851 Mon Sep 17 00:00:00 2001 From: juhoon Date: Thu, 11 Dec 2025 17:48:49 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix(api):=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=ED=96=89=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserApiController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java index 4b98874..36d98d9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java @@ -102,8 +102,8 @@ public ReIssueResponseDTO reissue( Claims claim = jwtProvider.getClaimFromRefreshToken(refreshToken); - String newAccessToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), (UserRole) claim.get("role")); - String newRefreshToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), (UserRole) claim.get("role")); + String newAccessToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); + String newRefreshToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); return ReIssueResponseDTO.builder() .accessToken(newAccessToken) From 25c32e8251edc05dfc2de1d529a6e94fbfd42a11 Mon Sep 17 00:00:00 2001 From: juhoon Date: Fri, 12 Dec 2025 09:40:31 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(setting):=20formatter=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/aws.yml | 15 +- build.gradle | 23 + .../kindergarten/KindergartenApplication.java | 7 +- .../address/controller/AddressController.java | 41 +- .../address/dto/AddressResponseDTO.java | 11 +- .../domain/address/dto/RegionDTO.java | 4 +- .../domain/address/dto/SubRegionDTO.java | 13 +- .../address/dto/SubRegionResponseDTO.java | 9 +- .../domain/address/entity/Region.java | 29 +- .../domain/address/entity/SubRegion.java | 56 +- .../address/repository/RegionRepository.java | 3 +- .../repository/SubRegionRepository.java | 4 +- .../address/service/AddressService.java | 101 +- .../controller/CommentController.java | 22 +- .../CommunityCommentController.java | 99 +- .../dto/request/CreateCommentRequestDTO.java | 10 +- .../dto/response/CommentResponseDTO.java | 111 +-- .../PageCommunityCommentsResponseDTO.java | 7 +- .../entity/CommunityComment.java | 83 +- .../CommunityCommentRepository.java | 105 +- .../service/CommunityCommentService.java | 291 +++--- .../controller/CommunityController.java | 125 ++- .../dto/request/CommunitySearchDTO.java | 19 +- .../CreateCommunityPostRequestDTO.java | 24 +- .../response/CommunityLikeResponseDTO.java | 6 +- .../response/CommunityPostResponseDTO.java | 36 +- .../entity/CommunityCategory.java | 51 +- .../communityPosts/entity/CommunityLike.java | 38 +- .../communityPosts/entity/CommunityPost.java | 143 +-- .../communityPosts/enums/PostCategory.java | 5 +- .../mapper/CommunityPostMapper.java | 65 +- .../CommunityCategoryRepository.java | 7 +- .../repository/CommunityLikeRepository.java | 49 +- .../repository/CommunityRepository.java | 94 +- .../service/CommunityLikeService.java | 102 +- .../service/CommunityService.java | 197 ++-- .../service/CommunityServiceInitializer.java | 14 +- .../controller/InquiryController.java | 116 ++- .../dto/request/AnswerInquiryRequestDTO.java | 8 +- .../dto/request/CreateInquiryRequestDTO.java | 16 +- .../dto/response/InquiryResponseDTO.java | 89 +- .../domain/inquires/entity/Inquiry.java | 81 +- .../domain/inquires/enums/InquiryStatus.java | 6 +- .../repository/InquiryRepository.java | 64 +- .../inquires/service/InquiryService.java | 219 ++--- ...indergartenInternshipReviewController.java | 120 +-- .../dto/CreateInternshipReviewRequestDTO.java | 18 +- .../dto/InternshipReviewDTO.java | 90 +- .../dto/InternshipReviewPagedResponseDTO.java | 15 +- .../dto/ModifyInternshipReviewRequestDTO.java | 20 +- .../entity/KindergartenInternshipReview.java | 165 ++-- ...ndergartenInternshipReviewLikeHistory.java | 21 +- .../enums/InternshipReviewStarRatingType.java | 5 +- ...InternshipReviewLikeHistoryRepository.java | 11 +- ...indergartenInternshipReviewRepository.java | 244 ++--- .../KindergartenInternshipReviewService.java | 413 ++++---- .../KindergartenWorkHistoryController.java | 53 +- .../dto/KindergartenWorkHistoryRequest.java | 31 +- .../dto/KindergartenWorkHistoryResponse.java | 33 +- .../entity/KindergartenWorkHistory.java | 39 +- .../KindergartenWorkHistoryRepository.java | 34 +- .../KindergartenWorkHistoryService.java | 72 +- .../KindergartenWorkReviewController.java | 114 ++- .../dto/CreateWorkReviewRequestDTO.java | 28 +- .../dto/ModifyWorkReviewRequestDTO.java | 28 +- .../dto/WorkReviewDTO.java | 119 +-- .../dto/WorkReviewPagedResponseDTO.java | 15 +- .../entity/KindergartenWorkReview.java | 207 ++-- .../KindergartenWorkReviewLikeHistory.java | 21 +- .../enums/WorkReviewStarRatingType.java | 7 +- ...gartenWorkReviewLikeHistoryRepository.java | 11 +- .../KindergartenWorkReviewRepository.java | 352 +++---- .../KindergartenWorkReviewService.java | 417 ++++---- .../controller/KindergartenController.java | 105 +- .../kindergatens/dto/KindergartenDTO.java | 104 +- ...dergartenInternshipReviewAggregateDTO.java | 24 +- .../dto/KindergartenResponseDTO.java | 295 +++--- .../dto/KindergartenSearchDTO.java | 30 +- .../dto/KindergartenSimpleDTO.java | 4 +- .../KindergartenWorkReviewAggregateDTO.java | 31 +- .../kindergatens/entity/Kindergarten.java | 261 ++--- ...KindergartenInternshipReviewAggregate.java | 40 +- .../KindergartenWorkReviewAggregate.java | 68 +- ...enInternshipReviewAggregateRepository.java | 5 +- .../repository/KindergartenRepository.java | 114 +-- ...ergartenWorkReviewAggregateRepository.java | 5 +- ...artenInternshipReviewAggregateService.java | 57 +- .../service/KindergartenService.java | 273 +++--- ...indergartenWorkReviewAggregateService.java | 77 +- .../controller/AdminNoticeController.java | 46 +- .../notice/controller/NoticeController.java | 17 +- .../dto/request/NoticeCreateRequestDTO.java | 47 +- .../dto/response/NoticeResponseDTO.java | 78 +- .../domain/notice/entity/Notice.java | 47 +- .../notice/repository/NoticeRepository.java | 33 +- .../domain/notice/service/NoticeService.java | 89 +- .../PushNotificationController.java | 116 ++- .../dto/PushNotificationRequestDTO.java | 20 +- .../dto/PushNotificationResponseDTO.java | 59 +- .../entity/PushNotification.java | 114 ++- .../enums/NotificationType.java | 8 +- .../event/PushNotificationEvent.java | 21 +- .../event/PushNotificationEventListener.java | 47 +- .../event/PushNotificationEventPublisher.java | 20 +- .../PushNotificationRepository.java | 44 +- .../service/NotificationTemplateService.java | 602 ++++++------ .../service/PushNotificationService.java | 893 +++++++++--------- .../controller/AdminReportController.java | 47 +- .../reports/controller/ReportController.java | 36 +- .../dto/request/CreateReportRequestDTO.java | 15 +- .../reports/dto/request/ReportSearchDTO.java | 6 +- .../dto/response/ReportResponseDTO.java | 69 +- .../domain/reports/entity/Report.java | 74 +- .../reports/enums/ReportTargetType.java | 7 +- .../reports/repository/ReportRepository.java | 48 +- .../domain/reports/service/ReportService.java | 265 +++--- .../user/controller/AdminUserController.java | 71 +- .../user/controller/UserApiController.java | 442 ++++----- .../domain/user/dto/HomeShortcutsDto.java | 96 +- .../domain/user/dto/JwtUserInfoDto.java | 4 +- .../user/dto/NotificationSettingsDTO.java | 8 +- .../domain/user/dto/SimpleUserDTO.java | 12 +- .../kindergarten/domain/user/dto/UserDTO.java | 73 +- .../domain/user/dto/kakao/KakaoAccount.java | 4 +- .../domain/user/dto/kakao/KakaoProfile.java | 6 +- .../CheckEmailCertificationRequestDTO.java | 4 +- .../request/EmailCertificationRequestDTO.java | 4 +- .../request/ModifyUserNicknameRequestDTO.java | 2 +- .../request/ModifyUserPasswordRequestDTO.java | 4 +- .../user/dto/request/SignInRequestDTO.java | 6 +- .../user/dto/request/SignUpRequestDTO.java | 58 +- .../UpdateTemporaryPasswordRequestDTO.java | 4 +- .../dto/request/UpdateUserRoleRequestDTO.java | 3 +- .../request/UpdateUserStatusRequestDTO.java | 13 +- .../user/dto/request/UserSearchDTO.java | 16 +- .../dto/response/AdminUserResponseDTO.java | 83 +- .../dto/response/ApplePublicKeyResponse.java | 24 +- .../user/dto/response/AppleTokenResponse.java | 12 +- .../user/dto/response/AppleUserResponse.java | 12 +- .../user/dto/response/GetUserResponseDTO.java | 2 +- .../user/dto/response/KakaoTokenResponse.java | 12 +- .../user/dto/response/KakaoUserResponse.java | 6 +- .../user/dto/response/NaverTokenResponse.java | 10 +- .../user/dto/response/NaverUserResponse.java | 20 +- .../user/dto/response/ReIssueResponseDTO.java | 14 +- .../user/dto/response/SignInResponseDTO.java | 14 +- .../user/dto/response/SignUpResponseDTO.java | 16 +- .../UpdateHomeShortcutsResponseDTO.java | 18 +- .../user/entity/EmailCertification.java | 28 +- .../kindergarten/domain/user/entity/User.java | 458 ++++----- .../domain/user/entity/UserProvider.java | 6 +- .../user/enums/EmailCertificationType.java | 3 +- .../user/enums/NotificationSetting.java | 8 +- .../domain/user/enums/UserRole.java | 7 +- .../domain/user/enums/UserStatus.java | 6 +- .../EmailCertificationRepository.java | 10 +- .../user/repository/UserRepository.java | 120 +-- .../domain/user/service/AppleAuthService.java | 217 +++-- .../user/service/CustomUserDetailService.java | 28 +- .../domain/user/service/UserService.java | 849 +++++++++-------- .../controller/UserBlockController.java | 55 +- .../dto/request/UserBlockRequestDto.java | 4 +- .../dto/response/BlockedUserResponseDto.java | 30 +- .../domain/userBlock/entity/UserBlock.java | 30 +- .../repository/UserBlockRepository.java | 26 +- .../userBlock/service/UserBlockService.java | 69 +- .../UserFavoriteKindergartenController.java | 71 +- .../dto/request/ToggleFavoriteRequestDTO.java | 2 +- .../response/FavoriteToggleResponseDTO.java | 10 +- .../entity/UserFavoriteKindergarten.java | 28 +- .../UserFavoriteKindergartenRepository.java | 58 +- .../UserFavoriteKindergartenService.java | 90 +- .../global/batch/config/BatchConfig.java | 199 ++-- .../batch/config/QuartzSchedulerConfig.java | 140 ++- .../global/batch/job/InstantJob.java | 16 +- .../global/batch/job/PushNotificationJob.java | 68 +- .../batch/job/TopPostsCacheRefreshJob.java | 36 +- .../KindergartenRegionProcessor.java | 56 +- .../KindergartenSubRegionProcessor.java | 141 +-- .../global/common/BaseEntity.java | 17 +- .../global/common/PageResponseDTO.java | 37 +- .../global/common/ResponseDto.java | 32 +- .../global/config/AsyncConfig.java | 36 +- .../global/config/CacheConfig.java | 14 +- .../global/config/FirebaseConfig.java | 70 +- .../global/config/SecurityConfig.java | 112 ++- .../global/config/SwaggerConfig.java | 45 +- .../global/config/WebMvcConfig.java | 11 +- .../global/enums/ReportStatus.java | 7 +- .../global/enums/ReviewStatus.java | 4 +- .../kindergarten/global/enums/ReviewType.java | 5 +- .../global/exception/BusinessException.java | 16 +- .../global/exception/ErrorCodes.java | 128 +-- .../global/exception/ErrorHandler.java | 29 +- .../global/exception/ErrorResponse.java | 28 +- .../global/facade/AddressFacade.java | 68 +- .../global/facade/CommunityFacade.java | 13 +- .../global/facade/KindergartenFacade.java | 111 ++- .../facade/KindergartenWorkHistoryFacade.java | 57 +- .../global/facade/NoticeFacade.java | 37 +- .../global/facade/SampleFacade.java | 12 +- .../global/facade/UserFacade.java | 351 ++++--- .../global/feignClient/AppleAuthClient.java | 6 +- .../global/feignClient/KakaoApiClient.java | 4 +- .../global/feignClient/KakaoAuthClient.java | 6 +- .../global/feignClient/NaverApiClient.java | 4 +- .../global/feignClient/NaverAuthClient.java | 4 +- .../global/interceptor/LoggerInterceptor.java | 78 +- .../global/jwt/JwtEntryPoint.java | 44 +- .../kindergarten/global/jwt/JwtFilter.java | 126 +-- .../kindergarten/global/jwt/JwtProvider.java | 244 +++-- .../global/provider/EmailProvider.java | 135 +-- .../KindergartenApplicationTests.java | 7 +- 213 files changed, 7853 insertions(+), 7669 deletions(-) diff --git a/.github/workflows/aws.yml b/.github/workflows/aws.yml index 89a0839..b433446 100644 --- a/.github/workflows/aws.yml +++ b/.github/workflows/aws.yml @@ -30,21 +30,17 @@ jobs: - name: Build JAR run: | + ./gradlew spotlessCheck + if [ "${{ github.ref }}" == "refs/heads/main" ]; then ./gradlew clean build -x test -Dspring.profiles.active=production else ./gradlew clean build -x test -Dspring.profiles.active=dev fi - - name: Debug build/libs - run: ls -al ./build/libs - - name: Copy JAR to Docker context run: cp ./build/libs/Kindergarten-0.0.1-SNAPSHOT.jar ./docker/app/app.jar - - name: Debug docker/app - run: ls -al ./docker/app - - name: Build and Push Docker image uses: docker/build-push-action@v5 with: @@ -96,13 +92,6 @@ jobs: sudo docker compose pull sudo docker compose up -d --remove-orphans - - name: Debug upload paths - run: | - set -e - ls -al docker/app/docker-compose.yml - ls -al docker/loki/config/config.yml - ls -al docker/promtail/config/config.yml - - name: Upload stack files (dev) if: github.ref == 'refs/heads/develop' uses: appleboy/scp-action@v1 diff --git a/build.gradle b/build.gradle index 66ef0a4..5abc7f7 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.7' + id 'com.diffplug.spotless' version '6.25.0' } group = 'com.onebyone' @@ -15,6 +16,28 @@ java { repositories { mavenCentral() + gradlePluginPortal() +} + +spotless { + java { + importOrder( + 'java|javax|jakarta', + 'org.springframework', + 'lombok', + '', + 'org.junit|org.mockito', + '\\#', + '\\#org.junit' + ) + + googleJavaFormat() + + formatAnnotations() + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } } ext { diff --git a/src/main/java/com/onebyone/kindergarten/KindergartenApplication.java b/src/main/java/com/onebyone/kindergarten/KindergartenApplication.java index 5e955a3..a3d42fd 100644 --- a/src/main/java/com/onebyone/kindergarten/KindergartenApplication.java +++ b/src/main/java/com/onebyone/kindergarten/KindergartenApplication.java @@ -12,8 +12,7 @@ @EnableScheduling public class KindergartenApplication { - public static void main(String[] args) { - SpringApplication.run(KindergartenApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(KindergartenApplication.class, args); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java b/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java index b594d6d..bd8e59f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/controller/AddressController.java @@ -4,6 +4,7 @@ import com.onebyone.kindergarten.domain.address.service.AddressService; import com.onebyone.kindergarten.global.facade.AddressFacade; import io.swagger.v3.oas.annotations.Operation; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; @@ -12,34 +13,28 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/address") public class AddressController { - private final AddressFacade addressFacade; - private final AddressService addressService; + private final AddressFacade addressFacade; + private final AddressService addressService; - @PostMapping("/batch/region") - @Operation(summary = "행정구역 등록 배치", description = "kindergarten 테이블의 region 컬럼에 데이터를 매핑시킵니다") - public void kindergartenRegionBatch( - @AuthenticationPrincipal UserDetails userDetails - ) { - addressFacade.regionBatch(Long.valueOf(userDetails.getUsername())); - } + @PostMapping("/batch/region") + @Operation(summary = "행정구역 등록 배치", description = "kindergarten 테이블의 region 컬럼에 데이터를 매핑시킵니다") + public void kindergartenRegionBatch(@AuthenticationPrincipal UserDetails userDetails) { + addressFacade.regionBatch(Long.valueOf(userDetails.getUsername())); + } - @PostMapping("/batch/sub-region") - @Operation(summary = "시군구 등록 배치", description = "kindergarten 테이블의 subRegion 컬럼에 데이터를 매핑시킵니다") - public void kindergartenSubRegionBatch( - @AuthenticationPrincipal UserDetails userDetails - ) { - addressFacade.subRegionBatch(Long.valueOf(userDetails.getUsername())); - } + @PostMapping("/batch/sub-region") + @Operation(summary = "시군구 등록 배치", description = "kindergarten 테이블의 subRegion 컬럼에 데이터를 매핑시킵니다") + public void kindergartenSubRegionBatch(@AuthenticationPrincipal UserDetails userDetails) { + addressFacade.subRegionBatch(Long.valueOf(userDetails.getUsername())); + } - @GetMapping - @Operation(summary = "주소 조회", description = "주소 정보를 조회합니다.") - public List getAddress() { - return addressService.getAddress(); - } + @GetMapping + @Operation(summary = "주소 조회", description = "주소 정보를 조회합니다.") + public List getAddress() { + return addressService.getAddress(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/dto/AddressResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/address/dto/AddressResponseDTO.java index cc0e867..43335be 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/dto/AddressResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/dto/AddressResponseDTO.java @@ -1,12 +1,11 @@ package com.onebyone.kindergarten.domain.address.dto; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class AddressResponseDTO { - private Long regionId; - private String regionName; - private List subRegions; -} \ No newline at end of file + private Long regionId; + private String regionName; + private List subRegions; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/dto/RegionDTO.java b/src/main/java/com/onebyone/kindergarten/domain/address/dto/RegionDTO.java index a479fc9..36979a8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/dto/RegionDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/dto/RegionDTO.java @@ -4,6 +4,6 @@ @Data public class RegionDTO { - private Long id; - private String name; + private Long id; + private String name; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionDTO.java b/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionDTO.java index b445749..a7e4030 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionDTO.java @@ -1,15 +1,14 @@ package com.onebyone.kindergarten.domain.address.dto; -import lombok.Data; - import java.util.ArrayList; import java.util.List; +import lombok.Data; @Data public class SubRegionDTO { - private Long subRegionId; - private Long regionId; - private String name; - private Long parentId; - private List children = new ArrayList<>(); + private Long subRegionId; + private Long regionId; + private String name; + private Long parentId; + private List children = new ArrayList<>(); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionResponseDTO.java index 860a8cc..51e33df 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/dto/SubRegionResponseDTO.java @@ -1,13 +1,12 @@ package com.onebyone.kindergarten.domain.address.dto; -import lombok.Data; - import java.util.ArrayList; import java.util.List; +import lombok.Data; @Data public class SubRegionResponseDTO { - private Long subRegionId; - private String name; - private List children = new ArrayList<>(); + private Long subRegionId; + private String name; + private List children = new ArrayList<>(); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/entity/Region.java b/src/main/java/com/onebyone/kindergarten/domain/address/entity/Region.java index 3a47d3f..92d7cac 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/entity/Region.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/entity/Region.java @@ -2,30 +2,29 @@ import com.onebyone.kindergarten.domain.address.dto.RegionDTO; import jakarta.persistence.*; -import lombok.Getter; - import java.util.ArrayList; import java.util.List; +import lombok.Getter; @Entity @Getter @Table(name = "region") public class Region { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "region_id") - private Long regionId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "region_id") + private Long regionId; - private String name; + private String name; - @OneToMany(mappedBy = "region", fetch = FetchType.LAZY) - private List subRegions = new ArrayList<>(); + @OneToMany(mappedBy = "region", fetch = FetchType.LAZY) + private List subRegions = new ArrayList<>(); - public static RegionDTO toDto(Region region) { - RegionDTO regionDTO = new RegionDTO(); - regionDTO.setId(region.getRegionId()); - regionDTO.setName(region.getName()); + public static RegionDTO toDto(Region region) { + RegionDTO regionDTO = new RegionDTO(); + regionDTO.setId(region.getRegionId()); + regionDTO.setName(region.getName()); - return regionDTO; - } + return regionDTO; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/entity/SubRegion.java b/src/main/java/com/onebyone/kindergarten/domain/address/entity/SubRegion.java index 8d34b8d..e0d1cfc 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/entity/SubRegion.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/entity/SubRegion.java @@ -8,32 +8,32 @@ @Getter @Table(name = "sub_region") public class SubRegion { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "sub_region_id") - private Long subRegionId; - - private String name; // 수원시, 장안구 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "region_id") - private Region region; // 경기도 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id") - private SubRegion parent; // 장안구 parent (수원시) - - public Long getRegionId() { - return region != null ? region.getRegionId() : null; - } - - public static SubRegionDTO toDTO(SubRegion subRegion) { - SubRegionDTO dto = new SubRegionDTO(); - dto.setSubRegionId(subRegion.getSubRegionId()); - dto.setRegionId(subRegion.getRegionId()); - dto.setName(subRegion.getName()); - dto.setParentId(subRegion.getParent() != null ? subRegion.getParent().getSubRegionId() : null); - - return dto; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "sub_region_id") + private Long subRegionId; + + private String name; // 수원시, 장안구 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "region_id") + private Region region; // 경기도 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private SubRegion parent; // 장안구 parent (수원시) + + public Long getRegionId() { + return region != null ? region.getRegionId() : null; + } + + public static SubRegionDTO toDTO(SubRegion subRegion) { + SubRegionDTO dto = new SubRegionDTO(); + dto.setSubRegionId(subRegion.getSubRegionId()); + dto.setRegionId(subRegion.getRegionId()); + dto.setName(subRegion.getName()); + dto.setParentId(subRegion.getParent() != null ? subRegion.getParent().getSubRegionId() : null); + + return dto; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/repository/RegionRepository.java b/src/main/java/com/onebyone/kindergarten/domain/address/repository/RegionRepository.java index 0bde5ee..37a5b54 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/repository/RegionRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/repository/RegionRepository.java @@ -5,5 +5,4 @@ import org.springframework.stereotype.Repository; @Repository -public interface RegionRepository extends JpaRepository { -} +public interface RegionRepository extends JpaRepository {} diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/repository/SubRegionRepository.java b/src/main/java/com/onebyone/kindergarten/domain/address/repository/SubRegionRepository.java index 5496892..b2a0c70 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/repository/SubRegionRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/repository/SubRegionRepository.java @@ -5,6 +5,6 @@ import org.springframework.stereotype.Repository; @Repository -public interface SubRegionRepository extends JpaRepository { - SubRegion findBySubRegionId(Long subRegionId); +public interface SubRegionRepository extends JpaRepository { + SubRegion findBySubRegionId(Long subRegionId); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/address/service/AddressService.java b/src/main/java/com/onebyone/kindergarten/domain/address/service/AddressService.java index 2180214..5742e02 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/address/service/AddressService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/address/service/AddressService.java @@ -5,64 +5,71 @@ import com.onebyone.kindergarten.domain.address.entity.SubRegion; import com.onebyone.kindergarten.domain.address.repository.RegionRepository; import com.onebyone.kindergarten.domain.address.repository.SubRegionRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class AddressService { - private final RegionRepository regionRepository; - private final SubRegionRepository subRegionRepository; + private final RegionRepository regionRepository; + private final SubRegionRepository subRegionRepository; - public List getAddress() { - List regions = regionRepository.findAll().stream().map(Region::toDto).toList(); - List subRegions = subRegionRepository.findAll().stream().map(SubRegion::toDTO).toList(); + public List getAddress() { + List regions = regionRepository.findAll().stream().map(Region::toDto).toList(); + List subRegions = + subRegionRepository.findAll().stream().map(SubRegion::toDTO).toList(); - // 시군구 map 생성 - Map subRegionMap = subRegions.stream() - .collect(Collectors.toMap( - SubRegionDTO::getSubRegionId, - sub -> { - SubRegionResponseDTO dto = new SubRegionResponseDTO(); - dto.setSubRegionId(sub.getSubRegionId()); - dto.setName(sub.getName()); - return dto; - } - )); + // 시군구 map 생성 + Map subRegionMap = + subRegions.stream() + .collect( + Collectors.toMap( + SubRegionDTO::getSubRegionId, + sub -> { + SubRegionResponseDTO dto = new SubRegionResponseDTO(); + dto.setSubRegionId(sub.getSubRegionId()); + dto.setName(sub.getName()); + return dto; + })); - // 하위 SubRegion 연결 - List rootSubRegions = new ArrayList<>(); - for (SubRegionDTO sub : subRegions) { - SubRegionResponseDTO dto = subRegionMap.get(sub.getSubRegionId()); - if (sub.getParentId() == null) { - rootSubRegions.add(dto); - } else { - SubRegionResponseDTO parent = subRegionMap.get(sub.getParentId()); - if (parent != null) { - parent.getChildren().add(dto); - } - } + // 하위 SubRegion 연결 + List rootSubRegions = new ArrayList<>(); + for (SubRegionDTO sub : subRegions) { + SubRegionResponseDTO dto = subRegionMap.get(sub.getSubRegionId()); + if (sub.getParentId() == null) { + rootSubRegions.add(dto); + } else { + SubRegionResponseDTO parent = subRegionMap.get(sub.getParentId()); + if (parent != null) { + parent.getChildren().add(dto); } - - // Region -> SubRegion 매핑 - return regions.stream() - .map(r -> { - AddressResponseDTO dto = new AddressResponseDTO(); - dto.setRegionId(r.getId()); - dto.setRegionName(r.getName()); - List regionSubRegions = rootSubRegions.stream() - .filter(s -> subRegions.stream() - .anyMatch(sr -> sr.getSubRegionId().equals(s.getSubRegionId()) - && sr.getRegionId().equals(r.getId()))) - .toList(); - dto.setSubRegions(regionSubRegions); - return dto; - }) - .toList(); + } } + + // Region -> SubRegion 매핑 + return regions.stream() + .map( + r -> { + AddressResponseDTO dto = new AddressResponseDTO(); + dto.setRegionId(r.getId()); + dto.setRegionName(r.getName()); + List regionSubRegions = + rootSubRegions.stream() + .filter( + s -> + subRegions.stream() + .anyMatch( + sr -> + sr.getSubRegionId().equals(s.getSubRegionId()) + && sr.getRegionId().equals(r.getId()))) + .toList(); + dto.setSubRegions(regionSubRegions); + return dto; + }) + .toList(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java index aa191b3..bc14029 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommentController.java @@ -15,15 +15,15 @@ @Tag(name = "댓글 관리", description = "독립적인 댓글 관리 API") public class CommentController { - private final CommunityCommentService commentService; + private final CommunityCommentService commentService; - @DeleteMapping("/{commentId}") - @Operation(summary = "댓글 삭제", description = "댓글을 삭제합니다. 본인이 작성한 댓글 또는 관리자가 삭제할 수 있습니다. 원댓글 삭제 시 대댓글도 함께 삭제됩니다.") - public ResponseDto deleteComment( - @PathVariable Long commentId, - @AuthenticationPrincipal UserDetails userDetails - ) { - commentService.deleteComment(commentId, Long.valueOf(userDetails.getUsername())); - return ResponseDto.success("댓글이 삭제되었습니다."); - } -} \ No newline at end of file + @DeleteMapping("/{commentId}") + @Operation( + summary = "댓글 삭제", + description = "댓글을 삭제합니다. 본인이 작성한 댓글 또는 관리자가 삭제할 수 있습니다. 원댓글 삭제 시 대댓글도 함께 삭제됩니다.") + public ResponseDto deleteComment( + @PathVariable Long commentId, @AuthenticationPrincipal UserDetails userDetails) { + commentService.deleteComment(commentId, Long.valueOf(userDetails.getUsername())); + return ResponseDto.success("댓글이 삭제되었습니다."); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java index 76ddf27..1bd05d9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/controller/CommunityCommentController.java @@ -1,23 +1,21 @@ package com.onebyone.kindergarten.domain.communityComments.controller; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; -import org.springframework.web.bind.annotation.*; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; - +import com.onebyone.kindergarten.domain.communityComments.dto.request.CreateCommentRequestDTO; +import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; import com.onebyone.kindergarten.domain.communityComments.service.CommunityCommentService; import com.onebyone.kindergarten.global.common.PageResponseDTO; import com.onebyone.kindergarten.global.common.ResponseDto; -import com.onebyone.kindergarten.domain.communityComments.dto.request.CreateCommentRequestDTO; -import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; - import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -25,44 +23,45 @@ @Tag(name = "댓글", description = "게시글의 댓글 관련 API") public class CommunityCommentController { - private final CommunityCommentService commentService; + private final CommunityCommentService commentService; + + @PostMapping + @Operation( + summary = "댓글 작성", + description = "게시글에 댓글이나 대댓글을 작성합니다. 대댓글인 경우 parentId에 원댓글 ID를 입력합니다.") + public ResponseDto createComment( + @PathVariable Long postId, + @Valid @RequestBody CreateCommentRequestDTO dto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + commentService.createComment(postId, dto, Long.valueOf(userDetails.getUsername()))); + } + + @GetMapping + @Operation(summary = "원댓글 목록 조회", description = "게시글의 원댓글 목록을 조회합니다 (대댓글 제외).") + public PageResponseDTO getOriginalComments( + @PathVariable Long postId, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable) { + return new PageResponseDTO<>(commentService.getOriginalComments(postId, pageable)); + } - @PostMapping - @Operation(summary = "댓글 작성", description = "게시글에 댓글이나 대댓글을 작성합니다. 대댓글인 경우 parentId에 원댓글 ID를 입력합니다.") - public ResponseDto createComment( - @PathVariable Long postId, - @Valid @RequestBody CreateCommentRequestDTO dto, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(commentService.createComment(postId, dto, Long.valueOf(userDetails.getUsername()))); - } + @GetMapping("/replies/{commentId}") + @Operation(summary = "대댓글 목록 조회", description = "특정 원댓글에 대한 대댓글 목록을 조회합니다.") + public ResponseDto> getReplies( + @PathVariable Long postId, @PathVariable Long commentId) { + return ResponseDto.success(commentService.getReplies(commentId)); + } - @GetMapping - @Operation(summary = "원댓글 목록 조회", description = "게시글의 원댓글 목록을 조회합니다 (대댓글 제외).") - public PageResponseDTO getOriginalComments( - @PathVariable Long postId, - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - return new PageResponseDTO<>(commentService.getOriginalComments(postId, pageable)); - } - - @GetMapping("/replies/{commentId}") - @Operation(summary = "대댓글 목록 조회", description = "특정 원댓글에 대한 대댓글 목록을 조회합니다.") - public ResponseDto> getReplies( - @PathVariable Long postId, - @PathVariable Long commentId - ) { - return ResponseDto.success(commentService.getReplies(commentId)); - } - - @GetMapping("/all") - @Operation(summary = "모든 댓글 조회", description = "게시글의 모든 댓글과 대댓글을 함께 조회합니다. 차단한 사용자의 댓글은 제외됩니다.") - public PageResponseDTO getAllComments( - @PathVariable Long postId, - @PageableDefault(size = 30, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails - ) { - Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; - return new PageResponseDTO<>(commentService.getAllCommentsWithReplies(postId, pageable, userId)); - } + @GetMapping("/all") + @Operation(summary = "모든 댓글 조회", description = "게시글의 모든 댓글과 대댓글을 함께 조회합니다. 차단한 사용자의 댓글은 제외됩니다.") + public PageResponseDTO getAllComments( + @PathVariable Long postId, + @PageableDefault(size = 30, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable, + @AuthenticationPrincipal UserDetails userDetails) { + Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; + return new PageResponseDTO<>( + commentService.getAllCommentsWithReplies(postId, pageable, userId)); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/request/CreateCommentRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/request/CreateCommentRequestDTO.java index 291608b..492b3a0 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/request/CreateCommentRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/request/CreateCommentRequestDTO.java @@ -8,9 +8,9 @@ @Getter @Setter public class CreateCommentRequestDTO { - @NotBlank(message = "댓글 내용은 필수입니다.") - @Size(max = 500, message = "댓글은 500자를 초과할 수 없습니다.") - private String content; + @NotBlank(message = "댓글 내용은 필수입니다.") + @Size(max = 500, message = "댓글은 500자를 초과할 수 없습니다.") + private String content; - private Long parentId; -} \ No newline at end of file + private Long parentId; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/CommentResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/CommentResponseDTO.java index bd7f053..01069c2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/CommentResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/CommentResponseDTO.java @@ -1,68 +1,73 @@ package com.onebyone.kindergarten.domain.communityComments.dto.response; -import java.time.LocalDateTime; - import com.onebyone.kindergarten.domain.communityComments.entity.CommunityComment; -import com.onebyone.kindergarten.global.enums.ReportStatus; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.UserRole; - +import com.onebyone.kindergarten.global.enums.ReportStatus; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; @Getter @Builder public class CommentResponseDTO { - private Long id; - - private String content; - - private String nickName; - - private String email; - - private String career; - - private UserRole userRole; + private Long id; + + private String content; + + private String nickName; + + private String email; + + private String career; + + private UserRole userRole; + + private LocalDateTime createdAt; + + private ReportStatus status; + + private Long parentId; - private LocalDateTime createdAt; - - private ReportStatus status; - - private Long parentId; - - private boolean isReply; + private boolean isReply; - public CommentResponseDTO(Long id, String content, String nickName, - String email, String career, UserRole userRole, - LocalDateTime createdAt, ReportStatus status, - Long parentId, boolean isReply) { - this.id = id; - this.content = content; - this.nickName = nickName; - this.email = email; - this.career = career; - this.userRole = userRole; - this.createdAt = createdAt; - this.status = status; - this.parentId = parentId; - this.isReply = isReply; - } + public CommentResponseDTO( + Long id, + String content, + String nickName, + String email, + String career, + UserRole userRole, + LocalDateTime createdAt, + ReportStatus status, + Long parentId, + boolean isReply) { + this.id = id; + this.content = content; + this.nickName = nickName; + this.email = email; + this.career = career; + this.userRole = userRole; + this.createdAt = createdAt; + this.status = status; + this.parentId = parentId; + this.isReply = isReply; + } - public static CommentResponseDTO fromEntity(CommunityComment comment) { - User user = comment.getUser(); + public static CommentResponseDTO fromEntity(CommunityComment comment) { + User user = comment.getUser(); - return CommentResponseDTO.builder() - .id(comment.getId()) - .content(comment.getContent()) - .nickName(user.getNickname()) - .email(user.getEmail()) - .career(user.getCareer()) - .userRole(user.getRole()) - .createdAt(comment.getCreatedAt()) - .status(comment.getStatus()) - .parentId(comment.getParent() != null ? comment.getParent().getId() : null) - .isReply(comment.isReply()) - .build(); - } -} \ No newline at end of file + return CommentResponseDTO.builder() + .id(comment.getId()) + .content(comment.getContent()) + .nickName(user.getNickname()) + .email(user.getEmail()) + .career(user.getCareer()) + .userRole(user.getRole()) + .createdAt(comment.getCreatedAt()) + .status(comment.getStatus()) + .parentId(comment.getParent() != null ? comment.getParent().getId() : null) + .isReply(comment.isReply()) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/PageCommunityCommentsResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/PageCommunityCommentsResponseDTO.java index a12b712..8daa0dd 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/PageCommunityCommentsResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/dto/response/PageCommunityCommentsResponseDTO.java @@ -1,11 +1,10 @@ package com.onebyone.kindergarten.domain.communityComments.dto.response; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class PageCommunityCommentsResponseDTO { - private List content; - private int totalPages; + private List content; + private int totalPages; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/entity/CommunityComment.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/entity/CommunityComment.java index 914d7a3..6164d71 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/entity/CommunityComment.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/entity/CommunityComment.java @@ -5,61 +5,60 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import com.onebyone.kindergarten.global.enums.ReportStatus; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity(name = "community_comment") @Getter @NoArgsConstructor public class CommunityComment extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 댓글 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 댓글 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private CommunityPost post; // 게시글 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private CommunityPost post; // 게시글 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 작성자 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 작성자 + @Column(nullable = false, length = 500) + private String content; // 내용 - @Column(nullable = false, length = 500) - private String content; // 내용 + @Enumerated(EnumType.STRING) + private ReportStatus status = ReportStatus.YET; // 댓글 상태 - 차단 여부 (PENDING, PROCESSED, REJECTED) - @Enumerated(EnumType.STRING) - private ReportStatus status = ReportStatus.YET; // 댓글 상태 - 차단 여부 (PENDING, PROCESSED, REJECTED) + // 대댓글 기능을 위한 부모 댓글 참조 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private CommunityComment parent; // 부모 댓글 (null이면 원댓글, 값이 있으면 대댓글) - // 대댓글 기능을 위한 부모 댓글 참조 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id") - private CommunityComment parent; // 부모 댓글 (null이면 원댓글, 값이 있으면 대댓글) + @Builder + public CommunityComment(CommunityPost post, User user, String content, CommunityComment parent) { + this.post = post; + this.user = user; + this.content = content; + this.parent = parent; + } - @Builder - public CommunityComment(CommunityPost post, User user, String content, CommunityComment parent) { - this.post = post; - this.user = user; - this.content = content; - this.parent = parent; - } + /// 댓글 신고 상태 변경 + public void updateStatus(ReportStatus status) { + this.status = status; + this.updatedAt = LocalDateTime.now(); + } - /// 댓글 신고 상태 변경 - public void updateStatus(ReportStatus status) { - this.status = status; - this.updatedAt = LocalDateTime.now(); - } - - /// 대댓글 여부 확인 - public boolean isReply() { - return this.parent != null; - } + /// 대댓글 여부 확인 + public boolean isReply() { + return this.parent != null; + } - /// 댓글 소프트 삭제 - public void markAsDeleted() { - this.updatedAt = LocalDateTime.now(); - this.deletedAt = LocalDateTime.now(); - } -} \ No newline at end of file + /// 댓글 소프트 삭제 + public void markAsDeleted() { + this.updatedAt = LocalDateTime.now(); + this.deletedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/repository/CommunityCommentRepository.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/repository/CommunityCommentRepository.java index b3a92c6..ba3646e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/repository/CommunityCommentRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/repository/CommunityCommentRepository.java @@ -1,5 +1,10 @@ package com.onebyone.kindergarten.domain.communityComments.repository; +import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; +import com.onebyone.kindergarten.domain.communityComments.entity.CommunityComment; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,63 +13,61 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.onebyone.kindergarten.domain.communityComments.entity.CommunityComment; -import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Repository public interface CommunityCommentRepository extends JpaRepository { - // 게시글의 원댓글 목록 조회 (parent IS NULL) - @Query("SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + - "c.id, c.content, u.nickname, u.email, u.career, u.role, c.createdAt, c.status, " + - "null, false) " + - "FROM community_comment c " + - "JOIN c.user u " + - "WHERE c.post.id = :postId AND c.parent IS NULL AND c.deletedAt IS NULL " + - "ORDER BY c.createdAt DESC") - Page findOriginalCommentsByPostId(@Param("postId") Long postId, Pageable pageable); - - // 특정 원댓글에 대한 대댓글 목록 조회 - @Query("SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + - "c.id, c.content, u.nickname, u.email, u.career, u.role, c.createdAt, c.status, " + - "c.parent.id, true) " + - "FROM community_comment c " + - "JOIN c.user u " + - "WHERE c.parent.id = :parentId AND c.deletedAt IS NULL " + - "ORDER BY c.createdAt ASC") - List findRepliesByParentId(@Param("parentId") Long parentId); + // 게시글의 원댓글 목록 조회 (parent IS NULL) + @Query( + "SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + + "c.id, c.content, u.nickname, u.email, u.career, u.role, c.createdAt, c.status, " + + "null, false) " + + "FROM community_comment c " + + "JOIN c.user u " + + "WHERE c.post.id = :postId AND c.parent IS NULL AND c.deletedAt IS NULL " + + "ORDER BY c.createdAt DESC") + Page findOriginalCommentsByPostId( + @Param("postId") Long postId, Pageable pageable); - // 게시글의 모든 댓글 조회 (대댓글 포함) - 최적화 버전 - @Query("SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + - "c.id, c.content, u.nickname, u.email ,u.career, u.role, c.createdAt, c.status, " + - "c.parent.id, CASE WHEN c.parent IS NOT NULL THEN true ELSE false END) " + - "FROM community_comment c " + - "JOIN c.user u " + - "LEFT JOIN c.parent p " + - "WHERE c.post.id = :postId AND c.deletedAt IS NULL " + - "AND (:#{#blockedUserIds.isEmpty()} = true OR u.id NOT IN :blockedUserIds) " + - "AND (p IS NULL OR p.deletedAt IS NULL) " + - "AND (p IS NULL OR :#{#blockedUserIds.isEmpty()} = true OR p.user.id NOT IN :blockedUserIds) " + - "ORDER BY COALESCE(p.createdAt, c.createdAt) DESC, " + - "CASE WHEN c.parent IS NULL THEN 0 ELSE 1 END, " + - "c.createdAt ASC") - Page findAllCommentsWithRepliesByPostId( - @Param("postId") Long postId, - @Param("blockedUserIds") List blockedUserIds, - Pageable pageable); + // 특정 원댓글에 대한 대댓글 목록 조회 + @Query( + "SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + + "c.id, c.content, u.nickname, u.email, u.career, u.role, c.createdAt, c.status, " + + "c.parent.id, true) " + + "FROM community_comment c " + + "JOIN c.user u " + + "WHERE c.parent.id = :parentId AND c.deletedAt IS NULL " + + "ORDER BY c.createdAt ASC") + List findRepliesByParentId(@Param("parentId") Long parentId); + // 게시글의 모든 댓글 조회 (대댓글 포함) - 최적화 버전 + @Query( + "SELECT new com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO(" + + "c.id, c.content, u.nickname, u.email ,u.career, u.role, c.createdAt, c.status, " + + "c.parent.id, CASE WHEN c.parent IS NOT NULL THEN true ELSE false END) " + + "FROM community_comment c " + + "JOIN c.user u " + + "LEFT JOIN c.parent p " + + "WHERE c.post.id = :postId AND c.deletedAt IS NULL " + + "AND (:#{#blockedUserIds.isEmpty()} = true OR u.id NOT IN :blockedUserIds) " + + "AND (p IS NULL OR p.deletedAt IS NULL) " + + "AND (p IS NULL OR :#{#blockedUserIds.isEmpty()} = true OR p.user.id NOT IN :blockedUserIds) " + + "ORDER BY COALESCE(p.createdAt, c.createdAt) DESC, " + + "CASE WHEN c.parent IS NULL THEN 0 ELSE 1 END, " + + "c.createdAt ASC") + Page findAllCommentsWithRepliesByPostId( + @Param("postId") Long postId, + @Param("blockedUserIds") List blockedUserIds, + Pageable pageable); - @Query("SELECT c FROM community_comment c WHERE c.user.id = :userId AND c.deletedAt IS NULL") - Page findByUserId(@Param("userId") Long userId, Pageable pageable); + @Query("SELECT c FROM community_comment c WHERE c.user.id = :userId AND c.deletedAt IS NULL") + Page findByUserId(@Param("userId") Long userId, Pageable pageable); - @Query("SELECT c FROM community_comment c JOIN FETCH c.user WHERE c.id = :id AND c.deletedAt IS NULL") - Optional findByIdWithUser(@Param("id") Long id); + @Query( + "SELECT c FROM community_comment c JOIN FETCH c.user WHERE c.id = :id AND c.deletedAt IS NULL") + Optional findByIdWithUser(@Param("id") Long id); - @Modifying - @Query("UPDATE community_comment c SET c.updatedAt = :now, c.deletedAt = :now WHERE c.parent.id = :parentId") - void updateRepliesDeletedAt(@Param("parentId") Long parentId, @Param("now") LocalDateTime now); + @Modifying + @Query( + "UPDATE community_comment c SET c.updatedAt = :now, c.deletedAt = :now WHERE c.parent.id = :parentId") + void updateRepliesDeletedAt(@Param("parentId") Long parentId, @Param("now") LocalDateTime now); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java b/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java index de173a4..ee42d59 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityComments/service/CommunityCommentService.java @@ -1,10 +1,22 @@ package com.onebyone.kindergarten.domain.communityComments.service; +import com.onebyone.kindergarten.domain.communityComments.dto.request.CreateCommentRequestDTO; +import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; import com.onebyone.kindergarten.domain.communityComments.dto.response.PageCommunityCommentsResponseDTO; +import com.onebyone.kindergarten.domain.communityComments.entity.CommunityComment; +import com.onebyone.kindergarten.domain.communityComments.repository.CommunityCommentRepository; +import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityPost; +import com.onebyone.kindergarten.domain.communityPosts.repository.CommunityRepository; +import com.onebyone.kindergarten.domain.pushNotification.service.NotificationTemplateService; +import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.UserRole; +import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.domain.userBlock.repository.UserBlockRepository; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -12,165 +24,156 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.onebyone.kindergarten.domain.communityComments.repository.CommunityCommentRepository; -import com.onebyone.kindergarten.domain.communityComments.dto.request.CreateCommentRequestDTO; -import com.onebyone.kindergarten.domain.communityComments.dto.response.CommentResponseDTO; -import com.onebyone.kindergarten.domain.communityComments.entity.CommunityComment; -import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityPost; -import com.onebyone.kindergarten.domain.communityPosts.repository.CommunityRepository; -import com.onebyone.kindergarten.domain.pushNotification.service.NotificationTemplateService; -import com.onebyone.kindergarten.domain.user.entity.User; -import com.onebyone.kindergarten.domain.user.service.UserService; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Collections; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class CommunityCommentService { - private final CommunityCommentRepository commentRepository; - private final CommunityRepository postRepository; - private final UserService userService; - private final UserBlockRepository userBlockRepository; - private final NotificationTemplateService notificationTemplateService; - - /// 댓글 작성 (원댓글 또는 대댓글) - @Transactional - public CommentResponseDTO createComment(Long postId, CreateCommentRequestDTO dto, Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 게시글 조회 (작성자 정보를 포함) - CommunityPost post = postRepository.findByIdWithUser(postId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); - - CommunityComment parent = null; - - // 대댓글인 경우 부모 댓글 조회 - if (dto.getParentId() != null) { - parent = commentRepository.findByIdWithUser(dto.getParentId()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_PARENT_COMMENT)); - - // 부모 댓글의 게시글과 요청된 게시글이 일치하는지 확인 - if (!parent.getPost().getId().equals(postId)) { - throw new BusinessException(ErrorCodes.PARENT_POST_MISMATCH); - } - - // 이미 대댓글인 경우 대댓글에 대댓글 작성 방지 - if (parent.isReply()) { - throw new BusinessException(ErrorCodes.REPLY_TO_REPLY_NOT_ALLOWED); - } - - // 삭제된 댓글에는 대댓글 작성 불가 - if (parent.getDeletedAt() != null) { - throw new BusinessException(ErrorCodes.REPLY_TO_DELETED_COMMENT_NOT_ALLOWED); - } - } - - // 댓글 작성 - CommunityComment comment = CommunityComment.builder() - .post(post) - .user(user) - .content(dto.getContent()) - .parent(parent) - .build(); - commentRepository.save(comment); - - // 댓글 수 업데이트 (최상위 댓글인 경우에만) - if (parent == null) { - postRepository.incrementCommentCount(postId); - } - - // 알림 대상자 결정 (게시글 작성자 또는 부모 댓글 작성자) - User notificationTarget = parent != null ? parent.getUser() : post.getUser(); - - // 알림 발송 - 게시글이 삭제되지 않은 경우에만 - if (post.getDeletedAt() == null) { - notificationTemplateService.sendCommentNotification( - notificationTarget.getId(), - user, - dto.getContent(), - parent != null, - postId - ); - } - - return CommentResponseDTO.fromEntity(comment); + private final CommunityCommentRepository commentRepository; + private final CommunityRepository postRepository; + private final UserService userService; + private final UserBlockRepository userBlockRepository; + private final NotificationTemplateService notificationTemplateService; + + /// 댓글 작성 (원댓글 또는 대댓글) + @Transactional + public CommentResponseDTO createComment(Long postId, CreateCommentRequestDTO dto, Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 게시글 조회 (작성자 정보를 포함) + CommunityPost post = + postRepository + .findByIdWithUser(postId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + + CommunityComment parent = null; + + // 대댓글인 경우 부모 댓글 조회 + if (dto.getParentId() != null) { + parent = + commentRepository + .findByIdWithUser(dto.getParentId()) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_PARENT_COMMENT)); + + // 부모 댓글의 게시글과 요청된 게시글이 일치하는지 확인 + if (!parent.getPost().getId().equals(postId)) { + throw new BusinessException(ErrorCodes.PARENT_POST_MISMATCH); + } + + // 이미 대댓글인 경우 대댓글에 대댓글 작성 방지 + if (parent.isReply()) { + throw new BusinessException(ErrorCodes.REPLY_TO_REPLY_NOT_ALLOWED); + } + + // 삭제된 댓글에는 대댓글 작성 불가 + if (parent.getDeletedAt() != null) { + throw new BusinessException(ErrorCodes.REPLY_TO_DELETED_COMMENT_NOT_ALLOWED); + } } - /// 게시글의 최상위 댓글 목록 조회 (대댓글 제외) - public Page getOriginalComments(Long postId, Pageable pageable) { - return commentRepository.findOriginalCommentsByPostId(postId, pageable); + // 댓글 작성 + CommunityComment comment = + CommunityComment.builder() + .post(post) + .user(user) + .content(dto.getContent()) + .parent(parent) + .build(); + commentRepository.save(comment); + + // 댓글 수 업데이트 (최상위 댓글인 경우에만) + if (parent == null) { + postRepository.incrementCommentCount(postId); } - - /// 특정 댓글의 대댓글 목록 조회 - public List getReplies(Long commentId) { - return commentRepository.findRepliesByParentId(commentId); + + // 알림 대상자 결정 (게시글 작성자 또는 부모 댓글 작성자) + User notificationTarget = parent != null ? parent.getUser() : post.getUser(); + + // 알림 발송 - 게시글이 삭제되지 않은 경우에만 + if (post.getDeletedAt() == null) { + notificationTemplateService.sendCommentNotification( + notificationTarget.getId(), user, dto.getContent(), parent != null, postId); } - - /// 게시글의 모든 댓글과 대댓글 목록 조회 (계층 구조로 정렬) - public Page getAllCommentsWithReplies(Long postId, Pageable pageable, Long userId) { - - // 차단된 사용자 ID 목록 조회 - List blockedUserIds = Collections.emptyList(); - if (userId != null) { - User user = userService.getUserById(userId); - blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); - if (blockedUserIds == null) { - blockedUserIds = Collections.emptyList(); - } - } - - return commentRepository.findAllCommentsWithRepliesByPostId(postId, blockedUserIds, pageable); + + return CommentResponseDTO.fromEntity(comment); + } + + /// 게시글의 최상위 댓글 목록 조회 (대댓글 제외) + public Page getOriginalComments(Long postId, Pageable pageable) { + return commentRepository.findOriginalCommentsByPostId(postId, pageable); + } + + /// 특정 댓글의 대댓글 목록 조회 + public List getReplies(Long commentId) { + return commentRepository.findRepliesByParentId(commentId); + } + + /// 게시글의 모든 댓글과 대댓글 목록 조회 (계층 구조로 정렬) + public Page getAllCommentsWithReplies( + Long postId, Pageable pageable, Long userId) { + + // 차단된 사용자 ID 목록 조회 + List blockedUserIds = Collections.emptyList(); + if (userId != null) { + User user = userService.getUserById(userId); + blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); + if (blockedUserIds == null) { + blockedUserIds = Collections.emptyList(); + } } - @Transactional(readOnly = true) - public PageCommunityCommentsResponseDTO getWroteMyCommunityComments(Long userId, int page, int size) { - Pageable pageable = PageRequest.of(page, size); + return commentRepository.findAllCommentsWithRepliesByPostId(postId, blockedUserIds, pageable); + } + + @Transactional(readOnly = true) + public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( + Long userId, int page, int size) { + Pageable pageable = PageRequest.of(page, size); + + Page commentsPage = + commentRepository.findByUserId(userId, pageable).map(CommentResponseDTO::fromEntity); + + PageCommunityCommentsResponseDTO response = new PageCommunityCommentsResponseDTO(); + response.setContent(commentsPage.getContent()); + response.setTotalPages(commentsPage.getTotalPages()); + + return response; + } + + /// 댓글 삭제 (소프트 삭제) + @Transactional + public void deleteComment(Long commentId, Long userId) { + // 댓글 조회 (작성자 정보 포함) + CommunityComment comment = + commentRepository + .findByIdWithUser(commentId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_COMMENT)); + + // 현재 사용자 조회 + User currentUser = userService.getUserById(userId); + + // 작성자 또는 관리자 권한 확인 + if (!comment.getUser().getId().equals(userId) + && !currentUser.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); + } - Page commentsPage = commentRepository.findByUserId(userId, pageable) - .map(CommentResponseDTO::fromEntity); + Long postId = comment.getPost().getId(); + boolean isOriginalComment = comment.getParent() == null; - PageCommunityCommentsResponseDTO response = new PageCommunityCommentsResponseDTO(); - response.setContent(commentsPage.getContent()); - response.setTotalPages(commentsPage.getTotalPages()); + // 댓글 소프트 삭제 (deletedAt 설정) + comment.markAsDeleted(); - return response; + // 대댓글이 있는 원댓글인 경우 대댓글들도 함께 삭제 + if (isOriginalComment) { + commentRepository.updateRepliesDeletedAt(commentId, LocalDateTime.now()); } - /// 댓글 삭제 (소프트 삭제) - @Transactional - public void deleteComment(Long commentId, Long userId) { - // 댓글 조회 (작성자 정보 포함) - CommunityComment comment = commentRepository.findByIdWithUser(commentId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_COMMENT)); - - // 현재 사용자 조회 - User currentUser = userService.getUserById(userId); - - // 작성자 또는 관리자 권한 확인 - if (!comment.getUser().getId().equals(userId) && !currentUser.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - Long postId = comment.getPost().getId(); - boolean isOriginalComment = comment.getParent() == null; - - // 댓글 소프트 삭제 (deletedAt 설정) - comment.markAsDeleted(); - - // 대댓글이 있는 원댓글인 경우 대댓글들도 함께 삭제 - if (isOriginalComment) { - commentRepository.updateRepliesDeletedAt(commentId, LocalDateTime.now()); - } - - // 원댓글이었다면 게시글의 댓글 수 감소 - if (isOriginalComment) { - postRepository.decrementCommentCount(postId, 1); - } + // 원댓글이었다면 게시글의 댓글 수 감소 + if (isOriginalComment) { + postRepository.decrementCommentCount(postId, 1); } + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java index b9add42..ddf0386 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/controller/CommunityController.java @@ -1,96 +1,89 @@ package com.onebyone.kindergarten.domain.communityPosts.controller; +import com.onebyone.kindergarten.domain.communityPosts.dto.request.CommunitySearchDTO; import com.onebyone.kindergarten.domain.communityPosts.dto.request.CreateCommunityPostRequestDTO; +import com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO; import com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityPostResponseDTO; +import com.onebyone.kindergarten.domain.communityPosts.service.CommunityLikeService; import com.onebyone.kindergarten.domain.communityPosts.service.CommunityService; -import com.onebyone.kindergarten.global.facade.CommunityFacade; import com.onebyone.kindergarten.global.common.PageResponseDTO; import com.onebyone.kindergarten.global.common.ResponseDto; -import com.onebyone.kindergarten.domain.communityPosts.dto.request.CommunitySearchDTO; -import com.onebyone.kindergarten.domain.communityPosts.service.CommunityLikeService; -import com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO; - +import com.onebyone.kindergarten.global.facade.CommunityFacade; 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 org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; -import org.springframework.web.bind.annotation.*; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; - -import java.util.List; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/community") @Tag(name = "커뮤니티 게시글", description = "커뮤니티 게시글 관련 API") public class CommunityController { - private final CommunityFacade communityFacade; - private final CommunityService communityService; - private final CommunityLikeService communityLikeService; + private final CommunityFacade communityFacade; + private final CommunityService communityService; + private final CommunityLikeService communityLikeService; - @PostMapping() - @Operation(summary = "커뮤니티 게시글 생성", description = "게시글을 생성합니다.") - public ResponseDto createPost( - @Valid @RequestBody CreateCommunityPostRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(communityService.createPost(request, Long.valueOf(userDetails.getUsername()))); - } + @PostMapping() + @Operation(summary = "커뮤니티 게시글 생성", description = "게시글을 생성합니다.") + public ResponseDto createPost( + @Valid @RequestBody CreateCommunityPostRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + communityService.createPost(request, Long.valueOf(userDetails.getUsername()))); + } - @DeleteMapping("/{id}") - @Operation(summary = "커뮤니티 게시글 삭제", description = "게시글을 삭제합니다. 본인이 작성한 게시글 또는 관리자가 삭제할 수 있습니다.") - public ResponseDto deletePost( - @PathVariable Long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - communityFacade.deletePost(id, Long.valueOf(userDetails.getUsername())); - return ResponseDto.success("게시글이 삭제되었습니다."); - } + @DeleteMapping("/{id}") + @Operation(summary = "커뮤니티 게시글 삭제", description = "게시글을 삭제합니다. 본인이 작성한 게시글 또는 관리자가 삭제할 수 있습니다.") + public ResponseDto deletePost( + @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) { + communityFacade.deletePost(id, Long.valueOf(userDetails.getUsername())); + return ResponseDto.success("게시글이 삭제되었습니다."); + } - @GetMapping() - @Operation(summary = "커뮤니티 게시글 목록 조회", description = "게시글 목록을 조회하고 검색합니다. 차단한 사용자의 게시글은 제외됩니다.") - public PageResponseDTO getPosts( - CommunitySearchDTO searchDTO, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails - ) { - Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; - return new PageResponseDTO<>(communityService.getPosts(searchDTO, pageable, userId)); - } + @GetMapping() + @Operation(summary = "커뮤니티 게시글 목록 조회", description = "게시글 목록을 조회하고 검색합니다. 차단한 사용자의 게시글은 제외됩니다.") + public PageResponseDTO getPosts( + CommunitySearchDTO searchDTO, + @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal UserDetails userDetails) { + Long userId = userDetails != null ? Long.valueOf(userDetails.getUsername()) : null; + return new PageResponseDTO<>(communityService.getPosts(searchDTO, pageable, userId)); + } - @GetMapping("/{id}") - @Operation(summary = "커뮤니티 게시글 상세 조회", description = "게시글을 상세 조회합니다.") - public ResponseDto getPost( - @PathVariable Long id - ) { - return ResponseDto.success(communityService.getPost(id)); - } + @GetMapping("/{id}") + @Operation(summary = "커뮤니티 게시글 상세 조회", description = "게시글을 상세 조회합니다.") + public ResponseDto getPost(@PathVariable Long id) { + return ResponseDto.success(communityService.getPost(id)); + } - @GetMapping("/top") - @Operation(summary = "인기 게시글 TOP 10 조회", description = "좋아요 수, 조회수, 최신도를 기반으로 한 점수로 인기 게시글 TOP 10을 조회합니다.") - public ResponseDto> getTopPosts() { - return ResponseDto.success(communityService.getTopPosts()); - } + @GetMapping("/top") + @Operation( + summary = "인기 게시글 TOP 10 조회", + description = "좋아요 수, 조회수, 최신도를 기반으로 한 점수로 인기 게시글 TOP 10을 조회합니다.") + public ResponseDto> getTopPosts() { + return ResponseDto.success(communityService.getTopPosts()); + } - @PostMapping("/{postId}/like") - @Operation(summary = "게시글 좋아요 토글", description = "게시글에 좋아요를 추가하거나 취소합니다.") - public ResponseDto toggleLike( - @PathVariable Long postId, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(communityLikeService.toggleLike(postId, Long.valueOf(userDetails.getUsername()))); - } + @PostMapping("/{postId}/like") + @Operation(summary = "게시글 좋아요 토글", description = "게시글에 좋아요를 추가하거나 취소합니다.") + public ResponseDto toggleLike( + @PathVariable Long postId, @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + communityLikeService.toggleLike(postId, Long.valueOf(userDetails.getUsername()))); + } - @GetMapping("/{postId}/like") - @Operation(summary = "게시글 좋아요 상태 조회", description = "현재 사용자가 게시글에 좋아요를 눌렀는지 확인합니다.") - public ResponseDto getLikeStatus( - @PathVariable Long postId, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(communityLikeService.getLikeInfo(postId, Long.valueOf(userDetails.getUsername()))); - } + @GetMapping("/{postId}/like") + @Operation(summary = "게시글 좋아요 상태 조회", description = "현재 사용자가 게시글에 좋아요를 눌렀는지 확인합니다.") + public ResponseDto getLikeStatus( + @PathVariable Long postId, @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + communityLikeService.getLikeInfo(postId, Long.valueOf(userDetails.getUsername()))); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CommunitySearchDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CommunitySearchDTO.java index f04ce63..88170b1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CommunitySearchDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CommunitySearchDTO.java @@ -1,19 +1,18 @@ package com.onebyone.kindergarten.domain.communityPosts.dto.request; import com.onebyone.kindergarten.domain.communityPosts.enums.PostCategory; +import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; -import java.time.LocalDateTime; - @Getter @Setter public class CommunitySearchDTO { - private String title; // 제목 - private String content; // 내용 - private PostCategory category; // 카테고리(enum) - private String categoryName; // 카테고리 이름 - private String userName; // 작성자 이름 - private LocalDateTime startDate; // 검색 시작일 - private LocalDateTime endDate; // 검색 종료일 -} \ No newline at end of file + private String title; // 제목 + private String content; // 내용 + private PostCategory category; // 카테고리(enum) + private String categoryName; // 카테고리 이름 + private String userName; // 작성자 이름 + private LocalDateTime startDate; // 검색 시작일 + private LocalDateTime endDate; // 검색 종료일 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CreateCommunityPostRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CreateCommunityPostRequestDTO.java index cca7e81..144c8dc 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CreateCommunityPostRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/request/CreateCommunityPostRequestDTO.java @@ -1,6 +1,5 @@ package com.onebyone.kindergarten.domain.communityPosts.dto.request; -import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityCategory; import com.onebyone.kindergarten.domain.communityPosts.enums.PostCategory; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -11,20 +10,17 @@ @Getter @NoArgsConstructor public class CreateCommunityPostRequestDTO { - @NotBlank(message = "제목은 필수입니다.") - @Size(max = 100, message = "제목은 100자를 넘을 수 없습니다.") - private String title; + @NotBlank(message = "제목은 필수입니다.") + @Size(max = 100, message = "제목은 100자를 넘을 수 없습니다.") + private String title; - @NotBlank(message = "내용은 필수입니다.") - @Size(max = 2000, message = "내용은 2000자를 넘을 수 없습니다.") - private String content; + @NotBlank(message = "내용은 필수입니다.") + @Size(max = 2000, message = "내용은 2000자를 넘을 수 없습니다.") + private String content; - @NotNull(message = "카테고리는 필수입니다.") - private PostCategory category; + @NotNull(message = "카테고리는 필수입니다.") private PostCategory category; - @NotNull(message = "커뮤니티 카테고리는 필수입니다.") - private String communityCategoryName; + @NotNull(message = "커뮤니티 카테고리는 필수입니다.") private String communityCategoryName; - @NotNull(message = "커뮤니티 카테고리 설명은 필수입니다.") - private String communityCategoryDescription; -} \ No newline at end of file + @NotNull(message = "커뮤니티 카테고리 설명은 필수입니다.") private String communityCategoryDescription; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityLikeResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityLikeResponseDTO.java index b01d591..d93a5ef 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityLikeResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityLikeResponseDTO.java @@ -6,6 +6,6 @@ @Getter @AllArgsConstructor public class CommunityLikeResponseDTO { - private boolean liked; // 좋아요 상태 - private int likeCount; // 좋아요 수 -} \ No newline at end of file + private boolean liked; // 좋아요 상태 + private int likeCount; // 좋아요 수 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityPostResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityPostResponseDTO.java index bdfaa7c..542fd27 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityPostResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/dto/response/CommunityPostResponseDTO.java @@ -1,27 +1,27 @@ package com.onebyone.kindergarten.domain.communityPosts.dto.response; + import com.onebyone.kindergarten.domain.communityPosts.enums.PostCategory; import com.onebyone.kindergarten.domain.user.enums.UserRole; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Getter @Builder public class CommunityPostResponseDTO { - private Long id; - private String title; - private String content; - private PostCategory category; - private String categoryName; - private String categoryDescription; - private String userNickname; - private String userEmail; - private UserRole userRole; - private String career; - private Integer viewCount; - private Integer likeCount; - private Integer commentCount; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; -} \ No newline at end of file + private Long id; + private String title; + private String content; + private PostCategory category; + private String categoryName; + private String categoryDescription; + private String userNickname; + private String userEmail; + private UserRole userRole; + private String career; + private Integer viewCount; + private Integer likeCount; + private Integer commentCount; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityCategory.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityCategory.java index 7a32cd4..41fcdfa 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityCategory.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityCategory.java @@ -11,29 +11,28 @@ @NoArgsConstructor @Table(name = "community_category") public class CommunityCategory extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, unique = true) - private String categoryName; // 카테고리 이름 - - @Column(length = 500) - private String description; // 카테고리 설명 - - @Column(name = "display_order", nullable = true) - private Integer displayOrder; // 정렬을 위한 순서 - - @Column(nullable = false) - private Boolean isActive = true; // 활성화 여부 - - - @Builder - public CommunityCategory(String categoryName, String description, Integer displayOrder, Boolean isActive) { - this.categoryName = categoryName; - this.description = description; - this.displayOrder = displayOrder; - this.isActive = isActive; - } - -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String categoryName; // 카테고리 이름 + + @Column(length = 500) + private String description; // 카테고리 설명 + + @Column(name = "display_order", nullable = true) + private Integer displayOrder; // 정렬을 위한 순서 + + @Column(nullable = false) + private Boolean isActive = true; // 활성화 여부 + + @Builder + public CommunityCategory( + String categoryName, String description, Integer displayOrder, Boolean isActive) { + this.categoryName = categoryName; + this.description = description; + this.displayOrder = displayOrder; + this.isActive = isActive; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityLike.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityLike.java index 6bc9355..52bca8b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityLike.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityLike.java @@ -13,28 +13,26 @@ @Table( // 유니크 제약 조건 추가 uniqueConstraints = { - @UniqueConstraint( - name = "uk_community_like", - columnNames = {"user_id", "post_id"} - ) - } -) + @UniqueConstraint( + name = "uk_community_like", + columnNames = {"user_id", "post_id"}) + }) public class CommunityLike extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private CommunityPost post; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private CommunityPost post; - @Builder - public CommunityLike(User user, CommunityPost post) { - this.user = user; - this.post = post; - } + @Builder + public CommunityLike(User user, CommunityPost post) { + this.user = user; + this.post = post; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java index dfa7c22..28cc483 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java @@ -5,82 +5,85 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import com.onebyone.kindergarten.global.enums.ReportStatus; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor public class CommunityPost extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 게시글 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 작성자 - - @Column(nullable = false, length = 100) - private String title; // 제목 - - @Column(nullable = false, length = 2000) - private String content; // 내용 - - @Enumerated(EnumType.STRING) - private PostCategory category; // 게시글 카테고리 - 선생님, 예비 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "category_id", nullable = false) - private CommunityCategory communityCategory; // 커뮤니티 카테고리 - - @Column(name = "like_count") - private Integer likeCount = 0; // 좋아요 수 - - @Column(name = "comment_count") - private Integer commentCount = 0; // 댓글 수 - - @Enumerated(EnumType.STRING) - private ReportStatus status = ReportStatus.YET; // 게시글 상태 - 차단 여부 (PENDING, PROCESSED, REJECTED) - - @Column(name = "view_count") - private Integer viewCount = 0; // 조회수 - - @Builder - public CommunityPost(User user, String title, String content, - PostCategory category, CommunityCategory communityCategory) { - this.user = user; - this.title = title; - this.content = content; - this.category = category; - this.communityCategory = communityCategory; - } - - public void setCategory(CommunityCategory category) { - this.communityCategory = category; - } - - // 좋아요 증가 - public void increaseLikeCount() { - this.likeCount++; - } - - // 좋아요 감소 - public void decreaseLikeCount() { - this.likeCount--; - } - - /// 게시물 신고 상태 변경 - public void updateStatus(ReportStatus status) { - this.status = status; - this.updatedAt = LocalDateTime.now(); - } - - /// 게시물 소프트 삭제 - public void markAsDeleted() { - this.updatedAt = LocalDateTime.now(); - this.deletedAt = LocalDateTime.now(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 게시글 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 작성자 + + @Column(nullable = false, length = 100) + private String title; // 제목 + + @Column(nullable = false, length = 2000) + private String content; // 내용 + + @Enumerated(EnumType.STRING) + private PostCategory category; // 게시글 카테고리 - 선생님, 예비 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + private CommunityCategory communityCategory; // 커뮤니티 카테고리 + + @Column(name = "like_count") + private Integer likeCount = 0; // 좋아요 수 + + @Column(name = "comment_count") + private Integer commentCount = 0; // 댓글 수 + + @Enumerated(EnumType.STRING) + private ReportStatus status = ReportStatus.YET; // 게시글 상태 - 차단 여부 (PENDING, PROCESSED, REJECTED) + + @Column(name = "view_count") + private Integer viewCount = 0; // 조회수 + + @Builder + public CommunityPost( + User user, + String title, + String content, + PostCategory category, + CommunityCategory communityCategory) { + this.user = user; + this.title = title; + this.content = content; + this.category = category; + this.communityCategory = communityCategory; + } + + public void setCategory(CommunityCategory category) { + this.communityCategory = category; + } + + // 좋아요 증가 + public void increaseLikeCount() { + this.likeCount++; + } + + // 좋아요 감소 + public void decreaseLikeCount() { + this.likeCount--; + } + + /// 게시물 신고 상태 변경 + public void updateStatus(ReportStatus status) { + this.status = status; + this.updatedAt = LocalDateTime.now(); + } + + /// 게시물 소프트 삭제 + public void markAsDeleted() { + this.updatedAt = LocalDateTime.now(); + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/enums/PostCategory.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/enums/PostCategory.java index cd04e01..a67e732 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/enums/PostCategory.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/enums/PostCategory.java @@ -1,3 +1,6 @@ package com.onebyone.kindergarten.domain.communityPosts.enums; -public enum PostCategory { TEACHER, PROSPECTIVE_TEACHER } +public enum PostCategory { + TEACHER, + PROSPECTIVE_TEACHER +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/mapper/CommunityPostMapper.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/mapper/CommunityPostMapper.java index 73b8ec5..3bd7ba5 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/mapper/CommunityPostMapper.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/mapper/CommunityPostMapper.java @@ -9,36 +9,37 @@ @Component public class CommunityPostMapper { - - /// 엔티티 변환 - public CommunityPost toEntity(CreateCommunityPostRequestDTO request, User user, CommunityCategory category) { - return CommunityPost.builder() - .title(request.getTitle()) - .content(request.getContent()) - .category(request.getCategory()) - .communityCategory(category) - .user(user) - .build(); - } - /// 응답 DTO 변환 - public CommunityPostResponseDTO toResponse(CommunityPost post) { - return CommunityPostResponseDTO.builder() - .id(post.getId()) - .title(post.getTitle()) - .content(post.getContent()) - .category(post.getCategory()) - .categoryName(post.getCommunityCategory().getCategoryName()) - .categoryDescription(post.getCommunityCategory().getDescription()) - .userNickname(post.getUser().getNickname()) - .userEmail(post.getUser().getEmail()) - .userRole(post.getUser().getRole()) - .career(post.getUser().getCareer()) - .viewCount(post.getViewCount()) - .likeCount(post.getLikeCount()) - .commentCount(post.getCommentCount()) - .createdAt(post.getCreatedAt()) - .updatedAt(post.getUpdatedAt()) - .build(); - } -} \ No newline at end of file + /// 엔티티 변환 + public CommunityPost toEntity( + CreateCommunityPostRequestDTO request, User user, CommunityCategory category) { + return CommunityPost.builder() + .title(request.getTitle()) + .content(request.getContent()) + .category(request.getCategory()) + .communityCategory(category) + .user(user) + .build(); + } + + /// 응답 DTO 변환 + public CommunityPostResponseDTO toResponse(CommunityPost post) { + return CommunityPostResponseDTO.builder() + .id(post.getId()) + .title(post.getTitle()) + .content(post.getContent()) + .category(post.getCategory()) + .categoryName(post.getCommunityCategory().getCategoryName()) + .categoryDescription(post.getCommunityCategory().getDescription()) + .userNickname(post.getUser().getNickname()) + .userEmail(post.getUser().getEmail()) + .userRole(post.getUser().getRole()) + .career(post.getUser().getCareer()) + .viewCount(post.getViewCount()) + .likeCount(post.getLikeCount()) + .commentCount(post.getCommentCount()) + .createdAt(post.getCreatedAt()) + .updatedAt(post.getUpdatedAt()) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityCategoryRepository.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityCategoryRepository.java index f751b68..db8f252 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityCategoryRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityCategoryRepository.java @@ -1,11 +1,10 @@ package com.onebyone.kindergarten.domain.communityPosts.repository; +import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityCategory; import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; -import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityCategory; public interface CommunityCategoryRepository extends JpaRepository { - // 카테고리 이름으로 조회 - Optional findByCategoryName(String categoryName); + // 카테고리 이름으로 조회 + Optional findByCategoryName(String categoryName); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityLikeRepository.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityLikeRepository.java index ff0a63d..d2bcba1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityLikeRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityLikeRepository.java @@ -1,37 +1,34 @@ package com.onebyone.kindergarten.domain.communityPosts.repository; +import com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO; import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityLike; import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityPost; import com.onebyone.kindergarten.domain.user.entity.User; -import com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Optional; - public interface CommunityLikeRepository extends JpaRepository { - @Query("SELECT new com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO(" + - "CASE WHEN COUNT(cl2) > 0 THEN true ELSE false END, " + - "CAST(COUNT(cl) AS int)) " + - "FROM CommunityPost cp " + - "LEFT JOIN CommunityLike cl ON cl.post = cp " + - "LEFT JOIN CommunityLike cl2 ON cl2.post = cp AND cl2.user = :user " + - "WHERE cp.id = :postId " + - "GROUP BY cp") - Optional findLikeInfo(@Param("postId") Long postId, @Param("user") User user); - - @Query("SELECT cl FROM CommunityLike cl " + - "WHERE cl.user = :user AND cl.post.id = :postId") - Optional findByUserAndPostId( - @Param("user") User user, - @Param("postId") Long postId - ); - - boolean existsByUserAndPost(User user, CommunityPost post); - - @Query("SELECT COUNT(cl) FROM CommunityLike cl WHERE cl.post.id = :postId") - int countByPostId(@Param("postId") Long postId); - -} \ No newline at end of file + @Query( + "SELECT new com.onebyone.kindergarten.domain.communityPosts.dto.response.CommunityLikeResponseDTO(" + + "CASE WHEN COUNT(cl2) > 0 THEN true ELSE false END, " + + "CAST(COUNT(cl) AS int)) " + + "FROM CommunityPost cp " + + "LEFT JOIN CommunityLike cl ON cl.post = cp " + + "LEFT JOIN CommunityLike cl2 ON cl2.post = cp AND cl2.user = :user " + + "WHERE cp.id = :postId " + + "GROUP BY cp") + Optional findLikeInfo( + @Param("postId") Long postId, @Param("user") User user); + + @Query("SELECT cl FROM CommunityLike cl " + "WHERE cl.user = :user AND cl.post.id = :postId") + Optional findByUserAndPostId( + @Param("user") User user, @Param("postId") Long postId); + + boolean existsByUserAndPost(User user, CommunityPost post); + + @Query("SELECT COUNT(cl) FROM CommunityLike cl WHERE cl.post.id = :postId") + int countByPostId(@Param("postId") Long postId); +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityRepository.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityRepository.java index b49aa5b..9d5f72c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/repository/CommunityRepository.java @@ -1,5 +1,9 @@ package com.onebyone.kindergarten.domain.communityPosts.repository; +import com.onebyone.kindergarten.domain.communityPosts.dto.request.CommunitySearchDTO; +import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityPost; +import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,64 +12,60 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.onebyone.kindergarten.domain.communityPosts.dto.request.CommunitySearchDTO; -import com.onebyone.kindergarten.domain.communityPosts.entity.CommunityPost; -import java.util.List; -import java.util.Optional; - @Repository public interface CommunityRepository extends JpaRepository { - @Query("SELECT p FROM CommunityPost p " + - "JOIN FETCH p.user " + - "JOIN FETCH p.communityCategory " + - "WHERE p.id = :id AND p.deletedAt IS NULL") - Optional findByIdWithUser(@Param("id") Long id); + @Query( + "SELECT p FROM CommunityPost p " + + "JOIN FETCH p.user " + + "JOIN FETCH p.communityCategory " + + "WHERE p.id = :id AND p.deletedAt IS NULL") + Optional findByIdWithUser(@Param("id") Long id); - @Modifying - @Query("UPDATE CommunityPost p " + - "SET p.viewCount = p.viewCount + 1 " + - "WHERE p.id = :id") - void increaseViewCount(@Param("id") Long id); + @Modifying + @Query("UPDATE CommunityPost p " + "SET p.viewCount = p.viewCount + 1 " + "WHERE p.id = :id") + void increaseViewCount(@Param("id") Long id); - @Query("SELECT p FROM CommunityPost p " + - "JOIN FETCH p.user " + - "JOIN FETCH p.communityCategory " + - "WHERE p.deletedAt IS NULL " + - "ORDER BY " + - "((p.likeCount * 3 + p.viewCount * 0.05) * " + - "(1.0 / (1.0 + (FUNCTION('DATEDIFF', CURRENT_DATE, CAST(p.createdAt AS date)) / 7.0)))) DESC " + - "LIMIT 10") - List findTop10WithUserOrderByLikeCountDescViewCountDesc(); + @Query( + "SELECT p FROM CommunityPost p " + + "JOIN FETCH p.user " + + "JOIN FETCH p.communityCategory " + + "WHERE p.deletedAt IS NULL " + + "ORDER BY " + + "((p.likeCount * 3 + p.viewCount * 0.05) * " + + "(1.0 / (1.0 + (FUNCTION('DATEDIFF', CURRENT_DATE, CAST(p.createdAt AS date)) / 7.0)))) DESC " + + "LIMIT 10") + List findTop10WithUserOrderByLikeCountDescViewCountDesc(); - @Query(""" - SELECT p FROM CommunityPost p - JOIN FETCH p.user u - JOIN FETCH p.communityCategory c + @Query( + """ + SELECT p FROM CommunityPost p + JOIN FETCH p.user u + JOIN FETCH p.communityCategory c WHERE p.deletedAt IS NULL - AND (:#{#search.title} IS NULL OR p.title LIKE %:#{#search.title}%) - AND (:#{#search.content} IS NULL OR p.content LIKE %:#{#search.content}%) - AND (:#{#search.category} IS NULL OR p.category = :#{#search.category}) - AND (:#{#search.categoryName} IS NULL OR c.categoryName LIKE %:#{#search.categoryName}%) - AND (:#{#search.userName} IS NULL OR u.nickname LIKE %:#{#search.userName}%) - AND (:#{#search.startDate} IS NULL OR p.createdAt >= :#{#search.startDate}) + AND (:#{#search.title} IS NULL OR p.title LIKE %:#{#search.title}%) + AND (:#{#search.content} IS NULL OR p.content LIKE %:#{#search.content}%) + AND (:#{#search.category} IS NULL OR p.category = :#{#search.category}) + AND (:#{#search.categoryName} IS NULL OR c.categoryName LIKE %:#{#search.categoryName}%) + AND (:#{#search.userName} IS NULL OR u.nickname LIKE %:#{#search.userName}%) + AND (:#{#search.startDate} IS NULL OR p.createdAt >= :#{#search.startDate}) AND (:#{#search.endDate} IS NULL OR p.createdAt <= :#{#search.endDate}) AND u.id NOT IN :blockedUserIds """) - Page search( - @Param("search") CommunitySearchDTO search, - @Param("blockedUserIds") List blockedUserIds, - Pageable pageable); + Page search( + @Param("search") CommunitySearchDTO search, + @Param("blockedUserIds") List blockedUserIds, + Pageable pageable); - @Modifying - @Query("UPDATE CommunityPost p SET p.likeCount = p.likeCount + :delta WHERE p.id = :postId") - void updateLikeCount(@Param("postId") Long postId, @Param("delta") int delta); + @Modifying + @Query("UPDATE CommunityPost p SET p.likeCount = p.likeCount + :delta WHERE p.id = :postId") + void updateLikeCount(@Param("postId") Long postId, @Param("delta") int delta); - @Modifying - @Query("UPDATE CommunityPost p SET p.commentCount = p.commentCount + 1 WHERE p.id = :postId") - void incrementCommentCount(@Param("postId") Long postId); + @Modifying + @Query("UPDATE CommunityPost p SET p.commentCount = p.commentCount + 1 WHERE p.id = :postId") + void incrementCommentCount(@Param("postId") Long postId); - @Modifying - @Query("UPDATE CommunityPost p SET p.commentCount = p.commentCount - :count WHERE p.id = :postId") - void decrementCommentCount(@Param("postId") Long postId, @Param("count") int count); + @Modifying + @Query("UPDATE CommunityPost p SET p.commentCount = p.commentCount - :count WHERE p.id = :postId") + void decrementCommentCount(@Param("postId") Long postId, @Param("count") int count); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java index e3357e6..526b805 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java @@ -19,60 +19,58 @@ @Transactional(readOnly = true) public class CommunityLikeService { - private final CommunityLikeRepository communityLikeRepository; - private final CommunityRepository communityRepository; - private final UserService userService; - private final NotificationTemplateService notificationTemplateService; + private final CommunityLikeRepository communityLikeRepository; + private final CommunityRepository communityRepository; + private final UserService userService; + private final NotificationTemplateService notificationTemplateService; - /// 게시글 좋아요 상태 조회 및 좋아요 수 조회 - public CommunityLikeResponseDTO getLikeInfo(Long postId, Long userId) { + /// 게시글 좋아요 상태 조회 및 좋아요 수 조회 + public CommunityLikeResponseDTO getLikeInfo(Long postId, Long userId) { - // 사용자 조회 - User user = userService.getUserById(userId); - - // 한 번의 쿼리로 좋아요 상태와 개수를 함께 조회 - return communityLikeRepository.findLikeInfo(postId, user) - .orElse(new CommunityLikeResponseDTO(false, 0)); - } + // 사용자 조회 + User user = userService.getUserById(userId); - /// 게시글 좋아요/좋아요 취소 토글 - @Transactional - public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { - // 사용자 조회 - User user = userService.getUserById(userId); + // 한 번의 쿼리로 좋아요 상태와 개수를 함께 조회 + return communityLikeRepository + .findLikeInfo(postId, user) + .orElse(new CommunityLikeResponseDTO(false, 0)); + } - // 게시글 존재 여부와 좋아요 여부를 한 번에 확인 - return communityLikeRepository.findByUserAndPostId(user, postId) - .map(like -> { - // 좋아요 취소 - communityLikeRepository.delete(like); - communityRepository.updateLikeCount(postId, -1); - return new CommunityLikeResponseDTO(false, like.getPost().getLikeCount() - 1); - }) - .orElseGet(() -> { - // 게시글 존재 여부 확인 및 좋아요 추가 - CommunityPost post = communityRepository.findById(postId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); - - CommunityLike newLike = CommunityLike.builder() - .user(user) - .post(post) - .build(); - communityLikeRepository.save(newLike); - communityRepository.updateLikeCount(postId, 1); + /// 게시글 좋아요/좋아요 취소 토글 + @Transactional + public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { + // 사용자 조회 + User user = userService.getUserById(userId); - // 알림 발송 - 본인 글이 아니고 삭제된 게시글이 아닌 경우 - if (!post.getUser().getId().equals(user.getId()) && - post.getDeletedAt() == null) { - notificationTemplateService.sendLikeNotification( - post.getUser().getId(), - user, - post.getTitle(), - post.getId() - ); - } - - return new CommunityLikeResponseDTO(true, post.getLikeCount() + 1); - }); - } -} \ No newline at end of file + // 게시글 존재 여부와 좋아요 여부를 한 번에 확인 + return communityLikeRepository + .findByUserAndPostId(user, postId) + .map( + like -> { + // 좋아요 취소 + communityLikeRepository.delete(like); + communityRepository.updateLikeCount(postId, -1); + return new CommunityLikeResponseDTO(false, like.getPost().getLikeCount() - 1); + }) + .orElseGet( + () -> { + // 게시글 존재 여부 확인 및 좋아요 추가 + CommunityPost post = + communityRepository + .findById(postId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + + CommunityLike newLike = CommunityLike.builder().user(user).post(post).build(); + communityLikeRepository.save(newLike); + communityRepository.updateLikeCount(postId, 1); + + // 알림 발송 - 본인 글이 아니고 삭제된 게시글이 아닌 경우 + if (!post.getUser().getId().equals(user.getId()) && post.getDeletedAt() == null) { + notificationTemplateService.sendLikeNotification( + post.getUser().getId(), user, post.getTitle(), post.getId()); + } + + return new CommunityLikeResponseDTO(true, post.getLikeCount() + 1); + }); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java index 429ed93..374a3f0 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityService.java @@ -15,6 +15,9 @@ import com.onebyone.kindergarten.global.config.CacheConfig; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -23,108 +26,112 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Collections; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class CommunityService { - private final CommunityRepository communityRepository; - private final CommunityCategoryRepository communityCategoryRepository; - private final UserService userService; - private final UserBlockRepository userBlockRepository; - private final CommunityPostMapper communityPostMapper; - - /// 게시글 생성 - @Transactional - public CommunityPostResponseDTO createPost(CreateCommunityPostRequestDTO request, Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 커뮤니티 카테고리 조회 또는 생성 - CommunityCategory communityCategory = communityCategoryRepository.findByCategoryName(request.getCommunityCategoryName()) - .orElseGet(() -> communityCategoryRepository.save( + private final CommunityRepository communityRepository; + private final CommunityCategoryRepository communityCategoryRepository; + private final UserService userService; + private final UserBlockRepository userBlockRepository; + private final CommunityPostMapper communityPostMapper; + + /// 게시글 생성 + @Transactional + public CommunityPostResponseDTO createPost(CreateCommunityPostRequestDTO request, Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 커뮤니티 카테고리 조회 또는 생성 + CommunityCategory communityCategory = + communityCategoryRepository + .findByCategoryName(request.getCommunityCategoryName()) + .orElseGet( + () -> + communityCategoryRepository.save( CommunityCategory.builder() - .categoryName(request.getCommunityCategoryName()) - .description(request.getCommunityCategoryDescription()) - .isActive(true) - .build() - )); - - // 게시글 생성 - CommunityPost savedPost = communityRepository.save(communityPostMapper.toEntity(request, user, communityCategory)); - return CommunityPostResponseDTO.builder() - .id(savedPost.getId()) - .title(savedPost.getTitle()) - .content(savedPost.getContent()) - .userNickname(user.getNickname()) - .build(); - } - - /// 게시글 목록 조회 - public Page getPosts(CommunitySearchDTO searchDTO, Pageable pageable, Long userId) { - - // 차단된 사용자 ID 목록 가져오기 - List blockedUserIds = Collections.emptyList(); - if (userId != null) { - User user = userService.getUserById(userId); - blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); - } - - // 차단된 사용자가 없는 경우 존재하지 않는 ID를 사용 - List finalBlockedUserIds = blockedUserIds.isEmpty() ? List.of(-1L) : blockedUserIds; - return communityRepository.search(searchDTO, finalBlockedUserIds, pageable) - .map(communityPostMapper::toResponse); - } - - /// 게시글 상세 조회 - @Transactional - public CommunityPostResponseDTO getPost(Long id) { - - // 게시글 조회 (User 정보 포함) - CommunityPost post = communityRepository.findByIdWithUser(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); - - // 게시글이 존재할 때만 조회수 증가 - communityRepository.increaseViewCount(id); - - return communityPostMapper.toResponse(post); - } - - /// 인기 게시글 TOP 10 조회 - @Cacheable(value = CacheConfig.TOP_POSTS_CACHE) - public List getTopPosts() { - return communityRepository.findTop10WithUserOrderByLikeCountDescViewCountDesc() - .stream() - .map(communityPostMapper::toResponse) - .collect(Collectors.toList()); + .categoryName(request.getCommunityCategoryName()) + .description(request.getCommunityCategoryDescription()) + .isActive(true) + .build())); + + // 게시글 생성 + CommunityPost savedPost = + communityRepository.save(communityPostMapper.toEntity(request, user, communityCategory)); + return CommunityPostResponseDTO.builder() + .id(savedPost.getId()) + .title(savedPost.getTitle()) + .content(savedPost.getContent()) + .userNickname(user.getNickname()) + .build(); + } + + /// 게시글 목록 조회 + public Page getPosts( + CommunitySearchDTO searchDTO, Pageable pageable, Long userId) { + + // 차단된 사용자 ID 목록 가져오기 + List blockedUserIds = Collections.emptyList(); + if (userId != null) { + User user = userService.getUserById(userId); + blockedUserIds = userBlockRepository.findBlockedUserIdsByUserId(user.getId()); } - /// 인기 게시글 캐시 갱신 - @CacheEvict(value = CacheConfig.TOP_POSTS_CACHE, allEntries = true) - public void refreshTopPostsCache() {} - - /// 게시글 삭제 (소프트 삭제) - @Transactional - public void deletePost(Long postId, Long userId) { - - // 게시글 조회 (작성자 정보 포함) - CommunityPost post = communityRepository.findByIdWithUser(postId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); - - // 현재 사용자 조회 - User currentUser = userService.getUserById(userId); - - // 작성자 또는 관리자 권한 확인 - if (!post.getUser().getId().equals(userId) && !currentUser.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - // 게시글 소프트 삭제 (deletedAt 설정) - post.markAsDeleted(); + // 차단된 사용자가 없는 경우 존재하지 않는 ID를 사용 + List finalBlockedUserIds = blockedUserIds.isEmpty() ? List.of(-1L) : blockedUserIds; + return communityRepository + .search(searchDTO, finalBlockedUserIds, pageable) + .map(communityPostMapper::toResponse); + } + + /// 게시글 상세 조회 + @Transactional + public CommunityPostResponseDTO getPost(Long id) { + + // 게시글 조회 (User 정보 포함) + CommunityPost post = + communityRepository + .findByIdWithUser(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + + // 게시글이 존재할 때만 조회수 증가 + communityRepository.increaseViewCount(id); + + return communityPostMapper.toResponse(post); + } + + /// 인기 게시글 TOP 10 조회 + @Cacheable(value = CacheConfig.TOP_POSTS_CACHE) + public List getTopPosts() { + return communityRepository.findTop10WithUserOrderByLikeCountDescViewCountDesc().stream() + .map(communityPostMapper::toResponse) + .collect(Collectors.toList()); + } + + /// 인기 게시글 캐시 갱신 + @CacheEvict(value = CacheConfig.TOP_POSTS_CACHE, allEntries = true) + public void refreshTopPostsCache() {} + + /// 게시글 삭제 (소프트 삭제) + @Transactional + public void deletePost(Long postId, Long userId) { + + // 게시글 조회 (작성자 정보 포함) + CommunityPost post = + communityRepository + .findByIdWithUser(postId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + + // 현재 사용자 조회 + User currentUser = userService.getUserById(userId); + + // 작성자 또는 관리자 권한 확인 + if (!post.getUser().getId().equals(userId) && !currentUser.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } + // 게시글 소프트 삭제 (deletedAt 설정) + post.markAsDeleted(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityServiceInitializer.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityServiceInitializer.java index a569ad6..ce89b40 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityServiceInitializer.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityServiceInitializer.java @@ -10,11 +10,11 @@ @RequiredArgsConstructor public class CommunityServiceInitializer implements CommandLineRunner { - private final CommunityService communityService; + private final CommunityService communityService; - @Override - public void run(String... args) { - communityService.getTopPosts(); - log.info("서버 시작 >> 인기 게시글 캐시 초기화가 완료되었습니다."); - } -} \ No newline at end of file + @Override + public void run(String... args) { + communityService.getTopPosts(); + log.info("서버 시작 >> 인기 게시글 캐시 초기화가 완료되었습니다."); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java index 02febc1..efc3ebf 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/controller/InquiryController.java @@ -23,70 +23,68 @@ @RequestMapping("/inquiry") @Tag(name = "문의하기", description = "문의하기 관련 API") public class InquiryController { - private final InquiryService inquiryService; + private final InquiryService inquiryService; - @PostMapping - @Operation(summary = "문의 생성", description = "새로운 문의를 생성합니다.") - public ResponseDto createInquiry( - @Valid @RequestBody CreateInquiryRequestDTO dto, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(inquiryService.createInquiry(dto, Long.valueOf(userDetails.getUsername()))); - } + @PostMapping + @Operation(summary = "문의 생성", description = "새로운 문의를 생성합니다.") + public ResponseDto createInquiry( + @Valid @RequestBody CreateInquiryRequestDTO dto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + inquiryService.createInquiry(dto, Long.valueOf(userDetails.getUsername()))); + } - @GetMapping("/{id}") - @Operation(summary = "문의 상세 조회", description = "문의 상세 정보를 조회합니다.") - public ResponseDto getInquiry( - @PathVariable Long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(inquiryService.getInquiry(id, Long.valueOf(userDetails.getUsername()))); - } + @GetMapping("/{id}") + @Operation(summary = "문의 상세 조회", description = "문의 상세 정보를 조회합니다.") + public ResponseDto getInquiry( + @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + inquiryService.getInquiry(id, Long.valueOf(userDetails.getUsername()))); + } - @GetMapping("/my") - @Operation(summary = "내 문의 목록 조회", description = "내가 작성한 문의 목록을 조회합니다.") - public PageResponseDTO getMyInquiries( - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails - ) { - return new PageResponseDTO<>(inquiryService.getUserInquiries(Long.valueOf(userDetails.getUsername()), pageable)); - } + @GetMapping("/my") + @Operation(summary = "내 문의 목록 조회", description = "내가 작성한 문의 목록을 조회합니다.") + public PageResponseDTO getMyInquiries( + @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + @AuthenticationPrincipal UserDetails userDetails) { + return new PageResponseDTO<>( + inquiryService.getUserInquiries(Long.valueOf(userDetails.getUsername()), pageable)); + } - @GetMapping("/all") - @Operation(summary = "모든 문의 목록 조회", description = "모든 문의 목록을 조회합니다. (관리자 전용)") - public PageResponseDTO getAllInquiries( - @PageableDefault Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails - ) { - return new PageResponseDTO<>(inquiryService.getAllInquiries(Long.valueOf(userDetails.getUsername()), pageable)); - } + @GetMapping("/all") + @Operation(summary = "모든 문의 목록 조회", description = "모든 문의 목록을 조회합니다. (관리자 전용)") + public PageResponseDTO getAllInquiries( + @PageableDefault Pageable pageable, @AuthenticationPrincipal UserDetails userDetails) { + return new PageResponseDTO<>( + inquiryService.getAllInquiries(Long.valueOf(userDetails.getUsername()), pageable)); + } - @GetMapping("/status/{status}") - @Operation(summary = "상태별 문의 목록 조회", description = "상태별 문의 목록을 조회합니다. (관리자 전용)") - public PageResponseDTO getInquiriesByStatus( - @PathVariable InquiryStatus status, - @PageableDefault Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails - ) { - return new PageResponseDTO<>(inquiryService.getInquiriesByStatus(status, Long.valueOf(userDetails.getUsername()), pageable)); - } + @GetMapping("/status/{status}") + @Operation(summary = "상태별 문의 목록 조회", description = "상태별 문의 목록을 조회합니다. (관리자 전용)") + public PageResponseDTO getInquiriesByStatus( + @PathVariable InquiryStatus status, + @PageableDefault Pageable pageable, + @AuthenticationPrincipal UserDetails userDetails) { + return new PageResponseDTO<>( + inquiryService.getInquiriesByStatus( + status, Long.valueOf(userDetails.getUsername()), pageable)); + } - @PostMapping("/{id}/answer") - @Operation(summary = "문의 답변", description = "문의에 답변합니다. (관리자 전용)") - public ResponseDto answerInquiry( - @PathVariable Long id, - @Valid @RequestBody AnswerInquiryRequestDTO dto, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(inquiryService.answerInquiry(id, dto, Long.valueOf(userDetails.getUsername()))); - } + @PostMapping("/{id}/answer") + @Operation(summary = "문의 답변", description = "문의에 답변합니다. (관리자 전용)") + public ResponseDto answerInquiry( + @PathVariable Long id, + @Valid @RequestBody AnswerInquiryRequestDTO dto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + inquiryService.answerInquiry(id, dto, Long.valueOf(userDetails.getUsername()))); + } - @PostMapping("/{id}/close") - @Operation(summary = "문의 마감", description = "문의를 마감합니다. (관리자 전용)") - public ResponseDto closeInquiry( - @PathVariable Long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(inquiryService.closeInquiry(id, Long.valueOf(userDetails.getUsername()))); - } + @PostMapping("/{id}/close") + @Operation(summary = "문의 마감", description = "문의를 마감합니다. (관리자 전용)") + public ResponseDto closeInquiry( + @PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + inquiryService.closeInquiry(id, Long.valueOf(userDetails.getUsername()))); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/AnswerInquiryRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/AnswerInquiryRequestDTO.java index b3b42c4..72e66f4 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/AnswerInquiryRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/AnswerInquiryRequestDTO.java @@ -8,7 +8,7 @@ @Getter @Setter public class AnswerInquiryRequestDTO { - @NotBlank(message = "답변은 필수 항목입니다.") - @Size(max = 1000, message = "답변은 1000자 이내로 작성해주세요.") - private String answer; -} \ No newline at end of file + @NotBlank(message = "답변은 필수 항목입니다.") + @Size(max = 1000, message = "답변은 1000자 이내로 작성해주세요.") + private String answer; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/CreateInquiryRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/CreateInquiryRequestDTO.java index dde154f..14f678e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/CreateInquiryRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/request/CreateInquiryRequestDTO.java @@ -8,11 +8,11 @@ @Getter @Setter public class CreateInquiryRequestDTO { - @NotBlank(message = "제목은 필수 항목입니다.") - @Size(max = 100, message = "제목은 100자 이내로 작성해주세요.") - private String title; - - @NotBlank(message = "내용은 필수 항목입니다.") - @Size(max = 1000, message = "내용은 1000자 이내로 작성해주세요.") - private String content; -} \ No newline at end of file + @NotBlank(message = "제목은 필수 항목입니다.") + @Size(max = 100, message = "제목은 100자 이내로 작성해주세요.") + private String title; + + @NotBlank(message = "내용은 필수 항목입니다.") + @Size(max = 1000, message = "내용은 1000자 이내로 작성해주세요.") + private String content; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/response/InquiryResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/response/InquiryResponseDTO.java index 56852cf..b8b2b50 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/response/InquiryResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/dto/response/InquiryResponseDTO.java @@ -3,55 +3,52 @@ import com.onebyone.kindergarten.domain.inquires.entity.Inquiry; import com.onebyone.kindergarten.domain.inquires.enums.InquiryStatus; import com.onebyone.kindergarten.domain.user.enums.UserRole; -import lombok.Getter; - import java.time.LocalDateTime; +import lombok.Getter; @Getter public class InquiryResponseDTO { - private final Long id; - private final String title; - private final String content; - private final String answer; - private final InquiryStatus status; - private final LocalDateTime createdAt; - private final Long userId; - private final String userNickname; - private final UserRole userRole; + private final Long id; + private final String title; + private final String content; + private final String answer; + private final InquiryStatus status; + private final LocalDateTime createdAt; + private final Long userId; + private final String userNickname; + private final UserRole userRole; - public InquiryResponseDTO( - Long id, - String title, - String content, - String answer, - InquiryStatus status, - LocalDateTime createdAt, - Long userId, - String userNickname, - UserRole userRole - ) { - this.id = id; - this.title = title; - this.content = content; - this.answer = answer; - this.status = status; - this.createdAt = createdAt; - this.userId = userId; - this.userNickname = userNickname; - this.userRole = userRole; - } + public InquiryResponseDTO( + Long id, + String title, + String content, + String answer, + InquiryStatus status, + LocalDateTime createdAt, + Long userId, + String userNickname, + UserRole userRole) { + this.id = id; + this.title = title; + this.content = content; + this.answer = answer; + this.status = status; + this.createdAt = createdAt; + this.userId = userId; + this.userNickname = userNickname; + this.userRole = userRole; + } - public static InquiryResponseDTO fromEntity(Inquiry inquiry) { - return new InquiryResponseDTO( - inquiry.getId(), - inquiry.getTitle(), - inquiry.getContent(), - inquiry.getAnswer(), - inquiry.getStatus(), - inquiry.getCreatedAt(), - inquiry.getUser().getId(), - inquiry.getUser().getNickname(), - inquiry.getUser().getRole() - ); - } -} \ No newline at end of file + public static InquiryResponseDTO fromEntity(Inquiry inquiry) { + return new InquiryResponseDTO( + inquiry.getId(), + inquiry.getTitle(), + inquiry.getContent(), + inquiry.getAnswer(), + inquiry.getStatus(), + inquiry.getCreatedAt(), + inquiry.getUser().getId(), + inquiry.getUser().getNickname(), + inquiry.getUser().getRole()); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/entity/Inquiry.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/entity/Inquiry.java index bf34165..f7b624f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/entity/Inquiry.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/entity/Inquiry.java @@ -4,53 +4,52 @@ import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor public class Inquiry extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 문의 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 문의자 - - @Column(nullable = false) - private String title; // 제목 - - @Column(nullable = false, length = 1000) - private String content; // 내용 - - @Column(length = 1000) - private String answer; // 답변 - - @Enumerated(EnumType.STRING) - private InquiryStatus status = InquiryStatus.PENDING; // 문의 상태 - 처리중, 답변완료, 답변대기 - - @Builder - public Inquiry(User user, String title, String content) { - this.user = user; - this.title = title; - this.content = content; - } - - // 답변 등록 - public void answerInquiry(String answer) { - this.answer = answer; - this.status = InquiryStatus.ANSWERED; - this.updatedAt = LocalDateTime.now(); - } - - // 문의 마감 - public void closeInquiry() { - this.status = InquiryStatus.CLOSED; - this.updatedAt = LocalDateTime.now(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 문의 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 문의자 + + @Column(nullable = false) + private String title; // 제목 + + @Column(nullable = false, length = 1000) + private String content; // 내용 + + @Column(length = 1000) + private String answer; // 답변 + + @Enumerated(EnumType.STRING) + private InquiryStatus status = InquiryStatus.PENDING; // 문의 상태 - 처리중, 답변완료, 답변대기 + + @Builder + public Inquiry(User user, String title, String content) { + this.user = user; + this.title = title; + this.content = content; + } + + // 답변 등록 + public void answerInquiry(String answer) { + this.answer = answer; + this.status = InquiryStatus.ANSWERED; + this.updatedAt = LocalDateTime.now(); + } + + // 문의 마감 + public void closeInquiry() { + this.status = InquiryStatus.CLOSED; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/enums/InquiryStatus.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/enums/InquiryStatus.java index 7175e04..1578fb8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/enums/InquiryStatus.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/enums/InquiryStatus.java @@ -1,3 +1,7 @@ package com.onebyone.kindergarten.domain.inquires.enums; -public enum InquiryStatus { PENDING, ANSWERED, CLOSED } \ No newline at end of file +public enum InquiryStatus { + PENDING, + ANSWERED, + CLOSED +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/repository/InquiryRepository.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/repository/InquiryRepository.java index d52aa3c..a557c0a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/repository/InquiryRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/repository/InquiryRepository.java @@ -1,48 +1,50 @@ package com.onebyone.kindergarten.domain.inquires.repository; +import com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO; import com.onebyone.kindergarten.domain.inquires.entity.Inquiry; import com.onebyone.kindergarten.domain.inquires.enums.InquiryStatus; import com.onebyone.kindergarten.domain.user.entity.User; -import com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; @Repository public interface InquiryRepository extends JpaRepository { - @Query("SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + - "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + - "u.id, u.nickname, u.role) " + - "FROM Inquiry i " + - "JOIN i.user u " + - "WHERE i.user = :user " + - "ORDER BY i.createdAt DESC") - Page findDtosByUser(@Param("user") User user, Pageable pageable); - - @Query("SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + - "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + - "u.id, u.nickname, u.role) " + - "FROM Inquiry i " + - "JOIN i.user u " + - "ORDER BY CASE WHEN i.status = 'PENDING' THEN 0 ELSE 1 END, i.createdAt DESC") - Page findAllDtosOrderByStatusAndCreatedAt(Pageable pageable); - - @Query("SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + - "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + - "u.id, u.nickname, u.role) " + - "FROM Inquiry i " + - "JOIN i.user u " + - "WHERE i.status = :status " + - "ORDER BY i.createdAt DESC") - Page findDtosByStatus(@Param("status") InquiryStatus status, Pageable pageable); + @Query( + "SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + + "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + + "u.id, u.nickname, u.role) " + + "FROM Inquiry i " + + "JOIN i.user u " + + "WHERE i.user = :user " + + "ORDER BY i.createdAt DESC") + Page findDtosByUser(@Param("user") User user, Pageable pageable); + + @Query( + "SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + + "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + + "u.id, u.nickname, u.role) " + + "FROM Inquiry i " + + "JOIN i.user u " + + "ORDER BY CASE WHEN i.status = 'PENDING' THEN 0 ELSE 1 END, i.createdAt DESC") + Page findAllDtosOrderByStatusAndCreatedAt(Pageable pageable); + + @Query( + "SELECT new com.onebyone.kindergarten.domain.inquires.dto.response.InquiryResponseDTO(" + + "i.id, i.title, i.content, i.answer, i.status, i.createdAt, " + + "u.id, u.nickname, u.role) " + + "FROM Inquiry i " + + "JOIN i.user u " + + "WHERE i.status = :status " + + "ORDER BY i.createdAt DESC") + Page findDtosByStatus( + @Param("status") InquiryStatus status, Pageable pageable); - @Query("SELECT i FROM Inquiry i " + - "JOIN FETCH i.user " + - "WHERE i.id = :id") - Optional findByIdWithUser(@Param("id") Long id); + @Query("SELECT i FROM Inquiry i " + "JOIN FETCH i.user " + "WHERE i.id = :id") + Optional findByIdWithUser(@Param("id") Long id); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java b/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java index 33644f5..ca7671d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/inquires/service/InquiryService.java @@ -23,132 +23,133 @@ @Transactional(readOnly = true) public class InquiryService { - private final InquiryRepository inquiryRepository; - private final UserService userService; - private final NotificationTemplateService notificationTemplateService; - - /// 문의 생성 - @Transactional - public InquiryResponseDTO createInquiry(CreateInquiryRequestDTO dto, Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 문의 생성 - Inquiry inquiry = Inquiry.builder() - .user(user) - .title(dto.getTitle()) - .content(dto.getContent()) - .build(); - inquiryRepository.save(inquiry); - - return InquiryResponseDTO.fromEntity(inquiry); - } + private final InquiryRepository inquiryRepository; + private final UserService userService; + private final NotificationTemplateService notificationTemplateService; - /// 문의 조회 (단일) - public InquiryResponseDTO getInquiry(Long id, Long userId) { + /// 문의 생성 + @Transactional + public InquiryResponseDTO createInquiry(CreateInquiryRequestDTO dto, Long userId) { - // 사용자 조회 - User user = userService.getUserById(userId); + // 사용자 조회 + User user = userService.getUserById(userId); - // 문의 조회 - Inquiry inquiry = inquiryRepository.findByIdWithUser(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); + // 문의 생성 + Inquiry inquiry = + Inquiry.builder().user(user).title(dto.getTitle()).content(dto.getContent()).build(); + inquiryRepository.save(inquiry); - // 본인 및 관리자 권한 체크 - if (!inquiry.getUser().getId().equals(user.getId()) && !user.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); - } + return InquiryResponseDTO.fromEntity(inquiry); + } - return InquiryResponseDTO.fromEntity(inquiry); - } + /// 문의 조회 (단일) + public InquiryResponseDTO getInquiry(Long id, Long userId) { - /// 내 문의 목록 조회 - public Page getUserInquiries(Long userId, Pageable pageable) { + // 사용자 조회 + User user = userService.getUserById(userId); - // 사용자 조회 - User user = userService.getUserById(userId); + // 문의 조회 + Inquiry inquiry = + inquiryRepository + .findByIdWithUser(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); - // 문의 목록 조회 - return inquiryRepository.findDtosByUser(user, pageable); + // 본인 및 관리자 권한 체크 + if (!inquiry.getUser().getId().equals(user.getId()) && !user.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); } - - /// 모든 문의 목록 조회 (관리자 전용) - public Page getAllInquiries(Long userId, Pageable pageable) { - // 사용자 조회 - User user = userService.getUserById(userId); + return InquiryResponseDTO.fromEntity(inquiry); + } + + /// 내 문의 목록 조회 + public Page getUserInquiries(Long userId, Pageable pageable) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 문의 목록 조회 + return inquiryRepository.findDtosByUser(user, pageable); + } + + /// 모든 문의 목록 조회 (관리자 전용) + public Page getAllInquiries(Long userId, Pageable pageable) { - // 관리자 권한 체크 - if (!user.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); - } + // 사용자 조회 + User user = userService.getUserById(userId); - return inquiryRepository.findAllDtosOrderByStatusAndCreatedAt(pageable); + // 관리자 권한 체크 + if (!user.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); } - - /// 상태별 문의 목록 조회 (관리자 전용) - public Page getInquiriesByStatus(InquiryStatus status, Long userId, Pageable pageable) { - // 사용자 조회 - User user = userService.getUserById(userId); + return inquiryRepository.findAllDtosOrderByStatusAndCreatedAt(pageable); + } - // 관리자 권한 체크 - if (!user.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); - } + /// 상태별 문의 목록 조회 (관리자 전용) + public Page getInquiriesByStatus( + InquiryStatus status, Long userId, Pageable pageable) { - return inquiryRepository.findDtosByStatus(status, pageable); + // 사용자 조회 + User user = userService.getUserById(userId); + + // 관리자 권한 체크 + if (!user.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); } - - /// 문의 답변 (관리자 전용) - @Transactional - public InquiryResponseDTO answerInquiry(Long id, AnswerInquiryRequestDTO dto, Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 관리자 권한 체크 - if (!user.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_WRITE); - } - - // 문의 조회 - Inquiry inquiry = inquiryRepository.findByIdWithUser(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); - - // 답변 등록 - inquiry.answerInquiry(dto.getAnswer()); - - // 알림 발송 - notificationTemplateService.sendInquiryAnswerNotification( - inquiry.getUser().getId(), - inquiry.getTitle(), - inquiry.getId() - ); - - return InquiryResponseDTO.fromEntity(inquiry); + + return inquiryRepository.findDtosByStatus(status, pageable); + } + + /// 문의 답변 (관리자 전용) + @Transactional + public InquiryResponseDTO answerInquiry(Long id, AnswerInquiryRequestDTO dto, Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 관리자 권한 체크 + if (!user.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_WRITE); } - - /// 문의 마감 (관리자 전용) - @Transactional - public InquiryResponseDTO closeInquiry(Long id, Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 관리자 권한 체크 - if (!user.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); - } - - // 문의 조회 - Inquiry inquiry = inquiryRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); - - // 문의 마감 - inquiry.closeInquiry(); - - return InquiryResponseDTO.fromEntity(inquiry); + + // 문의 조회 + Inquiry inquiry = + inquiryRepository + .findByIdWithUser(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); + + // 답변 등록 + inquiry.answerInquiry(dto.getAnswer()); + + // 알림 발송 + notificationTemplateService.sendInquiryAnswerNotification( + inquiry.getUser().getId(), inquiry.getTitle(), inquiry.getId()); + + return InquiryResponseDTO.fromEntity(inquiry); + } + + /// 문의 마감 (관리자 전용) + @Transactional + public InquiryResponseDTO closeInquiry(Long id, Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 관리자 권한 체크 + if (!user.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.INQUIRY_NOT_ADMIN_CANNOT_READ); } + + // 문의 조회 + Inquiry inquiry = + inquiryRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INQUIRY)); + + // 문의 마감 + inquiry.closeInquiry(); + + return InquiryResponseDTO.fromEntity(inquiry); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java index b53c316..a6af89c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/controller/KindergartenInternshipReviewController.java @@ -1,12 +1,12 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.controller; -import com.onebyone.kindergarten.global.facade.KindergartenFacade; -import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.CreateInternshipReviewRequestDTO; +import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.ModifyInternshipReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.enums.InternshipReviewStarRatingType; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.service.KindergartenInternshipReviewService; import com.onebyone.kindergarten.global.common.ResponseDto; +import com.onebyone.kindergarten.global.facade.KindergartenFacade; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -19,67 +19,67 @@ @RequiredArgsConstructor @RequestMapping("/internship") public class KindergartenInternshipReviewController { - private final KindergartenFacade kindergartenFacade; - private final KindergartenInternshipReviewService kindergartenInternshipReviewService; - - @Operation(summary = "실습리뷰-01 리뷰 생성", description = "리뷰 작성") - @PostMapping("/review") - public void createInternshipReview( - @RequestBody CreateInternshipReviewRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.createInternshipReview(request, Long.valueOf(userDetails.getUsername())); - } + private final KindergartenFacade kindergartenFacade; + private final KindergartenInternshipReviewService kindergartenInternshipReviewService; - @Operation(summary = "실습리뷰-02 리뷰 수정", description = "리뷰 수정") - @PutMapping("/review") - public void modifyInternshipReview( - @RequestBody ModifyInternshipReviewRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.modifyInternshipReview(request, Long.valueOf(userDetails.getUsername())); - } + @Operation(summary = "실습리뷰-01 리뷰 생성", description = "리뷰 작성") + @PostMapping("/review") + public void createInternshipReview( + @RequestBody CreateInternshipReviewRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.createInternshipReview(request, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "실습리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") - @PostMapping("/review/{internshipReviewId}/like") - public void likeInternshipReview( - @PathVariable("internshipReviewId") long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenInternshipReviewService.likeInternshipReview(id, Long.valueOf(userDetails.getUsername())); - } + @Operation(summary = "실습리뷰-02 리뷰 수정", description = "리뷰 수정") + @PutMapping("/review") + public void modifyInternshipReview( + @RequestBody ModifyInternshipReviewRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.modifyInternshipReview(request, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "실습리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") - @GetMapping("/reviews/{kindergartenId}") - public InternshipReviewPagedResponseDTO getReviews( - @PathVariable("kindergartenId") Long kindergartenId, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "LATEST") InternshipReviewPagedResponseDTO.SortType sortType, - @RequestParam(defaultValue = "ALL") InternshipReviewStarRatingType internshipReviewStarRatingType, - @RequestParam(defaultValue = "0") int starRating - ) { - return kindergartenInternshipReviewService.getReviews(kindergartenId, page, size, sortType, internshipReviewStarRatingType, starRating); - } + @Operation(summary = "실습리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") + @PostMapping("/review/{internshipReviewId}/like") + public void likeInternshipReview( + @PathVariable("internshipReviewId") long id, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenInternshipReviewService.likeInternshipReview( + id, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "실습리뷰-05 리뷰 삭제", description = "실습 리뷰를 삭제합니다. 본인이 작성한 리뷰 또는 관리자가 삭제할 수 있습니다.") - @DeleteMapping("/review/{internshipReviewId}") - public ResponseDto deleteInternshipReview( - @PathVariable("internshipReviewId") Long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.deleteInternshipReview(id, Long.valueOf(userDetails.getUsername())); - return ResponseDto.success("실습 리뷰가 삭제되었습니다."); - } + @Operation(summary = "실습리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") + @GetMapping("/reviews/{kindergartenId}") + public InternshipReviewPagedResponseDTO getReviews( + @PathVariable("kindergartenId") Long kindergartenId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "LATEST") InternshipReviewPagedResponseDTO.SortType sortType, + @RequestParam(defaultValue = "ALL") + InternshipReviewStarRatingType internshipReviewStarRatingType, + @RequestParam(defaultValue = "0") int starRating) { + return kindergartenInternshipReviewService.getReviews( + kindergartenId, page, size, sortType, internshipReviewStarRatingType, starRating); + } - @Operation(summary = "실습리뷰-06 전체 리뷰 조회", description = "유치원 상관없이 전체 실습 리뷰를 페이징 조회합니다. (정렬: LATEST-최신순, POPULAR-인기순)") - @GetMapping("/reviews") - public InternshipReviewPagedResponseDTO getAllReviews( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "LATEST") InternshipReviewPagedResponseDTO.SortType sortType - ) { - return kindergartenInternshipReviewService.getAllReviews(page, size, sortType); - } + @Operation( + summary = "실습리뷰-05 리뷰 삭제", + description = "실습 리뷰를 삭제합니다. 본인이 작성한 리뷰 또는 관리자가 삭제할 수 있습니다.") + @DeleteMapping("/review/{internshipReviewId}") + public ResponseDto deleteInternshipReview( + @PathVariable("internshipReviewId") Long id, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.deleteInternshipReview(id, Long.valueOf(userDetails.getUsername())); + return ResponseDto.success("실습 리뷰가 삭제되었습니다."); + } -} \ No newline at end of file + @Operation( + summary = "실습리뷰-06 전체 리뷰 조회", + description = "유치원 상관없이 전체 실습 리뷰를 페이징 조회합니다. (정렬: LATEST-최신순, POPULAR-인기순)") + @GetMapping("/reviews") + public InternshipReviewPagedResponseDTO getAllReviews( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "LATEST") InternshipReviewPagedResponseDTO.SortType sortType) { + return kindergartenInternshipReviewService.getAllReviews(page, size, sortType); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/CreateInternshipReviewRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/CreateInternshipReviewRequestDTO.java index ea13ac8..18a9d8c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/CreateInternshipReviewRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/CreateInternshipReviewRequestDTO.java @@ -4,13 +4,13 @@ @Data public class CreateInternshipReviewRequestDTO { - private Long kindergartenId; - private String workType; - private String oneLineComment; - private String instructionTeacherComment; - private Integer instructionTeacherScore; - private String learningSupportComment; - private Integer learningSupportScore; - private String workEnvironmentComment; - private Integer workEnvironmentScore; + private Long kindergartenId; + private String workType; + private String oneLineComment; + private String instructionTeacherComment; + private Integer instructionTeacherScore; + private String learningSupportComment; + private Integer learningSupportScore; + private String workEnvironmentComment; + private Integer workEnvironmentScore; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewDTO.java index d40561e..6aed287 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewDTO.java @@ -1,49 +1,57 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto; -import com.onebyone.kindergarten.domain.user.dto.SimpleUserDTO; -import lombok.Data; +import com.onebyone.kindergarten.domain.user.dto.SimpleUserDTO; import java.time.LocalDateTime; +import lombok.Data; @Data public class InternshipReviewDTO { - private Long internshipReviewId; - private SimpleUserDTO user; - private Long kindergartenId; - private String kindergartenName; - private String oneLineComment; - private String workEnvironmentComment; - private Integer workEnvironmentScore; - private String learningSupportComment; - private Integer learningSupportScore; - private String instructionTeacherComment; - private Integer instructionTeacherScore; - private Integer likeCount; - private Integer shareCount; - private LocalDateTime createdAt; - private String workType; + private Long internshipReviewId; + private SimpleUserDTO user; + private Long kindergartenId; + private String kindergartenName; + private String oneLineComment; + private String workEnvironmentComment; + private Integer workEnvironmentScore; + private String learningSupportComment; + private Integer learningSupportScore; + private String instructionTeacherComment; + private Integer instructionTeacherScore; + private Integer likeCount; + private Integer shareCount; + private LocalDateTime createdAt; + private String workType; - // JPQL 쿼리를 위한 생성자 (유치원 ID와 이름 포함) - public InternshipReviewDTO( - Long internshipReviewId, Long userId, String nickname, - Long kindergartenId, String kindergartenName, String oneLineComment, String workEnvironmentComment, - Integer workEnvironmentScore, String learningSupportComment, - Integer learningSupportScore, String instructionTeacherComment, - Integer instructionTeacherScore, Integer likeCount, - Integer shareCount, LocalDateTime createdAt - ) { - this.internshipReviewId = internshipReviewId; - this.user = new SimpleUserDTO(userId, nickname); - this.kindergartenId = kindergartenId; - this.kindergartenName = kindergartenName; - this.oneLineComment = oneLineComment; - this.workEnvironmentComment = workEnvironmentComment; - this.workEnvironmentScore = workEnvironmentScore; - this.learningSupportComment = learningSupportComment; - this.learningSupportScore = learningSupportScore; - this.instructionTeacherComment = instructionTeacherComment; - this.instructionTeacherScore = instructionTeacherScore; - this.likeCount = likeCount; - this.shareCount = shareCount; - this.createdAt = createdAt; - } + // JPQL 쿼리를 위한 생성자 (유치원 ID와 이름 포함) + public InternshipReviewDTO( + Long internshipReviewId, + Long userId, + String nickname, + Long kindergartenId, + String kindergartenName, + String oneLineComment, + String workEnvironmentComment, + Integer workEnvironmentScore, + String learningSupportComment, + Integer learningSupportScore, + String instructionTeacherComment, + Integer instructionTeacherScore, + Integer likeCount, + Integer shareCount, + LocalDateTime createdAt) { + this.internshipReviewId = internshipReviewId; + this.user = new SimpleUserDTO(userId, nickname); + this.kindergartenId = kindergartenId; + this.kindergartenName = kindergartenName; + this.oneLineComment = oneLineComment; + this.workEnvironmentComment = workEnvironmentComment; + this.workEnvironmentScore = workEnvironmentScore; + this.learningSupportComment = learningSupportComment; + this.learningSupportScore = learningSupportScore; + this.instructionTeacherComment = instructionTeacherComment; + this.instructionTeacherScore = instructionTeacherScore; + this.likeCount = likeCount; + this.shareCount = shareCount; + this.createdAt = createdAt; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewPagedResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewPagedResponseDTO.java index 9d99bd6..9828ac3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewPagedResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/InternshipReviewPagedResponseDTO.java @@ -1,18 +1,17 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto; +import java.util.List; import lombok.Builder; import lombok.Data; -import java.util.List; - @Data @Builder public class InternshipReviewPagedResponseDTO { - private List content; - private int totalPages; + private List content; + private int totalPages; - public enum SortType { - LATEST, // 최신순 - POPULAR // 좋아요순 - } + public enum SortType { + LATEST, // 최신순 + POPULAR // 좋아요순 + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/ModifyInternshipReviewRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/ModifyInternshipReviewRequestDTO.java index 6923f66..7049570 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/ModifyInternshipReviewRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/dto/ModifyInternshipReviewRequestDTO.java @@ -4,14 +4,14 @@ @Data public class ModifyInternshipReviewRequestDTO { - private Long kindergartenId; - private String workType; - private Long internshipReviewId; - private String oneLineComment; - private String instructionTeacherComment; - private Integer instructionTeacherScore; - private String learningSupportComment; - private Integer learningSupportScore; - private String workEnvironmentComment; - private Integer workEnvironmentScore; + private Long kindergartenId; + private String workType; + private Long internshipReviewId; + private String oneLineComment; + private String instructionTeacherComment; + private Integer instructionTeacherScore; + private String learningSupportComment; + private Integer learningSupportScore; + private String workEnvironmentComment; + private Integer workEnvironmentScore; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReview.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReview.java index d4b254b..c72835e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReview.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReview.java @@ -7,95 +7,98 @@ import com.onebyone.kindergarten.global.enums.ReportStatus; import com.onebyone.kindergarten.global.enums.ReviewStatus; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity(name = "kindergarten_internship_review") @Getter @Builder @NoArgsConstructor @AllArgsConstructor public class KindergartenInternshipReview extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 리뷰 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user", nullable = false) - private User user; // 작성자 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten", nullable = false) - private Kindergarten kindergarten; // 유치원 - - @Enumerated(EnumType.STRING) - private ReviewStatus reviewStatus = ReviewStatus.ACCEPTED; - - @Enumerated(EnumType.STRING) - private ReportStatus reportStatus = ReportStatus.YET; - - @Column(name = "work_type", nullable = false, columnDefinition = "varchar(255) default ''") - private String workType; // 근무 형태 - - @Column(name = "one_line_comment", nullable = false) - private String oneLineComment; // 한 줄 평가 - - @Column(name = "work_environment_comment", nullable = false) - private String workEnvironmentComment; // 분위기 평가 - - @Column(name = "work_environment_score", nullable = false) - private Integer workEnvironmentScore; // 분위기 점수 - - @Column(name = "learning_support_comment", nullable = false) - private String learningSupportComment; // 학습 도움 평가 - - @Column(name = "learning_support_score", nullable = false) - private Integer learningSupportScore; // 학습 도움 점수 - - @Column(name = "instruction_teacher_comment", nullable = false) - private String instructionTeacherComment; // 지도 교사 평가 - - @Column(name = "instruction_teacher_score", nullable = false) - private Integer instructionTeacherScore; // 지도 교사 점수 - - @Column(name = "like_count") - private Integer likeCount = 0; // 좋아요 수 - - @Column(name = "share_count") - private Integer shareCount = 0; // 공유 수 - - public void updateReview(ModifyInternshipReviewRequestDTO request) { - this.oneLineComment = request.getOneLineComment(); - this.workEnvironmentComment = request.getWorkEnvironmentComment(); - this.workEnvironmentScore = request.getWorkEnvironmentScore(); - this.learningSupportComment = request.getLearningSupportComment(); - this.learningSupportScore = request.getLearningSupportScore(); - this.instructionTeacherComment = request.getInstructionTeacherComment(); - this.instructionTeacherScore = request.getInstructionTeacherScore(); - this.updatedAt = LocalDateTime.now(); - } - - public void minusLikeCount() { - this.likeCount--; - } - - public void plusLikeCount() { - this.likeCount++; - } - - /// 리뷰 소프트 삭제 - public void markAsDeleted() { - this.updatedAt = LocalDateTime.now(); - this.deletedAt = LocalDateTime.now(); - this.reviewStatus = ReviewStatus.DELETED; - } - - public void updateStatus(ReportStatus status) { - this.updatedAt = LocalDateTime.now(); - this.reportStatus = status; - } -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 리뷰 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user", nullable = false) + private User user; // 작성자 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten", nullable = false) + private Kindergarten kindergarten; // 유치원 + + @Enumerated(EnumType.STRING) + @Builder.Default + private ReviewStatus reviewStatus = ReviewStatus.ACCEPTED; + + @Enumerated(EnumType.STRING) + @Builder.Default + private ReportStatus reportStatus = ReportStatus.YET; + + @Column(name = "work_type", nullable = false, columnDefinition = "varchar(255) default ''") + private String workType; // 근무 형태 + + @Column(name = "one_line_comment", nullable = false) + private String oneLineComment; // 한 줄 평가 + + @Column(name = "work_environment_comment", nullable = false) + private String workEnvironmentComment; // 분위기 평가 + + @Column(name = "work_environment_score", nullable = false) + private Integer workEnvironmentScore; // 분위기 점수 + + @Column(name = "learning_support_comment", nullable = false) + private String learningSupportComment; // 학습 도움 평가 + + @Column(name = "learning_support_score", nullable = false) + private Integer learningSupportScore; // 학습 도움 점수 + + @Column(name = "instruction_teacher_comment", nullable = false) + private String instructionTeacherComment; // 지도 교사 평가 + + @Column(name = "instruction_teacher_score", nullable = false) + private Integer instructionTeacherScore; // 지도 교사 점수 + + @Column(name = "like_count") + @Builder.Default + private Integer likeCount = 0; // 좋아요 수 + + @Column(name = "share_count") + @Builder.Default + private Integer shareCount = 0; // 공유 수 + + public void updateReview(ModifyInternshipReviewRequestDTO request) { + this.oneLineComment = request.getOneLineComment(); + this.workEnvironmentComment = request.getWorkEnvironmentComment(); + this.workEnvironmentScore = request.getWorkEnvironmentScore(); + this.learningSupportComment = request.getLearningSupportComment(); + this.learningSupportScore = request.getLearningSupportScore(); + this.instructionTeacherComment = request.getInstructionTeacherComment(); + this.instructionTeacherScore = request.getInstructionTeacherScore(); + this.updatedAt = LocalDateTime.now(); + } + + public void minusLikeCount() { + this.likeCount--; + } + + public void plusLikeCount() { + this.likeCount++; + } + + /// 리뷰 소프트 삭제 + public void markAsDeleted() { + this.updatedAt = LocalDateTime.now(); + this.deletedAt = LocalDateTime.now(); + this.reviewStatus = ReviewStatus.DELETED; + } + + public void updateStatus(ReportStatus status) { + this.updatedAt = LocalDateTime.now(); + this.reportStatus = status; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReviewLikeHistory.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReviewLikeHistory.java index faa4d3f..f3bf7d2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReviewLikeHistory.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/entity/KindergartenInternshipReviewLikeHistory.java @@ -14,16 +14,15 @@ @NoArgsConstructor @AllArgsConstructor public class KindergartenInternshipReviewLikeHistory extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 리뷰 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 리뷰 코드 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user", nullable = false) - private User user; // 작성자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user", nullable = false) + private User user; // 작성자 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten", nullable = false) - private KindergartenInternshipReview internshipReview; - -} \ No newline at end of file + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten", nullable = false) + private KindergartenInternshipReview internshipReview; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/enums/InternshipReviewStarRatingType.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/enums/InternshipReviewStarRatingType.java index 7038db5..1f69bce 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/enums/InternshipReviewStarRatingType.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/enums/InternshipReviewStarRatingType.java @@ -1,5 +1,8 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.enums; public enum InternshipReviewStarRatingType { - ALL, WORK_ENVIRONMENT, LEARNING_SUPPORT, INSTRUCTION_TEACHER + ALL, + WORK_ENVIRONMENT, + LEARNING_SUPPORT, + INSTRUCTION_TEACHER } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewLikeHistoryRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewLikeHistoryRepository.java index 6a770d9..d0832a1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewLikeHistoryRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewLikeHistoryRepository.java @@ -3,14 +3,15 @@ import com.onebyone.kindergarten.domain.kindergartenInternshipReview.entity.KindergartenInternshipReview; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.entity.KindergartenInternshipReviewLikeHistory; import com.onebyone.kindergarten.domain.user.entity.User; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository -public interface KindergartenInternshipReviewLikeHistoryRepository extends JpaRepository { - Optional findByUserAndInternshipReview(User user, KindergartenInternshipReview review); +public interface KindergartenInternshipReviewLikeHistoryRepository + extends JpaRepository { + Optional findByUserAndInternshipReview( + User user, KindergartenInternshipReview review); - void deleteByUserAndInternshipReview(User user, KindergartenInternshipReview review); + void deleteByUserAndInternshipReview(User user, KindergartenInternshipReview review); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewRepository.java index 64bba83..8972542 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/repository/KindergartenInternshipReviewRepository.java @@ -1,10 +1,11 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.repository; +import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.entity.KindergartenInternshipReview; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.enums.ReviewStatus; -import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -12,133 +13,134 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository -public interface KindergartenInternshipReviewRepository extends JpaRepository { - @Query("SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END " + - "FROM kindergarten_internship_review r " + - "WHERE r.user = :user " + - "AND r.kindergarten = :kindergarten " + - "AND r.deletedAt IS NULL") - boolean existsByUserAndKindergarten(@Param("user") User user, @Param("kindergarten") Kindergarten kindergarten); +public interface KindergartenInternshipReviewRepository + extends JpaRepository { + @Query( + "SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END " + + "FROM kindergarten_internship_review r " + + "WHERE r.user = :user " + + "AND r.kindergarten = :kindergarten " + + "AND r.deletedAt IS NULL") + boolean existsByUserAndKindergarten( + @Param("user") User user, @Param("kindergarten") Kindergarten kindergarten); - List findByKindergartenAndReviewStatus(Kindergarten kindergarten, ReviewStatus status); + List findByKindergartenAndReviewStatus( + Kindergarten kindergarten, ReviewStatus status); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL") - Page findReviewsWithUserInfo( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL") + Page findReviewsWithUserInfo( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + Pageable pageable); - /// 내가 작성한 실습 리뷰 조회 - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.user.id = :userId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL " + - "ORDER BY r.createdAt DESC") - Page findMyReviews( - @Param("userId") Long userId, - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + /// 내가 작성한 실습 리뷰 조회 + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.user.id = :userId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL " + + "ORDER BY r.createdAt DESC") + Page findMyReviews( + @Param("userId") Long userId, + @Param("reviewStatus") ReviewStatus reviewStatus, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.workEnvironmentScore = :score " + - "AND r.deletedAt IS NULL") - Page findByWorkEnvironmentScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") Integer score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.workEnvironmentScore = :score " + + "AND r.deletedAt IS NULL") + Page findByWorkEnvironmentScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") Integer score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.learningSupportScore = :score " + - "AND r.deletedAt IS NULL") - Page findByLearningSupportScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") Integer score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.learningSupportScore = :score " + + "AND r.deletedAt IS NULL") + Page findByLearningSupportScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") Integer score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.instructionTeacherScore = :score " + - "AND r.deletedAt IS NULL") - Page findByInstructionTeacherScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") Integer score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.instructionTeacherScore = :score " + + "AND r.deletedAt IS NULL") + Page findByInstructionTeacherScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") Integer score, + Pageable pageable); - /// 전체 실습 리뷰 조회 (유치원 상관없이) - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.learningSupportComment, r.learningSupportScore, " + - "r.instructionTeacherComment, r.instructionTeacherScore, " + - "r.likeCount, r.shareCount, r.createdAt) " + - "FROM kindergarten_internship_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL") - Page findAllReviewsWithUserInfo( - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + /// 전체 실습 리뷰 조회 (유치원 상관없이) + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.oneLineComment, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.learningSupportComment, r.learningSupportScore, " + + "r.instructionTeacherComment, r.instructionTeacherScore, " + + "r.likeCount, r.shareCount, r.createdAt) " + + "FROM kindergarten_internship_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL") + Page findAllReviewsWithUserInfo( + @Param("reviewStatus") ReviewStatus reviewStatus, Pageable pageable); - int countByUserIdAndReviewStatus(Long userId, ReviewStatus reviewStatus); + int countByUserIdAndReviewStatus(Long userId, ReviewStatus reviewStatus); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java index ea72802..0a42f7a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenInternshipReview/service/KindergartenInternshipReviewService.java @@ -1,8 +1,8 @@ package com.onebyone.kindergarten.domain.kindergartenInternshipReview.service; +import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.CreateInternshipReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; -import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.CreateInternshipReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.ModifyInternshipReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.entity.KindergartenInternshipReview; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.entity.KindergartenInternshipReviewLikeHistory; @@ -17,6 +17,7 @@ import com.onebyone.kindergarten.global.enums.ReviewStatus; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -25,217 +26,243 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @Service @RequiredArgsConstructor public class KindergartenInternshipReviewService { - private final UserService userService; - private final KindergartenService kindergartenService; - private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; - private final KindergartenInternshipReviewLikeHistoryRepository kindergartenInternshipReviewLikeHistoryRepository; - - public Kindergarten createInternshipReview(CreateInternshipReviewRequestDTO request, Long userId) { - User user = userService.getUserById(userId); - - Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); - - boolean exists = kindergartenInternshipReviewRepository.existsByUserAndKindergarten(user, kindergarten); - if (exists) { - throw new BusinessException(ErrorCodes.ALREADY_EXIST_INTERNSHIP_REVIEW); - } - - KindergartenInternshipReview review = KindergartenInternshipReview.builder() - .user(user) - .kindergarten(kindergarten) - .workType(request.getWorkType()) - .oneLineComment(request.getOneLineComment()) - .workEnvironmentComment(request.getWorkEnvironmentComment()) - .workEnvironmentScore(request.getWorkEnvironmentScore()) - .learningSupportComment(request.getLearningSupportComment()) - .learningSupportScore(request.getLearningSupportScore()) - .instructionTeacherComment(request.getInstructionTeacherComment()) - .instructionTeacherScore(request.getInstructionTeacherScore()) - .reviewStatus(ReviewStatus.ACCEPTED) - .likeCount(0) - .shareCount(0) - .build(); - - kindergartenInternshipReviewRepository.save(review); - - return kindergarten; + private final UserService userService; + private final KindergartenService kindergartenService; + private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; + private final KindergartenInternshipReviewLikeHistoryRepository + kindergartenInternshipReviewLikeHistoryRepository; + + public Kindergarten createInternshipReview( + CreateInternshipReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); + + Kindergarten kindergarten = + kindergartenService.getKindergartenById(request.getKindergartenId()); + + boolean exists = + kindergartenInternshipReviewRepository.existsByUserAndKindergarten(user, kindergarten); + if (exists) { + throw new BusinessException(ErrorCodes.ALREADY_EXIST_INTERNSHIP_REVIEW); } - public Kindergarten modifyInternshipReview(ModifyInternshipReviewRequestDTO request, Long userId) { - User user = userService.getUserById(userId); - Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); - - KindergartenInternshipReview review = kindergartenInternshipReviewRepository - .findById(request.getInternshipReviewId()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); - - // 리뷰와 유치원이 다를 때 - if (!review.getKindergarten().getId().equals(kindergarten.getId())) { - throw new BusinessException(ErrorCodes.INCORRECT_KINDERGARTEN_EXCEPTION); - } - - // 리뷰 작성자가 다를 때 - if (!review.getUser().getId().equals(user.getId())) { - throw new BusinessException(ErrorCodes.REVIEW_EDIT_NOT_OWNER); - } - - review.updateReview(request); - return kindergarten; + KindergartenInternshipReview review = + KindergartenInternshipReview.builder() + .user(user) + .kindergarten(kindergarten) + .workType(request.getWorkType()) + .oneLineComment(request.getOneLineComment()) + .workEnvironmentComment(request.getWorkEnvironmentComment()) + .workEnvironmentScore(request.getWorkEnvironmentScore()) + .learningSupportComment(request.getLearningSupportComment()) + .learningSupportScore(request.getLearningSupportScore()) + .instructionTeacherComment(request.getInstructionTeacherComment()) + .instructionTeacherScore(request.getInstructionTeacherScore()) + .reviewStatus(ReviewStatus.ACCEPTED) + .likeCount(0) + .shareCount(0) + .build(); + + kindergartenInternshipReviewRepository.save(review); + + return kindergarten; + } + + public Kindergarten modifyInternshipReview( + ModifyInternshipReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); + Kindergarten kindergarten = + kindergartenService.getKindergartenById(request.getKindergartenId()); + + KindergartenInternshipReview review = + kindergartenInternshipReviewRepository + .findById(request.getInternshipReviewId()) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); + + // 리뷰와 유치원이 다를 때 + if (!review.getKindergarten().getId().equals(kindergarten.getId())) { + throw new BusinessException(ErrorCodes.INCORRECT_KINDERGARTEN_EXCEPTION); } - @Transactional - public void likeInternshipReview(long reviewId, Long userId) { - User user = userService.getUserById(userId); - - KindergartenInternshipReview review = kindergartenInternshipReviewRepository.findById(reviewId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); - - Optional existingLike = kindergartenInternshipReviewLikeHistoryRepository.findByUserAndInternshipReview(user, review); - - if (existingLike.isPresent()) { - kindergartenInternshipReviewLikeHistoryRepository.delete(existingLike.get()); - review.minusLikeCount(); - } else { - KindergartenInternshipReviewLikeHistory newLike = KindergartenInternshipReviewLikeHistory.builder() - .user(user) - .internshipReview(review) - .build(); - - kindergartenInternshipReviewLikeHistoryRepository.save(newLike); - review.plusLikeCount(); - } - - kindergartenInternshipReviewRepository.save(review); + // 리뷰 작성자가 다를 때 + if (!review.getUser().getId().equals(user.getId())) { + throw new BusinessException(ErrorCodes.REVIEW_EDIT_NOT_OWNER); } - public InternshipReviewPagedResponseDTO getReviews(Long kindergartenId, int page, int size, InternshipReviewPagedResponseDTO.SortType sortType, InternshipReviewStarRatingType internshipReviewStarRatingType, int starRating) { - if (internshipReviewStarRatingType != InternshipReviewStarRatingType.ALL && starRating < 1 || starRating > 5) { - throw new BusinessException(ErrorCodes.ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION); - } - - Pageable pageable; - - switch (sortType) { - case POPULAR: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); - break; - case LATEST: - default: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - break; - } - - Page reviewPage; - - switch (internshipReviewStarRatingType) { - case WORK_ENVIRONMENT: - reviewPage = kindergartenInternshipReviewRepository - .findByWorkEnvironmentScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case LEARNING_SUPPORT: - reviewPage = kindergartenInternshipReviewRepository - .findByLearningSupportScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case INSTRUCTION_TEACHER: - reviewPage = kindergartenInternshipReviewRepository - .findByInstructionTeacherScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case ALL: - default: - reviewPage = kindergartenInternshipReviewRepository - .findReviewsWithUserInfo( - kindergartenId, ReviewStatus.ACCEPTED, pageable); - break; - } - - return InternshipReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + review.updateReview(request); + return kindergarten; + } + + @Transactional + public void likeInternshipReview(long reviewId, Long userId) { + User user = userService.getUserById(userId); + + KindergartenInternshipReview review = + kindergartenInternshipReviewRepository + .findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); + + Optional existingLike = + kindergartenInternshipReviewLikeHistoryRepository.findByUserAndInternshipReview( + user, review); + + if (existingLike.isPresent()) { + kindergartenInternshipReviewLikeHistoryRepository.delete(existingLike.get()); + review.minusLikeCount(); + } else { + KindergartenInternshipReviewLikeHistory newLike = + KindergartenInternshipReviewLikeHistory.builder() + .user(user) + .internshipReview(review) + .build(); + + kindergartenInternshipReviewLikeHistoryRepository.save(newLike); + review.plusLikeCount(); } - /// 내가 작성한 실습 리뷰 조회 - public InternshipReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { - User user = userService.getUserById(userId); - Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - - Page reviewPage = kindergartenInternshipReviewRepository.findMyReviews( - user.getId(), - ReviewStatus.ACCEPTED, - pageable - ); - - return InternshipReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + kindergartenInternshipReviewRepository.save(review); + } + + public InternshipReviewPagedResponseDTO getReviews( + Long kindergartenId, + int page, + int size, + InternshipReviewPagedResponseDTO.SortType sortType, + InternshipReviewStarRatingType internshipReviewStarRatingType, + int starRating) { + if (internshipReviewStarRatingType != InternshipReviewStarRatingType.ALL && starRating < 1 + || starRating > 5) { + throw new BusinessException(ErrorCodes.ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION); } - /// 전체 실습 리뷰 조회 (유치원 상관없이) - public InternshipReviewPagedResponseDTO getAllReviews(int page, int size, InternshipReviewPagedResponseDTO.SortType sortType) { - Pageable pageable; - - switch (sortType) { - case POPULAR: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); - break; - case LATEST: - default: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - break; - } - - Page reviewPage = kindergartenInternshipReviewRepository - .findAllReviewsWithUserInfo(ReviewStatus.ACCEPTED, pageable); - - return InternshipReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + Pageable pageable; + + switch (sortType) { + case POPULAR: + pageable = + PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); + break; + case LATEST: + default: + pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + break; } - /// 실습 리뷰 삭제 (소프트 삭제) - @Transactional - public void deleteInternshipReview(Long reviewId, String email) { - // 리뷰 조회 - KindergartenInternshipReview review = kindergartenInternshipReviewRepository.findById(reviewId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); - - // 현재 사용자 조회 - User currentUser = userService.getUserByEmail(email); - - // 작성자 또는 관리자 권한 확인 - if (!review.getUser().getEmail().equals(email) && !currentUser.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - // 리뷰 소프트 삭제 (deletedAt 설정) - review.markAsDeleted(); + Page reviewPage; + + switch (internshipReviewStarRatingType) { + case WORK_ENVIRONMENT: + reviewPage = + kindergartenInternshipReviewRepository.findByWorkEnvironmentScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case LEARNING_SUPPORT: + reviewPage = + kindergartenInternshipReviewRepository.findByLearningSupportScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case INSTRUCTION_TEACHER: + reviewPage = + kindergartenInternshipReviewRepository.findByInstructionTeacherScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case ALL: + default: + reviewPage = + kindergartenInternshipReviewRepository.findReviewsWithUserInfo( + kindergartenId, ReviewStatus.ACCEPTED, pageable); + break; } - public int countReviewsByUser(Long userId, ReviewStatus reviewStatus) { - return kindergartenInternshipReviewRepository.countByUserIdAndReviewStatus(userId, reviewStatus); + return InternshipReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 내가 작성한 실습 리뷰 조회 + public InternshipReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { + User user = userService.getUserById(userId); + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + + Page reviewPage = + kindergartenInternshipReviewRepository.findMyReviews( + user.getId(), ReviewStatus.ACCEPTED, pageable); + + return InternshipReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 전체 실습 리뷰 조회 (유치원 상관없이) + public InternshipReviewPagedResponseDTO getAllReviews( + int page, int size, InternshipReviewPagedResponseDTO.SortType sortType) { + Pageable pageable; + + switch (sortType) { + case POPULAR: + pageable = + PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); + break; + case LATEST: + default: + pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + break; } - public void deleteWorkReview(Long reviewId, Long userId, UserRole role) { - // 리뷰 조회 - KindergartenInternshipReview review = kindergartenInternshipReviewRepository.findById(reviewId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); - - // 작성자 또는 관리자 권한 확인 - if (!review.getUser().getId().equals(userId) && !role.equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } + Page reviewPage = + kindergartenInternshipReviewRepository.findAllReviewsWithUserInfo( + ReviewStatus.ACCEPTED, pageable); + + return InternshipReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 실습 리뷰 삭제 (소프트 삭제) + @Transactional + public void deleteInternshipReview(Long reviewId, String email) { + // 리뷰 조회 + KindergartenInternshipReview review = + kindergartenInternshipReviewRepository + .findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); + + // 현재 사용자 조회 + User currentUser = userService.getUserByEmail(email); + + // 작성자 또는 관리자 권한 확인 + if (!review.getUser().getEmail().equals(email) + && !currentUser.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); + } - // 리뷰 소프트 삭제 (deletedAt 설정) - review.markAsDeleted(); + // 리뷰 소프트 삭제 (deletedAt 설정) + review.markAsDeleted(); + } + + public int countReviewsByUser(Long userId, ReviewStatus reviewStatus) { + return kindergartenInternshipReviewRepository.countByUserIdAndReviewStatus( + userId, reviewStatus); + } + + public void deleteWorkReview(Long reviewId, Long userId, UserRole role) { + // 리뷰 조회 + KindergartenInternshipReview review = + kindergartenInternshipReviewRepository + .findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_INTERNSHIP_REVIEW)); + + // 작성자 또는 관리자 권한 확인 + if (!review.getUser().getId().equals(userId) && !role.equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } + + // 리뷰 소프트 삭제 (deletedAt 설정) + review.markAsDeleted(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java index ead001b..65082bd 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/controller/KindergartenWorkHistoryController.java @@ -7,41 +7,44 @@ import com.onebyone.kindergarten.global.facade.KindergartenWorkHistoryFacade; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.util.List; - @Tag(name = "유치원 근무 이력", description = "유치원 근무 이력 API") @RestController @RequiredArgsConstructor @RequestMapping("/certification") public class KindergartenWorkHistoryController { - private final KindergartenWorkHistoryFacade kindergartenWorkHistoryFacade; - private final KindergartenWorkHistoryService workHistoryService; + private final KindergartenWorkHistoryFacade kindergartenWorkHistoryFacade; + private final KindergartenWorkHistoryService workHistoryService; - @PostMapping - @Operation(summary = "유치원 근무 이력 추가", description = "유치원 근무 이력을 추가합니다.") - public ResponseDto addCertification( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody KindergartenWorkHistoryRequest request) { - return ResponseDto.success(kindergartenWorkHistoryFacade.addCertification(Long.valueOf(userDetails.getUsername()), request)); - } + @PostMapping + @Operation(summary = "유치원 근무 이력 추가", description = "유치원 근무 이력을 추가합니다.") + public ResponseDto addCertification( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody KindergartenWorkHistoryRequest request) { + return ResponseDto.success( + kindergartenWorkHistoryFacade.addCertification( + Long.valueOf(userDetails.getUsername()), request)); + } - @GetMapping - @Operation(summary = "유치원 근무 이력 조회", description = "유치원 근무 이력을 조회합니다.") - public ResponseDto> getCertification(@AuthenticationPrincipal UserDetails userDetails) { - return ResponseDto.success(workHistoryService.getCertification(Long.valueOf(userDetails.getUsername()))); - } + @GetMapping + @Operation(summary = "유치원 근무 이력 조회", description = "유치원 근무 이력을 조회합니다.") + public ResponseDto> getCertification( + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + workHistoryService.getCertification(Long.valueOf(userDetails.getUsername()))); + } - @DeleteMapping("/{certificationId}") - @Operation(summary = "유치원 근무 이력 삭제", description = "유치원 근무 이력을 삭제합니다.") - public ResponseDto deleteCertification( - @AuthenticationPrincipal UserDetails userDetails, - @PathVariable Long certificationId) { - kindergartenWorkHistoryFacade.deleteCertification(Long.valueOf(userDetails.getUsername()), certificationId); - return ResponseDto.success(null); - } -} \ No newline at end of file + @DeleteMapping("/{certificationId}") + @Operation(summary = "유치원 근무 이력 삭제", description = "유치원 근무 이력을 삭제합니다.") + public ResponseDto deleteCertification( + @AuthenticationPrincipal UserDetails userDetails, @PathVariable Long certificationId) { + kindergartenWorkHistoryFacade.deleteCertification( + Long.valueOf(userDetails.getUsername()), certificationId); + return ResponseDto.success(null); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryRequest.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryRequest.java index 0b95c5b..ab88ac1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryRequest.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryRequest.java @@ -4,26 +4,25 @@ import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.enums.ReviewType; +import java.time.LocalDate; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; - @Getter @NoArgsConstructor public class KindergartenWorkHistoryRequest { - private String kindergartenName; - private LocalDate startDate; - private LocalDate endDate; - private ReviewType workType; + private String kindergartenName; + private LocalDate startDate; + private LocalDate endDate; + private ReviewType workType; - public KindergartenWorkHistory toEntity(User user, Kindergarten kindergarten) { - return KindergartenWorkHistory.builder() - .user(user) - .kindergarten(kindergarten) - .startDate(startDate) - .endDate(endDate) - .workType(workType) - .build(); - } -} \ No newline at end of file + public KindergartenWorkHistory toEntity(User user, Kindergarten kindergarten) { + return KindergartenWorkHistory.builder() + .user(user) + .kindergarten(kindergarten) + .startDate(startDate) + .endDate(endDate) + .workType(workType) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryResponse.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryResponse.java index 73c8576..a6ef138 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/dto/KindergartenWorkHistoryResponse.java @@ -2,27 +2,26 @@ import com.onebyone.kindergarten.domain.kindergartenWorkHistories.entity.KindergartenWorkHistory; import com.onebyone.kindergarten.global.enums.ReviewType; +import java.time.LocalDate; import lombok.Builder; import lombok.Getter; -import java.time.LocalDate; - @Getter @Builder public class KindergartenWorkHistoryResponse { - private Long id; - private String kindergartenName; - private LocalDate startDate; - private LocalDate endDate; - private ReviewType workType; + private Long id; + private String kindergartenName; + private LocalDate startDate; + private LocalDate endDate; + private ReviewType workType; - public static KindergartenWorkHistoryResponse from(KindergartenWorkHistory history) { - return KindergartenWorkHistoryResponse.builder() - .id(history.getId()) - .kindergartenName(history.getKindergarten().getName()) - .startDate(history.getStartDate()) - .endDate(history.getEndDate()) - .workType(history.getWorkType()) - .build(); - } -} \ No newline at end of file + public static KindergartenWorkHistoryResponse from(KindergartenWorkHistory history) { + return KindergartenWorkHistoryResponse.builder() + .id(history.getId()) + .kindergartenName(history.getKindergarten().getName()) + .startDate(history.getStartDate()) + .endDate(history.getEndDate()) + .workType(history.getWorkType()) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/entity/KindergartenWorkHistory.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/entity/KindergartenWorkHistory.java index 6a9fc1c..a23d701 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/entity/KindergartenWorkHistory.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/entity/KindergartenWorkHistory.java @@ -1,41 +1,40 @@ package com.onebyone.kindergarten.domain.kindergartenWorkHistories.entity; -import com.onebyone.kindergarten.global.common.BaseEntity; -import com.onebyone.kindergarten.global.enums.ReviewType; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; import com.onebyone.kindergarten.domain.user.entity.User; +import com.onebyone.kindergarten.global.common.BaseEntity; +import com.onebyone.kindergarten.global.enums.ReviewType; import jakarta.persistence.*; +import java.time.LocalDate; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; - @Entity(name = "kindergarten_work_history") @Getter @NoArgsConstructor @AllArgsConstructor @Builder public class KindergartenWorkHistory extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 이력 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 이력 코드 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 유저 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 유저 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten_id", nullable = false) - private Kindergarten kindergarten; // 유치원 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten_id", nullable = false) + private Kindergarten kindergarten; // 유치원 - @Column(name = "start_date") - private LocalDate startDate; // 시작일 + @Column(name = "start_date") + private LocalDate startDate; // 시작일 - @Column(name = "end_date") - private LocalDate endDate; // 종료일 + @Column(name = "end_date") + private LocalDate endDate; // 종료일 - @Enumerated(EnumType.STRING) - private ReviewType workType; // 근무/실습 타입 - 근무, 실습 -} \ No newline at end of file + @Enumerated(EnumType.STRING) + private ReviewType workType; // 근무/실습 타입 - 근무, 실습 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/repository/KindergartenWorkHistoryRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/repository/KindergartenWorkHistoryRepository.java index 96779e9..ee0c82a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/repository/KindergartenWorkHistoryRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/repository/KindergartenWorkHistoryRepository.java @@ -3,25 +3,27 @@ import com.onebyone.kindergarten.domain.kindergartenWorkHistories.dto.KindergartenWorkHistoryResponse; import com.onebyone.kindergarten.domain.kindergartenWorkHistories.entity.KindergartenWorkHistory; import com.onebyone.kindergarten.domain.user.entity.User; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; - -public interface KindergartenWorkHistoryRepository extends JpaRepository { +public interface KindergartenWorkHistoryRepository + extends JpaRepository { - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkHistories.dto.KindergartenWorkHistoryResponse(" + - "h.id, k.name, h.startDate, h.endDate, h.workType) " + - "FROM kindergarten_work_history h " + - "JOIN h.kindergarten k " + - "WHERE h.user = :user " + - "ORDER BY h.startDate DESC") - List findDtosByUser(@Param("user") User user); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkHistories.dto.KindergartenWorkHistoryResponse(" + + "h.id, k.name, h.startDate, h.endDate, h.workType) " + + "FROM kindergarten_work_history h " + + "JOIN h.kindergarten k " + + "WHERE h.user = :user " + + "ORDER BY h.startDate DESC") + List findDtosByUser(@Param("user") User user); - @Query("SELECT h FROM kindergarten_work_history h " + - "JOIN FETCH h.kindergarten " + - "WHERE h.id = :id") - Optional findByIdWithKindergarten(@Param("id") Long id); -} \ No newline at end of file + @Query( + "SELECT h FROM kindergarten_work_history h " + + "JOIN FETCH h.kindergarten " + + "WHERE h.id = :id") + Optional findByIdWithKindergarten(@Param("id") Long id); +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java index 93f6bac..36aae4d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkHistories/service/KindergartenWorkHistoryService.java @@ -10,48 +10,48 @@ import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.temporal.ChronoUnit; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class KindergartenWorkHistoryService { - private final UserService userService; - private final KindergartenWorkHistoryRepository workHistoryRepository; - private final KindergartenRepository kindergartenRepository; - - /// 유치원 근무 이력 추가 - public KindergartenWorkHistoryResponse addCertification(User user, Kindergarten kindergarten, KindergartenWorkHistoryRequest request) { - // 유치원 근무 이력 저장 - KindergartenWorkHistory workHistory = request.toEntity(user, kindergarten); - workHistoryRepository.save(workHistory); - - return KindergartenWorkHistoryResponse.from(workHistory); - } - - /// 유치원 근무 이력 조회 - public List getCertification(Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 유치원 근무 이력 조회 - return workHistoryRepository.findDtosByUser(user); - } - - public KindergartenWorkHistory getKindergartenWorkHistory(Long certificationId) { - return workHistoryRepository.findById(certificationId).orElseThrow(() -> new BusinessException(ErrorCodes.WORK_HISTORY_NOT_FOUND)); - } - - /// 유치원 근무 이력 삭제 - public void deleteCertification(KindergartenWorkHistory workHistory) { - workHistoryRepository.delete(workHistory); - } -} \ No newline at end of file + private final UserService userService; + private final KindergartenWorkHistoryRepository workHistoryRepository; + private final KindergartenRepository kindergartenRepository; + + /// 유치원 근무 이력 추가 + public KindergartenWorkHistoryResponse addCertification( + User user, Kindergarten kindergarten, KindergartenWorkHistoryRequest request) { + // 유치원 근무 이력 저장 + KindergartenWorkHistory workHistory = request.toEntity(user, kindergarten); + workHistoryRepository.save(workHistory); + + return KindergartenWorkHistoryResponse.from(workHistory); + } + + /// 유치원 근무 이력 조회 + public List getCertification(Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 유치원 근무 이력 조회 + return workHistoryRepository.findDtosByUser(user); + } + + public KindergartenWorkHistory getKindergartenWorkHistory(Long certificationId) { + return workHistoryRepository + .findById(certificationId) + .orElseThrow(() -> new BusinessException(ErrorCodes.WORK_HISTORY_NOT_FOUND)); + } + + /// 유치원 근무 이력 삭제 + public void deleteCertification(KindergartenWorkHistory workHistory) { + workHistoryRepository.delete(workHistory); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java index 9da8070..0016ccc 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/controller/KindergartenWorkReviewController.java @@ -1,12 +1,12 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.controller; -import com.onebyone.kindergarten.global.facade.KindergartenFacade; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.CreateWorkReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.ModifyWorkReviewRequestDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.enums.WorkReviewStarRatingType; import com.onebyone.kindergarten.domain.kindergartenWorkReview.service.KindergartenWorkReviewService; import com.onebyone.kindergarten.global.common.ResponseDto; +import com.onebyone.kindergarten.global.facade.KindergartenFacade; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -19,67 +19,63 @@ @RequiredArgsConstructor @RequestMapping("/work") public class KindergartenWorkReviewController { - private final KindergartenFacade kindergartenFacade; - private final KindergartenWorkReviewService kindergartenWorkReviewService; - - @Operation(summary = "근무리뷰-01 리뷰 생성", description = "리뷰 작성") - @PostMapping("/review") - public void createWorkReview( - @RequestBody CreateWorkReviewRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.createWorkReview(request, Long.valueOf(userDetails.getUsername())); - } + private final KindergartenFacade kindergartenFacade; + private final KindergartenWorkReviewService kindergartenWorkReviewService; - @Operation(summary = "근무리뷰-02 리뷰 수정", description = "리뷰 수정") - @PutMapping("/review") - public void modifyWorkReview( - @RequestBody ModifyWorkReviewRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.modifyWorkReview(request, Long.valueOf(userDetails.getUsername())); - } + @Operation(summary = "근무리뷰-01 리뷰 생성", description = "리뷰 작성") + @PostMapping("/review") + public void createWorkReview( + @RequestBody CreateWorkReviewRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.createWorkReview(request, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "근무리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") - @PostMapping("/review/{workReviewId}/like") - public void likeWorkReview( - @PathVariable("workReviewId") long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenWorkReviewService.likeWorkReview(id, Long.valueOf(userDetails.getUsername())); - } + @Operation(summary = "근무리뷰-02 리뷰 수정", description = "리뷰 수정") + @PutMapping("/review") + public void modifyWorkReview( + @RequestBody ModifyWorkReviewRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.modifyWorkReview(request, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "근무리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") - @GetMapping("/reviews/{kindergartenId}") - public WorkReviewPagedResponseDTO getReviews( - @PathVariable long kindergartenId, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "LATEST") WorkReviewPagedResponseDTO.SortType sortType, - @RequestParam(defaultValue = "ALL") WorkReviewStarRatingType internshipReviewStarRatingType, - @RequestParam(defaultValue = "0") int starRating - ) { - return kindergartenWorkReviewService.getReviews(kindergartenId, page, size, sortType, internshipReviewStarRatingType, starRating); - } + @Operation(summary = "근무리뷰-03 리뷰 좋아요", description = "리뷰 좋아요") + @PostMapping("/review/{workReviewId}/like") + public void likeWorkReview( + @PathVariable("workReviewId") long id, @AuthenticationPrincipal UserDetails userDetails) { + kindergartenWorkReviewService.likeWorkReview(id, Long.valueOf(userDetails.getUsername())); + } - @Operation(summary = "근무리뷰-05 리뷰 삭제", description = "근무 리뷰를 삭제합니다. 본인이 작성한 리뷰 또는 관리자가 삭제할 수 있습니다.") - @DeleteMapping("/review/{workReviewId}") - public ResponseDto deleteWorkReview( - @PathVariable("workReviewId") Long id, - @AuthenticationPrincipal UserDetails userDetails - ) { - kindergartenFacade.deleteWorkReview(id, Long.valueOf(userDetails.getUsername())); - return ResponseDto.success("근무 리뷰가 삭제되었습니다."); - } + @Operation(summary = "근무리뷰-04 리뷰 페이징 조회", description = "리뷰 페이징 조회 (정렬: LATEST-최신순, POPULAR-인기순)") + @GetMapping("/reviews/{kindergartenId}") + public WorkReviewPagedResponseDTO getReviews( + @PathVariable long kindergartenId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "LATEST") WorkReviewPagedResponseDTO.SortType sortType, + @RequestParam(defaultValue = "ALL") WorkReviewStarRatingType internshipReviewStarRatingType, + @RequestParam(defaultValue = "0") int starRating) { + return kindergartenWorkReviewService.getReviews( + kindergartenId, page, size, sortType, internshipReviewStarRatingType, starRating); + } - @Operation(summary = "근무리뷰-06 전체 리뷰 조회", description = "유치원 상관없이 전체 근무 리뷰를 페이징 조회합니다. (정렬: LATEST-최신순, POPULAR-인기순)") - @GetMapping("/reviews") - public WorkReviewPagedResponseDTO getAllReviews( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "LATEST") WorkReviewPagedResponseDTO.SortType sortType - ) { - return kindergartenWorkReviewService.getAllReviews(page, size, sortType); - } + @Operation( + summary = "근무리뷰-05 리뷰 삭제", + description = "근무 리뷰를 삭제합니다. 본인이 작성한 리뷰 또는 관리자가 삭제할 수 있습니다.") + @DeleteMapping("/review/{workReviewId}") + public ResponseDto deleteWorkReview( + @PathVariable("workReviewId") Long id, @AuthenticationPrincipal UserDetails userDetails) { + kindergartenFacade.deleteWorkReview(id, Long.valueOf(userDetails.getUsername())); + return ResponseDto.success("근무 리뷰가 삭제되었습니다."); + } -} \ No newline at end of file + @Operation( + summary = "근무리뷰-06 전체 리뷰 조회", + description = "유치원 상관없이 전체 근무 리뷰를 페이징 조회합니다. (정렬: LATEST-최신순, POPULAR-인기순)") + @GetMapping("/reviews") + public WorkReviewPagedResponseDTO getAllReviews( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "LATEST") WorkReviewPagedResponseDTO.SortType sortType) { + return kindergartenWorkReviewService.getAllReviews(page, size, sortType); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/CreateWorkReviewRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/CreateWorkReviewRequestDTO.java index 31adc18..c107125 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/CreateWorkReviewRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/CreateWorkReviewRequestDTO.java @@ -4,18 +4,18 @@ @Data public class CreateWorkReviewRequestDTO { - private Long kindergartenId; - private String workType; - private Integer workYear; - private String oneLineComment; - private String benefitAndSalaryComment; - private Integer benefitAndSalaryScore; - private String workLifeBalanceComment; - private Integer workLifeBalanceScore; - private String workEnvironmentComment; - private Integer workEnvironmentScore; - private String managerComment; - private Integer managerScore; - private String customerComment; - private Integer customerScore; + private Long kindergartenId; + private String workType; + private Integer workYear; + private String oneLineComment; + private String benefitAndSalaryComment; + private Integer benefitAndSalaryScore; + private String workLifeBalanceComment; + private Integer workLifeBalanceScore; + private String workEnvironmentComment; + private Integer workEnvironmentScore; + private String managerComment; + private Integer managerScore; + private String customerComment; + private Integer customerScore; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/ModifyWorkReviewRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/ModifyWorkReviewRequestDTO.java index 2392164..cb56e15 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/ModifyWorkReviewRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/ModifyWorkReviewRequestDTO.java @@ -4,18 +4,18 @@ @Data public class ModifyWorkReviewRequestDTO { - private Long kindergartenId; - private String workType; - private Long workReviewId; - private String oneLineComment; - private String benefitAndSalaryComment; - private Integer benefitAndSalaryScore; - private String workLifeBalanceComment; - private Integer workLifeBalanceScore; - private String workEnvironmentComment; - private Integer workEnvironmentScore; - private String managerComment; - private Integer managerScore; - private String customerComment; - private Integer customerScore; + private Long kindergartenId; + private String workType; + private Long workReviewId; + private String oneLineComment; + private String benefitAndSalaryComment; + private Integer benefitAndSalaryScore; + private String workLifeBalanceComment; + private Integer workLifeBalanceScore; + private String workEnvironmentComment; + private Integer workEnvironmentScore; + private String managerComment; + private Integer managerScore; + private String customerComment; + private Integer customerScore; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewDTO.java index 21a92ea..102068b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewDTO.java @@ -1,63 +1,74 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.dto; import com.onebyone.kindergarten.domain.user.dto.SimpleUserDTO; -import lombok.Data; import java.time.LocalDateTime; +import lombok.Data; @Data public class WorkReviewDTO { - private Long workReviewId; - private SimpleUserDTO user; - private Long kindergartenId; - private String kindergartenName; - private Integer workYear; - private String oneLineComment; - private String benefitAndSalaryComment; - private Integer benefitAndSalaryScore; - private String workLifeBalanceComment; - private Integer workLifeBalanceScore; - private String workEnvironmentComment; - private Integer workEnvironmentScore; - private String managerComment; - private Integer managerScore; - private String customerComment; - private Integer customerScore; - private Integer likeCount; - private Integer shareCount; - private LocalDateTime createdAt; - private String workType; + private Long workReviewId; + private SimpleUserDTO user; + private Long kindergartenId; + private String kindergartenName; + private Integer workYear; + private String oneLineComment; + private String benefitAndSalaryComment; + private Integer benefitAndSalaryScore; + private String workLifeBalanceComment; + private Integer workLifeBalanceScore; + private String workEnvironmentComment; + private Integer workEnvironmentScore; + private String managerComment; + private Integer managerScore; + private String customerComment; + private Integer customerScore; + private Integer likeCount; + private Integer shareCount; + private LocalDateTime createdAt; + private String workType; - // JPQL 쿼리를 위한 생성자 (유치원 ID와 이름 포함) - public WorkReviewDTO( - Long workReviewId, Long userId, String nickname, - Long kindergartenId, String kindergartenName, Integer workYear, String oneLineComment, - String benefitAndSalaryComment, Integer benefitAndSalaryScore, - String workLifeBalanceComment, Integer workLifeBalanceScore, - String workEnvironmentComment, Integer workEnvironmentScore, - String managerComment, Integer managerScore, - String customerComment, Integer customerScore, - Integer likeCount, Integer shareCount, - LocalDateTime createdAt, String workType - ) { - this.workReviewId = workReviewId; - this.user = new SimpleUserDTO(userId, nickname); - this.kindergartenId = kindergartenId; - this.kindergartenName = kindergartenName; - this.workYear = workYear; - this.oneLineComment = oneLineComment; - this.benefitAndSalaryComment = benefitAndSalaryComment; - this.benefitAndSalaryScore = benefitAndSalaryScore; - this.workLifeBalanceComment = workLifeBalanceComment; - this.workLifeBalanceScore = workLifeBalanceScore; - this.workEnvironmentComment = workEnvironmentComment; - this.workEnvironmentScore = workEnvironmentScore; - this.managerComment = managerComment; - this.managerScore = managerScore; - this.customerComment = customerComment; - this.customerScore = customerScore; - this.likeCount = likeCount; - this.shareCount = shareCount; - this.createdAt = createdAt; - this.workType = workType; - } + // JPQL 쿼리를 위한 생성자 (유치원 ID와 이름 포함) + public WorkReviewDTO( + Long workReviewId, + Long userId, + String nickname, + Long kindergartenId, + String kindergartenName, + Integer workYear, + String oneLineComment, + String benefitAndSalaryComment, + Integer benefitAndSalaryScore, + String workLifeBalanceComment, + Integer workLifeBalanceScore, + String workEnvironmentComment, + Integer workEnvironmentScore, + String managerComment, + Integer managerScore, + String customerComment, + Integer customerScore, + Integer likeCount, + Integer shareCount, + LocalDateTime createdAt, + String workType) { + this.workReviewId = workReviewId; + this.user = new SimpleUserDTO(userId, nickname); + this.kindergartenId = kindergartenId; + this.kindergartenName = kindergartenName; + this.workYear = workYear; + this.oneLineComment = oneLineComment; + this.benefitAndSalaryComment = benefitAndSalaryComment; + this.benefitAndSalaryScore = benefitAndSalaryScore; + this.workLifeBalanceComment = workLifeBalanceComment; + this.workLifeBalanceScore = workLifeBalanceScore; + this.workEnvironmentComment = workEnvironmentComment; + this.workEnvironmentScore = workEnvironmentScore; + this.managerComment = managerComment; + this.managerScore = managerScore; + this.customerComment = customerComment; + this.customerScore = customerScore; + this.likeCount = likeCount; + this.shareCount = shareCount; + this.createdAt = createdAt; + this.workType = workType; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewPagedResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewPagedResponseDTO.java index 367daed..0b0e143 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewPagedResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/dto/WorkReviewPagedResponseDTO.java @@ -1,18 +1,17 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.dto; +import java.util.List; import lombok.Builder; import lombok.Data; -import java.util.List; - @Data @Builder public class WorkReviewPagedResponseDTO { - private List content; - private int totalPages; + private List content; + private int totalPages; - public enum SortType { - LATEST, // 최신순 - POPULAR // 좋아요순 - } + public enum SortType { + LATEST, // 최신순 + POPULAR // 좋아요순 + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReview.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReview.java index 12f7669..62fd0c2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReview.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReview.java @@ -1,121 +1,124 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.entity; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.ModifyWorkReviewRequestDTO; -import com.onebyone.kindergarten.global.common.BaseEntity; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; import com.onebyone.kindergarten.domain.user.entity.User; +import com.onebyone.kindergarten.global.common.BaseEntity; import com.onebyone.kindergarten.global.enums.ReportStatus; import com.onebyone.kindergarten.global.enums.ReviewStatus; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity(name = "kindergarten_work_review") @Builder @NoArgsConstructor @AllArgsConstructor @Getter public class KindergartenWorkReview extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 리뷰 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user", nullable = false) - private User user; // 작성자 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten", nullable = false) - private Kindergarten kindergarten; // 유치원 - - @Enumerated(EnumType.STRING) - private ReviewStatus reviewStatus = ReviewStatus.ACCEPTED; - - @Enumerated(EnumType.STRING) - private ReportStatus reportStatus = ReportStatus.YET; - - @Column(name = "work_type", nullable = false, columnDefinition = "varchar(255) default ''") - private String workType; // 근무 형태 - - @Column(name = "work_year", nullable = false) - private Integer workYear; // 근무/실습 년수 - - @Column(name = "one_line_comment", nullable = false) - private String oneLineComment; // 한 줄 평가 - - @Column(name = "benefit_and_salary_comment", nullable = false) - private String benefitAndSalaryComment; // 복지/급여 평가 - - @Column(name = "benefit_and_salary_score", nullable = false) - private Integer benefitAndSalaryScore; // 복지/급여 점수 - - @Column(name = "work_life_balance_comment", nullable = false) - private String workLifeBalanceComment; // 워라벨 평가 - - @Column(name = "work_life_balance_score", nullable = false) - private Integer workLifeBalanceScore; // 워라벨 점수 - - @Column(name = "work_environment_comment", nullable = false) - private String workEnvironmentComment; // 분위기 평가 - - @Column(name = "work_environment_score", nullable = false) - private Integer workEnvironmentScore; // 분위기 점수 - - @Column(name = "manager_comment", nullable = false) - private String managerComment; // 관리자 평가 - - @Column(name = "manager_score", nullable = false) - private Integer managerScore; // 관리자 점수 - - @Column(name = "customer_comment", nullable = false) - private String customerComment; // 고객 평가 - - @Column(name = "customer_score", nullable = false) - private Integer customerScore; // 고객 점수 - - @Column(name = "like_count") - private Integer likeCount = 0; // 좋아요 수 - - @Column(name = "share_count") - private Integer shareCount = 0; // 공유 수 - - public void updateReview(ModifyWorkReviewRequestDTO request) { - this.oneLineComment = request.getOneLineComment(); - this.workType = request.getWorkType(); - this.benefitAndSalaryComment = request.getBenefitAndSalaryComment(); - this.benefitAndSalaryScore = request.getBenefitAndSalaryScore(); - this.workLifeBalanceComment = request.getWorkLifeBalanceComment(); - this.workLifeBalanceScore = request.getWorkLifeBalanceScore(); - this.workEnvironmentComment = request.getWorkEnvironmentComment(); - this.workEnvironmentScore = request.getWorkEnvironmentScore(); - this.managerComment = request.getManagerComment(); - this.managerScore = request.getManagerScore(); - this.customerComment = request.getCustomerComment(); - this.customerScore = request.getCustomerScore(); - this.updatedAt = LocalDateTime.now(); - } - - public void minusLikeCount() { - this.likeCount--; - } - - public void plusLikeCount() { - this.likeCount++; - } - - /// 리뷰 소프트 삭제 - public void markAsDeleted() { - this.updatedAt = LocalDateTime.now(); - this.deletedAt = LocalDateTime.now(); - this.reviewStatus = ReviewStatus.DELETED; - } - - public void updateStatus(ReportStatus status) { - this.updatedAt = LocalDateTime.now(); - this.reportStatus = status; - } -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 리뷰 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user", nullable = false) + private User user; // 작성자 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten", nullable = false) + private Kindergarten kindergarten; // 유치원 + + @Enumerated(EnumType.STRING) + @Builder.Default + private ReviewStatus reviewStatus = ReviewStatus.ACCEPTED; + + @Enumerated(EnumType.STRING) + @Builder.Default + private ReportStatus reportStatus = ReportStatus.YET; + + @Column(name = "work_type", nullable = false, columnDefinition = "varchar(255) default ''") + private String workType; // 근무 형태 + + @Column(name = "work_year", nullable = false) + private Integer workYear; // 근무/실습 년수 + + @Column(name = "one_line_comment", nullable = false) + private String oneLineComment; // 한 줄 평가 + + @Column(name = "benefit_and_salary_comment", nullable = false) + private String benefitAndSalaryComment; // 복지/급여 평가 + + @Column(name = "benefit_and_salary_score", nullable = false) + private Integer benefitAndSalaryScore; // 복지/급여 점수 + + @Column(name = "work_life_balance_comment", nullable = false) + private String workLifeBalanceComment; // 워라벨 평가 + + @Column(name = "work_life_balance_score", nullable = false) + private Integer workLifeBalanceScore; // 워라벨 점수 + + @Column(name = "work_environment_comment", nullable = false) + private String workEnvironmentComment; // 분위기 평가 + + @Column(name = "work_environment_score", nullable = false) + private Integer workEnvironmentScore; // 분위기 점수 + + @Column(name = "manager_comment", nullable = false) + private String managerComment; // 관리자 평가 + + @Column(name = "manager_score", nullable = false) + private Integer managerScore; // 관리자 점수 + + @Column(name = "customer_comment", nullable = false) + private String customerComment; // 고객 평가 + + @Column(name = "customer_score", nullable = false) + private Integer customerScore; // 고객 점수 + + @Column(name = "like_count") + @Builder.Default + private Integer likeCount = 0; // 좋아요 수 + + @Column(name = "share_count") + @Builder.Default + private Integer shareCount = 0; // 공유 수 + + public void updateReview(ModifyWorkReviewRequestDTO request) { + this.oneLineComment = request.getOneLineComment(); + this.workType = request.getWorkType(); + this.benefitAndSalaryComment = request.getBenefitAndSalaryComment(); + this.benefitAndSalaryScore = request.getBenefitAndSalaryScore(); + this.workLifeBalanceComment = request.getWorkLifeBalanceComment(); + this.workLifeBalanceScore = request.getWorkLifeBalanceScore(); + this.workEnvironmentComment = request.getWorkEnvironmentComment(); + this.workEnvironmentScore = request.getWorkEnvironmentScore(); + this.managerComment = request.getManagerComment(); + this.managerScore = request.getManagerScore(); + this.customerComment = request.getCustomerComment(); + this.customerScore = request.getCustomerScore(); + this.updatedAt = LocalDateTime.now(); + } + + public void minusLikeCount() { + this.likeCount--; + } + + public void plusLikeCount() { + this.likeCount++; + } + + /// 리뷰 소프트 삭제 + public void markAsDeleted() { + this.updatedAt = LocalDateTime.now(); + this.deletedAt = LocalDateTime.now(); + this.reviewStatus = ReviewStatus.DELETED; + } + + public void updateStatus(ReportStatus status) { + this.updatedAt = LocalDateTime.now(); + this.reportStatus = status; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReviewLikeHistory.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReviewLikeHistory.java index c7ebd0c..39f5e56 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReviewLikeHistory.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/entity/KindergartenWorkReviewLikeHistory.java @@ -14,16 +14,15 @@ @NoArgsConstructor @AllArgsConstructor public class KindergartenWorkReviewLikeHistory extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 리뷰 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 리뷰 코드 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user", nullable = false) - private User user; // 작성자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user", nullable = false) + private User user; // 작성자 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten", nullable = false) - private KindergartenWorkReview workReview; - -} \ No newline at end of file + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten", nullable = false) + private KindergartenWorkReview workReview; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/enums/WorkReviewStarRatingType.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/enums/WorkReviewStarRatingType.java index a084d34..f0df289 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/enums/WorkReviewStarRatingType.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/enums/WorkReviewStarRatingType.java @@ -1,5 +1,10 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.enums; public enum WorkReviewStarRatingType { - ALL, BENEFIT_AND_SALARY, WORK_LIFE_BALANCE, WORK_ENVIRONMENT, MANAGER, CUSTOMER + ALL, + BENEFIT_AND_SALARY, + WORK_LIFE_BALANCE, + WORK_ENVIRONMENT, + MANAGER, + CUSTOMER } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewLikeHistoryRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewLikeHistoryRepository.java index 3dedad8..c9b6cca 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewLikeHistoryRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewLikeHistoryRepository.java @@ -3,14 +3,15 @@ import com.onebyone.kindergarten.domain.kindergartenWorkReview.entity.KindergartenWorkReview; import com.onebyone.kindergarten.domain.kindergartenWorkReview.entity.KindergartenWorkReviewLikeHistory; import com.onebyone.kindergarten.domain.user.entity.User; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository -public interface KindergartenWorkReviewLikeHistoryRepository extends JpaRepository { - Optional findByUserAndWorkReview(User user, KindergartenWorkReview review); +public interface KindergartenWorkReviewLikeHistoryRepository + extends JpaRepository { + Optional findByUserAndWorkReview( + User user, KindergartenWorkReview review); - void deleteByUserAndWorkReview(User user, KindergartenWorkReview review); + void deleteByUserAndWorkReview(User user, KindergartenWorkReview review); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewRepository.java index 886a2c9..1d17b5c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/repository/KindergartenWorkReviewRepository.java @@ -1,198 +1,200 @@ package com.onebyone.kindergarten.domain.kindergartenWorkReview.repository; +import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.entity.KindergartenWorkReview; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.enums.ReviewStatus; -import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - -public interface KindergartenWorkReviewRepository extends JpaRepository { - @Query("SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END " + - "FROM kindergarten_work_review r " + - "WHERE r.user = :user " + - "AND r.kindergarten = :kindergarten " + - "AND r.deletedAt IS NULL") - boolean existsByUserAndKindergarten(@Param("user") User user, @Param("kindergarten") Kindergarten kindergarten); +public interface KindergartenWorkReviewRepository + extends JpaRepository { + @Query( + "SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END " + + "FROM kindergarten_work_review r " + + "WHERE r.user = :user " + + "AND r.kindergarten = :kindergarten " + + "AND r.deletedAt IS NULL") + boolean existsByUserAndKindergarten( + @Param("user") User user, @Param("kindergarten") Kindergarten kindergarten); - List findByKindergartenAndReviewStatus(Kindergarten kindergarten, ReviewStatus status); + List findByKindergartenAndReviewStatus( + Kindergarten kindergarten, ReviewStatus status); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL") - Page findReviewsWithUserInfo( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL") + Page findReviewsWithUserInfo( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + Pageable pageable); - /// 내가 작성한 근무 리뷰 조회 - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.user.id = :userId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL " + - "ORDER BY r.createdAt DESC") - Page findMyReviews( - @Param("userId") Long userId, - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + /// 내가 작성한 근무 리뷰 조회 + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.user.id = :userId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL " + + "ORDER BY r.createdAt DESC") + Page findMyReviews( + @Param("userId") Long userId, + @Param("reviewStatus") ReviewStatus reviewStatus, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.benefitAndSalaryScore = :score " + - "AND r.deletedAt IS NULL") - Page findByBenefitAndSalaryScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") int score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.benefitAndSalaryScore = :score " + + "AND r.deletedAt IS NULL") + Page findByBenefitAndSalaryScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") int score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.workLifeBalanceScore = :score " + - "AND r.deletedAt IS NULL") - Page findByWorkLifeBalanceScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") int score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.workLifeBalanceScore = :score " + + "AND r.deletedAt IS NULL") + Page findByWorkLifeBalanceScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") int score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.workEnvironmentScore = :score " + - "AND r.deletedAt IS NULL") - Page findByWorkEnvironmentScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") int score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.workEnvironmentScore = :score " + + "AND r.deletedAt IS NULL") + Page findByWorkEnvironmentScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") int score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.managerScore = :score " + - "AND r.deletedAt IS NULL") - Page findByManagerScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") int score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.managerScore = :score " + + "AND r.deletedAt IS NULL") + Page findByManagerScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") int score, + Pageable pageable); - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.kindergarten.id = :kindergartenId " + - "AND r.reviewStatus = :reviewStatus " + - "AND r.customerScore = :score " + - "AND r.deletedAt IS NULL") - Page findByCustomerScore( - @Param("kindergartenId") Long kindergartenId, - @Param("reviewStatus") ReviewStatus reviewStatus, - @Param("score") int score, - Pageable pageable - ); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.kindergarten.id = :kindergartenId " + + "AND r.reviewStatus = :reviewStatus " + + "AND r.customerScore = :score " + + "AND r.deletedAt IS NULL") + Page findByCustomerScore( + @Param("kindergartenId") Long kindergartenId, + @Param("reviewStatus") ReviewStatus reviewStatus, + @Param("score") int score, + Pageable pageable); - /// 전체 근무 리뷰 조회 (유치원 상관없이) - @Query("SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + - "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + - "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + - "r.workLifeBalanceComment, r.workLifeBalanceScore, " + - "r.workEnvironmentComment, r.workEnvironmentScore, " + - "r.managerComment, r.managerScore, " + - "r.customerComment, r.customerScore, " + - "r.likeCount, r.shareCount, r.createdAt, r.workType) " + - "FROM kindergarten_work_review r " + - "JOIN r.user u " + - "JOIN r.kindergarten k " + - "WHERE r.reviewStatus = :reviewStatus " + - "AND r.deletedAt IS NULL") - Page findAllReviewsWithUserInfo( - @Param("reviewStatus") ReviewStatus reviewStatus, - Pageable pageable - ); + /// 전체 근무 리뷰 조회 (유치원 상관없이) + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewDTO(" + + "r.id, u.id, u.nickname, k.id, k.name, r.workYear, r.oneLineComment, " + + "r.benefitAndSalaryComment, r.benefitAndSalaryScore, " + + "r.workLifeBalanceComment, r.workLifeBalanceScore, " + + "r.workEnvironmentComment, r.workEnvironmentScore, " + + "r.managerComment, r.managerScore, " + + "r.customerComment, r.customerScore, " + + "r.likeCount, r.shareCount, r.createdAt, r.workType) " + + "FROM kindergarten_work_review r " + + "JOIN r.user u " + + "JOIN r.kindergarten k " + + "WHERE r.reviewStatus = :reviewStatus " + + "AND r.deletedAt IS NULL") + Page findAllReviewsWithUserInfo( + @Param("reviewStatus") ReviewStatus reviewStatus, Pageable pageable); - int countByUserIdAndReviewStatus(Long userId, ReviewStatus status); + int countByUserIdAndReviewStatus(Long userId, ReviewStatus status); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java index dc4c6cd..d973fff 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergartenWorkReview/service/KindergartenWorkReviewService.java @@ -18,6 +18,7 @@ import com.onebyone.kindergarten.global.enums.ReviewStatus; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -26,225 +27,235 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @Service @RequiredArgsConstructor public class KindergartenWorkReviewService { - private final UserService userService; - private final KindergartenService kindergartenService; - private final KindergartenWorkReviewRepository workReviewRepository; - private final KindergartenWorkReviewLikeHistoryRepository workReviewLikeHistoryRepository; - private final NotificationTemplateService notificationTemplateService; - private final KindergartenWorkReviewRepository kindergartenWorkReviewRepository; - - @Transactional - public Kindergarten createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { - User user = userService.getUserById(userId); - Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); - - boolean exists = workReviewRepository.existsByUserAndKindergarten(user, kindergarten); - if (exists) { - throw new BusinessException(ErrorCodes.ALREADY_EXIST_WORK_REVIEW); - } - - KindergartenWorkReview review = KindergartenWorkReview.builder() - .user(user) - .kindergarten(kindergarten) - .workType(request.getWorkType()) - .workYear(request.getWorkYear()) - .oneLineComment(request.getOneLineComment()) - .benefitAndSalaryComment(request.getBenefitAndSalaryComment()) - .benefitAndSalaryScore(request.getBenefitAndSalaryScore()) - .workLifeBalanceComment(request.getWorkLifeBalanceComment()) - .workLifeBalanceScore(request.getWorkLifeBalanceScore()) - .workEnvironmentComment(request.getWorkEnvironmentComment()) - .workEnvironmentScore(request.getWorkEnvironmentScore()) - .managerComment(request.getManagerComment()) - .managerScore(request.getManagerScore()) - .customerComment(request.getCustomerComment()) - .customerScore(request.getCustomerScore()) - .reviewStatus(ReviewStatus.ACCEPTED) - .likeCount(0) - .shareCount(0) - .build(); - - workReviewRepository.save(review); - return kindergarten; + private final UserService userService; + private final KindergartenService kindergartenService; + private final KindergartenWorkReviewRepository workReviewRepository; + private final KindergartenWorkReviewLikeHistoryRepository workReviewLikeHistoryRepository; + private final NotificationTemplateService notificationTemplateService; + private final KindergartenWorkReviewRepository kindergartenWorkReviewRepository; + + @Transactional + public Kindergarten createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); + Kindergarten kindergarten = + kindergartenService.getKindergartenById(request.getKindergartenId()); + + boolean exists = workReviewRepository.existsByUserAndKindergarten(user, kindergarten); + if (exists) { + throw new BusinessException(ErrorCodes.ALREADY_EXIST_WORK_REVIEW); } - @Transactional - public Kindergarten modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { - User user = userService.getUserById(userId); - Kindergarten kindergarten = kindergartenService.getKindergartenById(request.getKindergartenId()); - - KindergartenWorkReview review = workReviewRepository.findById(request.getWorkReviewId()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); - - if (!review.getKindergarten().getId().equals(kindergarten.getId())) { - throw new BusinessException(ErrorCodes.INCORRECT_KINDERGARTEN_EXCEPTION); - } - - if (!review.getUser().getId().equals(user.getId())) { - throw new BusinessException(ErrorCodes.REVIEW_EDIT_NOT_OWNER); - } - - review.updateReview(request); - return kindergarten; + KindergartenWorkReview review = + KindergartenWorkReview.builder() + .user(user) + .kindergarten(kindergarten) + .workType(request.getWorkType()) + .workYear(request.getWorkYear()) + .oneLineComment(request.getOneLineComment()) + .benefitAndSalaryComment(request.getBenefitAndSalaryComment()) + .benefitAndSalaryScore(request.getBenefitAndSalaryScore()) + .workLifeBalanceComment(request.getWorkLifeBalanceComment()) + .workLifeBalanceScore(request.getWorkLifeBalanceScore()) + .workEnvironmentComment(request.getWorkEnvironmentComment()) + .workEnvironmentScore(request.getWorkEnvironmentScore()) + .managerComment(request.getManagerComment()) + .managerScore(request.getManagerScore()) + .customerComment(request.getCustomerComment()) + .customerScore(request.getCustomerScore()) + .reviewStatus(ReviewStatus.ACCEPTED) + .likeCount(0) + .shareCount(0) + .build(); + + workReviewRepository.save(review); + return kindergarten; + } + + @Transactional + public Kindergarten modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { + User user = userService.getUserById(userId); + Kindergarten kindergarten = + kindergartenService.getKindergartenById(request.getKindergartenId()); + + KindergartenWorkReview review = + workReviewRepository + .findById(request.getWorkReviewId()) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); + + if (!review.getKindergarten().getId().equals(kindergarten.getId())) { + throw new BusinessException(ErrorCodes.INCORRECT_KINDERGARTEN_EXCEPTION); } - @Transactional - public void likeWorkReview(long reviewId, Long userId) { - User user = userService.getUserById(userId); - - KindergartenWorkReview review = workReviewRepository.findById(reviewId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); - - Optional existingLike = workReviewLikeHistoryRepository.findByUserAndWorkReview(user, review); - - if (existingLike.isPresent()) { - // 좋아요 취소 - workReviewLikeHistoryRepository.delete(existingLike.get()); - review.minusLikeCount(); - } else { - // 좋아요 추가 - KindergartenWorkReviewLikeHistory newLike = KindergartenWorkReviewLikeHistory.builder() - .user(user) - .workReview(review) - .build(); - - workReviewLikeHistoryRepository.save(newLike); - review.plusLikeCount(); - - // 알림 발송 - 본인 글이 아닌 경우 - if (!review.getUser().getId().equals(user.getId())) { - notificationTemplateService.sendReviewLikeNotification( - review.getUser().getId(), - user, - review.getOneLineComment(), - review.getKindergarten().getId() - ); - } - } - - workReviewRepository.save(review); + if (!review.getUser().getId().equals(user.getId())) { + throw new BusinessException(ErrorCodes.REVIEW_EDIT_NOT_OWNER); } - public WorkReviewPagedResponseDTO getReviews(Long kindergartenId, int page, int size, WorkReviewPagedResponseDTO.SortType sortType, WorkReviewStarRatingType workReviewStarRatingType, int starRating) { - if (workReviewStarRatingType != WorkReviewStarRatingType.ALL && starRating < 1 || starRating > 5) { - throw new BusinessException(ErrorCodes.ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION); - } - - Pageable pageable; - - switch (sortType) { - case POPULAR: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); - break; - case LATEST: - default: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - break; - } - - Page reviewPage; - - switch (workReviewStarRatingType) { - case BENEFIT_AND_SALARY: - reviewPage = kindergartenWorkReviewRepository - .findByBenefitAndSalaryScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case WORK_LIFE_BALANCE: - reviewPage = kindergartenWorkReviewRepository - .findByWorkLifeBalanceScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case WORK_ENVIRONMENT: - reviewPage = kindergartenWorkReviewRepository - .findByWorkEnvironmentScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case MANAGER: - reviewPage = kindergartenWorkReviewRepository - .findByManagerScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - case CUSTOMER: - reviewPage = kindergartenWorkReviewRepository - .findByCustomerScore( - kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); - break; - default: - reviewPage = workReviewRepository.findReviewsWithUserInfo( - kindergartenId, - ReviewStatus.ACCEPTED, - pageable - ); - break; - } - - return WorkReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + review.updateReview(request); + return kindergarten; + } + + @Transactional + public void likeWorkReview(long reviewId, Long userId) { + User user = userService.getUserById(userId); + + KindergartenWorkReview review = + workReviewRepository + .findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); + + Optional existingLike = + workReviewLikeHistoryRepository.findByUserAndWorkReview(user, review); + + if (existingLike.isPresent()) { + // 좋아요 취소 + workReviewLikeHistoryRepository.delete(existingLike.get()); + review.minusLikeCount(); + } else { + // 좋아요 추가 + KindergartenWorkReviewLikeHistory newLike = + KindergartenWorkReviewLikeHistory.builder().user(user).workReview(review).build(); + + workReviewLikeHistoryRepository.save(newLike); + review.plusLikeCount(); + + // 알림 발송 - 본인 글이 아닌 경우 + if (!review.getUser().getId().equals(user.getId())) { + notificationTemplateService.sendReviewLikeNotification( + review.getUser().getId(), + user, + review.getOneLineComment(), + review.getKindergarten().getId()); + } } - /// 내가 작성한 근무 리뷰 조회 - public WorkReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { - Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - - Page reviewPage = workReviewRepository.findMyReviews( - userId, - ReviewStatus.ACCEPTED, - pageable - ); + workReviewRepository.save(review); + } + + public WorkReviewPagedResponseDTO getReviews( + Long kindergartenId, + int page, + int size, + WorkReviewPagedResponseDTO.SortType sortType, + WorkReviewStarRatingType workReviewStarRatingType, + int starRating) { + if (workReviewStarRatingType != WorkReviewStarRatingType.ALL && starRating < 1 + || starRating > 5) { + throw new BusinessException(ErrorCodes.ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION); + } - return WorkReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + Pageable pageable; + + switch (sortType) { + case POPULAR: + pageable = + PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); + break; + case LATEST: + default: + pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + break; } - /// 전체 근무 리뷰 조회 (유치원 상관없이) - public WorkReviewPagedResponseDTO getAllReviews(int page, int size, WorkReviewPagedResponseDTO.SortType sortType) { - Pageable pageable; - - switch (sortType) { - case POPULAR: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); - break; - case LATEST: - default: - pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - break; - } - - Page reviewPage = workReviewRepository - .findAllReviewsWithUserInfo(ReviewStatus.ACCEPTED, pageable); - - return WorkReviewPagedResponseDTO.builder() - .content(reviewPage.getContent()) - .totalPages(reviewPage.getTotalPages()) - .build(); + Page reviewPage; + + switch (workReviewStarRatingType) { + case BENEFIT_AND_SALARY: + reviewPage = + kindergartenWorkReviewRepository.findByBenefitAndSalaryScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case WORK_LIFE_BALANCE: + reviewPage = + kindergartenWorkReviewRepository.findByWorkLifeBalanceScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case WORK_ENVIRONMENT: + reviewPage = + kindergartenWorkReviewRepository.findByWorkEnvironmentScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case MANAGER: + reviewPage = + kindergartenWorkReviewRepository.findByManagerScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + case CUSTOMER: + reviewPage = + kindergartenWorkReviewRepository.findByCustomerScore( + kindergartenId, ReviewStatus.ACCEPTED, starRating, pageable); + break; + default: + reviewPage = + workReviewRepository.findReviewsWithUserInfo( + kindergartenId, ReviewStatus.ACCEPTED, pageable); + break; } - /// 근무 리뷰 삭제 (소프트 삭제) - public void deleteWorkReview(Long reviewId, Long userId, UserRole role) { - // 리뷰 조회 - KindergartenWorkReview review = workReviewRepository.findById(reviewId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); - - // 작성자 또는 관리자 권한 확인 - if (!review.getUser().getId().equals(userId) && !role.equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - // 리뷰 소프트 삭제 (deletedAt 설정) - review.markAsDeleted(); + return WorkReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 내가 작성한 근무 리뷰 조회 + public WorkReviewPagedResponseDTO getMyReviews(Long userId, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + + Page reviewPage = + workReviewRepository.findMyReviews(userId, ReviewStatus.ACCEPTED, pageable); + + return WorkReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 전체 근무 리뷰 조회 (유치원 상관없이) + public WorkReviewPagedResponseDTO getAllReviews( + int page, int size, WorkReviewPagedResponseDTO.SortType sortType) { + Pageable pageable; + + switch (sortType) { + case POPULAR: + pageable = + PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "likeCount", "createdAt")); + break; + case LATEST: + default: + pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + break; } - public int countReviewsByUser(Long userId, ReviewStatus reviewStatus) { - return workReviewRepository.countByUserIdAndReviewStatus(userId, reviewStatus); + Page reviewPage = + workReviewRepository.findAllReviewsWithUserInfo(ReviewStatus.ACCEPTED, pageable); + + return WorkReviewPagedResponseDTO.builder() + .content(reviewPage.getContent()) + .totalPages(reviewPage.getTotalPages()) + .build(); + } + + /// 근무 리뷰 삭제 (소프트 삭제) + public void deleteWorkReview(Long reviewId, Long userId, UserRole role) { + // 리뷰 조회 + KindergartenWorkReview review = + workReviewRepository + .findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_WORK_REVIEW)); + + // 작성자 또는 관리자 권한 확인 + if (!review.getUser().getId().equals(userId) && !role.equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } -} \ No newline at end of file + + // 리뷰 소프트 삭제 (deletedAt 설정) + review.markAsDeleted(); + } + + public int countReviewsByUser(Long userId, ReviewStatus reviewStatus) { + return workReviewRepository.countByUserIdAndReviewStatus(userId, reviewStatus); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/controller/KindergartenController.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/controller/KindergartenController.java index a421a38..bde065a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/controller/KindergartenController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/controller/KindergartenController.java @@ -2,18 +2,17 @@ import com.onebyone.kindergarten.domain.kindergatens.dto.*; import com.onebyone.kindergarten.domain.kindergatens.service.KindergartenService; +import com.onebyone.kindergarten.global.common.PageResponseDTO; import com.onebyone.kindergarten.global.common.ResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; -import com.onebyone.kindergarten.global.common.PageResponseDTO; - -import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/kindergarten") @@ -21,61 +20,55 @@ @Tag(name = "유치원", description = "유치원 API") public class KindergartenController { - private final KindergartenService kindergartenService; - - @PostMapping("/batch") - @Operation(summary = "유치원 정보 저장", - description = "global/docs/kindergartens.json 파일을 사용하여 유치원 정보를 저장합니다.") - public ResponseEntity saveKindergartens(@RequestBody List kindergartenDTOs) { - return ResponseEntity.ok(kindergartenService.saveAll(kindergartenDTOs)); - } + private final KindergartenService kindergartenService; - @GetMapping - @Operation(summary = "유치원 검색", - description = "이름, 설립 유형, 주소, 학급 수, 원생 수, 위도, 경도, 반경으로 유치원을 검색합니다.") - public ResponseEntity> searchKindergartens( - KindergartenSearchDTO searchDTO, - @PageableDefault(size = 10) Pageable pageable) { - Page page = kindergartenService.searchKindergartens(searchDTO, pageable); - return ResponseEntity.ok(new PageResponseDTO<>(page)); - } + @PostMapping("/batch") + @Operation( + summary = "유치원 정보 저장", + description = "global/docs/kindergartens.json 파일을 사용하여 유치원 정보를 저장합니다.") + public ResponseEntity saveKindergartens( + @RequestBody List kindergartenDTOs) { + return ResponseEntity.ok(kindergartenService.saveAll(kindergartenDTOs)); + } - @GetMapping("/{id}") - @Operation(summary = "유치원 상세 조회", - description = "유치원의 ID를 사용하여 유치원 정보를 조회합니다.") - public ResponseEntity getKindergarten(@PathVariable Long id) { - return ResponseEntity.ok(kindergartenService.findById(id)); - } + @GetMapping + @Operation( + summary = "유치원 검색", + description = "이름, 설립 유형, 주소, 학급 수, 원생 수, 위도, 경도, 반경으로 유치원을 검색합니다.") + public ResponseEntity> searchKindergartens( + KindergartenSearchDTO searchDTO, @PageableDefault(size = 10) Pageable pageable) { + Page page = + kindergartenService.searchKindergartens(searchDTO, pageable); + return ResponseEntity.ok(new PageResponseDTO<>(page)); + } + @GetMapping("/{id}") + @Operation(summary = "유치원 상세 조회", description = "유치원의 ID를 사용하여 유치원 정보를 조회합니다.") + public ResponseEntity getKindergarten(@PathVariable Long id) { + return ResponseEntity.ok(kindergartenService.findById(id)); + } - @GetMapping("/nearby") - @Operation(summary = "주변 유치원 조회", - description = "현재 위치 기준으로 특정 반경 내의 유치원을 조회합니다.") - public ResponseDto> getNearby( - @RequestParam double latitude, - @RequestParam double longitude, - @RequestParam(defaultValue = "2.0") double radiusKm - ) { - return ResponseDto.success( - kindergartenService.getNearbyKindergarten( - latitude, - longitude, - radiusKm - ) - ); - } + @GetMapping("/nearby") + @Operation(summary = "주변 유치원 조회", description = "현재 위치 기준으로 특정 반경 내의 유치원을 조회합니다.") + public ResponseDto> getNearby( + @RequestParam double latitude, + @RequestParam double longitude, + @RequestParam(defaultValue = "2.0") double radiusKm) { + return ResponseDto.success( + kindergartenService.getNearbyKindergarten(latitude, longitude, radiusKm)); + } - @GetMapping("/{id}/simple") - @Operation(summary = "유치원 이름 조회", description = "유치원 이름을 가져옵니다") - public KindergartenSimpleDTO getSimpleKindergarten(@PathVariable Long id) { - return kindergartenService.getSimpleKindergarten(id); - } + @GetMapping("/{id}/simple") + @Operation(summary = "유치원 이름 조회", description = "유치원 이름을 가져옵니다") + public KindergartenSimpleDTO getSimpleKindergarten(@PathVariable Long id) { + return kindergartenService.getSimpleKindergarten(id); + } - @GetMapping("/region") - @Operation(summary = "유치원 지역 조회", description = "지역 내의 유치원을 조회합니다.") - public List getKindergartenByRegion( - @RequestParam("regionId") Long regionId, @RequestParam(value = "subRegionId", required = false) Long subRegionId - ) { - return kindergartenService.getKindergartenByRegion(regionId, subRegionId); - } + @GetMapping("/region") + @Operation(summary = "유치원 지역 조회", description = "지역 내의 유치원을 조회합니다.") + public List getKindergartenByRegion( + @RequestParam("regionId") Long regionId, + @RequestParam(value = "subRegionId", required = false) Long subRegionId) { + return kindergartenService.getKindergartenByRegion(regionId, subRegionId); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenDTO.java index 313bf56..2f178d2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenDTO.java @@ -1,77 +1,77 @@ package com.onebyone.kindergarten.domain.kindergatens.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDate; import lombok.Getter; import lombok.Setter; -import java.time.LocalDate; - @Getter @Setter public class KindergartenDTO { - private String name; + private String name; + + private String establishment; - private String establishment; + private LocalDate establishmentDate; - private LocalDate establishmentDate; + private LocalDate openDate; - private LocalDate openDate; + private String address; - private String address; + private String homepage; - private String homepage; + private String phoneNumber; - private String phoneNumber; + @JsonProperty("classCount3") + private Integer classCount3; - @JsonProperty("classCount3") - private Integer classCount3; + @JsonProperty("classCount4") + private Integer classCount4; - @JsonProperty("classCount4") - private Integer classCount4; + @JsonProperty("classCount5") + private Integer classCount5; - @JsonProperty("classCount5") - private Integer classCount5; - - @JsonProperty("pupilCount3") - private Integer pupilCount3; + @JsonProperty("pupilCount3") + private Integer pupilCount3; - @JsonProperty("pupilCount4") - private Integer pupilCount4; + @JsonProperty("pupilCount4") + private Integer pupilCount4; - @JsonProperty("pupilCount5") - private Integer pupilCount5; + @JsonProperty("pupilCount5") + private Integer pupilCount5; - @JsonProperty("mixPupilCount") - private Integer mixPupilCount; + @JsonProperty("mixPupilCount") + private Integer mixPupilCount; - @JsonProperty("specialPupilCount") - private Integer specialPupilCount; + @JsonProperty("specialPupilCount") + private Integer specialPupilCount; - @JsonProperty("latitude") - private Double latitude; + @JsonProperty("latitude") + private Double latitude; - @JsonProperty("longitude") - private Double longitude; + @JsonProperty("longitude") + private Double longitude; - public static KindergartenDTO from(com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten kindergarten) { - KindergartenDTO dto = new KindergartenDTO(); - dto.setName(kindergarten.getName()); - dto.setEstablishment(kindergarten.getEstablishment()); - dto.setEstablishmentDate(kindergarten.getEstablishmentDate()); - dto.setOpenDate(kindergarten.getOpenDate()); - dto.setAddress(kindergarten.getAddress()); - dto.setHomepage(kindergarten.getHomepage()); - dto.setPhoneNumber(kindergarten.getPhoneNumber()); - dto.setClassCount3(kindergarten.getClassCount3()); - dto.setClassCount4(kindergarten.getClassCount4()); - dto.setClassCount5(kindergarten.getClassCount5()); - dto.setPupilCount3(kindergarten.getPupilCount3()); - dto.setPupilCount4(kindergarten.getPupilCount4()); - dto.setPupilCount5(kindergarten.getPupilCount5()); - dto.setMixPupilCount(kindergarten.getMixPupilCount()); - dto.setSpecialPupilCount(kindergarten.getSpecialPupilCount()); - dto.setLatitude(kindergarten.getLatitude()); - dto.setLongitude(kindergarten.getLongitude()); - return dto; - } -} \ No newline at end of file + public static KindergartenDTO from( + com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten kindergarten) { + KindergartenDTO dto = new KindergartenDTO(); + dto.setName(kindergarten.getName()); + dto.setEstablishment(kindergarten.getEstablishment()); + dto.setEstablishmentDate(kindergarten.getEstablishmentDate()); + dto.setOpenDate(kindergarten.getOpenDate()); + dto.setAddress(kindergarten.getAddress()); + dto.setHomepage(kindergarten.getHomepage()); + dto.setPhoneNumber(kindergarten.getPhoneNumber()); + dto.setClassCount3(kindergarten.getClassCount3()); + dto.setClassCount4(kindergarten.getClassCount4()); + dto.setClassCount5(kindergarten.getClassCount5()); + dto.setPupilCount3(kindergarten.getPupilCount3()); + dto.setPupilCount4(kindergarten.getPupilCount4()); + dto.setPupilCount5(kindergarten.getPupilCount5()); + dto.setMixPupilCount(kindergarten.getMixPupilCount()); + dto.setSpecialPupilCount(kindergarten.getSpecialPupilCount()); + dto.setLatitude(kindergarten.getLatitude()); + dto.setLongitude(kindergarten.getLongitude()); + return dto; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenInternshipReviewAggregateDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenInternshipReviewAggregateDTO.java index d64bcd9..37f1b2a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenInternshipReviewAggregateDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenInternshipReviewAggregateDTO.java @@ -1,28 +1,28 @@ package com.onebyone.kindergarten.domain.kindergatens.dto; import com.onebyone.kindergarten.domain.kindergatens.entity.KindergartenInternshipReviewAggregate; +import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.math.BigDecimal; - @Getter @Builder @NoArgsConstructor @AllArgsConstructor public class KindergartenInternshipReviewAggregateDTO { - private BigDecimal workEnvironmentScoreAggregate; - private BigDecimal learningSupportScoreAggregate; - private BigDecimal instructionTeacherScoreAggregate; + private BigDecimal workEnvironmentScoreAggregate; + private BigDecimal learningSupportScoreAggregate; + private BigDecimal instructionTeacherScoreAggregate; - public static KindergartenInternshipReviewAggregateDTO from(KindergartenInternshipReviewAggregate entity) { - return KindergartenInternshipReviewAggregateDTO.builder() - .workEnvironmentScoreAggregate(entity.getWorkEnvironmentScoreAggregate()) - .learningSupportScoreAggregate(entity.getLearningSupportScoreAggregate()) - .instructionTeacherScoreAggregate(entity.getInstructionTeacherScoreAggregate()) - .build(); - } + public static KindergartenInternshipReviewAggregateDTO from( + KindergartenInternshipReviewAggregate entity) { + return KindergartenInternshipReviewAggregateDTO.builder() + .workEnvironmentScoreAggregate(entity.getWorkEnvironmentScoreAggregate()) + .learningSupportScoreAggregate(entity.getLearningSupportScoreAggregate()) + .instructionTeacherScoreAggregate(entity.getInstructionTeacherScoreAggregate()) + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenResponseDTO.java index 490aaec..db78f40 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenResponseDTO.java @@ -1,149 +1,182 @@ package com.onebyone.kindergarten.domain.kindergatens.dto; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; +import java.time.LocalDate; import lombok.Builder; import lombok.Getter; -import java.time.LocalDate; - @Getter @JsonInclude(JsonInclude.Include.NON_NULL) public class KindergartenResponseDTO { - private final Long id; - private final String name; - private final String establishment; - - @JsonFormat(pattern = "yyyy-MM-dd") - private final LocalDate establishmentDate; + private final Long id; + private final String name; + private final String establishment; + + @JsonFormat(pattern = "yyyy-MM-dd") + private final LocalDate establishmentDate; + + @JsonFormat(pattern = "yyyy-MM-dd") + private final LocalDate openDate; + + private final String address; + private final String homepage; + private final String phoneNumber; + + private final Integer classCount3; + private final Integer classCount4; + private final Integer classCount5; + private final Integer pupilCount3; + private final Integer pupilCount4; + private final Integer pupilCount5; + private final Integer mixPupilCount; + private final Integer specialPupilCount; + private final Double latitude; + private final Double longitude; + private final Integer totalClassCount; + private final Integer totalPupilCount; + private final KindergartenInternshipReviewAggregateDTO internshipReviewAggregate; + private final KindergartenWorkReviewAggregateDTO workReviewAggregate; - @JsonFormat(pattern = "yyyy-MM-dd") - private final LocalDate openDate; + // JPQL 생성자 + public KindergartenResponseDTO( + Long id, + String name, + String establishment, + LocalDate establishmentDate, + LocalDate openDate, + String address, + String homepage, + String phoneNumber, + Integer classCount3, + Integer classCount4, + Integer classCount5, + Integer pupilCount3, + Integer pupilCount4, + Integer pupilCount5, + Integer mixPupilCount, + Integer specialPupilCount, + Double latitude, + Double longitude) { - private final String address; - private final String homepage; - private final String phoneNumber; - - private final Integer classCount3; - private final Integer classCount4; - private final Integer classCount5; - private final Integer pupilCount3; - private final Integer pupilCount4; - private final Integer pupilCount5; - private final Integer mixPupilCount; - private final Integer specialPupilCount; - private final Double latitude; - private final Double longitude; - private final Integer totalClassCount; - private final Integer totalPupilCount; - private final KindergartenInternshipReviewAggregateDTO internshipReviewAggregate; - private final KindergartenWorkReviewAggregateDTO workReviewAggregate; + this.id = id; + this.name = name; + this.establishment = establishment; + this.establishmentDate = establishmentDate; + this.openDate = openDate; + this.address = address; + this.homepage = homepage; + this.phoneNumber = phoneNumber; + this.classCount3 = classCount3; + this.classCount4 = classCount4; + this.classCount5 = classCount5; + this.pupilCount3 = pupilCount3; + this.pupilCount4 = pupilCount4; + this.pupilCount5 = pupilCount5; + this.mixPupilCount = mixPupilCount; + this.specialPupilCount = specialPupilCount; + this.latitude = latitude; + this.longitude = longitude; - // JPQL 생성자 - public KindergartenResponseDTO( - Long id, String name, String establishment, LocalDate establishmentDate, LocalDate openDate, - String address, String homepage, String phoneNumber, - Integer classCount3, Integer classCount4, Integer classCount5, - Integer pupilCount3, Integer pupilCount4, Integer pupilCount5, - Integer mixPupilCount, Integer specialPupilCount, - Double latitude, Double longitude) { - - this.id = id; - this.name = name; - this.establishment = establishment; - this.establishmentDate = establishmentDate; - this.openDate = openDate; - this.address = address; - this.homepage = homepage; - this.phoneNumber = phoneNumber; - this.classCount3 = classCount3; - this.classCount4 = classCount4; - this.classCount5 = classCount5; - this.pupilCount3 = pupilCount3; - this.pupilCount4 = pupilCount4; - this.pupilCount5 = pupilCount5; - this.mixPupilCount = mixPupilCount; - this.specialPupilCount = specialPupilCount; - this.latitude = latitude; - this.longitude = longitude; + // 총계 계산 + this.totalClassCount = classCount3 + classCount4 + classCount5; + this.totalPupilCount = + pupilCount3 + pupilCount4 + pupilCount5 + mixPupilCount + specialPupilCount; - // 총계 계산 - this.totalClassCount = classCount3 + classCount4 + classCount5; - this.totalPupilCount = pupilCount3 + pupilCount4 + pupilCount5 + mixPupilCount + specialPupilCount; + this.internshipReviewAggregate = null; + this.workReviewAggregate = null; + } - this.internshipReviewAggregate = null; - this.workReviewAggregate = null; - } + // 모든 필드를 포함하는 생성자 (Builder용) + @Builder + public KindergartenResponseDTO( + Long id, + String name, + String establishment, + LocalDate establishmentDate, + LocalDate openDate, + String address, + String homepage, + String phoneNumber, + Integer classCount3, + Integer classCount4, + Integer classCount5, + Integer pupilCount3, + Integer pupilCount4, + Integer pupilCount5, + Integer mixPupilCount, + Integer specialPupilCount, + Double latitude, + Double longitude, + Integer totalClassCount, + Integer totalPupilCount, + KindergartenInternshipReviewAggregateDTO internshipReviewAggregate, + KindergartenWorkReviewAggregateDTO workReviewAggregate) { - // 모든 필드를 포함하는 생성자 (Builder용) - @Builder - public KindergartenResponseDTO( - Long id, String name, String establishment, LocalDate establishmentDate, LocalDate openDate, - String address, String homepage, String phoneNumber, - Integer classCount3, Integer classCount4, Integer classCount5, - Integer pupilCount3, Integer pupilCount4, Integer pupilCount5, - Integer mixPupilCount, Integer specialPupilCount, - Double latitude, Double longitude, - Integer totalClassCount, Integer totalPupilCount, KindergartenInternshipReviewAggregateDTO internshipReviewAggregate, - KindergartenWorkReviewAggregateDTO workReviewAggregate) { - - this.id = id; - this.name = name; - this.establishment = establishment; - this.establishmentDate = establishmentDate; - this.openDate = openDate; - this.address = address; - this.homepage = homepage; - this.phoneNumber = phoneNumber; - this.classCount3 = classCount3; - this.classCount4 = classCount4; - this.classCount5 = classCount5; - this.pupilCount3 = pupilCount3; - this.pupilCount4 = pupilCount4; - this.pupilCount5 = pupilCount5; - this.mixPupilCount = mixPupilCount; - this.specialPupilCount = specialPupilCount; - this.latitude = latitude; - this.longitude = longitude; - this.totalClassCount = totalClassCount; - this.totalPupilCount = totalPupilCount; - this.internshipReviewAggregate = internshipReviewAggregate; - this.workReviewAggregate = workReviewAggregate; - } + this.id = id; + this.name = name; + this.establishment = establishment; + this.establishmentDate = establishmentDate; + this.openDate = openDate; + this.address = address; + this.homepage = homepage; + this.phoneNumber = phoneNumber; + this.classCount3 = classCount3; + this.classCount4 = classCount4; + this.classCount5 = classCount5; + this.pupilCount3 = pupilCount3; + this.pupilCount4 = pupilCount4; + this.pupilCount5 = pupilCount5; + this.mixPupilCount = mixPupilCount; + this.specialPupilCount = specialPupilCount; + this.latitude = latitude; + this.longitude = longitude; + this.totalClassCount = totalClassCount; + this.totalPupilCount = totalPupilCount; + this.internshipReviewAggregate = internshipReviewAggregate; + this.workReviewAggregate = workReviewAggregate; + } - public static KindergartenResponseDTO from(Kindergarten kindergarten) { - int totalClass = kindergarten.getClassCount3() + kindergarten.getClassCount4() + kindergarten.getClassCount5(); - int totalPupil = kindergarten.getPupilCount3() + kindergarten.getPupilCount4() + kindergarten.getPupilCount5() - + kindergarten.getMixPupilCount() + kindergarten.getSpecialPupilCount(); + public static KindergartenResponseDTO from(Kindergarten kindergarten) { + int totalClass = + kindergarten.getClassCount3() + + kindergarten.getClassCount4() + + kindergarten.getClassCount5(); + int totalPupil = + kindergarten.getPupilCount3() + + kindergarten.getPupilCount4() + + kindergarten.getPupilCount5() + + kindergarten.getMixPupilCount() + + kindergarten.getSpecialPupilCount(); - return KindergartenResponseDTO.builder() - .id(kindergarten.getId()) - .name(kindergarten.getName()) - .establishment(kindergarten.getEstablishment()) - .establishmentDate(kindergarten.getEstablishmentDate()) - .openDate(kindergarten.getOpenDate()) - .address(kindergarten.getAddress()) - .homepage(kindergarten.getHomepage()) - .phoneNumber(kindergarten.getPhoneNumber()) - .classCount3(kindergarten.getClassCount3()) - .classCount4(kindergarten.getClassCount4()) - .classCount5(kindergarten.getClassCount5()) - .pupilCount3(kindergarten.getPupilCount3()) - .pupilCount4(kindergarten.getPupilCount4()) - .pupilCount5(kindergarten.getPupilCount5()) - .mixPupilCount(kindergarten.getMixPupilCount()) - .specialPupilCount(kindergarten.getSpecialPupilCount()) - .latitude(kindergarten.getLatitude()) - .longitude(kindergarten.getLongitude()) - .totalClassCount(totalClass) - .totalPupilCount(totalPupil) - .internshipReviewAggregate( - KindergartenInternshipReviewAggregateDTO.from( - kindergarten.getKindergartenInternshipReviewAggregate())) - .workReviewAggregate( - KindergartenWorkReviewAggregateDTO.from( - kindergarten.getKindergartenWorkReviewAggregate())) - .build(); - } -} \ No newline at end of file + return KindergartenResponseDTO.builder() + .id(kindergarten.getId()) + .name(kindergarten.getName()) + .establishment(kindergarten.getEstablishment()) + .establishmentDate(kindergarten.getEstablishmentDate()) + .openDate(kindergarten.getOpenDate()) + .address(kindergarten.getAddress()) + .homepage(kindergarten.getHomepage()) + .phoneNumber(kindergarten.getPhoneNumber()) + .classCount3(kindergarten.getClassCount3()) + .classCount4(kindergarten.getClassCount4()) + .classCount5(kindergarten.getClassCount5()) + .pupilCount3(kindergarten.getPupilCount3()) + .pupilCount4(kindergarten.getPupilCount4()) + .pupilCount5(kindergarten.getPupilCount5()) + .mixPupilCount(kindergarten.getMixPupilCount()) + .specialPupilCount(kindergarten.getSpecialPupilCount()) + .latitude(kindergarten.getLatitude()) + .longitude(kindergarten.getLongitude()) + .totalClassCount(totalClass) + .totalPupilCount(totalPupil) + .internshipReviewAggregate( + KindergartenInternshipReviewAggregateDTO.from( + kindergarten.getKindergartenInternshipReviewAggregate())) + .workReviewAggregate( + KindergartenWorkReviewAggregateDTO.from( + kindergarten.getKindergartenWorkReviewAggregate())) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSearchDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSearchDTO.java index e83d30f..6441109 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSearchDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSearchDTO.java @@ -6,18 +6,18 @@ @Getter @Setter public class KindergartenSearchDTO { - private String name; // 유치원 이름 - - private String establishment; // 설립 유형 - - private String address; // 주소 - - private Integer minClassCount; // 최소 학급 수 - - private Integer maxClassCount; // 최대 학급 수 - private Integer minPupilCount; // 최소 원생 수 - private Integer maxPupilCount; // 최대 원생 수 - private Double latitude; // 위도 - private Double longitude; // 경도 - private Double radius; // 반경 (km) -} \ No newline at end of file + private String name; // 유치원 이름 + + private String establishment; // 설립 유형 + + private String address; // 주소 + + private Integer minClassCount; // 최소 학급 수 + + private Integer maxClassCount; // 최대 학급 수 + private Integer minPupilCount; // 최소 원생 수 + private Integer maxPupilCount; // 최대 원생 수 + private Double latitude; // 위도 + private Double longitude; // 경도 + private Double radius; // 반경 (km) +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSimpleDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSimpleDTO.java index 69f633b..edec091 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSimpleDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenSimpleDTO.java @@ -4,6 +4,6 @@ @Data public class KindergartenSimpleDTO { - private Long kindergartenId; - private String name; + private Long kindergartenId; + private String name; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenWorkReviewAggregateDTO.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenWorkReviewAggregateDTO.java index 2abe3da..1d3da90 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenWorkReviewAggregateDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/dto/KindergartenWorkReviewAggregateDTO.java @@ -1,31 +1,30 @@ package com.onebyone.kindergarten.domain.kindergatens.dto; import com.onebyone.kindergarten.domain.kindergatens.entity.KindergartenWorkReviewAggregate; +import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.math.BigDecimal; - @Getter @Builder @NoArgsConstructor @AllArgsConstructor public class KindergartenWorkReviewAggregateDTO { - private BigDecimal benefitAndSalaryScoreAggregate; - private BigDecimal workLiftBalanceScoreAggregate; - private BigDecimal workEnvironmentScoreAggregate; - private BigDecimal managerScoreAggregate; - private BigDecimal customerScoreAggregate; + private BigDecimal benefitAndSalaryScoreAggregate; + private BigDecimal workLiftBalanceScoreAggregate; + private BigDecimal workEnvironmentScoreAggregate; + private BigDecimal managerScoreAggregate; + private BigDecimal customerScoreAggregate; - public static KindergartenWorkReviewAggregateDTO from(KindergartenWorkReviewAggregate entity) { - return KindergartenWorkReviewAggregateDTO.builder() - .benefitAndSalaryScoreAggregate(entity.getBenefitAndSalaryScoreAggregate()) - .workLiftBalanceScoreAggregate(entity.getWorkLiftBalanceScoreAggregate()) - .workEnvironmentScoreAggregate(entity.getWorkEnvironmentScoreAggregate()) - .managerScoreAggregate(entity.getManagerScoreAggregate()) - .customerScoreAggregate(entity.getCustomerScoreAggregate()) - .build(); - } + public static KindergartenWorkReviewAggregateDTO from(KindergartenWorkReviewAggregate entity) { + return KindergartenWorkReviewAggregateDTO.builder() + .benefitAndSalaryScoreAggregate(entity.getBenefitAndSalaryScoreAggregate()) + .workLiftBalanceScoreAggregate(entity.getWorkLiftBalanceScoreAggregate()) + .workEnvironmentScoreAggregate(entity.getWorkEnvironmentScoreAggregate()) + .managerScoreAggregate(entity.getManagerScoreAggregate()) + .customerScoreAggregate(entity.getCustomerScoreAggregate()) + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/Kindergarten.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/Kindergarten.java index b290fe2..db1e5f3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/Kindergarten.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/Kindergarten.java @@ -6,15 +6,14 @@ import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenSimpleDTO; import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - @Entity @NoArgsConstructor @AllArgsConstructor @@ -22,129 +21,131 @@ @Getter public class Kindergarten extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 유치원 코드 - KINDERCODE - - @Column(nullable = false) - private String name; // 유치원 이름 - KINDERNAME - - @Column(nullable = false) - private String establishment; // 설립 유형 - ESTABLISH - - @Column(name = "establishment_date", nullable = false) - private LocalDate establishmentDate; // 설립일 - EDATE - - @Column(name = "open_date", nullable = false) - private LocalDate openDate; // 개원일 - ODATE - - @Column(nullable = false) - private String address; // 주소 - ADDR - - private String homepage; // 홈페이지 - HPADDR - - @Column(name = "phone_number") - private String phoneNumber; // 전화번호 - TELNO - - // 만3세학급수 - CLCNT3 - @Column(name = "class_count3", nullable = false) - private Integer classCount3; - - // 만4세학급수 - CLCNT4 - @Column(name = "class_count4",nullable = false) - private Integer classCount4; - - // 만5세학급수 - CLCNT5 - @Column(name = "class_count5",nullable = false) - private Integer classCount5; - - // 만3세 유아수 - PPCNT3 - @Column(name = "pupil_count3", nullable = false) - private Integer pupilCount3; - - // 만4세 유아수 - PPCNT4 - @Column(name = "pupil_count4",nullable = false) - private Integer pupilCount4; - - // 만5세 유아수 - PPCNT5 - @Column(name = "pupil_count5",nullable = false) - private Integer pupilCount5; - - // 혼합 유아수 - MIXPPCNT - @Column(name = "mix_pupil_count", nullable = false) - private Integer mixPupilCount; - - // 특수 유아수 - SHPPCNT - @Column(name = "special_pupil_count", nullable = false) - private Integer specialPupilCount; - - private Double latitude; // 위도 - private Double longitude; // 경도 - - @OneToMany(mappedBy = "kindergarten", fetch = FetchType.LAZY) - private List workReviews; - - @OneToMany(mappedBy = "kindergarten", fetch = FetchType.LAZY) - private List internshipReviews; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten_internship_review_aggregate_id") - private KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten_work_review_aggregate_id") - private KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate; - - @Column(name = "region_id") - private Long regionId; - - @Column(name = "sub_region_id") - private Long subRegionId; - - /// 유치원 정보 업데이트 - public void update(KindergartenDTO kindergartenDTO) { - this.name = kindergartenDTO.getName(); - this.establishment = kindergartenDTO.getEstablishment(); - this.establishmentDate = kindergartenDTO.getEstablishmentDate(); - this.openDate = kindergartenDTO.getOpenDate(); - this.address = kindergartenDTO.getAddress(); - this.homepage = kindergartenDTO.getHomepage(); - this.phoneNumber = kindergartenDTO.getPhoneNumber(); - this.classCount3 = kindergartenDTO.getClassCount3(); - this.classCount4 = kindergartenDTO.getClassCount4(); - this.classCount5 = kindergartenDTO.getClassCount5(); - this.pupilCount3 = kindergartenDTO.getPupilCount3(); - this.pupilCount4 = kindergartenDTO.getPupilCount4(); - this.pupilCount5 = kindergartenDTO.getPupilCount5(); - this.mixPupilCount = kindergartenDTO.getMixPupilCount(); - this.specialPupilCount = kindergartenDTO.getSpecialPupilCount(); - this.latitude = kindergartenDTO.getLatitude(); - this.longitude = kindergartenDTO.getLongitude(); - this.updatedAt = LocalDateTime.now(); - } - - public void updateInternshipReviewAggregate(KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate ) { - this.kindergartenInternshipReviewAggregate = kindergartenInternshipReviewAggregate; - this.updatedAt = LocalDateTime.now(); - } - - public void updateWorkReviewAggregate(KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate) { - this.kindergartenWorkReviewAggregate = kindergartenWorkReviewAggregate; - this.updatedAt = LocalDateTime.now(); - } - - public KindergartenSimpleDTO toSimpleDTO() { - KindergartenSimpleDTO dto = new KindergartenSimpleDTO(); - dto.setKindergartenId(this.id); - dto.setName(this.name); - return dto; - } - - public void updateRegion(Long regionId) { - this.regionId = regionId; - } - - public void updateSubRegion(Long subRegionId) { - this.subRegionId = subRegionId; - } -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 유치원 코드 - KINDERCODE + + @Column(nullable = false) + private String name; // 유치원 이름 - KINDERNAME + + @Column(nullable = false) + private String establishment; // 설립 유형 - ESTABLISH + + @Column(name = "establishment_date", nullable = false) + private LocalDate establishmentDate; // 설립일 - EDATE + + @Column(name = "open_date", nullable = false) + private LocalDate openDate; // 개원일 - ODATE + + @Column(nullable = false) + private String address; // 주소 - ADDR + + private String homepage; // 홈페이지 - HPADDR + + @Column(name = "phone_number") + private String phoneNumber; // 전화번호 - TELNO + + // 만3세학급수 - CLCNT3 + @Column(name = "class_count3", nullable = false) + private Integer classCount3; + + // 만4세학급수 - CLCNT4 + @Column(name = "class_count4", nullable = false) + private Integer classCount4; + + // 만5세학급수 - CLCNT5 + @Column(name = "class_count5", nullable = false) + private Integer classCount5; + + // 만3세 유아수 - PPCNT3 + @Column(name = "pupil_count3", nullable = false) + private Integer pupilCount3; + + // 만4세 유아수 - PPCNT4 + @Column(name = "pupil_count4", nullable = false) + private Integer pupilCount4; + + // 만5세 유아수 - PPCNT5 + @Column(name = "pupil_count5", nullable = false) + private Integer pupilCount5; + + // 혼합 유아수 - MIXPPCNT + @Column(name = "mix_pupil_count", nullable = false) + private Integer mixPupilCount; + + // 특수 유아수 - SHPPCNT + @Column(name = "special_pupil_count", nullable = false) + private Integer specialPupilCount; + + private Double latitude; // 위도 + private Double longitude; // 경도 + + @OneToMany(mappedBy = "kindergarten", fetch = FetchType.LAZY) + private List workReviews; + + @OneToMany(mappedBy = "kindergarten", fetch = FetchType.LAZY) + private List internshipReviews; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten_internship_review_aggregate_id") + private KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten_work_review_aggregate_id") + private KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate; + + @Column(name = "region_id") + private Long regionId; + + @Column(name = "sub_region_id") + private Long subRegionId; + + /// 유치원 정보 업데이트 + public void update(KindergartenDTO kindergartenDTO) { + this.name = kindergartenDTO.getName(); + this.establishment = kindergartenDTO.getEstablishment(); + this.establishmentDate = kindergartenDTO.getEstablishmentDate(); + this.openDate = kindergartenDTO.getOpenDate(); + this.address = kindergartenDTO.getAddress(); + this.homepage = kindergartenDTO.getHomepage(); + this.phoneNumber = kindergartenDTO.getPhoneNumber(); + this.classCount3 = kindergartenDTO.getClassCount3(); + this.classCount4 = kindergartenDTO.getClassCount4(); + this.classCount5 = kindergartenDTO.getClassCount5(); + this.pupilCount3 = kindergartenDTO.getPupilCount3(); + this.pupilCount4 = kindergartenDTO.getPupilCount4(); + this.pupilCount5 = kindergartenDTO.getPupilCount5(); + this.mixPupilCount = kindergartenDTO.getMixPupilCount(); + this.specialPupilCount = kindergartenDTO.getSpecialPupilCount(); + this.latitude = kindergartenDTO.getLatitude(); + this.longitude = kindergartenDTO.getLongitude(); + this.updatedAt = LocalDateTime.now(); + } + + public void updateInternshipReviewAggregate( + KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate) { + this.kindergartenInternshipReviewAggregate = kindergartenInternshipReviewAggregate; + this.updatedAt = LocalDateTime.now(); + } + + public void updateWorkReviewAggregate( + KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate) { + this.kindergartenWorkReviewAggregate = kindergartenWorkReviewAggregate; + this.updatedAt = LocalDateTime.now(); + } + + public KindergartenSimpleDTO toSimpleDTO() { + KindergartenSimpleDTO dto = new KindergartenSimpleDTO(); + dto.setKindergartenId(this.id); + dto.setName(this.name); + return dto; + } + + public void updateRegion(Long regionId) { + this.regionId = regionId; + } + + public void updateSubRegion(Long subRegionId) { + this.subRegionId = subRegionId; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenInternshipReviewAggregate.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenInternshipReviewAggregate.java index 9e37ee5..6cbbb84 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenInternshipReviewAggregate.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenInternshipReviewAggregate.java @@ -2,40 +2,40 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.math.BigDecimal; -import java.time.LocalDateTime; - @Entity(name = "kindergarten_internship_review_aggregate") @Getter @Builder @AllArgsConstructor @NoArgsConstructor public class KindergartenInternshipReviewAggregate extends BaseEntity { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; - @OneToOne(fetch = FetchType.LAZY) - private Kindergarten kindergarten; // 유치원 + @OneToOne(fetch = FetchType.LAZY) + private Kindergarten kindergarten; // 유치원 - @Column(name = "work_environment_score_aggregate", precision = 10, scale = 2) - private BigDecimal workEnvironmentScoreAggregate; // 분위기 총합 + @Column(name = "work_environment_score_aggregate", precision = 10, scale = 2) + private BigDecimal workEnvironmentScoreAggregate; // 분위기 총합 - @Column(name = "learning_support_score_aggregate", precision = 10, scale = 2) - private BigDecimal learningSupportScoreAggregate; // 학습 총합 + @Column(name = "learning_support_score_aggregate", precision = 10, scale = 2) + private BigDecimal learningSupportScoreAggregate; // 학습 총합 - @Column(name = "instruction_teacher_score_aggregate", precision = 10, scale = 2) - private BigDecimal instructionTeacherScoreAggregate; // 지도교사 총합 + @Column(name = "instruction_teacher_score_aggregate", precision = 10, scale = 2) + private BigDecimal instructionTeacherScoreAggregate; // 지도교사 총합 - public void updateScoreAggregates(BigDecimal avgWorkEnvironmentScore, BigDecimal avgLearningSupportScore, BigDecimal avgInstructionTeacherScore) { - this.workEnvironmentScoreAggregate = avgWorkEnvironmentScore; - this.learningSupportScoreAggregate = avgLearningSupportScore; - this.instructionTeacherScoreAggregate = avgInstructionTeacherScore; - this.updatedAt = LocalDateTime.now(); - } + public void updateScoreAggregates( + BigDecimal avgWorkEnvironmentScore, + BigDecimal avgLearningSupportScore, + BigDecimal avgInstructionTeacherScore) { + this.workEnvironmentScoreAggregate = avgWorkEnvironmentScore; + this.learningSupportScoreAggregate = avgLearningSupportScore; + this.instructionTeacherScoreAggregate = avgInstructionTeacherScore; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenWorkReviewAggregate.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenWorkReviewAggregate.java index b5f0a3a..b9a079d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenWorkReviewAggregate.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/entity/KindergartenWorkReviewAggregate.java @@ -2,48 +2,50 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.math.BigDecimal; -import java.time.LocalDateTime; - @Entity(name = "kindergarten_work_review_aggregate") @Getter @Builder @AllArgsConstructor @NoArgsConstructor public class KindergartenWorkReviewAggregate extends BaseEntity { - @Id - @GeneratedValue - private Long id; - - @OneToOne(fetch = FetchType.LAZY) - private Kindergarten kindergarten; // 유치원 - - @Column(name = "benefit_and_salary_score_aggregate", precision = 10, scale = 2) - private BigDecimal benefitAndSalaryScoreAggregate; // 복지/급여 총합 - - @Column(name = "work_life_balance_score_aggregate", precision = 10, scale = 2) - private BigDecimal workLiftBalanceScoreAggregate; // 워라벨 총합 - - @Column(name = "work_environment_score_aggregate", precision = 10, scale = 2) - private BigDecimal workEnvironmentScoreAggregate; // 분위기 총합 - - @Column(name = "manager_score_aggregate", precision = 10, scale = 2) - private BigDecimal managerScoreAggregate; // 관리자 총합 - - @Column(name = "customer_score_aggregate", precision = 10, scale = 2) - private BigDecimal customerScoreAggregate; // 고객 총합 - - public void updateScoreAggregates(BigDecimal avgBenefitAndSalary, BigDecimal avgWorkLifeBalance, BigDecimal avgWorkEnvironment, BigDecimal avgManager, BigDecimal avgCustomer) { - this.benefitAndSalaryScoreAggregate = avgBenefitAndSalary; - this.workLiftBalanceScoreAggregate = avgWorkLifeBalance; - this.workEnvironmentScoreAggregate = avgWorkEnvironment; - this.managerScoreAggregate = avgManager; - this.customerScoreAggregate = avgCustomer; - this.updatedAt = LocalDateTime.now(); - } + @Id @GeneratedValue private Long id; + + @OneToOne(fetch = FetchType.LAZY) + private Kindergarten kindergarten; // 유치원 + + @Column(name = "benefit_and_salary_score_aggregate", precision = 10, scale = 2) + private BigDecimal benefitAndSalaryScoreAggregate; // 복지/급여 총합 + + @Column(name = "work_life_balance_score_aggregate", precision = 10, scale = 2) + private BigDecimal workLiftBalanceScoreAggregate; // 워라벨 총합 + + @Column(name = "work_environment_score_aggregate", precision = 10, scale = 2) + private BigDecimal workEnvironmentScoreAggregate; // 분위기 총합 + + @Column(name = "manager_score_aggregate", precision = 10, scale = 2) + private BigDecimal managerScoreAggregate; // 관리자 총합 + + @Column(name = "customer_score_aggregate", precision = 10, scale = 2) + private BigDecimal customerScoreAggregate; // 고객 총합 + + public void updateScoreAggregates( + BigDecimal avgBenefitAndSalary, + BigDecimal avgWorkLifeBalance, + BigDecimal avgWorkEnvironment, + BigDecimal avgManager, + BigDecimal avgCustomer) { + this.benefitAndSalaryScoreAggregate = avgBenefitAndSalary; + this.workLiftBalanceScoreAggregate = avgWorkLifeBalance; + this.workEnvironmentScoreAggregate = avgWorkEnvironment; + this.managerScoreAggregate = avgManager; + this.customerScoreAggregate = avgCustomer; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenInternshipReviewAggregateRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenInternshipReviewAggregateRepository.java index 1f76995..5311b07 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenInternshipReviewAggregateRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenInternshipReviewAggregateRepository.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface KindergartenInternshipReviewAggregateRepository extends JpaRepository { - KindergartenInternshipReviewAggregate findByKindergarten(Kindergarten kindergarten); +public interface KindergartenInternshipReviewAggregateRepository + extends JpaRepository { + KindergartenInternshipReviewAggregate findByKindergarten(Kindergarten kindergarten); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenRepository.java index ac5ce5d..9b08e45 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenRepository.java @@ -1,71 +1,79 @@ package com.onebyone.kindergarten.domain.kindergatens.repository; +import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenSearchDTO; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.EntityGraph; +import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenSearchDTO; - -import java.util.List; -import java.util.Optional; @Repository public interface KindergartenRepository extends JpaRepository { - /// 유치원 검색 - 이름, 설립 유형, 주소, 학급 수, 원생 수, 위도, 경도, 반경 - @EntityGraph(attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) - @Query("SELECT k FROM Kindergarten k " + - "WHERE (:#{#search.name} IS NULL OR k.name LIKE %:#{#search.name}%) " + - "AND (:#{#search.establishment} IS NULL OR k.establishment = :#{#search.establishment}) " + - "AND (:#{#search.address} IS NULL OR k.address LIKE %:#{#search.address}%) " + - "AND (:#{#search.minClassCount} IS NULL OR (k.classCount3 + k.classCount4 + k.classCount5) >= :#{#search.minClassCount}) " + - "AND (:#{#search.maxClassCount} IS NULL OR (k.classCount3 + k.classCount4 + k.classCount5) <= :#{#search.maxClassCount}) " + - "AND (:#{#search.minPupilCount} IS NULL OR (k.pupilCount3 + k.pupilCount4 + k.pupilCount5 + k.mixPupilCount + k.specialPupilCount) >= :#{#search.minPupilCount}) " + - "AND (:#{#search.maxPupilCount} IS NULL OR (k.pupilCount3 + k.pupilCount4 + k.pupilCount5 + k.mixPupilCount + k.specialPupilCount) <= :#{#search.maxPupilCount}) " + - "AND (:#{#search.latitude} IS NULL OR :#{#search.longitude} IS NULL OR :#{#search.radius} IS NULL OR " + - "6371 * acos(cos(radians(:#{#search.latitude})) * cos(radians(k.latitude)) * cos(radians(k.longitude) - radians(:#{#search.longitude})) + sin(radians(:#{#search.latitude})) * sin(radians(k.latitude))) <= :#{#search.radius})") - Page findBySearchCriteria(@Param("search") KindergartenSearchDTO search, Pageable pageable); + /// 유치원 검색 - 이름, 설립 유형, 주소, 학급 수, 원생 수, 위도, 경도, 반경 + @EntityGraph( + attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) + @Query( + "SELECT k FROM Kindergarten k " + + "WHERE (:#{#search.name} IS NULL OR k.name LIKE %:#{#search.name}%) " + + "AND (:#{#search.establishment} IS NULL OR k.establishment = :#{#search.establishment}) " + + "AND (:#{#search.address} IS NULL OR k.address LIKE %:#{#search.address}%) " + + "AND (:#{#search.minClassCount} IS NULL OR (k.classCount3 + k.classCount4 + k.classCount5) >= :#{#search.minClassCount}) " + + "AND (:#{#search.maxClassCount} IS NULL OR (k.classCount3 + k.classCount4 + k.classCount5) <= :#{#search.maxClassCount}) " + + "AND (:#{#search.minPupilCount} IS NULL OR (k.pupilCount3 + k.pupilCount4 + k.pupilCount5 + k.mixPupilCount + k.specialPupilCount) >= :#{#search.minPupilCount}) " + + "AND (:#{#search.maxPupilCount} IS NULL OR (k.pupilCount3 + k.pupilCount4 + k.pupilCount5 + k.mixPupilCount + k.specialPupilCount) <= :#{#search.maxPupilCount}) " + + "AND (:#{#search.latitude} IS NULL OR :#{#search.longitude} IS NULL OR :#{#search.radius} IS NULL OR " + + "6371 * acos(cos(radians(:#{#search.latitude})) * cos(radians(k.latitude)) * cos(radians(k.longitude) - radians(:#{#search.longitude})) + sin(radians(:#{#search.latitude})) * sin(radians(k.latitude))) <= :#{#search.radius})") + Page findBySearchCriteria( + @Param("search") KindergartenSearchDTO search, Pageable pageable); - /// 유치원 이름으로 검색 - Optional findByName(String name); + /// 유치원 이름으로 검색 + Optional findByName(String name); - /// 유치원 ID로 조회 - @EntityGraph(attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) - Optional findWithReviewsById(Long id); + /// 유치원 ID로 조회 + @EntityGraph( + attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) + Optional findWithReviewsById(Long id); - /// 유치원 이름과 주소로 검색 - @Query("SELECT k FROM Kindergarten k WHERE k.name = :name AND k.address = :address") - Optional findByNameAndAddress(@Param("name") String name, @Param("address") String address); + /// 유치원 이름과 주소로 검색 + @Query("SELECT k FROM Kindergarten k WHERE k.name = :name AND k.address = :address") + Optional findByNameAndAddress( + @Param("name") String name, @Param("address") String address); - /// 주변 유치원 조회 - @EntityGraph(attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) - @Query("SELECT k FROM Kindergarten k " + - "WHERE (6371 * acos(cos(radians(:latitude)) * cos(radians(k.latitude)) * " + - " cos(radians(k.longitude) - radians(:longitude)) + " + - " sin(radians(:latitude)) * sin(radians(k.latitude)))) <= :radiusKm " + - "ORDER BY (6371 * acos(cos(radians(:latitude)) * cos(radians(k.latitude)) * " + - " cos(radians(k.longitude) - radians(:longitude)) + " + - " sin(radians(:latitude)) * sin(radians(k.latitude))))") - List findNearbyKindergartenEntities( - @Param("latitude") double latitude, - @Param("longitude") double longitude, - @Param("radiusKm") double radiusKm - ); + /// 주변 유치원 조회 + @EntityGraph( + attributePaths = {"kindergartenInternshipReviewAggregate", "kindergartenWorkReviewAggregate"}) + @Query( + "SELECT k FROM Kindergarten k " + + "WHERE (6371 * acos(cos(radians(:latitude)) * cos(radians(k.latitude)) * " + + " cos(radians(k.longitude) - radians(:longitude)) + " + + " sin(radians(:latitude)) * sin(radians(k.latitude)))) <= :radiusKm " + + "ORDER BY (6371 * acos(cos(radians(:latitude)) * cos(radians(k.latitude)) * " + + " cos(radians(k.longitude) - radians(:longitude)) + " + + " sin(radians(:latitude)) * sin(radians(k.latitude))))") + List findNearbyKindergartenEntities( + @Param("latitude") double latitude, + @Param("longitude") double longitude, + @Param("radiusKm") double radiusKm); - @Query("SELECT k FROM Kindergarten k " + - "LEFT JOIN FETCH k.kindergartenWorkReviewAggregate w " + - "LEFT JOIN FETCH k.kindergartenInternshipReviewAggregate i " + - "WHERE k.regionId = :regionId") - List findAllByRegionIdWithFetch(@Param("regionId") Long regionId); + @Query( + "SELECT k FROM Kindergarten k " + + "LEFT JOIN FETCH k.kindergartenWorkReviewAggregate w " + + "LEFT JOIN FETCH k.kindergartenInternshipReviewAggregate i " + + "WHERE k.regionId = :regionId") + List findAllByRegionIdWithFetch(@Param("regionId") Long regionId); - @Query("SELECT k FROM Kindergarten k " + - "LEFT JOIN FETCH k.kindergartenWorkReviewAggregate w " + - "LEFT JOIN FETCH k.kindergartenInternshipReviewAggregate i " + - "WHERE k.regionId = :regionId " + - "AND k.subRegionId = :subRegionId") - List findAllByRegionIdAndSubRegionIdWithFetch(@Param("regionId") Long regionId, @Param("subRegionId") Long subRegionId); + @Query( + "SELECT k FROM Kindergarten k " + + "LEFT JOIN FETCH k.kindergartenWorkReviewAggregate w " + + "LEFT JOIN FETCH k.kindergartenInternshipReviewAggregate i " + + "WHERE k.regionId = :regionId " + + "AND k.subRegionId = :subRegionId") + List findAllByRegionIdAndSubRegionIdWithFetch( + @Param("regionId") Long regionId, @Param("subRegionId") Long subRegionId); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenWorkReviewAggregateRepository.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenWorkReviewAggregateRepository.java index f9f0493..29154b6 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenWorkReviewAggregateRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/repository/KindergartenWorkReviewAggregateRepository.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface KindergartenWorkReviewAggregateRepository extends JpaRepository { - KindergartenWorkReviewAggregate findByKindergarten(Kindergarten kindergarten); +public interface KindergartenWorkReviewAggregateRepository + extends JpaRepository { + KindergartenWorkReviewAggregate findByKindergarten(Kindergarten kindergarten); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenInternshipReviewAggregateService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenInternshipReviewAggregateService.java index a68ced0..52a239e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenInternshipReviewAggregateService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenInternshipReviewAggregateService.java @@ -6,43 +6,50 @@ import com.onebyone.kindergarten.domain.kindergatens.entity.KindergartenInternshipReviewAggregate; import com.onebyone.kindergarten.domain.kindergatens.repository.KindergartenInternshipReviewAggregateRepository; import com.onebyone.kindergarten.global.enums.ReviewStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.math.BigDecimal; import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class KindergartenInternshipReviewAggregateService { - private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; - private final KindergartenInternshipReviewAggregateRepository kindergartenInternshipReviewAggregateRepository; + private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; + private final KindergartenInternshipReviewAggregateRepository + kindergartenInternshipReviewAggregateRepository; - public void updateOrCreateAggregate(Kindergarten kindergarten) { - List acceptedReviews = kindergartenInternshipReviewRepository.findByKindergartenAndReviewStatus(kindergarten, ReviewStatus.ACCEPTED); + public void updateOrCreateAggregate(Kindergarten kindergarten) { + List acceptedReviews = + kindergartenInternshipReviewRepository.findByKindergartenAndReviewStatus( + kindergarten, ReviewStatus.ACCEPTED); - if (acceptedReviews.isEmpty()) { - return; - } + if (acceptedReviews.isEmpty()) { + return; + } - int totalWorkEnvironmentScore = 0; - int totalLearningSupportScore = 0; - int totalInstructionTeacherScore = 0; + int totalWorkEnvironmentScore = 0; + int totalLearningSupportScore = 0; + int totalInstructionTeacherScore = 0; - for (KindergartenInternshipReview review : acceptedReviews) { - totalWorkEnvironmentScore += review.getWorkEnvironmentScore(); - totalLearningSupportScore += review.getLearningSupportScore(); - totalInstructionTeacherScore += review.getInstructionTeacherScore(); - } + for (KindergartenInternshipReview review : acceptedReviews) { + totalWorkEnvironmentScore += review.getWorkEnvironmentScore(); + totalLearningSupportScore += review.getLearningSupportScore(); + totalInstructionTeacherScore += review.getInstructionTeacherScore(); + } - int reviewCount = acceptedReviews.size(); + int reviewCount = acceptedReviews.size(); - BigDecimal avgWorkEnvironmentScore = BigDecimal.valueOf((double) totalWorkEnvironmentScore / reviewCount); - BigDecimal avgLearningSupportScore = BigDecimal.valueOf((double) totalLearningSupportScore / reviewCount); - BigDecimal avgInstructionTeacherScore = BigDecimal.valueOf((double) totalInstructionTeacherScore / reviewCount); + BigDecimal avgWorkEnvironmentScore = + BigDecimal.valueOf((double) totalWorkEnvironmentScore / reviewCount); + BigDecimal avgLearningSupportScore = + BigDecimal.valueOf((double) totalLearningSupportScore / reviewCount); + BigDecimal avgInstructionTeacherScore = + BigDecimal.valueOf((double) totalInstructionTeacherScore / reviewCount); - KindergartenInternshipReviewAggregate aggregate = kindergartenInternshipReviewAggregateRepository.findByKindergarten(kindergarten); + KindergartenInternshipReviewAggregate aggregate = + kindergartenInternshipReviewAggregateRepository.findByKindergarten(kindergarten); - aggregate.updateScoreAggregates(avgWorkEnvironmentScore, avgLearningSupportScore, avgInstructionTeacherScore); - } + aggregate.updateScoreAggregates( + avgWorkEnvironmentScore, avgLearningSupportScore, avgInstructionTeacherScore); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenService.java index d006209..5562607 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenService.java @@ -12,150 +12,161 @@ import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; import jakarta.transaction.Transactional; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; - -import java.math.BigDecimal; -import java.util.List; -import java.util.ArrayList; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class KindergartenService { - private final KindergartenRepository kindergartenRepository; - private final KindergartenInternshipReviewAggregateRepository kindergartenInternshipReviewAggregateRepository; - private final KindergartenWorkReviewAggregateRepository kindergartenWorkReviewAggregateRepository; - private final SubRegionRepository subRegionRepository; - - /// 유치원 정보 저장 - @Transactional - public Boolean saveAll(List kindergartenDTOs) { - List updatedKindergartens = new ArrayList<>(); - - for (KindergartenDTO dto : kindergartenDTOs) { - // 유치원 이름과 주소로 검색 - kindergartenRepository.findByNameAndAddress(dto.getName(), dto.getAddress()) - .ifPresentOrElse( - // 존재하는 경우 - 정보 업데이트 - existingKindergarten -> { - existingKindergarten.update(dto); - updatedKindergartens.add(existingKindergarten); - }, - // 존재하지 않는 경우 - 새로 생성 - () -> { - Kindergarten newKindergarten = convertToEntity(dto); - updatedKindergartens.add(kindergartenRepository.save(newKindergarten)); - - KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate = KindergartenInternshipReviewAggregate.builder() - .instructionTeacherScoreAggregate(BigDecimal.valueOf(0.0)) - .workEnvironmentScoreAggregate(BigDecimal.valueOf(0.0)) - .learningSupportScoreAggregate(BigDecimal.valueOf(0.0)) - .kindergarten(newKindergarten) - .build(); - - kindergartenInternshipReviewAggregateRepository.save(kindergartenInternshipReviewAggregate); - newKindergarten.updateInternshipReviewAggregate(kindergartenInternshipReviewAggregate); - - KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate = KindergartenWorkReviewAggregate.builder() - .benefitAndSalaryScoreAggregate(BigDecimal.valueOf(0.0)) - .workLiftBalanceScoreAggregate(BigDecimal.valueOf(0.0)) - .customerScoreAggregate(BigDecimal.valueOf(0.0)) - .managerScoreAggregate(BigDecimal.valueOf(0.0)) - .workEnvironmentScoreAggregate(BigDecimal.valueOf(0.0)) - .kindergarten(newKindergarten) - .build(); - - kindergartenWorkReviewAggregateRepository.save(kindergartenWorkReviewAggregate); - newKindergarten.updateWorkReviewAggregate(kindergartenWorkReviewAggregate); - } - ); - } - return !updatedKindergartens.isEmpty(); - } - - /// 유치원 검색 - public Page searchKindergartens(KindergartenSearchDTO searchDTO, Pageable pageable) { - return kindergartenRepository.findBySearchCriteria(searchDTO, pageable) - .map(KindergartenResponseDTO::from); + private final KindergartenRepository kindergartenRepository; + private final KindergartenInternshipReviewAggregateRepository + kindergartenInternshipReviewAggregateRepository; + private final KindergartenWorkReviewAggregateRepository kindergartenWorkReviewAggregateRepository; + private final SubRegionRepository subRegionRepository; + + /// 유치원 정보 저장 + @Transactional + public Boolean saveAll(List kindergartenDTOs) { + List updatedKindergartens = new ArrayList<>(); + + for (KindergartenDTO dto : kindergartenDTOs) { + // 유치원 이름과 주소로 검색 + kindergartenRepository + .findByNameAndAddress(dto.getName(), dto.getAddress()) + .ifPresentOrElse( + // 존재하는 경우 - 정보 업데이트 + existingKindergarten -> { + existingKindergarten.update(dto); + updatedKindergartens.add(existingKindergarten); + }, + // 존재하지 않는 경우 - 새로 생성 + () -> { + Kindergarten newKindergarten = convertToEntity(dto); + updatedKindergartens.add(kindergartenRepository.save(newKindergarten)); + + KindergartenInternshipReviewAggregate kindergartenInternshipReviewAggregate = + KindergartenInternshipReviewAggregate.builder() + .instructionTeacherScoreAggregate(BigDecimal.valueOf(0.0)) + .workEnvironmentScoreAggregate(BigDecimal.valueOf(0.0)) + .learningSupportScoreAggregate(BigDecimal.valueOf(0.0)) + .kindergarten(newKindergarten) + .build(); + + kindergartenInternshipReviewAggregateRepository.save( + kindergartenInternshipReviewAggregate); + newKindergarten.updateInternshipReviewAggregate( + kindergartenInternshipReviewAggregate); + + KindergartenWorkReviewAggregate kindergartenWorkReviewAggregate = + KindergartenWorkReviewAggregate.builder() + .benefitAndSalaryScoreAggregate(BigDecimal.valueOf(0.0)) + .workLiftBalanceScoreAggregate(BigDecimal.valueOf(0.0)) + .customerScoreAggregate(BigDecimal.valueOf(0.0)) + .managerScoreAggregate(BigDecimal.valueOf(0.0)) + .workEnvironmentScoreAggregate(BigDecimal.valueOf(0.0)) + .kindergarten(newKindergarten) + .build(); + + kindergartenWorkReviewAggregateRepository.save(kindergartenWorkReviewAggregate); + newKindergarten.updateWorkReviewAggregate(kindergartenWorkReviewAggregate); + }); } - - /// 유치원 상세 조회 - public KindergartenResponseDTO findById(Long id) { - Kindergarten kindergarten = kindergartenRepository.findWithReviewsById(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)); - return KindergartenResponseDTO.from(kindergarten); - } - - - /// 주변 유치원 조회 - public List getNearbyKindergarten( - double latitude, - double longitude, - double radiusKm - ) { - List nearbyKindergartens = kindergartenRepository.findNearbyKindergartenEntities(latitude, longitude, radiusKm); - return nearbyKindergartens.stream() - .map(KindergartenResponseDTO::from) - .toList(); + return !updatedKindergartens.isEmpty(); + } + + /// 유치원 검색 + public Page searchKindergartens( + KindergartenSearchDTO searchDTO, Pageable pageable) { + return kindergartenRepository + .findBySearchCriteria(searchDTO, pageable) + .map(KindergartenResponseDTO::from); + } + + /// 유치원 상세 조회 + public KindergartenResponseDTO findById(Long id) { + Kindergarten kindergarten = + kindergartenRepository + .findWithReviewsById(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)); + return KindergartenResponseDTO.from(kindergarten); + } + + /// 주변 유치원 조회 + public List getNearbyKindergarten( + double latitude, double longitude, double radiusKm) { + List nearbyKindergartens = + kindergartenRepository.findNearbyKindergartenEntities(latitude, longitude, radiusKm); + return nearbyKindergartens.stream().map(KindergartenResponseDTO::from).toList(); + } + + public Kindergarten getKindergartenById(Long id) { + return kindergartenRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)); + } + + public Kindergarten getKindergartenByName(String kindergartenName) { + return kindergartenRepository + .findByName(kindergartenName) + .orElseThrow(() -> new BusinessException(ErrorCodes.KINDERGARTEN_NOT_FOUND)); + } + + /// DTO -> Entity 변환 + private Kindergarten convertToEntity(KindergartenDTO dto) { + return Kindergarten.builder() + .name(dto.getName()) + .establishment(dto.getEstablishment()) + .establishmentDate(dto.getEstablishmentDate()) + .openDate(dto.getOpenDate()) + .address(dto.getAddress()) + .homepage(dto.getHomepage()) + .phoneNumber(dto.getPhoneNumber()) + .classCount3(dto.getClassCount3()) + .classCount4(dto.getClassCount4()) + .classCount5(dto.getClassCount5()) + .pupilCount3(dto.getPupilCount3()) + .pupilCount4(dto.getPupilCount4()) + .pupilCount5(dto.getPupilCount5()) + .mixPupilCount(dto.getMixPupilCount()) + .specialPupilCount(dto.getSpecialPupilCount()) + .latitude(dto.getLatitude()) + .longitude(dto.getLongitude()) + .build(); + } + + public KindergartenSimpleDTO getSimpleKindergarten(Long id) { + return kindergartenRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)) + .toSimpleDTO(); + } + + public List getKindergartenByRegion(Long regionId, Long subRegionId) { + if (regionId == null) { + throw new BusinessException(ErrorCodes.REGION_NOT_FOUND_EXCEPTION); } - public Kindergarten getKindergartenById(Long id) { - return kindergartenRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)); - } + SubRegion subRegion = subRegionRepository.findBySubRegionId(subRegionId); - public Kindergarten getKindergartenByName(String kindergartenName) { - return kindergartenRepository.findByName(kindergartenName).orElseThrow(() -> new BusinessException(ErrorCodes.KINDERGARTEN_NOT_FOUND)); + if (subRegion == null) { + return kindergartenRepository.findAllByRegionIdWithFetch(regionId).stream() + .map(KindergartenResponseDTO::from) + .toList(); } - /// DTO -> Entity 변환 - private Kindergarten convertToEntity(KindergartenDTO dto) { - return Kindergarten.builder() - .name(dto.getName()) - .establishment(dto.getEstablishment()) - .establishmentDate(dto.getEstablishmentDate()) - .openDate(dto.getOpenDate()) - .address(dto.getAddress()) - .homepage(dto.getHomepage()) - .phoneNumber(dto.getPhoneNumber()) - .classCount3(dto.getClassCount3()) - .classCount4(dto.getClassCount4()) - .classCount5(dto.getClassCount5()) - .pupilCount3(dto.getPupilCount3()) - .pupilCount4(dto.getPupilCount4()) - .pupilCount5(dto.getPupilCount5()) - .mixPupilCount(dto.getMixPupilCount()) - .specialPupilCount(dto.getSpecialPupilCount()) - .latitude(dto.getLatitude()) - .longitude(dto.getLongitude()) - .build(); + if (!regionId.equals(subRegion.getRegionId())) { + throw new BusinessException(ErrorCodes.REGION_NOT_MATCHED_WITH_SUB_REGION); } - public KindergartenSimpleDTO getSimpleKindergarten(Long id) { - return kindergartenRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCodes.ENTITY_NOT_FOUND_EXCEPTION)).toSimpleDTO();} - - public List getKindergartenByRegion(Long regionId, Long subRegionId) { - if (regionId == null) { - throw new BusinessException(ErrorCodes.REGION_NOT_FOUND_EXCEPTION); - } - - SubRegion subRegion = subRegionRepository.findBySubRegionId(subRegionId); - - if (subRegion == null) { - return kindergartenRepository.findAllByRegionIdWithFetch(regionId).stream().map( - KindergartenResponseDTO::from - ).toList(); - } - - if (!regionId.equals(subRegion.getRegionId())) { - throw new BusinessException(ErrorCodes.REGION_NOT_MATCHED_WITH_SUB_REGION); - } - - return kindergartenRepository.findAllByRegionIdAndSubRegionIdWithFetch(regionId, subRegionId).stream().map( - KindergartenResponseDTO::from - ).toList(); - } + return kindergartenRepository + .findAllByRegionIdAndSubRegionIdWithFetch(regionId, subRegionId) + .stream() + .map(KindergartenResponseDTO::from) + .toList(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenWorkReviewAggregateService.java b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenWorkReviewAggregateService.java index 5d55518..b68497b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenWorkReviewAggregateService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/kindergatens/service/KindergartenWorkReviewAggregateService.java @@ -6,48 +6,51 @@ import com.onebyone.kindergarten.domain.kindergatens.entity.KindergartenWorkReviewAggregate; import com.onebyone.kindergarten.domain.kindergatens.repository.KindergartenWorkReviewAggregateRepository; import com.onebyone.kindergarten.global.enums.ReviewStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.math.BigDecimal; import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class KindergartenWorkReviewAggregateService { - private final KindergartenWorkReviewRepository workReviewRepository; - private final KindergartenWorkReviewAggregateRepository workReviewAggregateRepository; - - public void updateOrCreateAggregate(Kindergarten kindergarten) { - List acceptedReviews = workReviewRepository.findByKindergartenAndReviewStatus(kindergarten, ReviewStatus.ACCEPTED); - - if (acceptedReviews.isEmpty()) { - return; - } - - int totalBenefitAndSalary = 0; - int totalWorkLifeBalance = 0; - int totalWorkEnvironment = 0; - int totalManager = 0; - int totalCustomer = 0; - - for (KindergartenWorkReview review : acceptedReviews) { - totalBenefitAndSalary += review.getBenefitAndSalaryScore(); - totalWorkLifeBalance += review.getWorkLifeBalanceScore(); - totalWorkEnvironment += review.getWorkEnvironmentScore(); - totalManager += review.getManagerScore(); - totalCustomer += review.getCustomerScore(); - } - - int reviewCount = acceptedReviews.size(); - - BigDecimal avgBenefitAndSalary = BigDecimal.valueOf((double) totalBenefitAndSalary / reviewCount); - BigDecimal avgWorkLifeBalance = BigDecimal.valueOf((double) totalWorkLifeBalance / reviewCount); - BigDecimal avgWorkEnvironment = BigDecimal.valueOf((double) totalWorkEnvironment / reviewCount); - BigDecimal avgManager = BigDecimal.valueOf((double) totalManager / reviewCount); - BigDecimal avgCustomer = BigDecimal.valueOf((double) totalCustomer / reviewCount); - - KindergartenWorkReviewAggregate aggregate = workReviewAggregateRepository.findByKindergarten(kindergarten); - aggregate.updateScoreAggregates(avgBenefitAndSalary, avgWorkLifeBalance, avgWorkEnvironment, avgManager, avgCustomer); + private final KindergartenWorkReviewRepository workReviewRepository; + private final KindergartenWorkReviewAggregateRepository workReviewAggregateRepository; + + public void updateOrCreateAggregate(Kindergarten kindergarten) { + List acceptedReviews = + workReviewRepository.findByKindergartenAndReviewStatus(kindergarten, ReviewStatus.ACCEPTED); + + if (acceptedReviews.isEmpty()) { + return; + } + + int totalBenefitAndSalary = 0; + int totalWorkLifeBalance = 0; + int totalWorkEnvironment = 0; + int totalManager = 0; + int totalCustomer = 0; + + for (KindergartenWorkReview review : acceptedReviews) { + totalBenefitAndSalary += review.getBenefitAndSalaryScore(); + totalWorkLifeBalance += review.getWorkLifeBalanceScore(); + totalWorkEnvironment += review.getWorkEnvironmentScore(); + totalManager += review.getManagerScore(); + totalCustomer += review.getCustomerScore(); } + + int reviewCount = acceptedReviews.size(); + + BigDecimal avgBenefitAndSalary = + BigDecimal.valueOf((double) totalBenefitAndSalary / reviewCount); + BigDecimal avgWorkLifeBalance = BigDecimal.valueOf((double) totalWorkLifeBalance / reviewCount); + BigDecimal avgWorkEnvironment = BigDecimal.valueOf((double) totalWorkEnvironment / reviewCount); + BigDecimal avgManager = BigDecimal.valueOf((double) totalManager / reviewCount); + BigDecimal avgCustomer = BigDecimal.valueOf((double) totalCustomer / reviewCount); + + KindergartenWorkReviewAggregate aggregate = + workReviewAggregateRepository.findByKindergarten(kindergarten); + aggregate.updateScoreAggregates( + avgBenefitAndSalary, avgWorkLifeBalance, avgWorkEnvironment, avgManager, avgCustomer); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/controller/AdminNoticeController.java b/src/main/java/com/onebyone/kindergarten/domain/notice/controller/AdminNoticeController.java index c653d8f..1952f01 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/controller/AdminNoticeController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/controller/AdminNoticeController.java @@ -8,39 +8,35 @@ 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 org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/admin/notice") @Tag(name = "공지사항 관리", description = "공지사항 관리 API (관리자용)") public class AdminNoticeController { - - private final NoticeService noticeService; - private final NoticeFacade noticeFacade; - @GetMapping - @Operation(summary = "전체 공지사항 목록 조회", description = "모든 공지사항 목록을 조회합니다.") - public ResponseDto> getAllNotices() { - return ResponseDto.success(noticeService.getAllNotices()); - } + private final NoticeService noticeService; + private final NoticeFacade noticeFacade; + + @GetMapping + @Operation(summary = "전체 공지사항 목록 조회", description = "모든 공지사항 목록을 조회합니다.") + public ResponseDto> getAllNotices() { + return ResponseDto.success(noticeService.getAllNotices()); + } - @PostMapping - @Operation(summary = "공지사항 작성", description = "새로운 공지사항을 작성합니다.") - public ResponseDto createNotice( - @Valid @RequestBody NoticeCreateRequestDTO request - ) { - return ResponseDto.success(noticeFacade.createNotice(request)); - } + @PostMapping + @Operation(summary = "공지사항 작성", description = "새로운 공지사항을 작성합니다.") + public ResponseDto createNotice( + @Valid @RequestBody NoticeCreateRequestDTO request) { + return ResponseDto.success(noticeFacade.createNotice(request)); + } - @PatchMapping("/{noticeId}/public-status") - @Operation(summary = "공지사항 공개 여부 변경", description = "공지사항의 공개 여부를 변경합니다.") - public ResponseDto togglePublicStatus( - @PathVariable Long noticeId - ) { - return ResponseDto.success(noticeService.togglePublicStatus(noticeId)); - } -} \ No newline at end of file + @PatchMapping("/{noticeId}/public-status") + @Operation(summary = "공지사항 공개 여부 변경", description = "공지사항의 공개 여부를 변경합니다.") + public ResponseDto togglePublicStatus(@PathVariable Long noticeId) { + return ResponseDto.success(noticeService.togglePublicStatus(noticeId)); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/controller/NoticeController.java b/src/main/java/com/onebyone/kindergarten/domain/notice/controller/NoticeController.java index f2fa428..efeeb26 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/controller/NoticeController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/controller/NoticeController.java @@ -5,24 +5,23 @@ import com.onebyone.kindergarten.global.common.ResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/notice") @Tag(name = "공지사항", description = "공지사항 API") public class NoticeController { - - private final NoticeService noticeService; - @GetMapping - @Operation(summary = "공개 공지사항 목록 조회", description = "공개된 공지사항 목록을 조회합니다.") - public ResponseDto> getPublicNotices() { - return ResponseDto.success(noticeService.getPublicNotices()); - } + private final NoticeService noticeService; + + @GetMapping + @Operation(summary = "공개 공지사항 목록 조회", description = "공개된 공지사항 목록을 조회합니다.") + public ResponseDto> getPublicNotices() { + return ResponseDto.success(noticeService.getPublicNotices()); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/dto/request/NoticeCreateRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/notice/dto/request/NoticeCreateRequestDTO.java index f79391f..c040f35 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/dto/request/NoticeCreateRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/dto/request/NoticeCreateRequestDTO.java @@ -8,31 +8,30 @@ @Getter @Builder public class NoticeCreateRequestDTO { - @NotBlank(message = "제목은 필수입니다.") - @Size(max = 100, message = "제목은 100자를 초과할 수 없습니다.") - private String title; + @NotBlank(message = "제목은 필수입니다.") + @Size(max = 100, message = "제목은 100자를 초과할 수 없습니다.") + private String title; - @NotBlank(message = "내용은 필수입니다.") - @Size(max = 2000, message = "내용은 2000자를 초과할 수 없습니다.") - private String content; + @NotBlank(message = "내용은 필수입니다.") + @Size(max = 2000, message = "내용은 2000자를 초과할 수 없습니다.") + private String content; - @Builder.Default - private Boolean isPushSend = false; - - @Builder.Default - private Boolean isPublic = true; + @Builder.Default private Boolean isPushSend = false; - // JPQL 생성자 - public NoticeCreateRequestDTO() { - this.isPushSend = false; - this.isPublic = true; - } + @Builder.Default private Boolean isPublic = true; - @Builder - public NoticeCreateRequestDTO(String title, String content, Boolean isPushSend, Boolean isPublic) { - this.title = title; - this.content = content; - this.isPushSend = isPushSend != null ? isPushSend : false; - this.isPublic = isPublic != null ? isPublic : true; - } -} \ No newline at end of file + // JPQL 생성자 + public NoticeCreateRequestDTO() { + this.isPushSend = false; + this.isPublic = true; + } + + @Builder + public NoticeCreateRequestDTO( + String title, String content, Boolean isPushSend, Boolean isPublic) { + this.title = title; + this.content = content; + this.isPushSend = isPushSend != null ? isPushSend : false; + this.isPublic = isPublic != null ? isPublic : true; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/dto/response/NoticeResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/notice/dto/response/NoticeResponseDTO.java index 1a3c9f2..cfa2517 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/dto/response/NoticeResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/dto/response/NoticeResponseDTO.java @@ -1,48 +1,50 @@ package com.onebyone.kindergarten.domain.notice.dto.response; import com.onebyone.kindergarten.domain.notice.entity.Notice; -import lombok.Getter; - import java.time.LocalDateTime; +import lombok.Getter; @Getter public class NoticeResponseDTO { - private final Long id; - private final String title; - private final String content; - private final boolean isPushSend; - private final boolean isPublic; - private final LocalDateTime createdAt; - private final LocalDateTime updatedAt; + private final Long id; + private final String title; + private final String content; + private final boolean isPushSend; + private final boolean isPublic; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; - // JPQL 생성자 - public NoticeResponseDTO( - Long id, String title, String content, - boolean isPushSend, boolean isPublic, - LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.title = title; - this.content = content; - this.isPushSend = isPushSend; - this.isPublic = isPublic; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } + // JPQL 생성자 + public NoticeResponseDTO( + Long id, + String title, + String content, + boolean isPushSend, + boolean isPublic, + LocalDateTime createdAt, + LocalDateTime updatedAt) { + this.id = id; + this.title = title; + this.content = content; + this.isPushSend = isPushSend; + this.isPublic = isPublic; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } - // Entity 변환 생성자 - public NoticeResponseDTO(Notice notice) { - this( - notice.getId(), - notice.getTitle(), - notice.getContent(), - notice.isPushSend(), - notice.isPublic(), - notice.getCreatedAt(), - notice.getUpdatedAt() - ); - } + // Entity 변환 생성자 + public NoticeResponseDTO(Notice notice) { + this( + notice.getId(), + notice.getTitle(), + notice.getContent(), + notice.isPushSend(), + notice.isPublic(), + notice.getCreatedAt(), + notice.getUpdatedAt()); + } - public static NoticeResponseDTO from(Notice notice) { - return new NoticeResponseDTO(notice); - } -} \ No newline at end of file + public static NoticeResponseDTO from(Notice notice) { + return new NoticeResponseDTO(notice); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/entity/Notice.java b/src/main/java/com/onebyone/kindergarten/domain/notice/entity/Notice.java index ac03e7b..f52ec02 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/entity/Notice.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/entity/Notice.java @@ -2,43 +2,42 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor public class Notice extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 공지 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 공지 코드 - @Column(nullable = false, length = 100) - private String title; // 공지 제목 + @Column(nullable = false, length = 100) + private String title; // 공지 제목 - @Column(nullable = false, length = 2000) - private String content; // 공지 내용 + @Column(nullable = false, length = 2000) + private String content; // 공지 내용 - @Column(name = "is_push_send") - private boolean isPushSend; // 푸시 발송 여부 + @Column(name = "is_push_send") + private boolean isPushSend; // 푸시 발송 여부 - @Column(name = "is_public") - private boolean isPublic; // 공지 공개 여부 + @Column(name = "is_public") + private boolean isPublic; // 공지 공개 여부 - @Builder - public Notice(String title, String content, boolean isPushSend, boolean isPublic) { - this.title = title; - this.content = content; - this.isPushSend = isPushSend; - this.isPublic = isPublic; - } + @Builder + public Notice(String title, String content, boolean isPushSend, boolean isPublic) { + this.title = title; + this.content = content; + this.isPushSend = isPushSend; + this.isPublic = isPublic; + } - public void togglePublicStatus() { - this.isPublic = !this.isPublic; - this.updatedAt = LocalDateTime.now(); - } + public void togglePublicStatus() { + this.isPublic = !this.isPublic; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/repository/NoticeRepository.java b/src/main/java/com/onebyone/kindergarten/domain/notice/repository/NoticeRepository.java index da3d64c..b7c9ed9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/repository/NoticeRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/repository/NoticeRepository.java @@ -1,26 +1,27 @@ package com.onebyone.kindergarten.domain.notice.repository; -import com.onebyone.kindergarten.domain.notice.entity.Notice; import com.onebyone.kindergarten.domain.notice.dto.response.NoticeResponseDTO; +import com.onebyone.kindergarten.domain.notice.entity.Notice; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public interface NoticeRepository extends JpaRepository { - - @Query("SELECT new com.onebyone.kindergarten.domain.notice.dto.response.NoticeResponseDTO(" + - "n.id, n.title, n.content, n.isPushSend, n.isPublic, n.createdAt, n.updatedAt) " + - "FROM Notice n " + - "WHERE n.isPublic = true " + - "ORDER BY n.createdAt DESC") - List findPublicNoticeDtos(); - - @Query("SELECT new com.onebyone.kindergarten.domain.notice.dto.response.NoticeResponseDTO(" + - "n.id, n.title, n.content, n.isPushSend, n.isPublic, n.createdAt, n.updatedAt) " + - "FROM Notice n " + - "ORDER BY n.createdAt DESC") - List findAllNoticeDtos(); + + @Query( + "SELECT new com.onebyone.kindergarten.domain.notice.dto.response.NoticeResponseDTO(" + + "n.id, n.title, n.content, n.isPushSend, n.isPublic, n.createdAt, n.updatedAt) " + + "FROM Notice n " + + "WHERE n.isPublic = true " + + "ORDER BY n.createdAt DESC") + List findPublicNoticeDtos(); + + @Query( + "SELECT new com.onebyone.kindergarten.domain.notice.dto.response.NoticeResponseDTO(" + + "n.id, n.title, n.content, n.isPushSend, n.isPublic, n.createdAt, n.updatedAt) " + + "FROM Notice n " + + "ORDER BY n.createdAt DESC") + List findAllNoticeDtos(); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/notice/service/NoticeService.java b/src/main/java/com/onebyone/kindergarten/domain/notice/service/NoticeService.java index 743aa7d..8e66323 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/notice/service/NoticeService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/notice/service/NoticeService.java @@ -6,57 +6,58 @@ import com.onebyone.kindergarten.domain.notice.repository.NoticeRepository; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class NoticeService { - private final NoticeRepository noticeRepository; - - /// 공개된 공지사항 조회 - public List getPublicNotices() { - return noticeRepository.findPublicNoticeDtos(); - } - - /// 관리자용 전체 공지사항 조회 - public List getAllNotices() { - return noticeRepository.findAllNoticeDtos(); - } - - /// 공지사항 생성 - @Transactional - public NoticeResponseDTO createNotice(NoticeCreateRequestDTO dto) { - - // Entity 변환 - Notice notice = Notice.builder() - .title(dto.getTitle()) - .content(dto.getContent()) - .isPushSend(dto.getIsPushSend()) - .isPublic(dto.getIsPublic()) - .build(); - - // 공지사항 저장 - noticeRepository.save(notice); - return new NoticeResponseDTO(notice); - } - - /// 공지사항 공개 여부 변경 - @Transactional - public NoticeResponseDTO togglePublicStatus(Long noticeId) { - - // 공지사항 조회 - Notice notice = noticeRepository.findById(noticeId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_NOTICE)); - - // 공개 여부 토글 - notice.togglePublicStatus(); - return new NoticeResponseDTO(notice); - } - + private final NoticeRepository noticeRepository; + + /// 공개된 공지사항 조회 + public List getPublicNotices() { + return noticeRepository.findPublicNoticeDtos(); + } + + /// 관리자용 전체 공지사항 조회 + public List getAllNotices() { + return noticeRepository.findAllNoticeDtos(); + } + + /// 공지사항 생성 + @Transactional + public NoticeResponseDTO createNotice(NoticeCreateRequestDTO dto) { + + // Entity 변환 + Notice notice = + Notice.builder() + .title(dto.getTitle()) + .content(dto.getContent()) + .isPushSend(dto.getIsPushSend()) + .isPublic(dto.getIsPublic()) + .build(); + + // 공지사항 저장 + noticeRepository.save(notice); + return new NoticeResponseDTO(notice); + } + + /// 공지사항 공개 여부 변경 + @Transactional + public NoticeResponseDTO togglePublicStatus(Long noticeId) { + + // 공지사항 조회 + Notice notice = + noticeRepository + .findById(noticeId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_NOTICE)); + + // 공개 여부 토글 + notice.togglePublicStatus(); + return new NoticeResponseDTO(notice); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java index e8cde89..25d852c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/controller/PushNotificationController.java @@ -6,81 +6,77 @@ import com.onebyone.kindergarten.global.common.ResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/notification") @RequiredArgsConstructor @Tag(name = "푸시 알림", description = "푸시 알림 API") public class PushNotificationController { - private final PushNotificationService pushNotificationService; + private final PushNotificationService pushNotificationService; - @PostMapping("/save") - @Operation(summary = "푸시 알림 저장(for 배치)", description = "알림을 전송합니다.") - public ResponseDto sendNotification( - @RequestBody PushNotificationRequestDTO requestDTO - ) { - pushNotificationService.savePushNotification(requestDTO); - return ResponseDto.success("알림이 성공적으로 전송되었습니다."); - } + @PostMapping("/save") + @Operation(summary = "푸시 알림 저장(for 배치)", description = "알림을 전송합니다.") + public ResponseDto sendNotification(@RequestBody PushNotificationRequestDTO requestDTO) { + pushNotificationService.savePushNotification(requestDTO); + return ResponseDto.success("알림이 성공적으로 전송되었습니다."); + } - @GetMapping("/user/{userId}") - @Operation(summary = "사용자의 모든 알림 조회", description = "사용자의 모든 알림을 조회합니다.") - public ResponseDto> getUserNotifications( - @PathVariable Long userId - ) { - List notifications = pushNotificationService.getUserNotifications(userId); - return ResponseDto.success(notifications); - } + @GetMapping("/user/{userId}") + @Operation(summary = "사용자의 모든 알림 조회", description = "사용자의 모든 알림을 조회합니다.") + public ResponseDto> getUserNotifications( + @PathVariable Long userId) { + List notifications = + pushNotificationService.getUserNotifications(userId); + return ResponseDto.success(notifications); + } - @GetMapping("/my") - @Operation(summary = "현재 로그인한 사용자의 알림 조회", description = "현재 로그인한 사용자의 알림을 조회합니다.") - public ResponseDto> getMyNotifications( - @AuthenticationPrincipal UserDetails userDetails - ) { - List notifications = pushNotificationService.getUserNotificationByUserDetails(Long.valueOf(userDetails.getUsername())); - return ResponseDto.success(notifications); - } + @GetMapping("/my") + @Operation(summary = "현재 로그인한 사용자의 알림 조회", description = "현재 로그인한 사용자의 알림을 조회합니다.") + public ResponseDto> getMyNotifications( + @AuthenticationPrincipal UserDetails userDetails) { + List notifications = + pushNotificationService.getUserNotificationByUserDetails( + Long.valueOf(userDetails.getUsername())); + return ResponseDto.success(notifications); + } - @GetMapping("/my/unread") - @Operation(summary = "현재 로그인한 사용자의 읽지 않은 알림 조회", description = "현재 로그인한 사용자의 읽지 않은 알림을 조회합니다.") - public ResponseDto> getMyUnreadNotifications( - @AuthenticationPrincipal UserDetails userDetails - ) { - List notifications = pushNotificationService.getUnreadNotificationsByUserDetails(Long.valueOf(userDetails.getUsername())); - return ResponseDto.success(notifications); - } + @GetMapping("/my/unread") + @Operation(summary = "현재 로그인한 사용자의 읽지 않은 알림 조회", description = "현재 로그인한 사용자의 읽지 않은 알림을 조회합니다.") + public ResponseDto> getMyUnreadNotifications( + @AuthenticationPrincipal UserDetails userDetails) { + List notifications = + pushNotificationService.getUnreadNotificationsByUserDetails( + Long.valueOf(userDetails.getUsername())); + return ResponseDto.success(notifications); + } - @GetMapping("/my/unread/count") - @Operation(summary = "현재 로그인한 사용자의 읽지 않은 알림 개수 조회", description = "현재 로그인한 사용자의 읽지 않은 알림 개수를 조회합니다.") - public ResponseDto getUnreadCount( - @AuthenticationPrincipal UserDetails userDetails - ) { - long count = pushNotificationService.countUnreadNotifications(Long.valueOf(userDetails.getUsername())); - return ResponseDto.success(count); - } + @GetMapping("/my/unread/count") + @Operation( + summary = "현재 로그인한 사용자의 읽지 않은 알림 개수 조회", + description = "현재 로그인한 사용자의 읽지 않은 알림 개수를 조회합니다.") + public ResponseDto getUnreadCount(@AuthenticationPrincipal UserDetails userDetails) { + long count = + pushNotificationService.countUnreadNotifications(Long.valueOf(userDetails.getUsername())); + return ResponseDto.success(count); + } - @PatchMapping("/{notificationId}/read") - @Operation(summary = "알림 읽음 표시", description = "알림을 읽음 처리합니다.") - public ResponseDto markAsRead( - @PathVariable Long notificationId - ) { - pushNotificationService.markAsRead(notificationId); - return ResponseDto.success("알림이 읽음 처리되었습니다."); - } + @PatchMapping("/{notificationId}/read") + @Operation(summary = "알림 읽음 표시", description = "알림을 읽음 처리합니다.") + public ResponseDto markAsRead(@PathVariable Long notificationId) { + pushNotificationService.markAsRead(notificationId); + return ResponseDto.success("알림이 읽음 처리되었습니다."); + } - @PatchMapping("/my/read-all") - @Operation(summary = "모든 알림 읽음 표시", description = "모든 알림을 읽음 처리합니다.") - public ResponseDto markAllAsRead( - @AuthenticationPrincipal UserDetails userDetails - ) { - pushNotificationService.markAllAsRead(Long.valueOf(userDetails.getUsername())); - return ResponseDto.success("모든 알림이 읽음 처리되었습니다."); - } -} \ No newline at end of file + @PatchMapping("/my/read-all") + @Operation(summary = "모든 알림 읽음 표시", description = "모든 알림을 읽음 처리합니다.") + public ResponseDto markAllAsRead(@AuthenticationPrincipal UserDetails userDetails) { + pushNotificationService.markAllAsRead(Long.valueOf(userDetails.getUsername())); + return ResponseDto.success("모든 알림이 읽음 처리되었습니다."); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationRequestDTO.java index 1434dd0..2572ccf 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationRequestDTO.java @@ -11,13 +11,13 @@ @NoArgsConstructor @AllArgsConstructor public class PushNotificationRequestDTO { - private Long userId; - private String title; - private String message; - private NotificationType type; - private Long targetId; // 알림 클릭 시 이동할 대상의 ID (예: 게시글 ID, 리뷰 ID 등) - - /// 그룹화 - private String groupKey; - private Integer groupCount; -} \ No newline at end of file + private Long userId; + private String title; + private String message; + private NotificationType type; + private Long targetId; // 알림 클릭 시 이동할 대상의 ID (예: 게시글 ID, 리뷰 ID 등) + + /// 그룹화 + private String groupKey; + private Integer groupCount; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationResponseDTO.java index 4828f27..31eb3ee 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/dto/PushNotificationResponseDTO.java @@ -2,39 +2,38 @@ import com.onebyone.kindergarten.domain.pushNotification.entity.PushNotification; import com.onebyone.kindergarten.domain.pushNotification.enums.NotificationType; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Builder @Getter public class PushNotificationResponseDTO { - private Long id; - private Long userId; - private String title; - private String message; - private NotificationType type; - private Long targetId; - private Boolean isRead; - private LocalDateTime createdAt; - - /// 그룹화 - private String groupKey; - private Integer groupCount; - - public static PushNotificationResponseDTO from(PushNotification entity) { - return PushNotificationResponseDTO.builder() - .id(entity.getId()) - .userId(entity.getUser().getId()) - .title(entity.getTitle()) - .message(entity.getMessage()) - .type(entity.getType()) - .targetId(entity.getTargetId()) - .isRead(entity.getIsRead()) - .createdAt(entity.getCreatedAt()) - .groupKey(entity.getGroupKey()) - .groupCount(entity.getGroupCount()) - .build(); - } -} \ No newline at end of file + private Long id; + private Long userId; + private String title; + private String message; + private NotificationType type; + private Long targetId; + private Boolean isRead; + private LocalDateTime createdAt; + + /// 그룹화 + private String groupKey; + private Integer groupCount; + + public static PushNotificationResponseDTO from(PushNotification entity) { + return PushNotificationResponseDTO.builder() + .id(entity.getId()) + .userId(entity.getUser().getId()) + .title(entity.getTitle()) + .message(entity.getMessage()) + .type(entity.getType()) + .targetId(entity.getTargetId()) + .isRead(entity.getIsRead()) + .createdAt(entity.getCreatedAt()) + .groupKey(entity.getGroupKey()) + .groupCount(entity.getGroupCount()) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/entity/PushNotification.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/entity/PushNotification.java index fc417d2..22e6653 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/entity/PushNotification.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/entity/PushNotification.java @@ -4,74 +4,70 @@ import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity(name = "push_notification") @Getter @NoArgsConstructor @AllArgsConstructor @Builder public class PushNotification extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 알림 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 수신자 - - @Column(name = "fcm_token") - private String fcmToken; // FCM 토큰 (전송 시점의 토큰) - - @Column(nullable = false) - private String title; // 제목 - - @Column(nullable = false) - private String message; // 내용 - - @Enumerated(EnumType.STRING) - private NotificationType type; // 알림 타입 - 리뷰, 댓글, 좋아요, 시스템 - - @Builder.Default - private Boolean isRead = false; // 읽음 여부 - - @Builder.Default - private Boolean isSent = false; // 전송 여부 - - private Long targetId; // 알림 클릭 시 이동할 대상의 ID - - /// 그룹화 - private String groupKey; // 동일한 그룹의 알림 식별 키 - - @Builder.Default - private Integer groupCount = 1; // 그룹화된 알림 개수 - - /// 알림 읽음 처리 - public void markAsRead() { - this.isRead = true; - this.updatedAt = LocalDateTime.now(); - } - - /// 알림 전송 처리 - public void markAsSent() { - this.isSent = true; - this.updatedAt = LocalDateTime.now(); - } - - /// 그룹 카운트 증가 - public void increaseGroupCount() { - this.groupCount++; - this.updatedAt = LocalDateTime.now(); - } - - /// 그룹 메시지 업데이트 - public void updateGroupMessage(String newMessage) { - this.message = newMessage; - this.updatedAt = LocalDateTime.now(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 알림 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 수신자 + + @Column(name = "fcm_token") + private String fcmToken; // FCM 토큰 (전송 시점의 토큰) + + @Column(nullable = false) + private String title; // 제목 + + @Column(nullable = false) + private String message; // 내용 + + @Enumerated(EnumType.STRING) + private NotificationType type; // 알림 타입 - 리뷰, 댓글, 좋아요, 시스템 + + @Builder.Default private Boolean isRead = false; // 읽음 여부 + + @Builder.Default private Boolean isSent = false; // 전송 여부 + + private Long targetId; // 알림 클릭 시 이동할 대상의 ID + + /// 그룹화 + private String groupKey; // 동일한 그룹의 알림 식별 키 + + @Builder.Default private Integer groupCount = 1; // 그룹화된 알림 개수 + + /// 알림 읽음 처리 + public void markAsRead() { + this.isRead = true; + this.updatedAt = LocalDateTime.now(); + } + + /// 알림 전송 처리 + public void markAsSent() { + this.isSent = true; + this.updatedAt = LocalDateTime.now(); + } + + /// 그룹 카운트 증가 + public void increaseGroupCount() { + this.groupCount++; + this.updatedAt = LocalDateTime.now(); + } + + /// 그룹 메시지 업데이트 + public void updateGroupMessage(String newMessage) { + this.message = newMessage; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/enums/NotificationType.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/enums/NotificationType.java index af05c7c..099a4cc 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/enums/NotificationType.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/enums/NotificationType.java @@ -1,3 +1,9 @@ package com.onebyone.kindergarten.domain.pushNotification.enums; -public enum NotificationType { REVIEW, COMMENT, LIKE, SYSTEM, NOTICE } +public enum NotificationType { + REVIEW, + COMMENT, + LIKE, + SYSTEM, + NOTICE +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEvent.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEvent.java index 0dda20d..6b59656 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEvent.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEvent.java @@ -9,16 +9,13 @@ @Getter @RequiredArgsConstructor public class PushNotificationEvent { - private final Long userId; - private final String title; - private final String message; - private final NotificationType type; - private final Long targetId; + private final Long userId; + private final String title; + private final String message; + private final NotificationType type; + private final Long targetId; - /// 그룹화 - @Setter - private String groupKey; - @Setter - private Integer groupCount = 1; - -} \ No newline at end of file + /// 그룹화 + @Setter private String groupKey; + @Setter private Integer groupCount = 1; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventListener.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventListener.java index 6a5220b..04e7f0a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventListener.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventListener.java @@ -14,28 +14,29 @@ @Component @RequiredArgsConstructor public class PushNotificationEventListener { - private final PushNotificationService pushNotificationService; + private final PushNotificationService pushNotificationService; - /// 푸시 알림 이벤트 처리 메소드 - /// 이벤트를 수신하면 알림 정보를 DB에 저장하고, 이후 스케줄러가 주기적으로 미전송 알림을 발송 - @Async - @EventListener - public void handlePushNotificationEvent(PushNotificationEvent event) { - try { - log.debug("푸시 알림 이벤트 수신: userId={}, title={}", event.getUserId(), event.getTitle()); - PushNotificationRequestDTO requestDTO = PushNotificationRequestDTO.builder() - .userId(event.getUserId()) - .title(event.getTitle()) - .message(event.getMessage()) - .type(event.getType()) - .targetId(event.getTargetId()) - .groupKey(event.getGroupKey()) - .groupCount(event.getGroupCount()) - .build(); - pushNotificationService.savePushNotification(requestDTO); - log.info("푸시 알림 이벤트 처리 완료: userId={}, title={}", event.getUserId(), event.getTitle()); - } catch (Exception e) { - log.error("푸시 알림 이벤트 처리 중 오류 발생: {}", e.getMessage(), e); - } + /// 푸시 알림 이벤트 처리 메소드 + /// 이벤트를 수신하면 알림 정보를 DB에 저장하고, 이후 스케줄러가 주기적으로 미전송 알림을 발송 + @Async + @EventListener + public void handlePushNotificationEvent(PushNotificationEvent event) { + try { + log.debug("푸시 알림 이벤트 수신: userId={}, title={}", event.getUserId(), event.getTitle()); + PushNotificationRequestDTO requestDTO = + PushNotificationRequestDTO.builder() + .userId(event.getUserId()) + .title(event.getTitle()) + .message(event.getMessage()) + .type(event.getType()) + .targetId(event.getTargetId()) + .groupKey(event.getGroupKey()) + .groupCount(event.getGroupCount()) + .build(); + pushNotificationService.savePushNotification(requestDTO); + log.info("푸시 알림 이벤트 처리 완료: userId={}, title={}", event.getUserId(), event.getTitle()); + } catch (Exception e) { + log.error("푸시 알림 이벤트 처리 중 오류 발생: {}", e.getMessage(), e); } -} \ No newline at end of file + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventPublisher.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventPublisher.java index 1749f1d..b06231c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventPublisher.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/event/PushNotificationEventPublisher.java @@ -1,6 +1,5 @@ package com.onebyone.kindergarten.domain.pushNotification.event; -import com.onebyone.kindergarten.domain.pushNotification.enums.NotificationType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -12,12 +11,15 @@ @Component @RequiredArgsConstructor public class PushNotificationEventPublisher { - private final ApplicationEventPublisher eventPublisher; + private final ApplicationEventPublisher eventPublisher; - /// 푸시 알림 이벤트 발행 메소드 - public void publish(PushNotificationEvent event) { - log.debug("푸시 알림 이벤트 발행: userId={}, title={}, groupKey={}", - event.getUserId(), event.getTitle(), event.getGroupKey()); - eventPublisher.publishEvent(event); - } -} \ No newline at end of file + /// 푸시 알림 이벤트 발행 메소드 + public void publish(PushNotificationEvent event) { + log.debug( + "푸시 알림 이벤트 발행: userId={}, title={}, groupKey={}", + event.getUserId(), + event.getTitle(), + event.getGroupKey()); + eventPublisher.publishEvent(event); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/repository/PushNotificationRepository.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/repository/PushNotificationRepository.java index b9f22c3..79b8e1a 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/repository/PushNotificationRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/repository/PushNotificationRepository.java @@ -2,32 +2,34 @@ import com.onebyone.kindergarten.domain.pushNotification.entity.PushNotification; import com.onebyone.kindergarten.domain.user.entity.User; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Repository public interface PushNotificationRepository extends JpaRepository { - List findByUserOrderByCreatedAtDesc(User user); - - List findByUserAndIsReadFalseOrderByCreatedAtDesc(User user); - - @Modifying - @Query("UPDATE push_notification p SET p.isRead = true, p.updatedAt = :now WHERE p.user = :user") - void markAllAsRead(@Param("user") User user, @Param("now") LocalDateTime now); - - long countByUserAndIsReadFalse(User user); - - // 특정 시간 이전에 생성된 미전송 알림 조회 (FCM 토큰이 있는 것만 조회) - @Query("SELECT p FROM push_notification p WHERE p.isSent = false AND p.createdAt <= :cursorTime AND p.fcmToken IS NOT NULL AND p.fcmToken <> '' ORDER BY p.createdAt ASC") - List findUnsentNotificationsBeforeTime(@Param("cursorTime") LocalDateTime cursorTime); - - // 그룹 키로 미전송 알림 찾기 (중복 방지용) - Optional findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc(Long userId, String groupKey); -} \ No newline at end of file + List findByUserOrderByCreatedAtDesc(User user); + + List findByUserAndIsReadFalseOrderByCreatedAtDesc(User user); + + @Modifying + @Query("UPDATE push_notification p SET p.isRead = true, p.updatedAt = :now WHERE p.user = :user") + void markAllAsRead(@Param("user") User user, @Param("now") LocalDateTime now); + + long countByUserAndIsReadFalse(User user); + + // 특정 시간 이전에 생성된 미전송 알림 조회 (FCM 토큰이 있는 것만 조회) + @Query( + "SELECT p FROM push_notification p WHERE p.isSent = false AND p.createdAt <= :cursorTime AND p.fcmToken IS NOT NULL AND p.fcmToken <> '' ORDER BY p.createdAt ASC") + List findUnsentNotificationsBeforeTime( + @Param("cursorTime") LocalDateTime cursorTime); + + // 그룹 키로 미전송 알림 찾기 (중복 방지용) + Optional findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc( + Long userId, String groupKey); +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/NotificationTemplateService.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/NotificationTemplateService.java index 76e6599..acc1233 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/NotificationTemplateService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/NotificationTemplateService.java @@ -8,360 +8,300 @@ import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; import com.onebyone.kindergarten.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.time.Duration; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -/** - * 알림 타입별 템플릿을 관리하고 발행하는 서비스 - * 모든 알림 발송 로직은 이 클래스를 통해 처리하여 일관성 있는 알림 메시지 형식 유지 - */ +/** 알림 타입별 템플릿을 관리하고 발행하는 서비스 모든 알림 발송 로직은 이 클래스를 통해 처리하여 일관성 있는 알림 메시지 형식 유지 */ @Slf4j @Service @RequiredArgsConstructor public class NotificationTemplateService { - private final PushNotificationEventPublisher notificationEventPublisher; - private final PushNotificationRepository notificationRepository; - private final UserRepository userRepository; - - /// 메시지 템플릿 상수 - private static final String CHECK_APP_MESSAGE = " 👀 앱에서 자세히 확인해보세요!"; - - /// 댓글,답글 템플릿 - private static final String COMMENT_EMOJI = "✏️"; - private static final String REPLY_EMOJI = "💬"; - private static final String LIKE_EMOJI = "❤️"; - - private static final String SINGLE_COMMENT_MESSAGE = "%s %s님이 댓글을 남겼습니다.\n👀 새로운 댓글을 확인해보세요!"; - private static final String SINGLE_REPLY_MESSAGE = "%s %s님이 답글을 남겼습니다.\n👀 답글을 바로 확인해보세요!"; - private static final String GROUP_COMMENT_MESSAGE_2 = "%s %s님과 1명이 댓글을 남겼습니다.\n👀 댓글을 확인해보세요!"; - private static final String GROUP_COMMENT_MESSAGE_N = "%s %s님 외 %d명이 댓글을 남겼습니다.\n👀 댓글을 확인해보세요!"; - private static final String GROUP_REPLY_MESSAGE_2 = "%s %s님과 1명이 답글을 남겼습니다.\n👀 답글을 확인해보세요!"; - private static final String GROUP_REPLY_MESSAGE_N = "%s %s님 외 %d명이 답글을 남겼습니다.\n👀 답글을 확인해보세요!"; - - /// 좋아요 템플릿 - private static final String SINGLE_LIKE_MESSAGE = "%s %s님이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; - private static final String GROUP_LIKE_MESSAGE_2 = "%s %s님과 1명이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; - private static final String GROUP_LIKE_MESSAGE_N = "%s %s님 외 %d명이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; - - /// 그룹화 시간 설정 (20분 이내의 알림은 그룹화) - /// TODO : 테스트를 위해 현재 3분으로 사용 추후 20분으로 변경 필요 - private static final Duration GROUP_TIME_WINDOW = Duration.ofMinutes(3); - - /** - * 댓글 알림을 발송합니다. - */ - @Transactional - public void sendCommentNotification(Long targetUserId, User actionUser, String content, boolean isReply, Long postId) { - if (!targetUserId.equals(actionUser.getId())) { - String groupKey = (isReply ? "REPLY_" : "COMMENT_") + postId; - LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); - Optional existingNotification = findGroupableNotification(targetUserId, groupKey, sinceTime); - if (existingNotification.isPresent()) { - handleGroupedNotification(existingNotification.get(), actionUser, isReply); - } else { - createNewCommentNotification(targetUserId, actionUser, isReply, postId, groupKey); - } - } + private final PushNotificationEventPublisher notificationEventPublisher; + private final PushNotificationRepository notificationRepository; + private final UserRepository userRepository; + + /// 메시지 템플릿 상수 + private static final String CHECK_APP_MESSAGE = " 👀 앱에서 자세히 확인해보세요!"; + + /// 댓글,답글 템플릿 + private static final String COMMENT_EMOJI = "✏️"; + private static final String REPLY_EMOJI = "💬"; + private static final String LIKE_EMOJI = "❤️"; + + private static final String SINGLE_COMMENT_MESSAGE = "%s %s님이 댓글을 남겼습니다.\n👀 새로운 댓글을 확인해보세요!"; + private static final String SINGLE_REPLY_MESSAGE = "%s %s님이 답글을 남겼습니다.\n👀 답글을 바로 확인해보세요!"; + private static final String GROUP_COMMENT_MESSAGE_2 = "%s %s님과 1명이 댓글을 남겼습니다.\n👀 댓글을 확인해보세요!"; + private static final String GROUP_COMMENT_MESSAGE_N = "%s %s님 외 %d명이 댓글을 남겼습니다.\n👀 댓글을 확인해보세요!"; + private static final String GROUP_REPLY_MESSAGE_2 = "%s %s님과 1명이 답글을 남겼습니다.\n👀 답글을 확인해보세요!"; + private static final String GROUP_REPLY_MESSAGE_N = "%s %s님 외 %d명이 답글을 남겼습니다.\n👀 답글을 확인해보세요!"; + + /// 좋아요 템플릿 + private static final String SINGLE_LIKE_MESSAGE = "%s %s님이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; + private static final String GROUP_LIKE_MESSAGE_2 = + "%s %s님과 1명이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; + private static final String GROUP_LIKE_MESSAGE_N = + "%s %s님 외 %d명이 회원님의 게시글을 좋아합니다\n👀 지금 바로 확인해보세요!"; + + /// 그룹화 시간 설정 (20분 이내의 알림은 그룹화) + /// TODO : 테스트를 위해 현재 3분으로 사용 추후 20분으로 변경 필요 + private static final Duration GROUP_TIME_WINDOW = Duration.ofMinutes(3); + + /** 댓글 알림을 발송합니다. */ + @Transactional + public void sendCommentNotification( + Long targetUserId, User actionUser, String content, boolean isReply, Long postId) { + if (!targetUserId.equals(actionUser.getId())) { + String groupKey = (isReply ? "REPLY_" : "COMMENT_") + postId; + LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); + Optional existingNotification = + findGroupableNotification(targetUserId, groupKey, sinceTime); + if (existingNotification.isPresent()) { + handleGroupedNotification(existingNotification.get(), actionUser, isReply); + } else { + createNewCommentNotification(targetUserId, actionUser, isReply, postId, groupKey); + } } - - /** - * 게시글 좋아요 알림을 발송합니다. - */ - @Transactional - public void sendLikeNotification(Long targetUserId, User actionUser, String contentTitle, Long targetId) { - if (!targetUserId.equals(actionUser.getId())) { - String groupKey = "LIKE_" + targetId; - LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); - Optional existingNotification = findGroupableNotification(targetUserId, groupKey, sinceTime); - if (existingNotification.isPresent()) { - handleGroupedLikeNotification(existingNotification.get(), actionUser); - } else { - createNewLikeNotification(targetUserId, actionUser, targetId, groupKey, NotificationType.LIKE); - } - } + } + + /** 게시글 좋아요 알림을 발송합니다. */ + @Transactional + public void sendLikeNotification( + Long targetUserId, User actionUser, String contentTitle, Long targetId) { + if (!targetUserId.equals(actionUser.getId())) { + String groupKey = "LIKE_" + targetId; + LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); + Optional existingNotification = + findGroupableNotification(targetUserId, groupKey, sinceTime); + if (existingNotification.isPresent()) { + handleGroupedLikeNotification(existingNotification.get(), actionUser); + } else { + createNewLikeNotification( + targetUserId, actionUser, targetId, groupKey, NotificationType.LIKE); + } } - - /** - * 리뷰 좋아요 알림을 발송합니다. - */ - @Transactional - public void sendReviewLikeNotification(Long targetUserId, User actionUser, String contentTitle, Long targetId) { - if (!targetUserId.equals(actionUser.getId())) { - String groupKey = "REVIEW_LIKE_" + targetId; - LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); - Optional existingNotification = findGroupableNotification(targetUserId, groupKey, sinceTime); - if (existingNotification.isPresent()) { - handleGroupedLikeNotification(existingNotification.get(), actionUser); - } else { - createNewLikeNotification(targetUserId, actionUser, targetId, groupKey, NotificationType.REVIEW); - } - } + } + + /** 리뷰 좋아요 알림을 발송합니다. */ + @Transactional + public void sendReviewLikeNotification( + Long targetUserId, User actionUser, String contentTitle, Long targetId) { + if (!targetUserId.equals(actionUser.getId())) { + String groupKey = "REVIEW_LIKE_" + targetId; + LocalDateTime sinceTime = LocalDateTime.now().minus(GROUP_TIME_WINDOW); + Optional existingNotification = + findGroupableNotification(targetUserId, groupKey, sinceTime); + if (existingNotification.isPresent()) { + handleGroupedLikeNotification(existingNotification.get(), actionUser); + } else { + createNewLikeNotification( + targetUserId, actionUser, targetId, groupKey, NotificationType.REVIEW); + } } - - /** - * 문의 답변 알림을 발송합니다. - */ - public void sendInquiryAnswerNotification(Long targetUserId, String inquiryTitle, Long inquiryId) { - String message = "✅ 문의하신 내용에 답변이 등록되었습니다." + CHECK_APP_MESSAGE; - PushNotificationEvent event = createNotificationEvent( - targetUserId, - "문의 답변 알림", - message, - NotificationType.SYSTEM, - inquiryId, - null - ); - notificationEventPublisher.publish(event); - log.debug("문의 답변 알림 발송: 대상자={}", targetUserId); - } - - /** - * 공지사항 알림을 발송합니다. - */ - public void sendNoticeNotification(Long targetUserId, String noticeTitle, String noticeContent, Long noticeId) { - String message = "📢 [" + noticeTitle + "]\n" + CHECK_APP_MESSAGE; - PushNotificationEvent event = createNotificationEvent( - targetUserId, - "공지사항", - message, - NotificationType.NOTICE, - noticeId, - null - ); - notificationEventPublisher.publish(event); - log.debug("공지사항 알림 발송: 대상자={}", targetUserId); - } - - /** - * 시스템 알림을 발송합니다. - */ - public void sendSystemNotification(Long targetUserId, String title, String content, Long targetId) { - String message = "🔔 [" + title + "]\n" + CHECK_APP_MESSAGE; - PushNotificationEvent event = createNotificationEvent( - targetUserId, - "시스템 알림", - message, - NotificationType.SYSTEM, - targetId, - null - ); - notificationEventPublisher.publish(event); - log.debug("시스템 알림 발송: 대상자={}", targetUserId); - } - - /** - * 모든 활성 사용자에게 공지사항 알림을 발송합니다. - * 사용자별 알림 설정을 확인하여 알림을 받을 사용자에게만 전송합니다. - * 배치 처리로 스레드 풀 포화를 방지합니다. - */ - public void sendNoticeNotificationToAllUsers(String noticeTitle, String noticeContent, Long noticeId) { - try { - List activeUsers = userRepository.findAllActiveUsers(); - - log.info("공지사항 푸시 알림 전송 시작 - 공지 ID: {}, 전체 사용자 수: {}", - noticeId, activeUsers.size()); - - int sentCount = 0; - int skippedCount = 0; - final int BATCH_SIZE = 20; // 배치 크기 설정 (스레드 풀 용량 35개 고려) - - for (int i = 0; i < activeUsers.size(); i += BATCH_SIZE) { - int endIndex = Math.min(i + BATCH_SIZE, activeUsers.size()); - List batch = activeUsers.subList(i, endIndex); - - log.debug("배치 처리 중: {}/{} (배치 크기: {})", - endIndex, activeUsers.size(), batch.size()); - - /// 푸시 알림 전송 - for (User user : batch) { - try { - if (shouldSendNoticeNotification(user)) { - sendNoticeNotification( - user.getId(), - noticeTitle, - noticeContent, - noticeId - ); - sentCount++; - } else { - skippedCount++; - log.debug("알림 설정으로 인해 푸시 전송 스킵 - 사용자 ID: {}", user.getId()); - } - } catch (Exception e) { - log.error("사용자 {}에게 공지사항 푸시 알림 전송 실패: {}", - user.getId(), e.getMessage(), e); - skippedCount++; - } - } - - /// 배치 간 대기 (스레드 풀 여유 확보) - if (endIndex < activeUsers.size()) { - try { - TimeUnit.MILLISECONDS.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.warn("배치 처리 중 인터럽트 발생"); - break; - } - } + } + + /** 문의 답변 알림을 발송합니다. */ + public void sendInquiryAnswerNotification( + Long targetUserId, String inquiryTitle, Long inquiryId) { + String message = "✅ 문의하신 내용에 답변이 등록되었습니다." + CHECK_APP_MESSAGE; + PushNotificationEvent event = + createNotificationEvent( + targetUserId, "문의 답변 알림", message, NotificationType.SYSTEM, inquiryId, null); + notificationEventPublisher.publish(event); + log.debug("문의 답변 알림 발송: 대상자={}", targetUserId); + } + + /** 공지사항 알림을 발송합니다. */ + public void sendNoticeNotification( + Long targetUserId, String noticeTitle, String noticeContent, Long noticeId) { + String message = "📢 [" + noticeTitle + "]\n" + CHECK_APP_MESSAGE; + PushNotificationEvent event = + createNotificationEvent( + targetUserId, "공지사항", message, NotificationType.NOTICE, noticeId, null); + notificationEventPublisher.publish(event); + log.debug("공지사항 알림 발송: 대상자={}", targetUserId); + } + + /** 시스템 알림을 발송합니다. */ + public void sendSystemNotification( + Long targetUserId, String title, String content, Long targetId) { + String message = "🔔 [" + title + "]\n" + CHECK_APP_MESSAGE; + PushNotificationEvent event = + createNotificationEvent( + targetUserId, "시스템 알림", message, NotificationType.SYSTEM, targetId, null); + notificationEventPublisher.publish(event); + log.debug("시스템 알림 발송: 대상자={}", targetUserId); + } + + /** 모든 활성 사용자에게 공지사항 알림을 발송합니다. 사용자별 알림 설정을 확인하여 알림을 받을 사용자에게만 전송합니다. 배치 처리로 스레드 풀 포화를 방지합니다. */ + public void sendNoticeNotificationToAllUsers( + String noticeTitle, String noticeContent, Long noticeId) { + try { + List activeUsers = userRepository.findAllActiveUsers(); + + log.info("공지사항 푸시 알림 전송 시작 - 공지 ID: {}, 전체 사용자 수: {}", noticeId, activeUsers.size()); + + int sentCount = 0; + int skippedCount = 0; + final int BATCH_SIZE = 20; // 배치 크기 설정 (스레드 풀 용량 35개 고려) + + for (int i = 0; i < activeUsers.size(); i += BATCH_SIZE) { + int endIndex = Math.min(i + BATCH_SIZE, activeUsers.size()); + List batch = activeUsers.subList(i, endIndex); + + log.debug("배치 처리 중: {}/{} (배치 크기: {})", endIndex, activeUsers.size(), batch.size()); + + /// 푸시 알림 전송 + for (User user : batch) { + try { + if (shouldSendNoticeNotification(user)) { + sendNoticeNotification(user.getId(), noticeTitle, noticeContent, noticeId); + sentCount++; + } else { + skippedCount++; + log.debug("알림 설정으로 인해 푸시 전송 스킵 - 사용자 ID: {}", user.getId()); } - - log.info("공지사항 푸시 알림 전송 완료 - 공지 ID: {}, 전송: {}명, 스킵: {}명", - noticeId, sentCount, skippedCount); - } catch (Exception e) { - log.error("공지사항 푸시 알림 전송 중 오류 발생 - 공지 ID: {}, 오류: {}", - noticeId, e.getMessage(), e); + } catch (Exception e) { + log.error("사용자 {}에게 공지사항 푸시 알림 전송 실패: {}", user.getId(), e.getMessage(), e); + skippedCount++; + } } - } - /** - * 사용자가 공지사항 알림을 받을 수 있는지 확인합니다. - */ - private boolean shouldSendNoticeNotification(User user) { - /// 전체 알림이 비활성화된 경우 - if (!user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) { - return false; + /// 배치 간 대기 (스레드 풀 여유 확보) + if (endIndex < activeUsers.size()) { + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("배치 처리 중 인터럽트 발생"); + break; + } } - - /// 공지사항은 EVENT_NOTIFICATIONS 설정 확인 - return user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS); - } - - - /// ===== 헬퍼 메서드 ===== - + } - /** - * 알림 그룹화 가능 여부를 확인합니다. - * 미전송 알림 중에서만 그룹화 대상을 찾습니다. - */ - private Optional findGroupableNotification( - Long userId, - String groupKey, - LocalDateTime sinceTime) { - return notificationRepository.findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc(userId, groupKey) - .filter(notification -> notification.getCreatedAt().isAfter(sinceTime)); + log.info("공지사항 푸시 알림 전송 완료 - 공지 ID: {}, 전송: {}명, 스킵: {}명", noticeId, sentCount, skippedCount); + } catch (Exception e) { + log.error("공지사항 푸시 알림 전송 중 오류 발생 - 공지 ID: {}, 오류: {}", noticeId, e.getMessage(), e); } + } - /** - * 알림 그룹화 처리 - */ - private void handleGroupedNotification(PushNotification notification, User actionUser, boolean isReply) { - notification.increaseGroupCount(); - String updatedMessage = createGroupedMessage(actionUser.getNickname(), notification.getGroupCount(), isReply); - notification.updateGroupMessage(updatedMessage); - notificationRepository.save(notification); - log.debug("댓글 알림 그룹화 (count={}): 대상자={}", notification.getGroupCount(), notification.getUser().getId()); + /** 사용자가 공지사항 알림을 받을 수 있는지 확인합니다. */ + private boolean shouldSendNoticeNotification(User user) { + /// 전체 알림이 비활성화된 경우 + if (!user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) { + return false; } - /** - * 좋아요 알림 그룹화 처리 - */ - private void handleGroupedLikeNotification(PushNotification notification, User actionUser) { - notification.increaseGroupCount(); - String updatedMessage = createGroupedLikeMessage(actionUser.getNickname(), notification.getGroupCount()); - notification.updateGroupMessage(updatedMessage); - notificationRepository.save(notification); - log.debug("좋아요 알림 그룹화 (count={}): 대상자={}", notification.getGroupCount(), notification.getUser().getId()); + /// 공지사항은 EVENT_NOTIFICATIONS 설정 확인 + return user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS); + } + + /// ===== 헬퍼 메서드 ===== + + /** 알림 그룹화 가능 여부를 확인합니다. 미전송 알림 중에서만 그룹화 대상을 찾습니다. */ + private Optional findGroupableNotification( + Long userId, String groupKey, LocalDateTime sinceTime) { + return notificationRepository + .findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc(userId, groupKey) + .filter(notification -> notification.getCreatedAt().isAfter(sinceTime)); + } + + /** 알림 그룹화 처리 */ + private void handleGroupedNotification( + PushNotification notification, User actionUser, boolean isReply) { + notification.increaseGroupCount(); + String updatedMessage = + createGroupedMessage(actionUser.getNickname(), notification.getGroupCount(), isReply); + notification.updateGroupMessage(updatedMessage); + notificationRepository.save(notification); + log.debug( + "댓글 알림 그룹화 (count={}): 대상자={}", + notification.getGroupCount(), + notification.getUser().getId()); + } + + /** 좋아요 알림 그룹화 처리 */ + private void handleGroupedLikeNotification(PushNotification notification, User actionUser) { + notification.increaseGroupCount(); + String updatedMessage = + createGroupedLikeMessage(actionUser.getNickname(), notification.getGroupCount()); + notification.updateGroupMessage(updatedMessage); + notificationRepository.save(notification); + log.debug( + "좋아요 알림 그룹화 (count={}): 대상자={}", + notification.getGroupCount(), + notification.getUser().getId()); + } + + /** 새로운 댓글 알림 생성 */ + private void createNewCommentNotification( + Long targetUserId, User actionUser, boolean isReply, Long postId, String groupKey) { + String emoji = isReply ? REPLY_EMOJI : COMMENT_EMOJI; + String title = isReply ? "답글 알림" : "댓글 알림"; + String message = + String.format( + isReply ? SINGLE_REPLY_MESSAGE : SINGLE_COMMENT_MESSAGE, + emoji, + actionUser.getNickname()); + + PushNotificationEvent event = + createNotificationEvent( + targetUserId, title, message, NotificationType.COMMENT, postId, groupKey); + + notificationEventPublisher.publish(event); + log.debug("댓글 알림 발송: 대상자={}", targetUserId); + } + + /** 새로운 좋아요 알림 생성 */ + private void createNewLikeNotification( + Long targetUserId, User actionUser, Long targetId, String groupKey, NotificationType type) { + String message = String.format(SINGLE_LIKE_MESSAGE, LIKE_EMOJI, actionUser.getNickname()); + + PushNotificationEvent event = + createNotificationEvent(targetUserId, "좋아요 알림", message, type, targetId, groupKey); + + notificationEventPublisher.publish(event); + log.debug("좋아요 알림 발송: 대상자={}, 타입={}", targetUserId, type); + } + + /** 그룹화된 메시지 생성 */ + private String createGroupedMessage(String actorName, int count, boolean isReply) { + String emoji = isReply ? REPLY_EMOJI : COMMENT_EMOJI; + if (count == 2) { + return String.format( + isReply ? GROUP_REPLY_MESSAGE_2 : GROUP_COMMENT_MESSAGE_2, emoji, actorName); } - - /** - * 새로운 댓글 알림 생성 - */ - private void createNewCommentNotification(Long targetUserId, User actionUser, boolean isReply, Long postId, String groupKey) { - String emoji = isReply ? REPLY_EMOJI : COMMENT_EMOJI; - String title = isReply ? "답글 알림" : "댓글 알림"; - String message = String.format( - isReply ? SINGLE_REPLY_MESSAGE : SINGLE_COMMENT_MESSAGE, - emoji, - actionUser.getNickname() - ); - - PushNotificationEvent event = createNotificationEvent( - targetUserId, - title, - message, - NotificationType.COMMENT, - postId, - groupKey - ); - - notificationEventPublisher.publish(event); - log.debug("댓글 알림 발송: 대상자={}", targetUserId); - } - - /** - * 새로운 좋아요 알림 생성 - */ - private void createNewLikeNotification(Long targetUserId, User actionUser, Long targetId, String groupKey, NotificationType type) { - String message = String.format(SINGLE_LIKE_MESSAGE, LIKE_EMOJI, actionUser.getNickname()); - - PushNotificationEvent event = createNotificationEvent( - targetUserId, - "좋아요 알림", - message, - type, - targetId, - groupKey - ); - - notificationEventPublisher.publish(event); - log.debug("좋아요 알림 발송: 대상자={}, 타입={}", targetUserId, type); - } - - /** - * 그룹화된 메시지 생성 - */ - private String createGroupedMessage(String actorName, int count, boolean isReply) { - String emoji = isReply ? REPLY_EMOJI : COMMENT_EMOJI; - if (count == 2) { - return String.format( - isReply ? GROUP_REPLY_MESSAGE_2 : GROUP_COMMENT_MESSAGE_2, - emoji, - actorName - ); - } - return String.format( - isReply ? GROUP_REPLY_MESSAGE_N : GROUP_COMMENT_MESSAGE_N, - emoji, - actorName, - count - 1 - ); + return String.format( + isReply ? GROUP_REPLY_MESSAGE_N : GROUP_COMMENT_MESSAGE_N, emoji, actorName, count - 1); + } + + /** 그룹화된 좋아요 메시지 생성 */ + private String createGroupedLikeMessage(String actorName, int count) { + if (count == 2) { + return String.format(GROUP_LIKE_MESSAGE_2, LIKE_EMOJI, actorName); } - - /** - * 그룹화된 좋아요 메시지 생성 - */ - private String createGroupedLikeMessage(String actorName, int count) { - if (count == 2) { - return String.format(GROUP_LIKE_MESSAGE_2, LIKE_EMOJI, actorName); - } - return String.format(GROUP_LIKE_MESSAGE_N, LIKE_EMOJI, actorName, count - 1); - } - - /** - * 알림 이벤트 생성 - */ - private PushNotificationEvent createNotificationEvent( - Long userId, - String title, - String message, - NotificationType type, - Long targetId, - String groupKey) { - PushNotificationEvent event = new PushNotificationEvent(userId, title, message, type, targetId); - if (groupKey != null) { - event.setGroupKey(groupKey); - } - return event; + return String.format(GROUP_LIKE_MESSAGE_N, LIKE_EMOJI, actorName, count - 1); + } + + /** 알림 이벤트 생성 */ + private PushNotificationEvent createNotificationEvent( + Long userId, + String title, + String message, + NotificationType type, + Long targetId, + String groupKey) { + PushNotificationEvent event = new PushNotificationEvent(userId, title, message, type, targetId); + if (groupKey != null) { + event.setGroupKey(groupKey); } -} \ No newline at end of file + return event; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java index 04dda9f..8466baa 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/pushNotification/service/PushNotificationService.java @@ -1,498 +1,515 @@ package com.onebyone.kindergarten.domain.pushNotification.service; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutureCallback; +import com.google.api.core.ApiFutures; import com.google.common.util.concurrent.MoreExecutors; import com.google.firebase.messaging.*; import com.onebyone.kindergarten.domain.pushNotification.dto.PushNotificationRequestDTO; import com.onebyone.kindergarten.domain.pushNotification.dto.PushNotificationResponseDTO; import com.onebyone.kindergarten.domain.pushNotification.entity.PushNotification; +import com.onebyone.kindergarten.domain.pushNotification.enums.NotificationType; import com.onebyone.kindergarten.domain.pushNotification.repository.PushNotificationRepository; import com.onebyone.kindergarten.domain.user.entity.User; +import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; import com.onebyone.kindergarten.domain.user.repository.UserRepository; import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; - -import com.google.api.core.ApiFuture; -import com.google.api.core.ApiFutureCallback; -import com.google.api.core.ApiFutures; -import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; -import com.onebyone.kindergarten.domain.pushNotification.enums.NotificationType; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @RequiredArgsConstructor public class PushNotificationService { - private final FirebaseMessaging firebaseMessaging; - private final PushNotificationRepository pushNotificationRepository; - private final UserRepository userRepository; - private final UserService userService; - - /// 알림 전송 여부 확인 ( 내부 메서드 ) - private boolean shouldSendNotification(User user, NotificationType type) { - /// NotificationType null 체크 - if (type == null) { - log.warn("NotificationType is null - userId: {}", user.getId()); - return false; - } - - /// 사용자가 전체 알림을 비활성화한 경우 모든 알림 차단 - if (!user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) { - return false; - } - - /// 알림 타입에 따라 특정 설정 확인 - return switch (type) { - case REVIEW, COMMENT, LIKE -> user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS); - case SYSTEM, NOTICE -> user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS); - }; + private final FirebaseMessaging firebaseMessaging; + private final PushNotificationRepository pushNotificationRepository; + private final UserRepository userRepository; + private final UserService userService; + + /// 알림 전송 여부 확인 ( 내부 메서드 ) + private boolean shouldSendNotification(User user, NotificationType type) { + /// NotificationType null 체크 + if (type == null) { + log.warn("NotificationType is null - userId: {}", user.getId()); + return false; } - /// 푸시 알림 저장 (FCM 발송하지 않음) - @Transactional - public void savePushNotification(PushNotificationRequestDTO requestDTO) { - // 사용자 조회 - User user = userRepository.findById(requestDTO.getUserId()) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); - - // 알림 설정 확인 및 FCM 토큰 유효성 검증 - boolean shouldSend = shouldSendNotification(user, requestDTO.getType()); - String userFcmToken = user.getFcmToken(); - boolean hasValidToken = isValidFcmToken(userFcmToken); - - // 알림 설정이 꺼져있거나 FCM 토큰이 없는 경우 - // → 알림은 저장하되 전송 완료(isSent=true)로 표시 - // → 앱 내 알림 목록에서는 확인 가능, 푸시 알림만 전송 안 함 - if (!shouldSend || !hasValidToken) { - String reason = !shouldSend ? "알림 설정 비활성화" : "FCM 토큰 없음"; - log.debug("{} - 사용자 ID: {}, 알림은 저장하되 푸시 전송 스킵", reason, user.getId()); - - PushNotification notification = PushNotification.builder() - .user(user) - .fcmToken(null) - .title(requestDTO.getTitle()) - .message(requestDTO.getMessage()) - .type(requestDTO.getType()) - .targetId(requestDTO.getTargetId()) - .isRead(false) - .isSent(true) // 전송 완료로 표시하여 스케줄러가 재시도하지 않도록 - .groupKey(requestDTO.getGroupKey()) - .groupCount(requestDTO.getGroupCount() != null ? requestDTO.getGroupCount() : 1) - .build(); - - pushNotificationRepository.save(notification); - log.debug("푸시 전송 스킵 알림 저장 완료 (사유: {}) - 사용자 ID: {}, 타입: {}", - reason, user.getId(), requestDTO.getType()); - return; - } - - // 그룹키가 있는 경우, 기존 미전송 알림 확인하여 중복 방지 - if (requestDTO.getGroupKey() != null && !requestDTO.getGroupKey().isEmpty()) { - Optional existingNotification = - pushNotificationRepository.findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc( - requestDTO.getUserId(), - requestDTO.getGroupKey() - ); - - if (existingNotification.isPresent()) { - // 기존 미전송 알림이 있으면 중복 생성하지 않고 로그만 남김 - log.debug("중복 알림 방지 - 사용자 ID: {}, 그룹키: {}, 기존 알림 ID: {}", - user.getId(), requestDTO.getGroupKey(), existingNotification.get().getId()); - return; - } - } + /// 사용자가 전체 알림을 비활성화한 경우 모든 알림 차단 + if (!user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) { + return false; + } - // 알림 저장 (FCM 발송하지 않음) - PushNotification notification = PushNotification.builder() - .user(user) - .fcmToken(userFcmToken) - .title(requestDTO.getTitle()) - .message(requestDTO.getMessage()) - .type(requestDTO.getType()) - .targetId(requestDTO.getTargetId()) - .isRead(false) - .isSent(false) - - /// 그룹화 - .groupKey(requestDTO.getGroupKey()) - .groupCount(requestDTO.getGroupCount() != null ? requestDTO.getGroupCount() : 1) - .build(); + /// 알림 타입에 따라 특정 설정 확인 + return switch (type) { + case REVIEW, COMMENT, LIKE -> + user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS); + case SYSTEM, NOTICE -> user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS); + }; + } + + /// 푸시 알림 저장 (FCM 발송하지 않음) + @Transactional + public void savePushNotification(PushNotificationRequestDTO requestDTO) { + // 사용자 조회 + User user = + userRepository + .findById(requestDTO.getUserId()) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + + // 알림 설정 확인 및 FCM 토큰 유효성 검증 + boolean shouldSend = shouldSendNotification(user, requestDTO.getType()); + String userFcmToken = user.getFcmToken(); + boolean hasValidToken = isValidFcmToken(userFcmToken); + + // 알림 설정이 꺼져있거나 FCM 토큰이 없는 경우 + // → 알림은 저장하되 전송 완료(isSent=true)로 표시 + // → 앱 내 알림 목록에서는 확인 가능, 푸시 알림만 전송 안 함 + if (!shouldSend || !hasValidToken) { + String reason = !shouldSend ? "알림 설정 비활성화" : "FCM 토큰 없음"; + log.debug("{} - 사용자 ID: {}, 알림은 저장하되 푸시 전송 스킵", reason, user.getId()); + + PushNotification notification = + PushNotification.builder() + .user(user) + .fcmToken(null) + .title(requestDTO.getTitle()) + .message(requestDTO.getMessage()) + .type(requestDTO.getType()) + .targetId(requestDTO.getTargetId()) + .isRead(false) + .isSent(true) // 전송 완료로 표시하여 스케줄러가 재시도하지 않도록 + .groupKey(requestDTO.getGroupKey()) + .groupCount(requestDTO.getGroupCount() != null ? requestDTO.getGroupCount() : 1) + .build(); + + pushNotificationRepository.save(notification); + log.debug( + "푸시 전송 스킵 알림 저장 완료 (사유: {}) - 사용자 ID: {}, 타입: {}", + reason, + user.getId(), + requestDTO.getType()); + return; + } - pushNotificationRepository.save(notification); - log.info("푸시 알림 저장 완료 - 사용자 ID: {}, 타입: {}, 그룹키: {}", - user.getId(), requestDTO.getType(), requestDTO.getGroupKey()); + // 그룹키가 있는 경우, 기존 미전송 알림 확인하여 중복 방지 + if (requestDTO.getGroupKey() != null && !requestDTO.getGroupKey().isEmpty()) { + Optional existingNotification = + pushNotificationRepository.findFirstByUserIdAndGroupKeyAndIsSentFalseOrderByCreatedAtDesc( + requestDTO.getUserId(), requestDTO.getGroupKey()); + + if (existingNotification.isPresent()) { + // 기존 미전송 알림이 있으면 중복 생성하지 않고 로그만 남김 + log.debug( + "중복 알림 방지 - 사용자 ID: {}, 그룹키: {}, 기존 알림 ID: {}", + user.getId(), + requestDTO.getGroupKey(), + existingNotification.get().getId()); + return; + } } - /// FCM 통해 여러 알림 동시 전송 (비동기 처리) - @Transactional - public void sendAllFCMNotificationsByAsync(List notifications) { - // 성공한 알림들을 저장할 리스트 (catch 블록에서도 접근 가능하도록 외부에 선언) - List updatedNotifications = new ArrayList<>(); - - try { - // 알림이 비어있거나 null인 경우 처리 - if (notifications == null || notifications.isEmpty()) { - log.info("전송할 알림이 없습니다."); - return; - } - - // 개별 메시지 목록 생성 - List messages = new ArrayList<>(); - List validNotifications = new ArrayList<>(); - - for (PushNotification notification : notifications) { - /// 중복 전송 방지: 이미 전송된 알림은 건너뛰기 - if (notification.getIsSent() != null && notification.getIsSent()) { - log.debug("이미 전송된 알림 발견 (ID: {}), 건너뛰기", notification.getId()); - continue; - } - - if (notification.getFcmToken() == null || notification.getFcmToken().isEmpty()) { - log.warn("FCM 토큰이 없는 알림 발견 (ID: {}), 건너뛰기", notification.getId()); - continue; - } - - validNotifications.add(notification); - - // 추가 데이터 설정 - Map data = new HashMap<>(); - data.put("type", notification.getType().name()); - if (notification.getTargetId() != null) { - data.put("targetId", notification.getTargetId().toString()); - } - - ApsAlert alert = ApsAlert.builder() - .setTitle(notification.getTitle()) - .setBody(notification.getMessage()) - .build(); - - Aps aps = Aps.builder() - .setAlert(alert) - .setSound("default") - ///.setBadge() - .build(); - - // 개별 메시지 생성 - 알림 엔티티에 저장된 FCM 토큰 사용 - Message message = Message.builder() - .setToken(notification.getFcmToken()) - .setNotification(Notification.builder() - .setTitle(notification.getTitle()) - .setBody(notification.getMessage()) - ///.setImage("이미지 URL") - .build()) - .putAllData(data) - .setApnsConfig( - ApnsConfig.builder() - .setAps(aps) - .build() - ) - .setAndroidConfig(AndroidConfig.builder() - .setPriority(AndroidConfig.Priority.HIGH) - .setNotification(AndroidNotification.builder() - .setTitle(notification.getTitle()) - .setBody(notification.getMessage()) - .build()) - .build()) - .build(); - - messages.add(message); - } - - // 실제 전송할 메시지가 없는 경우 종료 - if (messages.isEmpty()) { - log.info("실제 전송할 수 있는 메시지가 없습니다."); - return; + // 알림 저장 (FCM 발송하지 않음) + PushNotification notification = + PushNotification.builder() + .user(user) + .fcmToken(userFcmToken) + .title(requestDTO.getTitle()) + .message(requestDTO.getMessage()) + .type(requestDTO.getType()) + .targetId(requestDTO.getTargetId()) + .isRead(false) + .isSent(false) + + /// 그룹화 + .groupKey(requestDTO.getGroupKey()) + .groupCount(requestDTO.getGroupCount() != null ? requestDTO.getGroupCount() : 1) + .build(); + + pushNotificationRepository.save(notification); + log.info( + "푸시 알림 저장 완료 - 사용자 ID: {}, 타입: {}, 그룹키: {}", + user.getId(), + requestDTO.getType(), + requestDTO.getGroupKey()); + } + + /// FCM 통해 여러 알림 동시 전송 (비동기 처리) + @Transactional + public void sendAllFCMNotificationsByAsync(List notifications) { + // 성공한 알림들을 저장할 리스트 (catch 블록에서도 접근 가능하도록 외부에 선언) + List updatedNotifications = new ArrayList<>(); + + try { + // 알림이 비어있거나 null인 경우 처리 + if (notifications == null || notifications.isEmpty()) { + log.info("전송할 알림이 없습니다."); + return; + } + + // 개별 메시지 목록 생성 + List messages = new ArrayList<>(); + List validNotifications = new ArrayList<>(); + + for (PushNotification notification : notifications) { + /// 중복 전송 방지: 이미 전송된 알림은 건너뛰기 + if (notification.getIsSent() != null && notification.getIsSent()) { + log.debug("이미 전송된 알림 발견 (ID: {}), 건너뛰기", notification.getId()); + continue; } - // 비동기 메시지 전송을 위한 Future 목록 - List> futures = new ArrayList<>(); - - // 모든 메시지를 비동기로 전송 - for (Message message : messages) { - ApiFuture future = firebaseMessaging.sendAsync(message); - CompletableFuture completableFuture = new CompletableFuture<>(); - ApiFutures.addCallback(future, new ApiFutureCallback() { - - @Override - public void onSuccess(String messageId) { - completableFuture.complete(messageId); - } + if (notification.getFcmToken() == null || notification.getFcmToken().isEmpty()) { + log.warn("FCM 토큰이 없는 알림 발견 (ID: {}), 건너뛰기", notification.getId()); + continue; + } - @Override - public void onFailure(Throwable throwable) { - completableFuture.completeExceptionally(throwable); - } - }, MoreExecutors.directExecutor()); + validNotifications.add(notification); - futures.add(completableFuture); + // 추가 데이터 설정 + Map data = new HashMap<>(); + data.put("type", notification.getType().name()); + if (notification.getTargetId() != null) { + data.put("targetId", notification.getTargetId().toString()); } - // 모든 Future가 완료될 때까지 대기 (최대 30초 타임아웃) - try { - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .orTimeout(30, TimeUnit.SECONDS) - .join(); - } catch (Exception e) { - log.error("FCM 알림 전송 중 타임아웃 또는 오류 발생: {}", e.getMessage()); - // 타임아웃이어도 개별 Future 결과는 처리 - } + ApsAlert alert = + ApsAlert.builder() + .setTitle(notification.getTitle()) + .setBody(notification.getMessage()) + .build(); - // 응답 결과 수집 - int successCount = 0; - int failureCount = 0; - - // 각 Future의 결과 처리 - for (int i = 0; i < futures.size(); i++) { - CompletableFuture future = futures.get(i); - PushNotification notification = validNotifications.get(i); - - try { - // 성공적으로 완료된 경우 - String messageId = future.get(); - successCount++; - - // 성공 처리 - notification.markAsSent(); - updatedNotifications.add(notification); - log.info("FCM 알림 전송 성공: {}, 메시지 ID: {}", notification.getId(), messageId); - } catch (InterruptedException | ExecutionException e) { - // 실패한 경우 - failureCount++; - - // Firebase 관련 예외인지 확인 - Throwable cause = e.getCause(); - if (cause instanceof FirebaseMessagingException fme) { - handleFirebaseMessagingException(notification, fme); - } else { - log.error("FCM 알림 전송 실패: {}, 에러: {}", - notification.getId(), e.getMessage()); - } - - // 실패한 알림도 전송 완료로 표시하여 재시도 방지 - notification.markAsSent(); - updatedNotifications.add(notification); - } - } + Aps aps = + Aps.builder() + .setAlert(alert) + .setSound("default") + /// .setBadge() + .build(); - // 알림 상태 일괄 업데이트 (별도 트랜잭션으로 처리) - updateNotificationStatus(updatedNotifications); - - log.info("FCM 알림 배치 전송 결과 - 성공: {}, 실패: {}, 총: {}", - successCount, - failureCount, - validNotifications.size()); - - log.info("FCM 알림 배치 전송 요청 완료: {} 개", validNotifications.size()); - } catch (Exception e) { - // 예외가 발생하더라도 성공한 알림들은 반드시 상태 업데이트 - if (!updatedNotifications.isEmpty()) { - try { - updateNotificationStatus(updatedNotifications); - log.info("성공한 알림 상태 업데이트 완료: {} 개", updatedNotifications.size()); - } catch (Exception updateException) { - log.error("알림 상태 업데이트 중 추가 오류 발생: {}", updateException.getMessage(), updateException); - } - } - } - } + // 개별 메시지 생성 - 알림 엔티티에 저장된 FCM 토큰 사용 + Message message = + Message.builder() + .setToken(notification.getFcmToken()) + .setNotification( + Notification.builder() + .setTitle(notification.getTitle()) + .setBody(notification.getMessage()) + /// .setImage("이미지 URL") + .build()) + .putAllData(data) + .setApnsConfig(ApnsConfig.builder().setAps(aps).build()) + .setAndroidConfig( + AndroidConfig.builder() + .setPriority(AndroidConfig.Priority.HIGH) + .setNotification( + AndroidNotification.builder() + .setTitle(notification.getTitle()) + .setBody(notification.getMessage()) + .build()) + .build()) + .build(); - /// 알림 상태 일괄 업데이트 (별도 트랜잭션으로 처리) - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void updateNotificationStatus(List notifications) { - if (notifications == null || notifications.isEmpty()) { - return; - } + messages.add(message); + } + + // 실제 전송할 메시지가 없는 경우 종료 + if (messages.isEmpty()) { + log.info("실제 전송할 수 있는 메시지가 없습니다."); + return; + } + + // 비동기 메시지 전송을 위한 Future 목록 + List> futures = new ArrayList<>(); + + // 모든 메시지를 비동기로 전송 + for (Message message : messages) { + ApiFuture future = firebaseMessaging.sendAsync(message); + CompletableFuture completableFuture = new CompletableFuture<>(); + ApiFutures.addCallback( + future, + new ApiFutureCallback() { + + @Override + public void onSuccess(String messageId) { + completableFuture.complete(messageId); + } + + @Override + public void onFailure(Throwable throwable) { + completableFuture.completeExceptionally(throwable); + } + }, + MoreExecutors.directExecutor()); + + futures.add(completableFuture); + } + + // 모든 Future가 완료될 때까지 대기 (최대 30초 타임아웃) + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .orTimeout(30, TimeUnit.SECONDS) + .join(); + } catch (Exception e) { + log.error("FCM 알림 전송 중 타임아웃 또는 오류 발생: {}", e.getMessage()); + // 타임아웃이어도 개별 Future 결과는 처리 + } + + // 응답 결과 수집 + int successCount = 0; + int failureCount = 0; + + // 각 Future의 결과 처리 + for (int i = 0; i < futures.size(); i++) { + CompletableFuture future = futures.get(i); + PushNotification notification = validNotifications.get(i); try { - // 알림 상태 업데이트 - pushNotificationRepository.saveAll(notifications); - log.info("알림 상태 일괄 업데이트 완료: {} 개", notifications.size()); - } catch (Exception e) { - log.error("알림 상태 일괄 업데이트 중 오류 발생: {}", e.getMessage(), e); + // 성공적으로 완료된 경우 + String messageId = future.get(); + successCount++; + + // 성공 처리 + notification.markAsSent(); + updatedNotifications.add(notification); + log.info("FCM 알림 전송 성공: {}, 메시지 ID: {}", notification.getId(), messageId); + } catch (InterruptedException | ExecutionException e) { + // 실패한 경우 + failureCount++; + + // Firebase 관련 예외인지 확인 + Throwable cause = e.getCause(); + if (cause instanceof FirebaseMessagingException fme) { + handleFirebaseMessagingException(notification, fme); + } else { + log.error("FCM 알림 전송 실패: {}, 에러: {}", notification.getId(), e.getMessage()); + } + + // 실패한 알림도 전송 완료로 표시하여 재시도 방지 + notification.markAsSent(); + updatedNotifications.add(notification); } - } - - /// 특정 시간 이전에 생성된 미전송 알림 조회 - @Transactional(readOnly = true) - public List getUnsentNotificationsBeforeTime(LocalDateTime cursorTime) { - return pushNotificationRepository.findUnsentNotificationsBeforeTime(cursorTime); - } + } - /// 사용자의 모든 알림 조회 - @Transactional(readOnly = true) - public List getUserNotifications(Long userId) { + // 알림 상태 일괄 업데이트 (별도 트랜잭션으로 처리) + updateNotificationStatus(updatedNotifications); - // 사용자 조회 - User user = userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + log.info( + "FCM 알림 배치 전송 결과 - 성공: {}, 실패: {}, 총: {}", + successCount, + failureCount, + validNotifications.size()); - // 모든 알림 조회 - return pushNotificationRepository.findByUserOrderByCreatedAtDesc(user) - .stream() - .map(PushNotificationResponseDTO::from) - .collect(Collectors.toList()); + log.info("FCM 알림 배치 전송 요청 완료: {} 개", validNotifications.size()); + } catch (Exception e) { + // 예외가 발생하더라도 성공한 알림들은 반드시 상태 업데이트 + if (!updatedNotifications.isEmpty()) { + try { + updateNotificationStatus(updatedNotifications); + log.info("성공한 알림 상태 업데이트 완료: {} 개", updatedNotifications.size()); + } catch (Exception updateException) { + log.error("알림 상태 업데이트 중 추가 오류 발생: {}", updateException.getMessage(), updateException); + } + } } + } - /// 현재 로그인한 사용자의 알림 조회 - @Transactional(readOnly = true) - public List getUserNotificationByUserDetails(Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 모든 알림 조회 - return pushNotificationRepository.findByUserOrderByCreatedAtDesc(user) - .stream() - .map(PushNotificationResponseDTO::from) - .collect(Collectors.toList()); + /// 알림 상태 일괄 업데이트 (별도 트랜잭션으로 처리) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void updateNotificationStatus(List notifications) { + if (notifications == null || notifications.isEmpty()) { + return; } - - /// 사용자의 읽지 않은 알림 조회 - @Transactional(readOnly = true) - public List getUnreadNotificationsByUserDetails(Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 읽지 않은 알림 조회 - return pushNotificationRepository.findByUserAndIsReadFalseOrderByCreatedAtDesc(user) - .stream() - .map(PushNotificationResponseDTO::from) - .collect(Collectors.toList()); - } - - /// 알림 읽음 표시 - @Transactional - public void markAsRead(Long notificationId) { - - // 알림 조회 - PushNotification notification = pushNotificationRepository.findById(notificationId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); - - // 읽음 처리 - notification.markAsRead(); - pushNotificationRepository.save(notification); + try { + // 알림 상태 업데이트 + pushNotificationRepository.saveAll(notifications); + log.info("알림 상태 일괄 업데이트 완료: {} 개", notifications.size()); + } catch (Exception e) { + log.error("알림 상태 일괄 업데이트 중 오류 발생: {}", e.getMessage(), e); } - - /// 사용자의 모든 알림 읽음 표시 - @Transactional - public void markAllAsRead(Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 모든 알림 읽음 처리 - pushNotificationRepository.markAllAsRead(user, LocalDateTime.now()); + } + + /// 특정 시간 이전에 생성된 미전송 알림 조회 + @Transactional(readOnly = true) + public List getUnsentNotificationsBeforeTime(LocalDateTime cursorTime) { + return pushNotificationRepository.findUnsentNotificationsBeforeTime(cursorTime); + } + + /// 사용자의 모든 알림 조회 + @Transactional(readOnly = true) + public List getUserNotifications(Long userId) { + + // 사용자 조회 + User user = + userRepository + .findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + + // 모든 알림 조회 + return pushNotificationRepository.findByUserOrderByCreatedAtDesc(user).stream() + .map(PushNotificationResponseDTO::from) + .collect(Collectors.toList()); + } + + /// 현재 로그인한 사용자의 알림 조회 + @Transactional(readOnly = true) + public List getUserNotificationByUserDetails(Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 모든 알림 조회 + return pushNotificationRepository.findByUserOrderByCreatedAtDesc(user).stream() + .map(PushNotificationResponseDTO::from) + .collect(Collectors.toList()); + } + + /// 사용자의 읽지 않은 알림 조회 + @Transactional(readOnly = true) + public List getUnreadNotificationsByUserDetails(Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 읽지 않은 알림 조회 + return pushNotificationRepository.findByUserAndIsReadFalseOrderByCreatedAtDesc(user).stream() + .map(PushNotificationResponseDTO::from) + .collect(Collectors.toList()); + } + + /// 알림 읽음 표시 + @Transactional + public void markAsRead(Long notificationId) { + + // 알림 조회 + PushNotification notification = + pushNotificationRepository + .findById(notificationId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + + // 읽음 처리 + notification.markAsRead(); + pushNotificationRepository.save(notification); + } + + /// 사용자의 모든 알림 읽음 표시 + @Transactional + public void markAllAsRead(Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 모든 알림 읽음 처리 + pushNotificationRepository.markAllAsRead(user, LocalDateTime.now()); + } + + /// 읽지 않은 알림 개수 조회 + @Transactional(readOnly = true) + public Long countUnreadNotifications(Long userId) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 읽지 않은 알림 개수 조회 + return pushNotificationRepository.countByUserAndIsReadFalse(user); + } + + /// Firebase 메시징 예외 처리 (토큰 정리 포함) + private void handleFirebaseMessagingException( + PushNotification notification, FirebaseMessagingException exception) { + MessagingErrorCode errorCode = exception.getMessagingErrorCode(); + String errorMessage = exception.getMessage(); + + log.error( + "FCM 알림 전송 실패 - 알림 ID: {}, 에러 코드: {}, 메시지: {}", + notification.getId(), + errorCode, + errorMessage); + + // 토큰 관련 오류인 경우 사용자의 FCM 토큰 정리 + switch (errorCode) { + case UNREGISTERED: + // 토큰이 등록되지 않음 (앱 삭제됨) + log.warn("등록되지 않은 FCM 토큰 발견 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); + clearUserFcmToken(notification.getUser()); + break; + + case INVALID_ARGUMENT: + // 잘못된 토큰 형식 + if (errorMessage != null && errorMessage.contains("Requested entity was not found")) { + log.warn("유효하지 않은 FCM 토큰 발견 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); + clearUserFcmToken(notification.getUser()); + } + break; + + case SENDER_ID_MISMATCH: + // 잘못된 발신자 ID + log.warn("FCM 발신자 ID 불일치 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); + clearUserFcmToken(notification.getUser()); + break; + + case QUOTA_EXCEEDED: + // 할당량 초과 - 토큰은 유효하므로 정리하지 않음 + log.warn("FCM 할당량 초과 - 알림 ID: {}", notification.getId()); + break; + + case UNAVAILABLE: + // 서비스 일시 불가 - 토큰은 유효하므로 정리하지 않음 + log.warn("FCM 서비스 일시 불가 - 알림 ID: {}", notification.getId()); + break; + + case INTERNAL: + // 내부 오류 - 토큰은 유효하므로 정리하지 않음 + log.warn("FCM 내부 오류 - 알림 ID: {}", notification.getId()); + break; + + default: + log.error("알 수 없는 FCM 오류 - 알림 ID: {}, 에러 코드: {}", notification.getId(), errorCode); + break; } - - /// 읽지 않은 알림 개수 조회 - @Transactional(readOnly = true) - public Long countUnreadNotifications(Long userId) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 읽지 않은 알림 개수 조회 - return pushNotificationRepository.countByUserAndIsReadFalse(user); + } + + /// 사용자의 FCM 토큰 정리 (별도 트랜잭션) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void clearUserFcmToken(User user) { + try { + User currentUser = userRepository.findById(user.getId()).orElse(null); + + if (currentUser != null && currentUser.getFcmToken() != null) { + currentUser.updateFcmToken(null); + userRepository.save(currentUser); + log.info("사용자 FCM 토큰 정리 완료 - 사용자 ID: {}", user.getId()); + } + } catch (Exception e) { + log.error("사용자 FCM 토큰 정리 중 오류 발생 - 사용자 ID: {}, 에러: {}", user.getId(), e.getMessage(), e); } + } - /// Firebase 메시징 예외 처리 (토큰 정리 포함) - private void handleFirebaseMessagingException(PushNotification notification, FirebaseMessagingException exception) { - MessagingErrorCode errorCode = exception.getMessagingErrorCode(); - String errorMessage = exception.getMessage(); - - log.error("FCM 알림 전송 실패 - 알림 ID: {}, 에러 코드: {}, 메시지: {}", - notification.getId(), errorCode, errorMessage); - - // 토큰 관련 오류인 경우 사용자의 FCM 토큰 정리 - switch (errorCode) { - case UNREGISTERED: - // 토큰이 등록되지 않음 (앱 삭제됨) - log.warn("등록되지 않은 FCM 토큰 발견 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); - clearUserFcmToken(notification.getUser()); - break; - - case INVALID_ARGUMENT: - // 잘못된 토큰 형식 - if (errorMessage != null && errorMessage.contains("Requested entity was not found")) { - log.warn("유효하지 않은 FCM 토큰 발견 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); - clearUserFcmToken(notification.getUser()); - } - break; - - case SENDER_ID_MISMATCH: - // 잘못된 발신자 ID - log.warn("FCM 발신자 ID 불일치 - 사용자 ID: {}, 토큰 정리", notification.getUser().getId()); - clearUserFcmToken(notification.getUser()); - break; - - case QUOTA_EXCEEDED: - // 할당량 초과 - 토큰은 유효하므로 정리하지 않음 - log.warn("FCM 할당량 초과 - 알림 ID: {}", notification.getId()); - break; - - case UNAVAILABLE: - // 서비스 일시 불가 - 토큰은 유효하므로 정리하지 않음 - log.warn("FCM 서비스 일시 불가 - 알림 ID: {}", notification.getId()); - break; - - case INTERNAL: - // 내부 오류 - 토큰은 유효하므로 정리하지 않음 - log.warn("FCM 내부 오류 - 알림 ID: {}", notification.getId()); - break; - - default: - log.error("알 수 없는 FCM 오류 - 알림 ID: {}, 에러 코드: {}", notification.getId(), errorCode); - break; - } + /// FCM 토큰 유효성 검증 + public boolean isValidFcmToken(String fcmToken) { + if (fcmToken == null || fcmToken.trim().isEmpty()) { + return false; } - /// 사용자의 FCM 토큰 정리 (별도 트랜잭션) - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void clearUserFcmToken(User user) { - try { - User currentUser = userRepository.findById(user.getId()) - .orElse(null); - - if (currentUser != null && currentUser.getFcmToken() != null) { - currentUser.updateFcmToken(null); - userRepository.save(currentUser); - log.info("사용자 FCM 토큰 정리 완료 - 사용자 ID: {}", user.getId()); - } - } catch (Exception e) { - log.error("사용자 FCM 토큰 정리 중 오류 발생 - 사용자 ID: {}, 에러: {}", - user.getId(), e.getMessage(), e); - } + String trimmedToken = fcmToken.trim(); + if (trimmedToken.length() < 140) { // FCM 토큰은 일반적으로 140자 이상 + return false; } - /// FCM 토큰 유효성 검증 - public boolean isValidFcmToken(String fcmToken) { - if (fcmToken == null || fcmToken.trim().isEmpty()) { - return false; - } - - String trimmedToken = fcmToken.trim(); - if (trimmedToken.length() < 140) { // FCM 토큰은 일반적으로 140자 이상 - return false; - } - - // 기본적인 문자 패턴 검증 (영숫자, 하이픈, 언더스코어, 콜론만 허용) - return trimmedToken.matches("^[a-zA-Z0-9_:.-]+$"); - } -} \ No newline at end of file + // 기본적인 문자 패턴 검증 (영숫자, 하이픈, 언더스코어, 콜론만 허용) + return trimmedToken.matches("^[a-zA-Z0-9_:.-]+$"); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/AdminReportController.java b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/AdminReportController.java index d717133..0f03d5c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/AdminReportController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/AdminReportController.java @@ -20,32 +20,27 @@ @RequiredArgsConstructor public class AdminReportController { - private final ReportService reportService; + private final ReportService reportService; - @PatchMapping("/{reportId}/status") - @Operation(summary = "신고 처리", description = "신고를 처리합니다.") - public ResponseDto processReport( - @PathVariable Long reportId, - @RequestParam ReportStatus status - ) { - return ResponseDto.success(reportService.processReport(reportId, status)); - } + @PatchMapping("/{reportId}/status") + @Operation(summary = "신고 처리", description = "신고를 처리합니다.") + public ResponseDto processReport( + @PathVariable Long reportId, @RequestParam ReportStatus status) { + return ResponseDto.success(reportService.processReport(reportId, status)); + } - @GetMapping - @Operation(summary = "전체 신고 목록 조회", description = "모든 신고 내역을 조회합니다.") - public PageResponseDTO getAllReports( - ReportSearchDTO searchDTO, - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - return new PageResponseDTO<>(reportService.getAllReports(searchDTO, pageable)); - } + @GetMapping + @Operation(summary = "전체 신고 목록 조회", description = "모든 신고 내역을 조회합니다.") + public PageResponseDTO getAllReports( + ReportSearchDTO searchDTO, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable) { + return new PageResponseDTO<>(reportService.getAllReports(searchDTO, pageable)); + } - @GetMapping("/{reportId}") - @Operation(summary = "신고 상세 조회", description = "신고 상세 내역을 조회합니다.") - public ResponseDto getReportDetail( - @PathVariable Long reportId - ) { - return ResponseDto.success(reportService.getReportDetail(reportId)); - } - -} \ No newline at end of file + @GetMapping("/{reportId}") + @Operation(summary = "신고 상세 조회", description = "신고 상세 내역을 조회합니다.") + public ResponseDto getReportDetail(@PathVariable Long reportId) { + return ResponseDto.success(reportService.getReportDetail(reportId)); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java index eb9fab4..a2c4035 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/controller/ReportController.java @@ -22,24 +22,24 @@ @RequiredArgsConstructor public class ReportController { - private final ReportService reportService; + private final ReportService reportService; - @PostMapping - @Operation(summary = "신고하기", description = "게시글이나 댓글을 신고합니다.") - public ResponseDto createReport( - @Valid @RequestBody CreateReportRequestDTO dto, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(reportService.createReport(dto, Long.valueOf(userDetails.getUsername()))); - } - - @GetMapping("/my") - @Operation(summary = "내 신고 목록", description = "자신이 신고한 내역을 조회합니다.") - public PageResponseDTO getMyReports( - @AuthenticationPrincipal UserDetails userDetails, - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - return new PageResponseDTO<>(reportService.getMyReports(Long.valueOf(userDetails.getUsername()), pageable)); - } + @PostMapping + @Operation(summary = "신고하기", description = "게시글이나 댓글을 신고합니다.") + public ResponseDto createReport( + @Valid @RequestBody CreateReportRequestDTO dto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + reportService.createReport(dto, Long.valueOf(userDetails.getUsername()))); + } + @GetMapping("/my") + @Operation(summary = "내 신고 목록", description = "자신이 신고한 내역을 조회합니다.") + public PageResponseDTO getMyReports( + @AuthenticationPrincipal UserDetails userDetails, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable) { + return new PageResponseDTO<>( + reportService.getMyReports(Long.valueOf(userDetails.getUsername()), pageable)); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/CreateReportRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/CreateReportRequestDTO.java index a25dd88..fa63ac2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/CreateReportRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/CreateReportRequestDTO.java @@ -7,12 +7,9 @@ @Getter public class CreateReportRequestDTO { - @NotNull - private Long targetId; - - @NotNull - private ReportTargetType targetType; - - @NotBlank - private String reason; -} \ No newline at end of file + @NotNull private Long targetId; + + @NotNull private ReportTargetType targetType; + + @NotBlank private String reason; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/ReportSearchDTO.java b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/ReportSearchDTO.java index 8ba1759..3b8ef2c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/ReportSearchDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/request/ReportSearchDTO.java @@ -8,6 +8,6 @@ @Getter @Setter public class ReportSearchDTO { - private ReportStatus status; - private ReportTargetType targetType; -} \ No newline at end of file + private ReportStatus status; + private ReportTargetType targetType; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/response/ReportResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/response/ReportResponseDTO.java index 111e822..9b5d7ab 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/dto/response/ReportResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/dto/response/ReportResponseDTO.java @@ -3,42 +3,45 @@ import com.onebyone.kindergarten.domain.reports.entity.Report; import com.onebyone.kindergarten.domain.reports.enums.ReportTargetType; import com.onebyone.kindergarten.global.enums.ReportStatus; -import lombok.Getter; - import java.time.LocalDateTime; +import lombok.Getter; @Getter public class ReportResponseDTO { - private final Long id; - private final String reporterNickname; - private final Long targetId; - private final ReportTargetType targetType; - private final String reason; - private final ReportStatus status; - private final LocalDateTime createdAt; + private final Long id; + private final String reporterNickname; + private final Long targetId; + private final ReportTargetType targetType; + private final String reason; + private final ReportStatus status; + private final LocalDateTime createdAt; - // JPQL 위한 생성자 추가 - public ReportResponseDTO(Long id, String reporterNickname, Long targetId, - ReportTargetType targetType, String reason, - ReportStatus status, LocalDateTime createdAt) { - this.id = id; - this.reporterNickname = reporterNickname; - this.targetId = targetId; - this.targetType = targetType; - this.reason = reason; - this.status = status; - this.createdAt = createdAt; - } + // JPQL 위한 생성자 추가 + public ReportResponseDTO( + Long id, + String reporterNickname, + Long targetId, + ReportTargetType targetType, + String reason, + ReportStatus status, + LocalDateTime createdAt) { + this.id = id; + this.reporterNickname = reporterNickname; + this.targetId = targetId; + this.targetType = targetType; + this.reason = reason; + this.status = status; + this.createdAt = createdAt; + } - public static ReportResponseDTO fromEntity(Report report) { - return new ReportResponseDTO( - report.getId(), - report.getReporter().getNickname(), - report.getTargetId(), - report.getTargetType(), - report.getReason(), - report.getStatus(), - report.getCreatedAt() - ); - } -} \ No newline at end of file + public static ReportResponseDTO fromEntity(Report report) { + return new ReportResponseDTO( + report.getId(), + report.getReporter().getNickname(), + report.getTargetId(), + report.getTargetType(), + report.getReason(), + report.getStatus(), + report.getCreatedAt()); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/entity/Report.java b/src/main/java/com/onebyone/kindergarten/domain/reports/entity/Report.java index 0f97222..3d7d582 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/entity/Report.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/entity/Report.java @@ -5,47 +5,51 @@ import com.onebyone.kindergarten.global.common.BaseEntity; import com.onebyone.kindergarten.global.enums.ReportStatus; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor public class Report extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 신고 코드 - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "reporter_id", nullable = false) - private User reporter; // 신고자 - - @Column(nullable = false) - private Long targetId; // 신고 대상 코드 - - @Enumerated(EnumType.STRING) - private ReportTargetType targetType; // 신고 대상 타입 - 리뷰, 게시글, 댓글 - - @Column(nullable = false) - private String reason; // 신고 사유 - - @Enumerated(EnumType.STRING) - private ReportStatus status; // 신고 상태 - 처리중, 처리완료, 거절 - - @Builder - public Report(User reporter, Long targetId, ReportTargetType targetType, String reason, ReportStatus status) { - this.reporter = reporter; - this.targetId = targetId; - this.targetType = targetType; - this.reason = reason; - this.status = status; - } - - public void updateStatus(ReportStatus status) { - this.status = status; - this.updatedAt = LocalDateTime.now(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 신고 코드 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "reporter_id", nullable = false) + private User reporter; // 신고자 + + @Column(nullable = false) + private Long targetId; // 신고 대상 코드 + + @Enumerated(EnumType.STRING) + private ReportTargetType targetType; // 신고 대상 타입 - 리뷰, 게시글, 댓글 + + @Column(nullable = false) + private String reason; // 신고 사유 + + @Enumerated(EnumType.STRING) + private ReportStatus status; // 신고 상태 - 처리중, 처리완료, 거절 + + @Builder + public Report( + User reporter, + Long targetId, + ReportTargetType targetType, + String reason, + ReportStatus status) { + this.reporter = reporter; + this.targetId = targetId; + this.targetType = targetType; + this.reason = reason; + this.status = status; + } + + public void updateStatus(ReportStatus status) { + this.status = status; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/enums/ReportTargetType.java b/src/main/java/com/onebyone/kindergarten/domain/reports/enums/ReportTargetType.java index acc34b8..2599cf0 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/enums/ReportTargetType.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/enums/ReportTargetType.java @@ -1,3 +1,8 @@ package com.onebyone.kindergarten.domain.reports.enums; -public enum ReportTargetType { WORK_REVIEW, INTERNSHIP_REVIEW, POST, COMMENT } \ No newline at end of file +public enum ReportTargetType { + WORK_REVIEW, + INTERNSHIP_REVIEW, + POST, + COMMENT +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/repository/ReportRepository.java b/src/main/java/com/onebyone/kindergarten/domain/reports/repository/ReportRepository.java index 6a9cf6b..0534882 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/repository/ReportRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/repository/ReportRepository.java @@ -1,10 +1,11 @@ package com.onebyone.kindergarten.domain.reports.repository; +import com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO; import com.onebyone.kindergarten.domain.reports.entity.Report; import com.onebyone.kindergarten.domain.reports.enums.ReportTargetType; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.global.enums.ReportStatus; -import com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -12,34 +13,29 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface ReportRepository extends JpaRepository { - @Query("SELECT new com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO(" + - "r.id, u.nickname, r.targetId, r.targetType, r.reason, r.status, r.createdAt) " + - "FROM Report r " + - "JOIN r.reporter u " + - "WHERE r.reporter = :reporter") - Page findDtosByReporter( - @Param("reporter") User reporter, - Pageable pageable); - - @Query("SELECT new com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO(" + - "r.id, u.nickname, r.targetId, r.targetType, r.reason, r.status, r.createdAt) " + - "FROM Report r " + - "JOIN r.reporter u " + - "WHERE (:status is null OR r.status = :status) " + - "AND (:targetType is null OR r.targetType = :targetType)") - Page findAllDtosByCondition( - @Param("status") ReportStatus status, - @Param("targetType") ReportTargetType targetType, - Pageable pageable); + @Query( + "SELECT new com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO(" + + "r.id, u.nickname, r.targetId, r.targetType, r.reason, r.status, r.createdAt) " + + "FROM Report r " + + "JOIN r.reporter u " + + "WHERE r.reporter = :reporter") + Page findDtosByReporter(@Param("reporter") User reporter, Pageable pageable); - @Query("SELECT r FROM Report r " + - "JOIN FETCH r.reporter " + - "WHERE r.id = :id") - Optional findByIdWithReporter(@Param("id") Long id); + @Query( + "SELECT new com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO(" + + "r.id, u.nickname, r.targetId, r.targetType, r.reason, r.status, r.createdAt) " + + "FROM Report r " + + "JOIN r.reporter u " + + "WHERE (:status is null OR r.status = :status) " + + "AND (:targetType is null OR r.targetType = :targetType)") + Page findAllDtosByCondition( + @Param("status") ReportStatus status, + @Param("targetType") ReportTargetType targetType, + Pageable pageable); + @Query("SELECT r FROM Report r " + "JOIN FETCH r.reporter " + "WHERE r.id = :id") + Optional findByIdWithReporter(@Param("id") Long id); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java b/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java index 990c1a5..5e78499 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/reports/service/ReportService.java @@ -8,146 +8,167 @@ import com.onebyone.kindergarten.domain.kindergartenInternshipReview.repository.KindergartenInternshipReviewRepository; import com.onebyone.kindergarten.domain.kindergartenWorkReview.entity.KindergartenWorkReview; import com.onebyone.kindergarten.domain.kindergartenWorkReview.repository.KindergartenWorkReviewRepository; -import com.onebyone.kindergarten.domain.reports.enums.ReportTargetType; -import com.onebyone.kindergarten.domain.reports.repository.ReportRepository; import com.onebyone.kindergarten.domain.reports.dto.request.CreateReportRequestDTO; +import com.onebyone.kindergarten.domain.reports.dto.request.ReportSearchDTO; import com.onebyone.kindergarten.domain.reports.dto.response.ReportResponseDTO; import com.onebyone.kindergarten.domain.reports.entity.Report; +import com.onebyone.kindergarten.domain.reports.enums.ReportTargetType; +import com.onebyone.kindergarten.domain.reports.repository.ReportRepository; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.enums.ReportStatus; -import com.onebyone.kindergarten.domain.reports.dto.request.ReportSearchDTO; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.concurrent.CompletableFuture; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.concurrent.CompletableFuture; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class ReportService { - private final ReportRepository reportRepository; - private final UserService userService; - private final CommunityRepository communityRepository; - private final CommunityCommentRepository commentRepository; - private final KindergartenWorkReviewRepository kindergartenWorkReviewRepository; - private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; - - /// 신고 생성 (사용자) - @Transactional - public ReportResponseDTO createReport(CreateReportRequestDTO dto, Long userId) { - - // 사용자 조회 - User reporter = userService.getUserById(userId); - - // 신고 대상 존재 여부 확인을 위한 메서드 추가 - validateReportTarget(dto.getTargetType(), dto.getTargetId()); - - // 신고 저장 - Report report = Report.builder() - .reporter(reporter) - .targetId(dto.getTargetId()) - .targetType(dto.getTargetType()) - .reason(dto.getReason()) - .status(ReportStatus.PENDING) - .build(); - - reportRepository.save(report); - return ReportResponseDTO.fromEntity(report); - } - - /// 내 신고 목록 조회 (사용자) - public Page getMyReports(Long userId, Pageable pageable) { - - // 사용자 조회 - User user = userService.getUserById(userId); - - // 신고 목록 조회 - return reportRepository.findDtosByReporter(user, pageable); - } - - /// 신고 처리 (관리자) - @Transactional - public ReportResponseDTO processReport(Long reportId, ReportStatus status) { - - // 신고 존재 여부 확인 - Report report = reportRepository.findByIdWithReporter(reportId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_REPORT)); - - // 신고 상태 업데이트 - report.updateStatus(status); - - // 신고 대상 상태 업데이트 - CompletableFuture.runAsync(() -> - _updateTargetStatus(report.getTargetType(), report.getTargetId(), status) - ); - - return ReportResponseDTO.fromEntity(report); - } - - /// 전체 신고 목록 조회 (관리자) - public Page getAllReports(ReportSearchDTO searchDTO, Pageable pageable) { - - // 신고 목록 조회 - return reportRepository.findAllDtosByCondition( - searchDTO.getStatus(), - searchDTO.getTargetType(), - pageable - ); - } - - /// 신고 상세 조회 (관리자) - public ReportResponseDTO getReportDetail(Long reportId) { - - // 신고 상세 조회 - return reportRepository.findByIdWithReporter(reportId) - .map(ReportResponseDTO::fromEntity) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_REPORT)); + private final ReportRepository reportRepository; + private final UserService userService; + private final CommunityRepository communityRepository; + private final CommunityCommentRepository commentRepository; + private final KindergartenWorkReviewRepository kindergartenWorkReviewRepository; + private final KindergartenInternshipReviewRepository kindergartenInternshipReviewRepository; + + /// 신고 생성 (사용자) + @Transactional + public ReportResponseDTO createReport(CreateReportRequestDTO dto, Long userId) { + + // 사용자 조회 + User reporter = userService.getUserById(userId); + + // 신고 대상 존재 여부 확인을 위한 메서드 추가 + validateReportTarget(dto.getTargetType(), dto.getTargetId()); + + // 신고 저장 + Report report = + Report.builder() + .reporter(reporter) + .targetId(dto.getTargetId()) + .targetType(dto.getTargetType()) + .reason(dto.getReason()) + .status(ReportStatus.PENDING) + .build(); + + reportRepository.save(report); + return ReportResponseDTO.fromEntity(report); + } + + /// 내 신고 목록 조회 (사용자) + public Page getMyReports(Long userId, Pageable pageable) { + + // 사용자 조회 + User user = userService.getUserById(userId); + + // 신고 목록 조회 + return reportRepository.findDtosByReporter(user, pageable); + } + + /// 신고 처리 (관리자) + @Transactional + public ReportResponseDTO processReport(Long reportId, ReportStatus status) { + + // 신고 존재 여부 확인 + Report report = + reportRepository + .findByIdWithReporter(reportId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_REPORT)); + + // 신고 상태 업데이트 + report.updateStatus(status); + + // 신고 대상 상태 업데이트 + CompletableFuture.runAsync( + () -> _updateTargetStatus(report.getTargetType(), report.getTargetId(), status)); + + return ReportResponseDTO.fromEntity(report); + } + + /// 전체 신고 목록 조회 (관리자) + public Page getAllReports(ReportSearchDTO searchDTO, Pageable pageable) { + + // 신고 목록 조회 + return reportRepository.findAllDtosByCondition( + searchDTO.getStatus(), searchDTO.getTargetType(), pageable); + } + + /// 신고 상세 조회 (관리자) + public ReportResponseDTO getReportDetail(Long reportId) { + + // 신고 상세 조회 + return reportRepository + .findByIdWithReporter(reportId) + .map(ReportResponseDTO::fromEntity) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_REPORT)); + } + + /// 신고 대상 상태 업데이트 + private void _updateTargetStatus( + ReportTargetType targetType, Long targetId, ReportStatus status) { + switch (targetType) { + case POST -> { + CommunityPost post = + communityRepository + .findById(targetId) + .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_POST_TARGET)); + post.updateStatus(status); + } + case COMMENT -> { + CommunityComment comment = + commentRepository + .findById(targetId) + .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_COMMENT_TARGET)); + comment.updateStatus(status); + } + case WORK_REVIEW -> { + KindergartenWorkReview kindergartenWorkReview = + kindergartenWorkReviewRepository + .findById(targetId) + .orElseThrow( + () -> new BusinessException(ErrorCodes.INVALID_REPORT_WORK_REVIEW_TARGET)); + kindergartenWorkReview.updateStatus(status); + } + case INTERNSHIP_REVIEW -> { + KindergartenInternshipReview kindergartenInternshipReview = + kindergartenInternshipReviewRepository + .findById(targetId) + .orElseThrow( + () -> + new BusinessException(ErrorCodes.INVALID_REPORT_INTERNSHIP_REVIEW_TARGET)); + kindergartenInternshipReview.updateStatus(status); + } + default -> throw new BusinessException(ErrorCodes.INVALID_REPORT_TARGET_TYPE); } - - /// 신고 대상 상태 업데이트 - private void _updateTargetStatus(ReportTargetType targetType, Long targetId, ReportStatus status) { - switch (targetType) { - case POST -> { - CommunityPost post = communityRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_POST_TARGET)); - post.updateStatus(status); - } - case COMMENT -> { - CommunityComment comment = commentRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_COMMENT_TARGET)); - comment.updateStatus(status); - } - case WORK_REVIEW -> { - KindergartenWorkReview kindergartenWorkReview = kindergartenWorkReviewRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_WORK_REVIEW_TARGET)); - kindergartenWorkReview.updateStatus(status); - } - case INTERNSHIP_REVIEW -> { - KindergartenInternshipReview kindergartenInternshipReview = kindergartenInternshipReviewRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_INTERNSHIP_REVIEW_TARGET)); - kindergartenInternshipReview.updateStatus(status); - } - default -> throw new BusinessException(ErrorCodes.INVALID_REPORT_TARGET_TYPE); - } - } - - /// 신고 대상 검증 - private void validateReportTarget(ReportTargetType targetType, Long targetId) { - switch (targetType) { - case POST -> communityRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_POST_TARGET)); - case COMMENT -> commentRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_COMMENT_TARGET)); - case WORK_REVIEW -> kindergartenWorkReviewRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_WORK_REVIEW_TARGET)); - case INTERNSHIP_REVIEW -> kindergartenInternshipReviewRepository.findById(targetId) - .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_INTERNSHIP_REVIEW_TARGET)); - } + } + + /// 신고 대상 검증 + private void validateReportTarget(ReportTargetType targetType, Long targetId) { + switch (targetType) { + case POST -> + communityRepository + .findById(targetId) + .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_POST_TARGET)); + case COMMENT -> + commentRepository + .findById(targetId) + .orElseThrow(() -> new BusinessException(ErrorCodes.INVALID_REPORT_COMMENT_TARGET)); + case WORK_REVIEW -> + kindergartenWorkReviewRepository + .findById(targetId) + .orElseThrow( + () -> new BusinessException(ErrorCodes.INVALID_REPORT_WORK_REVIEW_TARGET)); + case INTERNSHIP_REVIEW -> + kindergartenInternshipReviewRepository + .findById(targetId) + .orElseThrow( + () -> new BusinessException(ErrorCodes.INVALID_REPORT_INTERNSHIP_REVIEW_TARGET)); } + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java b/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java index 461cba8..87d3c49 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/controller/AdminUserController.java @@ -23,45 +23,42 @@ @Tag(name = "유저 관리", description = "유저 관리 API (관리자용)") @RequiredArgsConstructor public class AdminUserController { - - private final UserService userService; - @GetMapping - @Operation(summary = "전체 유저 목록 조회", description = "모든 유저 목록 리스트 조회") - public PageResponseDTO getAllUsers( - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - Page users = userService.getAllUsers(pageable); - return new PageResponseDTO<>(users); - } + private final UserService userService; - @GetMapping("/search") - @Operation(summary = "유저 검색", description = "조건부 유저 검색") - public PageResponseDTO searchUsers( - UserSearchDTO searchDTO, - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - Page users = userService.searchUsers(searchDTO, pageable); - return new PageResponseDTO<>(users); - } + @GetMapping + @Operation(summary = "전체 유저 목록 조회", description = "모든 유저 목록 리스트 조회") + public PageResponseDTO getAllUsers( + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable) { + Page users = userService.getAllUsers(pageable); + return new PageResponseDTO<>(users); + } - @GetMapping("/{userId}") - @Operation(summary = "유저 상세 조회", description = "특정 유저 상세 정보 조회") - public ResponseDto getUserDetail( - @PathVariable Long userId - ) { - AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); - return ResponseDto.success(user); - } + @GetMapping("/search") + @Operation(summary = "유저 검색", description = "조건부 유저 검색") + public PageResponseDTO searchUsers( + UserSearchDTO searchDTO, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable) { + Page users = userService.searchUsers(searchDTO, pageable); + return new PageResponseDTO<>(users); + } - @PatchMapping("/{userId}/status") - @Operation(summary = "유저 상태 변경", description = "관리자가 유저의 상태를 변경합니다. (ACTIVE, SUSPENDED, DELETED)") - public ResponseDto updateUserStatus( - @PathVariable Long userId, - @Valid @RequestBody UpdateUserStatusRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - userService.updateUserStatus(userId, request); - return ResponseDto.success("유저 상태가 변경되었습니다."); - } + @GetMapping("/{userId}") + @Operation(summary = "유저 상세 조회", description = "특정 유저 상세 정보 조회") + public ResponseDto getUserDetail(@PathVariable Long userId) { + AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); + return ResponseDto.success(user); + } + + @PatchMapping("/{userId}/status") + @Operation(summary = "유저 상태 변경", description = "관리자가 유저의 상태를 변경합니다. (ACTIVE, SUSPENDED, DELETED)") + public ResponseDto updateUserStatus( + @PathVariable Long userId, + @Valid @RequestBody UpdateUserStatusRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + userService.updateUserStatus(userId, request); + return ResponseDto.success("유저 상태가 변경되었습니다."); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java index 36d98d9..5f5aaa6 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/controller/UserApiController.java @@ -1,8 +1,6 @@ package com.onebyone.kindergarten.domain.user.controller; import com.onebyone.kindergarten.domain.communityComments.dto.response.PageCommunityCommentsResponseDTO; -import com.onebyone.kindergarten.domain.user.enums.UserRole; -import com.onebyone.kindergarten.global.facade.UserFacade; import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewPagedResponseDTO; import com.onebyone.kindergarten.domain.user.dto.HomeShortcutsDto; @@ -13,10 +11,12 @@ import com.onebyone.kindergarten.domain.user.dto.response.SignInResponseDTO; import com.onebyone.kindergarten.domain.user.dto.response.SignUpResponseDTO; import com.onebyone.kindergarten.domain.user.dto.response.UpdateHomeShortcutsResponseDTO; +import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.common.ResponseDto; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import com.onebyone.kindergarten.global.facade.UserFacade; import com.onebyone.kindergarten.global.jwt.JwtProvider; import io.jsonwebtoken.Claims; import io.swagger.v3.oas.annotations.Operation; @@ -24,6 +24,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import org.apache.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -31,233 +34,230 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - @Tag(name = "유저 API", description = "유저 API") @RestController @RequestMapping("/users") @RequiredArgsConstructor public class UserApiController { - private final UserFacade userFacade; - private final UserService userService; - private final JwtProvider jwtProvider; - - @Operation(summary = "유저-01 회원가입", description = "계정 생성합니다.") - @PostMapping("/sign-up") - public SignUpResponseDTO signUp( - @RequestBody SignUpRequestDTO request) { - return userFacade.signUp(request); - } - - @Operation(summary = "유저-02 로그인", description = "로그인 입니다") - @PostMapping("/sign-in") - public SignInResponseDTO signIn( - @RequestBody SignInRequestDTO request) { - return userFacade.signIn(request); + private final UserFacade userFacade; + private final UserService userService; + private final JwtProvider jwtProvider; + + @Operation(summary = "유저-01 회원가입", description = "계정 생성합니다.") + @PostMapping("/sign-up") + public SignUpResponseDTO signUp(@RequestBody SignUpRequestDTO request) { + return userFacade.signUp(request); + } + + @Operation(summary = "유저-02 로그인", description = "로그인 입니다") + @PostMapping("/sign-in") + public SignInResponseDTO signIn(@RequestBody SignInRequestDTO request) { + return userFacade.signIn(request); + } + + @Operation(summary = "유저-03 비밀번호 변경", description = "비밀번호 변경입니다.") + @PatchMapping("/password") + public void changePassword( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody ModifyUserPasswordRequestDTO request) { + userService.changePassword(Long.valueOf(userDetails.getUsername()), request); + } + + @Operation(summary = "유저-04 닉네임 변경", description = "닉네임 변경입니다.") + @PatchMapping("/nickname") + public void changeNickname( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody ModifyUserNicknameRequestDTO request) { + userService.changeNickname(Long.valueOf(userDetails.getUsername()), request); + } + + @Operation(summary = "유저-05 회원탈퇴", description = "회원 탈퇴입니다.") + @PostMapping("/withdraw") + public void withdraw(@AuthenticationPrincipal UserDetails userDetails) { + userService.withdraw(Long.valueOf(userDetails.getUsername())); + } + + @Operation(summary = "유저-06 유저정보", description = "유저 조회입니다.") + @GetMapping + public GetUserResponseDTO getUser(@AuthenticationPrincipal UserDetails userDetails) { + return new GetUserResponseDTO( + userService.getUserToDTO(Long.valueOf(userDetails.getUsername()))); + } + + @Operation(summary = "유저-07 토큰 재발급", description = "토큰 재발급입니다.") + @PostMapping("/reissue") + public ReIssueResponseDTO reissue(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); } - @Operation(summary = "유저-03 비밀번호 변경", description = "비밀번호 변경입니다.") - @PatchMapping("/password") - public void changePassword( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody ModifyUserPasswordRequestDTO request) { - userService.changePassword(Long.valueOf(userDetails.getUsername()), request); + String refreshToken = authHeader.substring(7); + + Claims claim = jwtProvider.getClaimFromRefreshToken(refreshToken); + + String newAccessToken = + jwtProvider.generateAccessToken( + Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); + String newRefreshToken = + jwtProvider.generateAccessToken( + Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); + + return ReIssueResponseDTO.builder() + .accessToken(newAccessToken) + .refreshToken(newRefreshToken) + .build(); + } + + @Operation(summary = "유저-08 카카오 소셜 로그인", description = "카카오 소셜로그인을 진행합니다") + @GetMapping("/kakao/callback") + public SignInResponseDTO getKakaoAuthorizationCode( + @RequestParam(name = "code") String code, + @RequestParam(name = "fcmToken", required = false) String fcmToken) { + return userFacade.kakaoLogin(code, fcmToken); + } + + @Operation(summary = "유저-09 네이버 소셜 로그인", description = "네이버 소셜로그인을 진행합니다") + @GetMapping("/naver/callback") + public SignInResponseDTO getNaverAuthorizationCode( + @RequestParam(name = "code") String code, + @RequestParam(name = "state") String state, + @RequestParam(name = "fcmToken", required = false) String fcmToken) { + return userFacade.naverLogin(code, state, fcmToken); + } + + @Operation(summary = "유저-10 애플 소셜 로그인", description = "애플 소셜로그인을 진행합니다") + @PostMapping("/apple/callback") + public void appleCallback( + @RequestParam("id_token") String idToken, + @RequestParam(value = "code", required = false) String code, + @RequestParam(value = "state", required = false) String state, + HttpServletResponse response) + throws IOException { + + try { + /// 애플 로그인 처리 (state 파라미터 - FCM 토큰 사용) + /// -> 애플 정책상 state 파라미터 가능 + SignInResponseDTO loginResponse = userFacade.appleLogin(idToken, state); + + /// 프론트엔드로 리다이렉트 (성공) + String frontendUrl = "https://one-by-one-fe.vercel.app/users/apple/callback"; + String redirectUrl = + String.format( + "%s?access_token=%s&refresh_token=%s", + frontendUrl, loginResponse.getAccessToken(), loginResponse.getRefreshToken()); + + response.sendRedirect(redirectUrl); + + } catch (Exception e) { + /// 프론트엔드로 리다이렉트 (에러) + String frontendUrl = "https://one-by-one-fe.vercel.app/users/apple/callback"; + String redirectUrl = + String.format( + "%s?error=login_failed&message=%s", + frontendUrl, URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8)); + + response.sendRedirect(redirectUrl); } - - @Operation(summary = "유저-04 닉네임 변경", description = "닉네임 변경입니다.") - @PatchMapping("/nickname") - public void changeNickname( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody ModifyUserNicknameRequestDTO request) { - userService.changeNickname(Long.valueOf(userDetails.getUsername()), request); - } - - @Operation(summary = "유저-05 회원탈퇴", description = "회원 탈퇴입니다.") - @PostMapping("/withdraw") - public void withdraw( - @AuthenticationPrincipal UserDetails userDetails) { - userService.withdraw(Long.valueOf(userDetails.getUsername())); + } + + @Operation(summary = "유저-010 작성한 댓글 조회", description = "작성한 댓글을 조회합니다.") + @GetMapping("/user/community-comments") + public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return userFacade.getWroteMyCommunityComments( + Long.valueOf(userDetails.getUsername()), page, size); + } + + @Operation(summary = "유저-11 홈 바로가기 정보 업데이트", description = "사용자의 홈 바로가기 정보를 업데이트합니다.") + @PutMapping("/shortcuts") + public UpdateHomeShortcutsResponseDTO updateHomeShortcut( + @AuthenticationPrincipal UserDetails userDetails, + @Valid @RequestBody HomeShortcutsDto request) { + userService.updateHomeShortcut(Long.valueOf(userDetails.getUsername()), request); + return UpdateHomeShortcutsResponseDTO.success(); + } + + @Operation(summary = "유저-12 이메일 인증 번호 발송", description = "인증번호를 발송합니다.") + @PostMapping("/email-certification") + public ResponseEntity emailCertification( + @RequestBody EmailCertificationRequestDTO request) { + boolean isSent = userFacade.emailCertification(request); + + if (isSent) { + return ResponseEntity.ok("인증번호가 성공적으로 발송되었습니다."); + } else { + return ResponseEntity.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).body("인증번호 발송에 실패했습니다."); } - - @Operation(summary = "유저-06 유저정보", description = "유저 조회입니다.") - @GetMapping - public GetUserResponseDTO getUser( - @AuthenticationPrincipal UserDetails userDetails) { - return new GetUserResponseDTO(userService.getUserToDTO(Long.valueOf(userDetails.getUsername()))); - } - - @Operation(summary = "유저-07 토큰 재발급", description = "토큰 재발급입니다.") - @PostMapping("/reissue") - public ReIssueResponseDTO reissue( - HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); - } - - String refreshToken = authHeader.substring(7); - - Claims claim = jwtProvider.getClaimFromRefreshToken(refreshToken); - - String newAccessToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); - String newRefreshToken = jwtProvider.generateAccessToken(Long.valueOf(claim.getSubject()), UserRole.valueOf(claim.get("role", String.class))); - - return ReIssueResponseDTO.builder() - .accessToken(newAccessToken) - .refreshToken(newRefreshToken) - .build(); - } - - @Operation(summary = "유저-08 카카오 소셜 로그인", description = "카카오 소셜로그인을 진행합니다") - @GetMapping("/kakao/callback") - public SignInResponseDTO getKakaoAuthorizationCode( - @RequestParam(name = "code") String code, - @RequestParam(name = "fcmToken", required = false) String fcmToken) { - return userFacade.kakaoLogin(code, fcmToken); + } + + @Operation(summary = "유저-13 이메일 인증 번호 검증", description = "인증번호를 검증합니다.") + @PostMapping("/check-email-certification") + public ResponseEntity checkEmailCertification( + @RequestBody CheckEmailCertificationRequestDTO request) { + userService.checkEmailCertification(request); + return ResponseEntity.ok("이메일 인증에 성공했습니다."); + } + + @Operation(summary = "유저-14 유저 역할 변경", description = "사용자의 역할(교사, 예비교사) 수정합니다.") + @PostMapping("/role") + public ResponseEntity updateUserRole( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody UpdateUserRoleRequestDTO request) { + userService.updateUserRole(Long.valueOf(userDetails.getUsername()), request); + return ResponseEntity.ok("권한이 변경되었습니다."); + } + + @Operation(summary = "유저-15 이메일 검증 및 임시 비밀번호 발급", description = "유저의 비밀번호를 임시 비밀번호로 변경합니다.") + @PatchMapping("/temporary-password") + public ResponseEntity updateTemporaryPasswordCertification( + @RequestBody UpdateTemporaryPasswordRequestDTO request) { + boolean isSent = userFacade.updateTemporaryPasswordCertification(request); + + if (isSent) { + return ResponseEntity.ok("임시 비밀번호 변경이 완료되었습니다."); + } else { + return ResponseEntity.status(HttpStatus.SC_BAD_REQUEST).body("임시 비밀번호 변경이 실패했습니다."); } - - @Operation(summary = "유저-09 네이버 소셜 로그인", description = "네이버 소셜로그인을 진행합니다") - @GetMapping("/naver/callback") - public SignInResponseDTO getNaverAuthorizationCode( - @RequestParam(name = "code") String code, - @RequestParam(name = "state") String state, - @RequestParam(name = "fcmToken", required = false) String fcmToken) { - return userFacade.naverLogin(code, state, fcmToken); - } - - @Operation(summary = "유저-10 애플 소셜 로그인", description = "애플 소셜로그인을 진행합니다") - @PostMapping("/apple/callback") - public void appleCallback( - @RequestParam("id_token") String idToken, - @RequestParam(value = "code", required = false) String code, - @RequestParam(value = "state", required = false) String state, - HttpServletResponse response) throws IOException { - - try { - /// 애플 로그인 처리 (state 파라미터 - FCM 토큰 사용) - /// -> 애플 정책상 state 파라미터 가능 - SignInResponseDTO loginResponse = userFacade.appleLogin(idToken, state); - - /// 프론트엔드로 리다이렉트 (성공) - String frontendUrl = "https://one-by-one-fe.vercel.app/users/apple/callback"; - String redirectUrl = String.format("%s?access_token=%s&refresh_token=%s", - frontendUrl, - loginResponse.getAccessToken(), - loginResponse.getRefreshToken()); - - response.sendRedirect(redirectUrl); - - } catch (Exception e) { - /// 프론트엔드로 리다이렉트 (에러) - String frontendUrl = "https://one-by-one-fe.vercel.app/users/apple/callback"; - String redirectUrl = String.format("%s?error=login_failed&message=%s", - frontendUrl, - URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8)); - - response.sendRedirect(redirectUrl); - } - } - - @Operation(summary = "유저-010 작성한 댓글 조회", description = "작성한 댓글을 조회합니다.") - @GetMapping("/user/community-comments") - public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( - @AuthenticationPrincipal UserDetails userDetails, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { - return userFacade.getWroteMyCommunityComments(Long.valueOf(userDetails.getUsername()), page, size); - } - - @Operation(summary = "유저-11 홈 바로가기 정보 업데이트", description = "사용자의 홈 바로가기 정보를 업데이트합니다.") - @PutMapping("/shortcuts") - public UpdateHomeShortcutsResponseDTO updateHomeShortcut( - @AuthenticationPrincipal UserDetails userDetails, - @Valid @RequestBody HomeShortcutsDto request) { - userService.updateHomeShortcut(Long.valueOf(userDetails.getUsername()), request); - return UpdateHomeShortcutsResponseDTO.success(); - } - - @Operation(summary = "유저-12 이메일 인증 번호 발송", description = "인증번호를 발송합니다.") - @PostMapping("/email-certification") - public ResponseEntity emailCertification( - @RequestBody EmailCertificationRequestDTO request) { - boolean isSent = userFacade.emailCertification(request); - - if (isSent) { - return ResponseEntity.ok("인증번호가 성공적으로 발송되었습니다."); - } else { - return ResponseEntity.status(HttpStatus.SC_INTERNAL_SERVER_ERROR) - .body("인증번호 발송에 실패했습니다."); - } - } - - @Operation(summary = "유저-13 이메일 인증 번호 검증", description = "인증번호를 검증합니다.") - @PostMapping("/check-email-certification") - public ResponseEntity checkEmailCertification( - @RequestBody CheckEmailCertificationRequestDTO request) { - userService.checkEmailCertification(request); - return ResponseEntity.ok("이메일 인증에 성공했습니다."); - } - - @Operation(summary = "유저-14 유저 역할 변경", description = "사용자의 역할(교사, 예비교사) 수정합니다.") - @PostMapping("/role") - public ResponseEntity updateUserRole( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody UpdateUserRoleRequestDTO request) { - userService.updateUserRole(Long.valueOf(userDetails.getUsername()), request); - return ResponseEntity.ok("권한이 변경되었습니다."); - } - - @Operation(summary = "유저-15 이메일 검증 및 임시 비밀번호 발급", description = "유저의 비밀번호를 임시 비밀번호로 변경합니다.") - @PatchMapping("/temporary-password") - public ResponseEntity updateTemporaryPasswordCertification( - @RequestBody UpdateTemporaryPasswordRequestDTO request) { - boolean isSent = userFacade.updateTemporaryPasswordCertification(request); - - if (isSent) { - return ResponseEntity.ok("임시 비밀번호 변경이 완료되었습니다."); - } else { - return ResponseEntity.status(HttpStatus.SC_BAD_REQUEST) - .body("임시 비밀번호 변경이 실패했습니다."); - } - } - - @Operation(summary = "유저-016 내가 작성한 실습 리뷰 조회", description = "내가 작성한 실습 리뷰를 조회합니다.") - @GetMapping("/user/internship-reviews") - public InternshipReviewPagedResponseDTO getMyInternshipReviews( - @AuthenticationPrincipal UserDetails userDetails, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { - return userFacade.getMyInternshipReviews(Long.valueOf(userDetails.getUsername()), page, size); - } - - @Operation(summary = "유저-017 내가 작성한 근무 리뷰 조회", description = "내가 작성한 근무 리뷰를 조회합니다.") - @GetMapping("/user/work-reviews") - public WorkReviewPagedResponseDTO getMyWorkReviews( - @AuthenticationPrincipal UserDetails userDetails, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { - return userFacade.getMyWorkReviews(Long.valueOf(userDetails.getUsername()), page, size); - } - - @Operation(summary = "유저-018 알림 설정 조회", description = "사용자의 알림 설정을 조회합니다.") - @GetMapping("/notification-settings") - public ResponseDto getNotificationSettings( - @AuthenticationPrincipal UserDetails userDetails - ) { - NotificationSettingsDTO settings = userService.getNotificationSettings(Long.valueOf(userDetails.getUsername())); - return ResponseDto.success(settings); - } - - @Operation(summary = "유저-019 알림 설정 업데이트", description = "사용자의 알림 설정을 업데이트합니다.") - @PutMapping("/notification-settings") - public ResponseDto updateNotificationSettings( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody NotificationSettingsDTO request) { - NotificationSettingsDTO updatedSettings = userService.updateNotificationSettings(Long.valueOf(userDetails.getUsername()), request); - return ResponseDto.success(updatedSettings); - } - + } + + @Operation(summary = "유저-016 내가 작성한 실습 리뷰 조회", description = "내가 작성한 실습 리뷰를 조회합니다.") + @GetMapping("/user/internship-reviews") + public InternshipReviewPagedResponseDTO getMyInternshipReviews( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return userFacade.getMyInternshipReviews(Long.valueOf(userDetails.getUsername()), page, size); + } + + @Operation(summary = "유저-017 내가 작성한 근무 리뷰 조회", description = "내가 작성한 근무 리뷰를 조회합니다.") + @GetMapping("/user/work-reviews") + public WorkReviewPagedResponseDTO getMyWorkReviews( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return userFacade.getMyWorkReviews(Long.valueOf(userDetails.getUsername()), page, size); + } + + @Operation(summary = "유저-018 알림 설정 조회", description = "사용자의 알림 설정을 조회합니다.") + @GetMapping("/notification-settings") + public ResponseDto getNotificationSettings( + @AuthenticationPrincipal UserDetails userDetails) { + NotificationSettingsDTO settings = + userService.getNotificationSettings(Long.valueOf(userDetails.getUsername())); + return ResponseDto.success(settings); + } + + @Operation(summary = "유저-019 알림 설정 업데이트", description = "사용자의 알림 설정을 업데이트합니다.") + @PutMapping("/notification-settings") + public ResponseDto updateNotificationSettings( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody NotificationSettingsDTO request) { + NotificationSettingsDTO updatedSettings = + userService.updateNotificationSettings(Long.valueOf(userDetails.getUsername()), request); + return ResponseDto.success(updatedSettings); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/HomeShortcutsDto.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/HomeShortcutsDto.java index d69f115..a9d60f4 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/HomeShortcutsDto.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/HomeShortcutsDto.java @@ -6,75 +6,71 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Getter @NoArgsConstructor @AllArgsConstructor @Builder public class HomeShortcutsDto { - @Valid - @NotNull(message = "바로가기 목록은 null이 될 수 없습니다") - private List shortcuts; + @Valid + @NotNull(message = "바로가기 목록은 null이 될 수 없습니다") private List shortcuts; - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class ShortcutItem { - @NotBlank(message = "바로가기 이름은 필수입니다") - @Size(max = 50, message = "바로가기 이름은 50자를 초과할 수 없습니다") - private String name; // 바로가기 이름 + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ShortcutItem { + @NotBlank(message = "바로가기 이름은 필수입니다") + @Size(max = 50, message = "바로가기 이름은 50자를 초과할 수 없습니다") + private String name; // 바로가기 이름 - @NotBlank(message = "아이콘명은 필수입니다") - @Size(max = 100, message = "아이콘명은 100자를 초과할 수 없습니다") - private String iconName; // 바로가기 아이콘명 + @NotBlank(message = "아이콘명은 필수입니다") + @Size(max = 100, message = "아이콘명은 100자를 초과할 수 없습니다") + private String iconName; // 바로가기 아이콘명 - @NotBlank(message = "링크는 필수입니다") - @Size(max = 100, message = "링크는 100자를 초과할 수 없습니다") - private String link; // 바로가기 링크 - } + @NotBlank(message = "링크는 필수입니다") + @Size(max = 100, message = "링크는 100자를 초과할 수 없습니다") + private String link; // 바로가기 링크 + } - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class Response { - private List shortcuts; + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class Response { + private List shortcuts; - public static Response from(HomeShortcutsDto dto) { - return Response.builder() - .shortcuts(dto.getShortcuts()) - .build(); - } + public static Response from(HomeShortcutsDto dto) { + return Response.builder().shortcuts(dto.getShortcuts()).build(); } + } - public String toJson() { - try { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - throw new RuntimeException("JSON 변환 중 오류가 발생했습니다.", e); - } + public String toJson() { + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException("JSON 변환 중 오류가 발생했습니다.", e); } + } - public static HomeShortcutsDto fromJson(String json) { - try { - if (json == null || json.isEmpty()) { - return new HomeShortcutsDto(new ArrayList<>()); - } + public static HomeShortcutsDto fromJson(String json) { + try { + if (json == null || json.isEmpty()) { + return new HomeShortcutsDto(new ArrayList<>()); + } - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(json, HomeShortcutsDto.class); - } catch (JsonProcessingException e) { - throw new RuntimeException("JSON 파싱 중 오류가 발생했습니다.", e); - } + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(json, HomeShortcutsDto.class); + } catch (JsonProcessingException e) { + throw new RuntimeException("JSON 파싱 중 오류가 발생했습니다.", e); } -} \ No newline at end of file + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java index 0142d38..5da1bb9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/JwtUserInfoDto.java @@ -7,6 +7,6 @@ @Data @AllArgsConstructor public class JwtUserInfoDto { - private Long userId; - private UserRole role; + private Long userId; + private UserRole role; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/NotificationSettingsDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/NotificationSettingsDTO.java index 0262cb7..8319100 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/NotificationSettingsDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/NotificationSettingsDTO.java @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor public class NotificationSettingsDTO { - private boolean allNotificationsEnabled; - private boolean communityNotificationsEnabled; - private boolean eventNotificationsEnabled; -} \ No newline at end of file + private boolean allNotificationsEnabled; + private boolean communityNotificationsEnabled; + private boolean eventNotificationsEnabled; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/SimpleUserDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/SimpleUserDTO.java index 3c28218..65e0c94 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/SimpleUserDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/SimpleUserDTO.java @@ -4,11 +4,11 @@ @Data public class SimpleUserDTO { - private Long userId; - private String nickname; + private Long userId; + private String nickname; - public SimpleUserDTO(Long userId, String nickname) { - this.userId = userId; - this.nickname = nickname; - } + public SimpleUserDTO(Long userId, String nickname) { + this.userId = userId; + this.nickname = nickname; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/UserDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/UserDTO.java index 51882c7..057410b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/UserDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/UserDTO.java @@ -3,47 +3,46 @@ import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenDTO; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Data; -import java.time.LocalDateTime; - @Data @AllArgsConstructor public class UserDTO { - private Long userId; - private String nickname; - private String profileImageUrl; - private String role; - private String career; - private KindergartenDTO kindergarten; - private HomeShortcutsDto.Response homeShortcut; - private boolean isRestoredUser; - private LocalDateTime previousDeletedAt; - private boolean allNotificationsEnabled; - private boolean communityNotificationsEnabled; - private boolean eventNotificationsEnabled; - private boolean hasWrittenReview; + private Long userId; + private String nickname; + private String profileImageUrl; + private String role; + private String career; + private KindergartenDTO kindergarten; + private HomeShortcutsDto.Response homeShortcut; + private boolean isRestoredUser; + private LocalDateTime previousDeletedAt; + private boolean allNotificationsEnabled; + private boolean communityNotificationsEnabled; + private boolean eventNotificationsEnabled; + private boolean hasWrittenReview; + + public static UserDTO from(User user) { + HomeShortcutsDto homeShortcutsDto = + user.getHomeShortcut() != null + ? HomeShortcutsDto.fromJson(user.getHomeShortcut()) + : new HomeShortcutsDto(); - public static UserDTO from(User user) { - HomeShortcutsDto homeShortcutsDto = user.getHomeShortcut() != null ? - HomeShortcutsDto.fromJson(user.getHomeShortcut()) : - new HomeShortcutsDto(); - - return new UserDTO( - user.getId(), - user.getNickname(), - user.getProfileImageUrl(), - user.getRole().name(), - user.getCareer(), - user.getKindergarten() != null ? KindergartenDTO.from(user.getKindergarten()) : null, - HomeShortcutsDto.Response.from(homeShortcutsDto), - user.isRestoredUser(), - user.getPreviousDeletedAt(), - user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS), - user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS), - user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS), - user.hasWrittenReview() - ); - } -} \ No newline at end of file + return new UserDTO( + user.getId(), + user.getNickname(), + user.getProfileImageUrl(), + user.getRole().name(), + user.getCareer(), + user.getKindergarten() != null ? KindergartenDTO.from(user.getKindergarten()) : null, + HomeShortcutsDto.Response.from(homeShortcutsDto), + user.isRestoredUser(), + user.getPreviousDeletedAt(), + user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS), + user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS), + user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS), + user.hasWrittenReview()); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoAccount.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoAccount.java index 4e30cab..c408dde 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoAccount.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoAccount.java @@ -4,6 +4,6 @@ @Data public class KakaoAccount { - private String email; - private KakaoProfile profile; + private String email; + private KakaoProfile profile; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoProfile.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoProfile.java index 435e751..39d6394 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoProfile.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/kakao/KakaoProfile.java @@ -4,6 +4,6 @@ @Data public class KakaoProfile { - private String nickname; - private String profile_image_url; -} \ No newline at end of file + private String nickname; + private String profile_image_url; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/CheckEmailCertificationRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/CheckEmailCertificationRequestDTO.java index e20e3bf..b0096fa 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/CheckEmailCertificationRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/CheckEmailCertificationRequestDTO.java @@ -4,6 +4,6 @@ @Data public class CheckEmailCertificationRequestDTO { - private String email; - private String certification; + private String email; + private String certification; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/EmailCertificationRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/EmailCertificationRequestDTO.java index a9c8c10..d0aab8d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/EmailCertificationRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/EmailCertificationRequestDTO.java @@ -5,6 +5,6 @@ @Data public class EmailCertificationRequestDTO { - String email; - EmailCertificationType certificationType; + String email; + EmailCertificationType certificationType; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserNicknameRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserNicknameRequestDTO.java index 2af3828..265554f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserNicknameRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserNicknameRequestDTO.java @@ -4,5 +4,5 @@ @Getter public class ModifyUserNicknameRequestDTO { - private String newNickname; + private String newNickname; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserPasswordRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserPasswordRequestDTO.java index 966ceb3..0abe8cc 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserPasswordRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/ModifyUserPasswordRequestDTO.java @@ -4,6 +4,6 @@ @Getter public class ModifyUserPasswordRequestDTO { - private String currentPassword; - private String newPassword; + private String currentPassword; + private String newPassword; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignInRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignInRequestDTO.java index 2920c5e..1c75cd9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignInRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignInRequestDTO.java @@ -4,7 +4,7 @@ @Getter public class SignInRequestDTO { - private String email; - private String password; - private String fcmToken; + private String email; + private String password; + private String fcmToken; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignUpRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignUpRequestDTO.java index 91b7d75..68d70f2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignUpRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/SignUpRequestDTO.java @@ -9,32 +9,38 @@ @Getter public class SignUpRequestDTO { - private String email; - private String password; - private UserProvider provider; - private String nickname; - private UserRole role; - private String profileImageUrl; + private String email; + private String password; + private UserProvider provider; + private String nickname; + private UserRole role; + private String profileImageUrl; - @Builder - public SignUpRequestDTO(String email, String password, UserProvider provider, String nickname, UserRole role, String profileImageUrl) { - this.email = email; - this.password = password; - this.provider = provider; - this.nickname = nickname; - this.role = role; - this.profileImageUrl = profileImageUrl; - } + @Builder + public SignUpRequestDTO( + String email, + String password, + UserProvider provider, + String nickname, + UserRole role, + String profileImageUrl) { + this.email = email; + this.password = password; + this.provider = provider; + this.nickname = nickname; + this.role = role; + this.profileImageUrl = profileImageUrl; + } - public User toEntity(String encodedPassword) { - return User.builder() - .email(email) - .password(encodedPassword) - .provider(provider) - .nickname(nickname) - .role(role) - .status(UserStatus.ACTIVE) - .profileImageUrl(profileImageUrl) - .build(); - } + public User toEntity(String encodedPassword) { + return User.builder() + .email(email) + .password(encodedPassword) + .provider(provider) + .nickname(nickname) + .role(role) + .status(UserStatus.ACTIVE) + .profileImageUrl(profileImageUrl) + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateTemporaryPasswordRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateTemporaryPasswordRequestDTO.java index 5bb5a18..adb4c1f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateTemporaryPasswordRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateTemporaryPasswordRequestDTO.java @@ -4,6 +4,6 @@ @Data public class UpdateTemporaryPasswordRequestDTO { - private String email; - private String code; + private String email; + private String code; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserRoleRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserRoleRequestDTO.java index bff67c8..53db7d8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserRoleRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserRoleRequestDTO.java @@ -1,10 +1,9 @@ package com.onebyone.kindergarten.domain.user.dto.request; import com.onebyone.kindergarten.domain.user.enums.UserRole; - import lombok.Data; @Data public class UpdateUserRoleRequestDTO { - private UserRole role; + private UserRole role; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserStatusRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserStatusRequestDTO.java index c610ae3..49f5fe5 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserStatusRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UpdateUserStatusRequestDTO.java @@ -10,11 +10,10 @@ @NoArgsConstructor @Schema(description = "유저 상태 변경 요청 DTO") public class UpdateUserStatusRequestDTO { - - @NotNull(message = "상태는 필수입니다.") - @Schema(description = "변경할 유저 상태", example = "ACTIVE") - private UserStatus status; - - @Schema(description = "상태 변경 사유", example = "부적절한 게시물 작성으로 인한 정지") - private String reason; + + @NotNull(message = "상태는 필수입니다.") @Schema(description = "변경할 유저 상태", example = "ACTIVE") + private UserStatus status; + + @Schema(description = "상태 변경 사유", example = "부적절한 게시물 작성으로 인한 정지") + private String reason; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UserSearchDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UserSearchDTO.java index 7063288..e829e78 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UserSearchDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/request/UserSearchDTO.java @@ -11,12 +11,12 @@ @NoArgsConstructor @AllArgsConstructor public class UserSearchDTO { - private String email; - private String nickname; - private UserRole role; - private UserProvider provider; - private UserStatus status; - private String kindergartenName; - private Boolean hasWrittenReview; - private Boolean isRestoredUser; + private String email; + private String nickname; + private UserRole role; + private UserProvider provider; + private UserStatus status; + private String kindergartenName; + private Boolean hasWrittenReview; + private Boolean isRestoredUser; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AdminUserResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AdminUserResponseDTO.java index 7813611..49a0b87 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AdminUserResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AdminUserResponseDTO.java @@ -2,58 +2,59 @@ import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.entity.UserProvider; +import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.domain.user.enums.UserStatus; -import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Data @Builder @NoArgsConstructor @AllArgsConstructor public class AdminUserResponseDTO { - private Long id; - private String email; - private String nickname; - private UserRole role; - private UserProvider provider; - private UserStatus status; - private String profileImageUrl; - private String career; - private String kindergartenName; - private LocalDateTime createdAt; - private LocalDateTime deletedAt; - private LocalDateTime previousDeletedAt; - private boolean isRestoredUser; - private boolean hasWrittenReview; - private boolean allNotificationsEnabled; - private boolean communityNotificationsEnabled; - private boolean eventNotificationsEnabled; + private Long id; + private String email; + private String nickname; + private UserRole role; + private UserProvider provider; + private UserStatus status; + private String profileImageUrl; + private String career; + private String kindergartenName; + private LocalDateTime createdAt; + private LocalDateTime deletedAt; + private LocalDateTime previousDeletedAt; + private boolean isRestoredUser; + private boolean hasWrittenReview; + private boolean allNotificationsEnabled; + private boolean communityNotificationsEnabled; + private boolean eventNotificationsEnabled; - public static AdminUserResponseDTO from(User user) { - return AdminUserResponseDTO.builder() - .id(user.getId()) - .email(user.getEmail()) - .nickname(user.getNickname()) - .role(user.getRole()) - .provider(user.getProvider()) - .status(user.getStatus()) - .profileImageUrl(user.getProfileImageUrl()) - .career(user.getCareer()) - .kindergartenName(user.getKindergarten() != null ? user.getKindergarten().getName() : null) - .createdAt(user.getCreatedAt()) - .deletedAt(user.getDeletedAt()) - .previousDeletedAt(user.getPreviousDeletedAt()) - .isRestoredUser(user.isRestoredUser()) - .hasWrittenReview(user.hasWrittenReview()) - .allNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) - .communityNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS)) - .eventNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS)) - .build(); - } + public static AdminUserResponseDTO from(User user) { + return AdminUserResponseDTO.builder() + .id(user.getId()) + .email(user.getEmail()) + .nickname(user.getNickname()) + .role(user.getRole()) + .provider(user.getProvider()) + .status(user.getStatus()) + .profileImageUrl(user.getProfileImageUrl()) + .career(user.getCareer()) + .kindergartenName(user.getKindergarten() != null ? user.getKindergarten().getName() : null) + .createdAt(user.getCreatedAt()) + .deletedAt(user.getDeletedAt()) + .previousDeletedAt(user.getPreviousDeletedAt()) + .isRestoredUser(user.isRestoredUser()) + .hasWrittenReview(user.hasWrittenReview()) + .allNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) + .communityNotificationsEnabled( + user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS)) + .eventNotificationsEnabled( + user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS)) + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ApplePublicKeyResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ApplePublicKeyResponse.java index 4030936..3035f51 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ApplePublicKeyResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ApplePublicKeyResponse.java @@ -1,19 +1,19 @@ package com.onebyone.kindergarten.domain.user.dto.response; -import lombok.Data; import java.util.List; +import lombok.Data; @Data public class ApplePublicKeyResponse { - private List keys; + private List keys; - @Data - public static class Key { - private String kty; - private String kid; - private String use; - private String alg; - private String n; - private String e; - } -} \ No newline at end of file + @Data + public static class Key { + private String kty; + private String kid; + private String use; + private String alg; + private String n; + private String e; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleTokenResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleTokenResponse.java index 4ccfeec..3c2498c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleTokenResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleTokenResponse.java @@ -4,9 +4,9 @@ @Data public class AppleTokenResponse { - private String access_token; - private String token_type; - private String expires_in; - private String refresh_token; - private String id_token; // 애플의 JWT 토큰 -} \ No newline at end of file + private String access_token; + private String token_type; + private String expires_in; + private String refresh_token; + private String id_token; // 애플의 JWT 토큰 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleUserResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleUserResponse.java index e23359c..51eeaf2 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleUserResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/AppleUserResponse.java @@ -4,9 +4,9 @@ @Data public class AppleUserResponse { - private String sub; // 애플 고유 사용자 ID - private String email; - private String name; - private Boolean email_verified; - private Boolean is_private_email; // 이메일 숨기기 여부 -} \ No newline at end of file + private String sub; // 애플 고유 사용자 ID + private String email; + private String name; + private Boolean email_verified; + private Boolean is_private_email; // 이메일 숨기기 여부 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/GetUserResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/GetUserResponseDTO.java index b75675b..e20d2f1 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/GetUserResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/GetUserResponseDTO.java @@ -9,5 +9,5 @@ @NoArgsConstructor @AllArgsConstructor public class GetUserResponseDTO { - private UserDTO user; + private UserDTO user; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoTokenResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoTokenResponse.java index f73c023..eaeb29f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoTokenResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoTokenResponse.java @@ -4,9 +4,9 @@ @Data public class KakaoTokenResponse { - private String access_token; - private String token_type; - private String expires_in; - private String refresh_token; - private String scope; -} \ No newline at end of file + private String access_token; + private String token_type; + private String expires_in; + private String refresh_token; + private String scope; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoUserResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoUserResponse.java index 8d43036..0a5632e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoUserResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/KakaoUserResponse.java @@ -5,6 +5,6 @@ @Data public class KakaoUserResponse { - private Long id; - private KakaoAccount kakao_account; -} \ No newline at end of file + private Long id; + private KakaoAccount kakao_account; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverTokenResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverTokenResponse.java index c43a1d9..710daf6 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverTokenResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverTokenResponse.java @@ -4,8 +4,8 @@ @Data public class NaverTokenResponse { - private String access_token; - private String refresh_token; - private String token_type; - private String expires_in; -} \ No newline at end of file + private String access_token; + private String refresh_token; + private String token_type; + private String expires_in; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverUserResponse.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverUserResponse.java index fac886c..d506a2d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverUserResponse.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/NaverUserResponse.java @@ -4,15 +4,15 @@ @Data public class NaverUserResponse { - private String resultcode; - private String message; - private Response response; + private String resultcode; + private String message; + private Response response; - @Data - public static class Response { - private String id; - private String email; - private String profile_image; - private String nickname; - } + @Data + public static class Response { + private String id; + private String email; + private String profile_image; + private String nickname; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ReIssueResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ReIssueResponseDTO.java index e7c8f60..b51f221 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ReIssueResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/ReIssueResponseDTO.java @@ -5,12 +5,12 @@ @Data public class ReIssueResponseDTO { - private String accessToken; - private String refreshToken; + private String accessToken; + private String refreshToken; - @Builder - public ReIssueResponseDTO(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } + @Builder + public ReIssueResponseDTO(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignInResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignInResponseDTO.java index 1aa794a..cf3b68d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignInResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignInResponseDTO.java @@ -5,12 +5,12 @@ @Getter public class SignInResponseDTO { - private String accessToken; - private String refreshToken; + private String accessToken; + private String refreshToken; - @Builder - public SignInResponseDTO(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } + @Builder + public SignInResponseDTO(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignUpResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignUpResponseDTO.java index 19a8404..aa12c1f 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignUpResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/SignUpResponseDTO.java @@ -5,12 +5,12 @@ @Getter public class SignUpResponseDTO { - private String accessToken; - private String refreshToken; + private String accessToken; + private String refreshToken; - @Builder - public SignUpResponseDTO(String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } -} \ No newline at end of file + @Builder + public SignUpResponseDTO(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/UpdateHomeShortcutsResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/UpdateHomeShortcutsResponseDTO.java index d39841b..2c37474 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/UpdateHomeShortcutsResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/dto/response/UpdateHomeShortcutsResponseDTO.java @@ -10,13 +10,13 @@ @AllArgsConstructor @Builder public class UpdateHomeShortcutsResponseDTO { - private boolean success; - private String message; + private boolean success; + private String message; - public static UpdateHomeShortcutsResponseDTO success() { - return UpdateHomeShortcutsResponseDTO.builder() - .success(true) - .message("홈 바로가기 정보가 성공적으로 업데이트되었습니다.") - .build(); - } -} \ No newline at end of file + public static UpdateHomeShortcutsResponseDTO success() { + return UpdateHomeShortcutsResponseDTO.builder() + .success(true) + .message("홈 바로가기 정보가 성공적으로 업데이트되었습니다.") + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/entity/EmailCertification.java b/src/main/java/com/onebyone/kindergarten/domain/user/entity/EmailCertification.java index 69cce83..9a18086 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/entity/EmailCertification.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/entity/EmailCertification.java @@ -3,35 +3,33 @@ import com.onebyone.kindergarten.domain.user.enums.EmailCertificationType; import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; - +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Getter @Builder @AllArgsConstructor @NoArgsConstructor @Entity(name = "email_certification") public class EmailCertification extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long emailCertificationId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long emailCertificationId; - @Enumerated(EnumType.STRING) - private EmailCertificationType type; + @Enumerated(EnumType.STRING) + private EmailCertificationType type; - private String email; + private String email; - private String code; + private String code; - private boolean isCertificated; + private boolean isCertificated; - public void completeCertification() { - this.isCertificated = true; - this.updatedAt = LocalDateTime.now(); - } + public void completeCertification() { + this.isCertificated = true; + this.updatedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/entity/User.java b/src/main/java/com/onebyone/kindergarten/domain/user/entity/User.java index 9b6891f..decad72 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/entity/User.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/entity/User.java @@ -1,15 +1,14 @@ package com.onebyone.kindergarten.domain.user.entity; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; +import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.domain.user.enums.UserStatus; -import com.onebyone.kindergarten.domain.user.enums.NotificationSetting; import com.onebyone.kindergarten.global.common.BaseEntity; import jakarta.persistence.*; -import lombok.*; - import java.time.LocalDateTime; import java.util.Set; +import lombok.*; @Entity(name = "user") @Getter @@ -17,226 +16,239 @@ @RequiredArgsConstructor @AllArgsConstructor public class User extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 사용자 ID - - @Column(nullable = false, unique = true) - private String email; // 이메일 - - @Column(nullable = false, unique = false) - private String password; // 비밀번호 - - @Column(name = "fcm_token") - private String fcmToken; - - @Enumerated(EnumType.STRING) - private UserProvider provider; // 제공자 - 일반, 구글, 애플 - - @Column(name = "kakao_id") - private Long kakaoProviderId; // 카카오 로그인 회사 당 할당받는 유저 pk - - @Column(name = "naver_id") - private String naverProviderId; // 네이버 로그인 회사 당 할당받는 유저 pk - - @Column(name = "apple_id") - private String appleProviderId; // 애플 로그인 고유 사용자 ID - - @Column(nullable = false) - private String nickname; // 닉네임 - 랜덤 생성 - - @Enumerated(EnumType.STRING) - private UserRole role; // 역할 - 선생님, 예비, 관리자 - - @Column(nullable = true) - private String profileImageUrl; // 프로필 이미지 URL - - @Enumerated(EnumType.STRING) - private UserStatus status; // 상태 - 활성, 정지, 삭제 - - @Column(name = "career") - private String career; // 커리어 - - @Column(name = "home_shortcut", columnDefinition = "TEXT") - private String homeShortcut; // 홈 바로가기 정보 (JSON 형태로 저장) - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten_id") - private Kindergarten Kindergarten; - - @Column(name = "previous_deleted_at") - private LocalDateTime previousDeletedAt; - - @Column(name = "all_notifications_enabled") - @Builder.Default - private Boolean allNotificationsEnabled = true; - - @Column(name = "community_notifications_enabled") - @Builder.Default - private Boolean communityNotificationsEnabled = true; - - @Column(name = "event_notifications_enabled") - @Builder.Default - private Boolean eventNotificationsEnabled = true; - - @Column(name = "has_written_review") - @Builder.Default - private Boolean hasWrittenReview = false; - - public boolean hasNotificationEnabled(NotificationSetting setting) { - return switch (setting) { - case ALL_NOTIFICATIONS -> allNotificationsEnabled == null || allNotificationsEnabled; - case COMMUNITY_NOTIFICATIONS -> communityNotificationsEnabled == null || communityNotificationsEnabled; - case EVENT_NOTIFICATIONS -> eventNotificationsEnabled == null || eventNotificationsEnabled; - }; - } - - public void setNotificationSettings(Set enabledSettings) { - this.allNotificationsEnabled = enabledSettings.contains(NotificationSetting.ALL_NOTIFICATIONS); - this.communityNotificationsEnabled = enabledSettings.contains(NotificationSetting.COMMUNITY_NOTIFICATIONS); - this.eventNotificationsEnabled = enabledSettings.contains(NotificationSetting.EVENT_NOTIFICATIONS); - this.updatedAt = LocalDateTime.now(); - } - - public static User registerKakao(String email, String password, Long kakaoProviderId, String nickname, - UserRole role, String profileImageUrl) { - return User.builder() - .email(email) - .password(password) - .provider(UserProvider.KAKAO) - .kakaoProviderId(kakaoProviderId) - .nickname(nickname) - .role(role) - .profileImageUrl(profileImageUrl) - .status(UserStatus.ACTIVE) - .build(); - } - - public static User registerNaver(String email, String password, String naverProviderId, String nickname, - UserRole role, String profileImageUrl) { - return User.builder() - .email(email) - .password(password) - .provider(UserProvider.NAVER) - .naverProviderId(naverProviderId) - .nickname(nickname) - .role(role) - .profileImageUrl(profileImageUrl) - .status(UserStatus.ACTIVE) - .build(); - } - - public static User registerApple(String email, String password, String appleProviderId, String nickname, - UserRole role) { - return User.builder() - .email(email) - .password(password) - .provider(UserProvider.APPLE) - .appleProviderId(appleProviderId) - .nickname(nickname) - .role(role) - .status(UserStatus.ACTIVE) - .build(); - } - - public void changeNickname(String nickname) { - this.nickname = nickname; - this.updatedAt = LocalDateTime.now(); - } - - public void changePassword(String password) { - this.password = password; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 사용자 ID + + @Column(nullable = false, unique = true) + private String email; // 이메일 + + @Column(nullable = false, unique = false) + private String password; // 비밀번호 + + @Column(name = "fcm_token") + private String fcmToken; + + @Enumerated(EnumType.STRING) + private UserProvider provider; // 제공자 - 일반, 구글, 애플 + + @Column(name = "kakao_id") + private Long kakaoProviderId; // 카카오 로그인 회사 당 할당받는 유저 pk + + @Column(name = "naver_id") + private String naverProviderId; // 네이버 로그인 회사 당 할당받는 유저 pk + + @Column(name = "apple_id") + private String appleProviderId; // 애플 로그인 고유 사용자 ID + + @Column(nullable = false) + private String nickname; // 닉네임 - 랜덤 생성 + + @Enumerated(EnumType.STRING) + private UserRole role; // 역할 - 선생님, 예비, 관리자 + + @Column(nullable = true) + private String profileImageUrl; // 프로필 이미지 URL + + @Enumerated(EnumType.STRING) + private UserStatus status; // 상태 - 활성, 정지, 삭제 + + @Column(name = "career") + private String career; // 커리어 + + @Column(name = "home_shortcut", columnDefinition = "TEXT") + private String homeShortcut; // 홈 바로가기 정보 (JSON 형태로 저장) + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten_id") + private Kindergarten Kindergarten; + + @Column(name = "previous_deleted_at") + private LocalDateTime previousDeletedAt; + + @Column(name = "all_notifications_enabled") + @Builder.Default + private Boolean allNotificationsEnabled = true; + + @Column(name = "community_notifications_enabled") + @Builder.Default + private Boolean communityNotificationsEnabled = true; + + @Column(name = "event_notifications_enabled") + @Builder.Default + private Boolean eventNotificationsEnabled = true; + + @Column(name = "has_written_review") + @Builder.Default + private Boolean hasWrittenReview = false; + + public boolean hasNotificationEnabled(NotificationSetting setting) { + return switch (setting) { + case ALL_NOTIFICATIONS -> allNotificationsEnabled == null || allNotificationsEnabled; + case COMMUNITY_NOTIFICATIONS -> + communityNotificationsEnabled == null || communityNotificationsEnabled; + case EVENT_NOTIFICATIONS -> eventNotificationsEnabled == null || eventNotificationsEnabled; + }; + } + + public void setNotificationSettings(Set enabledSettings) { + this.allNotificationsEnabled = enabledSettings.contains(NotificationSetting.ALL_NOTIFICATIONS); + this.communityNotificationsEnabled = + enabledSettings.contains(NotificationSetting.COMMUNITY_NOTIFICATIONS); + this.eventNotificationsEnabled = + enabledSettings.contains(NotificationSetting.EVENT_NOTIFICATIONS); + this.updatedAt = LocalDateTime.now(); + } + + public static User registerKakao( + String email, + String password, + Long kakaoProviderId, + String nickname, + UserRole role, + String profileImageUrl) { + return User.builder() + .email(email) + .password(password) + .provider(UserProvider.KAKAO) + .kakaoProviderId(kakaoProviderId) + .nickname(nickname) + .role(role) + .profileImageUrl(profileImageUrl) + .status(UserStatus.ACTIVE) + .build(); + } + + public static User registerNaver( + String email, + String password, + String naverProviderId, + String nickname, + UserRole role, + String profileImageUrl) { + return User.builder() + .email(email) + .password(password) + .provider(UserProvider.NAVER) + .naverProviderId(naverProviderId) + .nickname(nickname) + .role(role) + .profileImageUrl(profileImageUrl) + .status(UserStatus.ACTIVE) + .build(); + } + + public static User registerApple( + String email, String password, String appleProviderId, String nickname, UserRole role) { + return User.builder() + .email(email) + .password(password) + .provider(UserProvider.APPLE) + .appleProviderId(appleProviderId) + .nickname(nickname) + .role(role) + .status(UserStatus.ACTIVE) + .build(); + } + + public void changeNickname(String nickname) { + this.nickname = nickname; + this.updatedAt = LocalDateTime.now(); + } + + public void changePassword(String password) { + this.password = password; + this.updatedAt = LocalDateTime.now(); + } + + public void withdraw() { + this.status = UserStatus.DELETED; + this.deletedAt = LocalDateTime.now(); + /// 랜덤 6자리 숫자 생성 + String randomNum = String.format("%06d", (int) (Math.random() * 1000000)); + this.nickname = "D_" + randomNum; + /// 개인정보 마스킹 + this.profileImageUrl = null; + this.homeShortcut = null; + this.fcmToken = null; + this.updatedAt = LocalDateTime.now(); + } + + /// 현재 deletedAt 값을 previousDeletedAt에 저장 + public void restore() { + this.previousDeletedAt = this.deletedAt; + this.status = UserStatus.ACTIVE; + this.deletedAt = null; + this.updatedAt = LocalDateTime.now(); + } + + public void updateFcmToken(String fcmToken) { + // FCM 토큰 유효성 검증 + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + String trimmedToken = fcmToken.trim(); + // 최소 길이 및 기본 패턴 검증 + if (trimmedToken.length() >= 140 && trimmedToken.matches("^[a-zA-Z0-9_:.-]+$")) { + this.fcmToken = trimmedToken; this.updatedAt = LocalDateTime.now(); - } - - public void withdraw() { - this.status = UserStatus.DELETED; - this.deletedAt = LocalDateTime.now(); - /// 랜덤 6자리 숫자 생성 - String randomNum = String.format("%06d", (int)(Math.random() * 1000000)); - this.nickname = "D_" + randomNum; - /// 개인정보 마스킹 - this.profileImageUrl = null; - this.homeShortcut = null; + } else { this.fcmToken = null; - this.updatedAt = LocalDateTime.now(); - } - - /// 현재 deletedAt 값을 previousDeletedAt에 저장 - public void restore() { - this.previousDeletedAt = this.deletedAt; - this.status = UserStatus.ACTIVE; - this.deletedAt = null; - this.updatedAt = LocalDateTime.now(); - } - - public void updateFcmToken(String fcmToken) { - // FCM 토큰 유효성 검증 - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - String trimmedToken = fcmToken.trim(); - // 최소 길이 및 기본 패턴 검증 - if (trimmedToken.length() >= 140 && trimmedToken.matches("^[a-zA-Z0-9_:.-]+$")) { - this.fcmToken = trimmedToken; - this.updatedAt = LocalDateTime.now(); - } else { - this.fcmToken = null; - } - } else { - this.fcmToken = null; - } - } - - public String getTotalCareer() { - return career; - } - - public void updateCareer(String career) { - this.career = career; - this.updatedAt = LocalDateTime.now(); - } - - public void updateHomeShortcut(String homeShortcutJson) { - this.homeShortcut = homeShortcutJson; - this.updatedAt = LocalDateTime.now(); - } - - public void updateUserRole(UserRole role) { - this.role = role; - this.updatedAt = LocalDateTime.now(); - } - - public void updateProfileImageUrl(String profileImageUrl) { - this.profileImageUrl = profileImageUrl; - this.updatedAt = LocalDateTime.now(); - } - - public boolean isRestoredUser() { - return this.previousDeletedAt != null; - } - - public void markAsReviewWriter() { - this.hasWrittenReview = true; - this.updatedAt = LocalDateTime.now(); - } - - public void unMarkAsReviewWriter() { - this.hasWrittenReview = false; - this.updatedAt = LocalDateTime.now(); - } - - public boolean hasWrittenReview() { - return this.hasWrittenReview != null && this.hasWrittenReview; - } - - /// 유저 상태 변경 (관리자용) - public void updateStatus(UserStatus status) { - this.status = status; - this.updatedAt = LocalDateTime.now(); - - if (status == UserStatus.SUSPENDED) { - // TODO : 정지 시 특별한 처리가 필요하다면 여기에 추가 - } else if (status == UserStatus.ACTIVE) { - // TODO : 활성화 시 특별한 처리가 필요하다면 여기에 추가 - } - } + } + } else { + this.fcmToken = null; + } + } + + public String getTotalCareer() { + return career; + } + + public void updateCareer(String career) { + this.career = career; + this.updatedAt = LocalDateTime.now(); + } + + public void updateHomeShortcut(String homeShortcutJson) { + this.homeShortcut = homeShortcutJson; + this.updatedAt = LocalDateTime.now(); + } + + public void updateUserRole(UserRole role) { + this.role = role; + this.updatedAt = LocalDateTime.now(); + } + + public void updateProfileImageUrl(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + this.updatedAt = LocalDateTime.now(); + } + + public boolean isRestoredUser() { + return this.previousDeletedAt != null; + } + + public void markAsReviewWriter() { + this.hasWrittenReview = true; + this.updatedAt = LocalDateTime.now(); + } + + public void unMarkAsReviewWriter() { + this.hasWrittenReview = false; + this.updatedAt = LocalDateTime.now(); + } + + public boolean hasWrittenReview() { + return this.hasWrittenReview != null && this.hasWrittenReview; + } + + /// 유저 상태 변경 (관리자용) + public void updateStatus(UserStatus status) { + this.status = status; + this.updatedAt = LocalDateTime.now(); + + if (status == UserStatus.SUSPENDED) { + // TODO : 정지 시 특별한 처리가 필요하다면 여기에 추가 + } else if (status == UserStatus.ACTIVE) { + // TODO : 활성화 시 특별한 처리가 필요하다면 여기에 추가 + } + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/entity/UserProvider.java b/src/main/java/com/onebyone/kindergarten/domain/user/entity/UserProvider.java index 7d838d6..aa18f4d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/entity/UserProvider.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/entity/UserProvider.java @@ -1,5 +1,9 @@ package com.onebyone.kindergarten.domain.user.entity; public enum UserProvider { - LOCAL, GOOGLE, APPLE, KAKAO, NAVER + LOCAL, + GOOGLE, + APPLE, + KAKAO, + NAVER } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/enums/EmailCertificationType.java b/src/main/java/com/onebyone/kindergarten/domain/user/enums/EmailCertificationType.java index 3c857a7..5fdafa6 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/enums/EmailCertificationType.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/enums/EmailCertificationType.java @@ -1,5 +1,6 @@ package com.onebyone.kindergarten.domain.user.enums; public enum EmailCertificationType { - EMAIL, TEMPORARY_PASSWORD; + EMAIL, + TEMPORARY_PASSWORD; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/enums/NotificationSetting.java b/src/main/java/com/onebyone/kindergarten/domain/user/enums/NotificationSetting.java index bc5533a..61eac1e 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/enums/NotificationSetting.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/enums/NotificationSetting.java @@ -1,7 +1,7 @@ package com.onebyone.kindergarten.domain.user.enums; public enum NotificationSetting { - ALL_NOTIFICATIONS, /// 전체 알림 - COMMUNITY_NOTIFICATIONS, /// 커뮤니티 알림 - EVENT_NOTIFICATIONS /// 공지 및 이벤트 혜택 알림 -} \ No newline at end of file + ALL_NOTIFICATIONS, /// 전체 알림 + COMMUNITY_NOTIFICATIONS, /// 커뮤니티 알림 + EVENT_NOTIFICATIONS /// 공지 및 이벤트 혜택 알림 +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserRole.java b/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserRole.java index de9eb3d..c1bb346 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserRole.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserRole.java @@ -1,3 +1,8 @@ package com.onebyone.kindergarten.domain.user.enums; -public enum UserRole {TEACHER, PROSPECTIVE_TEACHER, GENERAL, ADMIN} +public enum UserRole { + TEACHER, + PROSPECTIVE_TEACHER, + GENERAL, + ADMIN +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserStatus.java b/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserStatus.java index 3ec42be..4e17bd8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserStatus.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/enums/UserStatus.java @@ -1,3 +1,7 @@ package com.onebyone.kindergarten.domain.user.enums; -public enum UserStatus { ACTIVE, SUSPENDED, DELETED } \ No newline at end of file +public enum UserStatus { + ACTIVE, + SUSPENDED, + DELETED +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/repository/EmailCertificationRepository.java b/src/main/java/com/onebyone/kindergarten/domain/user/repository/EmailCertificationRepository.java index 29ce079..c4b6458 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/repository/EmailCertificationRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/repository/EmailCertificationRepository.java @@ -1,14 +1,14 @@ package com.onebyone.kindergarten.domain.user.repository; +import com.onebyone.kindergarten.domain.user.entity.EmailCertification; import com.onebyone.kindergarten.domain.user.enums.EmailCertificationType; import org.springframework.data.jpa.repository.JpaRepository; -import com.onebyone.kindergarten.domain.user.entity.EmailCertification; - public interface EmailCertificationRepository extends JpaRepository { - EmailCertification findByEmail(String email); + EmailCertification findByEmail(String email); - EmailCertification findByEmailAndCodeAndTypeAndDeletedAtIsNull(String email, String code, EmailCertificationType type); + EmailCertification findByEmailAndCodeAndTypeAndDeletedAtIsNull( + String email, String code, EmailCertificationType type); - boolean existsByEmail(String email); + boolean existsByEmail(String email); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java b/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java index 856e0eb..1cb4581 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/repository/UserRepository.java @@ -1,78 +1,80 @@ package com.onebyone.kindergarten.domain.user.repository; import com.onebyone.kindergarten.domain.user.entity.User; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; - @Repository public interface UserRepository extends JpaRepository { - Boolean existsByEmail(String email); + Boolean existsByEmail(String email); - Optional findByEmailAndDeletedAtIsNull(String email); + Optional findByEmailAndDeletedAtIsNull(String email); - @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.id = :userId AND u.deletedAt IS NULL") - Optional findIdWithKindergarten(@Param("userId") Long userId); + @Query( + "SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.id = :userId AND u.deletedAt IS NULL") + Optional findIdWithKindergarten(@Param("userId") Long userId); - Optional findByEmailAndDeletedAtIsNotNull(String email); + Optional findByEmailAndDeletedAtIsNotNull(String email); - @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.id = :id") - Optional findByIdWithKindergarten(@Param("id") Long id); + @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten WHERE u.id = :id") + Optional findByIdWithKindergarten(@Param("id") Long id); - @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten k") - Page findAllUsersWithKindergarten(Pageable pageable); + @Query("SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten k") + Page findAllUsersWithKindergarten(Pageable pageable); - @Query(value = "SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten k WHERE " + - "(:email IS NULL OR " + - " (LENGTH(:email) <= 3 AND u.email LIKE %:email%) OR " + - " (LENGTH(:email) > 3 AND u.email LIKE :email%)) AND " + - "(:nickname IS NULL OR " + - " (LENGTH(:nickname) <= 2 AND u.nickname LIKE %:nickname%) OR " + - " (LENGTH(:nickname) > 2 AND u.nickname LIKE :nickname%)) AND " + - "(:role IS NULL OR u.role = :role) AND " + - "(:provider IS NULL OR u.provider = :provider) AND " + - "(:status IS NULL OR u.status = :status) AND " + - "(:kindergartenName IS NULL OR k.name LIKE %:kindergartenName%) AND " + - "(:hasWrittenReview IS NULL OR u.hasWrittenReview = :hasWrittenReview) AND " + - "(:isRestoredUser IS NULL OR " + - " (:isRestoredUser = true AND u.previousDeletedAt IS NOT NULL) OR " + - " (:isRestoredUser = false AND u.previousDeletedAt IS NULL))", - countQuery = "SELECT COUNT(u) FROM user u LEFT JOIN u.Kindergarten k WHERE " + - "(:email IS NULL OR " + - " (LENGTH(:email) <= 3 AND u.email LIKE %:email%) OR " + - " (LENGTH(:email) > 3 AND u.email LIKE :email%)) AND " + - "(:nickname IS NULL OR " + - " (LENGTH(:nickname) <= 2 AND u.nickname LIKE %:nickname%) OR " + - " (LENGTH(:nickname) > 2 AND u.nickname LIKE :nickname%)) AND " + - "(:role IS NULL OR u.role = :role) AND " + - "(:provider IS NULL OR u.provider = :provider) AND " + - "(:status IS NULL OR u.status = :status) AND " + - "(:kindergartenName IS NULL OR k.name LIKE %:kindergartenName%) AND " + - "(:hasWrittenReview IS NULL OR u.hasWrittenReview = :hasWrittenReview) AND " + - "(:isRestoredUser IS NULL OR " + - " (:isRestoredUser = true AND u.previousDeletedAt IS NOT NULL) OR " + - " (:isRestoredUser = false AND u.previousDeletedAt IS NULL))") - Page findUsersWithFilters( - @Param("email") String email, - @Param("nickname") String nickname, - @Param("role") com.onebyone.kindergarten.domain.user.enums.UserRole role, - @Param("provider") com.onebyone.kindergarten.domain.user.entity.UserProvider provider, - @Param("status") com.onebyone.kindergarten.domain.user.enums.UserStatus status, - @Param("kindergartenName") String kindergartenName, - @Param("hasWrittenReview") Boolean hasWrittenReview, - @Param("isRestoredUser") Boolean isRestoredUser, - Pageable pageable - ); + @Query( + value = + "SELECT u FROM user u LEFT JOIN FETCH u.Kindergarten k WHERE " + + "(:email IS NULL OR " + + " (LENGTH(:email) <= 3 AND u.email LIKE %:email%) OR " + + " (LENGTH(:email) > 3 AND u.email LIKE :email%)) AND " + + "(:nickname IS NULL OR " + + " (LENGTH(:nickname) <= 2 AND u.nickname LIKE %:nickname%) OR " + + " (LENGTH(:nickname) > 2 AND u.nickname LIKE :nickname%)) AND " + + "(:role IS NULL OR u.role = :role) AND " + + "(:provider IS NULL OR u.provider = :provider) AND " + + "(:status IS NULL OR u.status = :status) AND " + + "(:kindergartenName IS NULL OR k.name LIKE %:kindergartenName%) AND " + + "(:hasWrittenReview IS NULL OR u.hasWrittenReview = :hasWrittenReview) AND " + + "(:isRestoredUser IS NULL OR " + + " (:isRestoredUser = true AND u.previousDeletedAt IS NOT NULL) OR " + + " (:isRestoredUser = false AND u.previousDeletedAt IS NULL))", + countQuery = + "SELECT COUNT(u) FROM user u LEFT JOIN u.Kindergarten k WHERE " + + "(:email IS NULL OR " + + " (LENGTH(:email) <= 3 AND u.email LIKE %:email%) OR " + + " (LENGTH(:email) > 3 AND u.email LIKE :email%)) AND " + + "(:nickname IS NULL OR " + + " (LENGTH(:nickname) <= 2 AND u.nickname LIKE %:nickname%) OR " + + " (LENGTH(:nickname) > 2 AND u.nickname LIKE :nickname%)) AND " + + "(:role IS NULL OR u.role = :role) AND " + + "(:provider IS NULL OR u.provider = :provider) AND " + + "(:status IS NULL OR u.status = :status) AND " + + "(:kindergartenName IS NULL OR k.name LIKE %:kindergartenName%) AND " + + "(:hasWrittenReview IS NULL OR u.hasWrittenReview = :hasWrittenReview) AND " + + "(:isRestoredUser IS NULL OR " + + " (:isRestoredUser = true AND u.previousDeletedAt IS NOT NULL) OR " + + " (:isRestoredUser = false AND u.previousDeletedAt IS NULL))") + Page findUsersWithFilters( + @Param("email") String email, + @Param("nickname") String nickname, + @Param("role") com.onebyone.kindergarten.domain.user.enums.UserRole role, + @Param("provider") com.onebyone.kindergarten.domain.user.entity.UserProvider provider, + @Param("status") com.onebyone.kindergarten.domain.user.enums.UserStatus status, + @Param("kindergartenName") String kindergartenName, + @Param("hasWrittenReview") Boolean hasWrittenReview, + @Param("isRestoredUser") Boolean isRestoredUser, + Pageable pageable); - /// 모든 활성 사용자 조회 - @Query("SELECT u FROM user u WHERE u.deletedAt IS NULL") - List findAllActiveUsers(); + /// 모든 활성 사용자 조회 + @Query("SELECT u FROM user u WHERE u.deletedAt IS NULL") + List findAllActiveUsers(); - Optional findByIdAndDeletedAtIsNull(Long userId); + Optional findByIdAndDeletedAtIsNull(Long userId); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/service/AppleAuthService.java b/src/main/java/com/onebyone/kindergarten/domain/user/service/AppleAuthService.java index 0051383..a940e1b 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/service/AppleAuthService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/service/AppleAuthService.java @@ -1,17 +1,12 @@ package com.onebyone.kindergarten.domain.user.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onebyone.kindergarten.global.feignClient.AppleAuthClient; import com.onebyone.kindergarten.domain.user.dto.response.ApplePublicKeyResponse; import com.onebyone.kindergarten.domain.user.dto.response.AppleUserResponse; +import com.onebyone.kindergarten.global.feignClient.AppleAuthClient; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - import java.math.BigInteger; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -22,117 +17,119 @@ import java.security.spec.RSAPublicKeySpec; import java.util.Base64; import java.util.Date; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor @Slf4j public class AppleAuthService { - - private final AppleAuthClient appleAuthClient; - private final ObjectMapper objectMapper; - - @Value("${oauth.apple.team-id}") - private String teamId; - - @Value("${oauth.apple.client-id}") - private String clientId; - - @Value("${oauth.apple.key-id}") - private String keyId; - - @Value("${oauth.apple.private-key}") - private String privateKey; - - @Value("${oauth.apple.audience:https://appleid.apple.com}") - private String audience; - - public AppleUserResponse verifyIdToken(String idToken) { - try { - // 1. JWT 헤더에서 kid 추출 - String[] tokenParts = idToken.split("\\."); - String header = new String(Base64.getUrlDecoder().decode(tokenParts[0])); - String kid = objectMapper.readTree(header).get("kid").asText(); - - // 2. Apple 공개키 조회 - ApplePublicKeyResponse publicKeys = appleAuthClient.getPublicKeys(); - ApplePublicKeyResponse.Key appleKey = publicKeys.getKeys().stream() - .filter(key -> key.getKid().equals(kid)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Apple 공개키를 찾을 수 없습니다.")); - - // 3. 공개키로 JWT 검증 - PublicKey publicKey = generatePublicKey(appleKey.getN(), appleKey.getE()); - Claims claims = Jwts.parserBuilder() - .setSigningKey(publicKey) - .build() - .parseClaimsJws(idToken) - .getBody(); - - // 4. 사용자 정보 추출 - AppleUserResponse userResponse = new AppleUserResponse(); - userResponse.setSub(claims.getSubject()); - userResponse.setEmail(claims.get("email", String.class)); - userResponse.setEmail_verified(claims.get("email_verified", Boolean.class)); - userResponse.setIs_private_email(claims.get("is_private_email", Boolean.class)); - - // name은 첫 로그인 시에만 제공되므로 null일 수 있음 - Object nameObj = claims.get("name"); - if (nameObj != null) { - userResponse.setName(nameObj.toString()); - } - - return userResponse; - - } catch (Exception e) { - log.error("Apple ID Token 검증 실패: {}", e.getMessage()); - throw new RuntimeException("Apple 로그인 검증에 실패했습니다.", e); - } - } - private PublicKey generatePublicKey(String nStr, String eStr) throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] nBytes = Base64.getUrlDecoder().decode(nStr); - byte[] eBytes = Base64.getUrlDecoder().decode(eStr); + private final AppleAuthClient appleAuthClient; + private final ObjectMapper objectMapper; - BigInteger n = new BigInteger(1, nBytes); - BigInteger e = new BigInteger(1, eBytes); + @Value("${oauth.apple.team-id}") + private String teamId; - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePublic(publicKeySpec); - } - - /** - * 애플로 토큰 요청 시 사용할 Client Secret JWT 생성 - */ - public String generateClientSecret() { - try { - long now = System.currentTimeMillis() / 1000; - - return Jwts.builder() - .setHeaderParam("kid", keyId) - .setHeaderParam("alg", "ES256") - .setIssuer(teamId) - .setIssuedAt(new Date(now * 1000)) - .setExpiration(new Date((now + 3600) * 1000)) // 1시간 후 만료 - .setAudience(audience) - .setSubject(clientId) - .signWith(getPrivateKey(), SignatureAlgorithm.ES256) - .compact(); - } catch (Exception e) { - log.error("Apple Client Secret 생성 실패: {}", e.getMessage()); - throw new RuntimeException("Apple Client Secret 생성에 실패했습니다.", e); - } + @Value("${oauth.apple.client-id}") + private String clientId; + + @Value("${oauth.apple.key-id}") + private String keyId; + + @Value("${oauth.apple.private-key}") + private String privateKey; + + @Value("${oauth.apple.audience:https://appleid.apple.com}") + private String audience; + + public AppleUserResponse verifyIdToken(String idToken) { + try { + // 1. JWT 헤더에서 kid 추출 + String[] tokenParts = idToken.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(tokenParts[0])); + String kid = objectMapper.readTree(header).get("kid").asText(); + + // 2. Apple 공개키 조회 + ApplePublicKeyResponse publicKeys = appleAuthClient.getPublicKeys(); + ApplePublicKeyResponse.Key appleKey = + publicKeys.getKeys().stream() + .filter(key -> key.getKid().equals(kid)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Apple 공개키를 찾을 수 없습니다.")); + + // 3. 공개키로 JWT 검증 + PublicKey publicKey = generatePublicKey(appleKey.getN(), appleKey.getE()); + Claims claims = + Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(idToken).getBody(); + + // 4. 사용자 정보 추출 + AppleUserResponse userResponse = new AppleUserResponse(); + userResponse.setSub(claims.getSubject()); + userResponse.setEmail(claims.get("email", String.class)); + userResponse.setEmail_verified(claims.get("email_verified", Boolean.class)); + userResponse.setIs_private_email(claims.get("is_private_email", Boolean.class)); + + // name은 첫 로그인 시에만 제공되므로 null일 수 있음 + Object nameObj = claims.get("name"); + if (nameObj != null) { + userResponse.setName(nameObj.toString()); + } + + return userResponse; + + } catch (Exception e) { + log.error("Apple ID Token 검증 실패: {}", e.getMessage()); + throw new RuntimeException("Apple 로그인 검증에 실패했습니다.", e); } - - private PrivateKey getPrivateKey() throws Exception { - String privateKeyContent = privateKey - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); - - byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("EC"); - return keyFactory.generatePrivate(keySpec); + } + + private PublicKey generatePublicKey(String nStr, String eStr) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] nBytes = Base64.getUrlDecoder().decode(nStr); + byte[] eBytes = Base64.getUrlDecoder().decode(eStr); + + BigInteger n = new BigInteger(1, nBytes); + BigInteger e = new BigInteger(1, eBytes); + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(publicKeySpec); + } + + /** 애플로 토큰 요청 시 사용할 Client Secret JWT 생성 */ + public String generateClientSecret() { + try { + long now = System.currentTimeMillis() / 1000; + + return Jwts.builder() + .setHeaderParam("kid", keyId) + .setHeaderParam("alg", "ES256") + .setIssuer(teamId) + .setIssuedAt(new Date(now * 1000)) + .setExpiration(new Date((now + 3600) * 1000)) // 1시간 후 만료 + .setAudience(audience) + .setSubject(clientId) + .signWith(getPrivateKey(), SignatureAlgorithm.ES256) + .compact(); + } catch (Exception e) { + log.error("Apple Client Secret 생성 실패: {}", e.getMessage()); + throw new RuntimeException("Apple Client Secret 생성에 실패했습니다.", e); } -} \ No newline at end of file + } + + private PrivateKey getPrivateKey() throws Exception { + String privateKeyContent = + privateKey + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePrivate(keySpec); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/service/CustomUserDetailService.java b/src/main/java/com/onebyone/kindergarten/domain/user/service/CustomUserDetailService.java index 4c99973..e3141f3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/service/CustomUserDetailService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/service/CustomUserDetailService.java @@ -13,20 +13,20 @@ @Service @RequiredArgsConstructor -public class CustomUserDetailService implements UserDetailsService{ - private final UserRepository userRepository; +public class CustomUserDetailService implements UserDetailsService { + private final UserRepository userRepository; - @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - User user = userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = + userRepository + .findByEmailAndDeletedAtIsNull(email) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - return org.springframework.security.core.userdetails.User.builder() - .username(email) - .password(user.getPassword()) - .roles( - user.getRole() == UserRole.ADMIN ? "ADMIN" : "USER" - ) - .build(); - } + return org.springframework.security.core.userdetails.User.builder() + .username(email) + .password(user.getPassword()) + .roles(user.getRole() == UserRole.ADMIN ? "ADMIN" : "USER") + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java index 9395e63..c087aad 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java @@ -2,6 +2,7 @@ import com.onebyone.kindergarten.domain.user.dto.*; import com.onebyone.kindergarten.domain.user.dto.request.*; +import com.onebyone.kindergarten.domain.user.dto.response.AdminUserResponseDTO; import com.onebyone.kindergarten.domain.user.dto.response.AppleUserResponse; import com.onebyone.kindergarten.domain.user.dto.response.KakaoUserResponse; import com.onebyone.kindergarten.domain.user.dto.response.NaverUserResponse; @@ -12,488 +13,516 @@ import com.onebyone.kindergarten.domain.user.enums.UserRole; import com.onebyone.kindergarten.domain.user.repository.EmailCertificationRepository; import com.onebyone.kindergarten.domain.user.repository.UserRepository; -import com.onebyone.kindergarten.global.exception.ErrorCodes; import com.onebyone.kindergarten.global.exception.BusinessException; -import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - +import com.onebyone.kindergarten.global.exception.ErrorCodes; import java.time.LocalDate; import java.time.temporal.ChronoUnit; -import java.util.Optional; import java.util.HashSet; +import java.util.Optional; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import com.onebyone.kindergarten.domain.user.dto.response.AdminUserResponseDTO; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class UserService { - private final UserRepository userRepository; - private final BCryptPasswordEncoder passwordEncoder; - private final EmailCertificationRepository emailCertificationRepository; - - @Transactional - public JwtUserInfoDto signUp(SignUpRequestDTO request) { - if (isExistedEmail(request.getEmail())) { - throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); - } - -// EmailCertification emailCertification = emailCertificationRepository.findByEmail(request.getEmail()); -// if (emailCertification == null || !emailCertification.isCertificated()) { -// throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); -// } - - String encodedPassword = encodePassword(request.getPassword()); - User user = userRepository.save(request.toEntity(encodedPassword)); - - return new JwtUserInfoDto( - user.getId(), - user.getRole() - ); + private final UserRepository userRepository; + private final BCryptPasswordEncoder passwordEncoder; + private final EmailCertificationRepository emailCertificationRepository; + + @Transactional + public JwtUserInfoDto signUp(SignUpRequestDTO request) { + if (isExistedEmail(request.getEmail())) { + throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); } - @Transactional(readOnly = true) - public boolean isExistedEmail(String email) { - return userRepository.findByEmailAndDeletedAtIsNull(email).isPresent(); + // EmailCertification emailCertification = + // emailCertificationRepository.findByEmail(request.getEmail()); + // if (emailCertification == null || !emailCertification.isCertificated()) { + // throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); + // } + + String encodedPassword = encodePassword(request.getPassword()); + User user = userRepository.save(request.toEntity(encodedPassword)); + + return new JwtUserInfoDto(user.getId(), user.getRole()); + } + + @Transactional(readOnly = true) + public boolean isExistedEmail(String email) { + return userRepository.findByEmailAndDeletedAtIsNull(email).isPresent(); + } + + private String encodePassword(String password) { + return passwordEncoder.encode(password); + } + + @Transactional + public JwtUserInfoDto signIn(SignInRequestDTO request) { + // 먼저 활성 사용자 확인 + Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(request.getEmail()); + if (activeUser.isPresent()) { + User user = activeUser.get(); + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); + } + + if (request.getFcmToken() != null) { + user.updateFcmToken(request.getFcmToken()); + } + + return new JwtUserInfoDto(user.getId(), user.getRole()); } - private String encodePassword(String password) { - return passwordEncoder.encode(password); - } + // 탈퇴된 사용자 확인 및 복구 + Optional deletedUser = + userRepository.findByEmailAndDeletedAtIsNotNull(request.getEmail()); + if (deletedUser.isPresent()) { + User user = deletedUser.get(); + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); + } - @Transactional - public JwtUserInfoDto signIn(SignInRequestDTO request) { - // 먼저 활성 사용자 확인 - Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(request.getEmail()); - if (activeUser.isPresent()) { - User user = activeUser.get(); - if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); - } - - if (request.getFcmToken() != null) { - user.updateFcmToken(request.getFcmToken()); - } - - return new JwtUserInfoDto( - user.getId(), - user.getRole() - ); - } - - // 탈퇴된 사용자 확인 및 복구 - Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(request.getEmail()); - if (deletedUser.isPresent()) { - User user = deletedUser.get(); - if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); - } - - // 계정 복구 - user.restore(); - - if (request.getFcmToken() != null) { - user.updateFcmToken(request.getFcmToken()); - } - - return new JwtUserInfoDto( - user.getId(), - user.getRole() - ); - } - - throw new BusinessException(ErrorCodes.NOT_FOUND_EMAIL); - } + // 계정 복구 + user.restore(); + + if (request.getFcmToken() != null) { + user.updateFcmToken(request.getFcmToken()); + } - @Transactional - public void changeNickname(Long userId, ModifyUserNicknameRequestDTO request) { - User user = getUserById(userId); - - // 현재 닉네임과 동일한지 확인 - if (user.getNickname().equals(request.getNewNickname())) { - return; // 동일한 닉네임이면 변경하지 않음 - } - - user.changeNickname(request.getNewNickname()); + return new JwtUserInfoDto(user.getId(), user.getRole()); } - @Transactional - public void changePassword(Long userId, ModifyUserPasswordRequestDTO request) { - User user = getUserById(userId); + throw new BusinessException(ErrorCodes.NOT_FOUND_EMAIL); + } - if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { - throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); - } + @Transactional + public void changeNickname(Long userId, ModifyUserNicknameRequestDTO request) { + User user = getUserById(userId); - user.changePassword(passwordEncoder.encode(request.getNewPassword())); + // 현재 닉네임과 동일한지 확인 + if (user.getNickname().equals(request.getNewNickname())) { + return; // 동일한 닉네임이면 변경하지 않음 } - @Transactional - public void withdraw(Long userId) { - User user = getUserById(userId); - user.withdraw(); - } + user.changeNickname(request.getNewNickname()); + } - public User getUserByEmail(String email) { - return userRepository.findByEmailAndDeletedAtIsNull(email) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - } + @Transactional + public void changePassword(Long userId, ModifyUserPasswordRequestDTO request) { + User user = getUserById(userId); - public void addCareer(User user, LocalDate startDate, LocalDate endDate) { - int careerMonths = calculateCareerMonths(user, startDate, endDate, true); - user.updateCareer(String.valueOf(careerMonths)); + if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { + throw new BusinessException(ErrorCodes.INVALID_PASSWORD_ERROR); } - public void removeCareer(User user, LocalDate startDate, LocalDate endDate) { - int careerMonths = calculateCareerMonths(user, startDate, endDate, false); - user.updateCareer(String.valueOf(careerMonths)); + user.changePassword(passwordEncoder.encode(request.getNewPassword())); + } + + @Transactional + public void withdraw(Long userId) { + User user = getUserById(userId); + user.withdraw(); + } + + public User getUserByEmail(String email) { + return userRepository + .findByEmailAndDeletedAtIsNull(email) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + } + + public void addCareer(User user, LocalDate startDate, LocalDate endDate) { + int careerMonths = calculateCareerMonths(user, startDate, endDate, true); + user.updateCareer(String.valueOf(careerMonths)); + } + + public void removeCareer(User user, LocalDate startDate, LocalDate endDate) { + int careerMonths = calculateCareerMonths(user, startDate, endDate, false); + user.updateCareer(String.valueOf(careerMonths)); + } + + public User getUserById(Long userId) { + return userRepository + .findByIdAndDeletedAtIsNull(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + } + + public UserDTO getUserToDTO(Long userId) { + return UserDTO.from( + userRepository + .findIdWithKindergarten(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL))); + } + + @Transactional + public User signUpByKakao(KakaoUserResponse userResponse) { + String email = userResponse.getKakao_account().getEmail(); + + String nickname; + if (userResponse.getKakao_account().getProfile() != null + && userResponse.getKakao_account().getProfile().getNickname() != null + && !userResponse.getKakao_account().getProfile().getNickname().trim().isEmpty()) { + String originalNickname = userResponse.getKakao_account().getProfile().getNickname().trim(); + nickname = + originalNickname.length() > 10 ? originalNickname.substring(0, 10) : originalNickname; + } else { + // "카카오" (3글자) + ID 마지막 6자리 = 최대 9글자 + String idSuffix = String.valueOf(userResponse.getId()); + if (idSuffix.length() > 6) { + idSuffix = idSuffix.substring(idSuffix.length() - 6); + } + nickname = "카카오" + idSuffix; } - public User getUserById(Long userId) { - return userRepository.findByIdAndDeletedAtIsNull(userId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + // 활성 사용자 확인 + Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); + if (activeUser.isPresent()) { + return activeUser.get(); } - public UserDTO getUserToDTO(Long userId) { - return UserDTO.from(userRepository.findIdWithKindergarten(userId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL))); + // 탈퇴된 사용자 확인 및 복구 + Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(email); + if (deletedUser.isPresent()) { + User user = deletedUser.get(); + user.restore(); + + // 소셜 로그인 정보 업데이트 + if (userResponse.getKakao_account().getProfile() != null) { + user.updateProfileImageUrl( + userResponse.getKakao_account().getProfile().getProfile_image_url()); + } + + return user; } - @Transactional - public User signUpByKakao(KakaoUserResponse userResponse) { - String email = userResponse.getKakao_account().getEmail(); - - String nickname; - if (userResponse.getKakao_account().getProfile() != null - && userResponse.getKakao_account().getProfile().getNickname() != null - && !userResponse.getKakao_account().getProfile().getNickname().trim().isEmpty()) { - String originalNickname = userResponse.getKakao_account().getProfile().getNickname().trim(); - nickname = originalNickname.length() > 10 ? originalNickname.substring(0, 10) : originalNickname; - } else { - // "카카오" (3글자) + ID 마지막 6자리 = 최대 9글자 - String idSuffix = String.valueOf(userResponse.getId()); - if (idSuffix.length() > 6) { - idSuffix = idSuffix.substring(idSuffix.length() - 6); - } - nickname = "카카오" + idSuffix; - } - - // 활성 사용자 확인 - Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); - if (activeUser.isPresent()) { - return activeUser.get(); - } - - // 탈퇴된 사용자 확인 및 복구 - Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(email); - if (deletedUser.isPresent()) { - User user = deletedUser.get(); - user.restore(); - - // 소셜 로그인 정보 업데이트 - if (userResponse.getKakao_account().getProfile() != null) { - user.updateProfileImageUrl(userResponse.getKakao_account().getProfile().getProfile_image_url()); - } - - return user; - } - - // 새로운 사용자 생성 - String dummyPassword = encodePassword("kakao_" + userResponse.getId()); - // 프로필 이미지 업데이트 - String profileImageUrl = userResponse.getKakao_account().getProfile() != null - ? userResponse.getKakao_account().getProfile().getProfile_image_url() + // 새로운 사용자 생성 + String dummyPassword = encodePassword("kakao_" + userResponse.getId()); + // 프로필 이미지 업데이트 + String profileImageUrl = + userResponse.getKakao_account().getProfile() != null + ? userResponse.getKakao_account().getProfile().getProfile_image_url() : null; - User user = User.registerKakao(email, dummyPassword, userResponse.getId(), nickname, UserRole.GENERAL, - profileImageUrl); + User user = + User.registerKakao( + email, + dummyPassword, + userResponse.getId(), + nickname, + UserRole.GENERAL, + profileImageUrl); - userRepository.save(user); + userRepository.save(user); - return user; - } + return user; + } - @Transactional - public User signUpByNaver(NaverUserResponse userResponse) { - String email = userResponse.getResponse().getEmail(); - - // 활성 사용자 확인 - Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); - if (activeUser.isPresent()) { - return activeUser.get(); - } - - // 탈퇴된 사용자 확인 및 복구 - Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(email); - if (deletedUser.isPresent()) { - User user = deletedUser.get(); - user.restore(); - - // 소셜 로그인 정보 업데이트 - if (userResponse.getResponse().getProfile_image() != null) { - user.updateProfileImageUrl(userResponse.getResponse().getProfile_image()); - } - - return user; - } - - // 새로운 사용자 생성 - String dummyPassword = encodePassword("naver_" + userResponse.getResponse().getId()); - - // 네이버 닉네임 길이 제한 처리 - String naverNickname = userResponse.getResponse().getNickname(); - if (naverNickname != null && !naverNickname.trim().isEmpty()) { - naverNickname = naverNickname.trim(); - if (naverNickname.length() > 10) { - naverNickname = naverNickname.substring(0, 10); - } - } else { - // 닉네임이 없는 경우 기본 닉네임 생성: "네이버" + ID 마지막 6자리 - String idSuffix = userResponse.getResponse().getId(); - if (idSuffix != null && idSuffix.length() > 6) { - idSuffix = idSuffix.substring(idSuffix.length() - 6); - } - naverNickname = "네이버" + (idSuffix != null ? idSuffix : "사용자"); - } - - User user = User.registerNaver(email, dummyPassword, userResponse.getResponse().getId(), - naverNickname, UserRole.GENERAL, - userResponse.getResponse().getProfile_image()); - - userRepository.save(user); - - return user; + @Transactional + public User signUpByNaver(NaverUserResponse userResponse) { + String email = userResponse.getResponse().getEmail(); + + // 활성 사용자 확인 + Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(email); + if (activeUser.isPresent()) { + return activeUser.get(); } - @Transactional - public User signUpByApple(AppleUserResponse userResponse) { - String appleUserId = userResponse.getSub(); - String providedEmail = userResponse.getEmail(); - - // 이메일 숨기기 처리: 시스템 이메일 생성 - String systemEmail; - if (providedEmail != null && providedEmail.endsWith("@privaterelay.appleid.com")) { - // 익명 이메일인 경우 시스템 이메일 생성 - systemEmail = "apple_user_" + appleUserId.substring(0, Math.min(appleUserId.length(), 10)) - + "@kindergarten.system"; - } else if (providedEmail != null) { - // 실제 이메일인 경우 그대로 사용 - systemEmail = providedEmail; - } else { - // 이메일이 없는 경우 시스템 이메일 생성 - systemEmail = "apple_user_" + appleUserId.substring(0, Math.min(appleUserId.length(), 10)) - + "@kindergarten.system"; - } - - // 활성 사용자 확인 - Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(systemEmail); - if (activeUser.isPresent()) { - return activeUser.get(); - } - - // 탈퇴된 사용자 확인 및 복구 - Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(systemEmail); - if (deletedUser.isPresent()) { - User user = deletedUser.get(); - user.restore(); - return user; - } - - // 새로운 사용자 생성 - String dummyPassword = encodePassword("apple_" + appleUserId); - String nickname; - if (userResponse.getName() != null && !userResponse.getName().trim().isEmpty()) { - String originalName = userResponse.getName().trim(); - nickname = originalName.length() > 10 ? originalName.substring(0, 10) : originalName; - } else { - // "애플" (2글자) + 사용자 ID 마지막 6자리 = 최대 8글자 - String idSuffix = appleUserId.length() > 6 ? appleUserId.substring(appleUserId.length() - 6) : appleUserId; - nickname = "애플" + idSuffix; - } - - User user = User.registerApple(systemEmail, dummyPassword, appleUserId, nickname, UserRole.GENERAL); - - userRepository.save(user); - - return user; + // 탈퇴된 사용자 확인 및 복구 + Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(email); + if (deletedUser.isPresent()) { + User user = deletedUser.get(); + user.restore(); + + // 소셜 로그인 정보 업데이트 + if (userResponse.getResponse().getProfile_image() != null) { + user.updateProfileImageUrl(userResponse.getResponse().getProfile_image()); + } + + return user; } - @Transactional - public void updateHomeShortcut(Long userId, HomeShortcutsDto homeShortcutsDto) { - User user = getUserById(userId); - user.updateHomeShortcut(homeShortcutsDto.toJson()); + // 새로운 사용자 생성 + String dummyPassword = encodePassword("naver_" + userResponse.getResponse().getId()); + + // 네이버 닉네임 길이 제한 처리 + String naverNickname = userResponse.getResponse().getNickname(); + if (naverNickname != null && !naverNickname.trim().isEmpty()) { + naverNickname = naverNickname.trim(); + if (naverNickname.length() > 10) { + naverNickname = naverNickname.substring(0, 10); + } + } else { + // 닉네임이 없는 경우 기본 닉네임 생성: "네이버" + ID 마지막 6자리 + String idSuffix = userResponse.getResponse().getId(); + if (idSuffix != null && idSuffix.length() > 6) { + idSuffix = idSuffix.substring(idSuffix.length() - 6); + } + naverNickname = "네이버" + (idSuffix != null ? idSuffix : "사용자"); } - @Transactional - public void saveSignUpCertification(EmailCertificationRequestDTO request, String certification) { - if (userRepository.existsByEmail(request.getEmail())) { - throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); - } + User user = + User.registerNaver( + email, + dummyPassword, + userResponse.getResponse().getId(), + naverNickname, + UserRole.GENERAL, + userResponse.getResponse().getProfile_image()); + + userRepository.save(user); + + return user; + } + + @Transactional + public User signUpByApple(AppleUserResponse userResponse) { + String appleUserId = userResponse.getSub(); + String providedEmail = userResponse.getEmail(); + + // 이메일 숨기기 처리: 시스템 이메일 생성 + String systemEmail; + if (providedEmail != null && providedEmail.endsWith("@privaterelay.appleid.com")) { + // 익명 이메일인 경우 시스템 이메일 생성 + systemEmail = + "apple_user_" + + appleUserId.substring(0, Math.min(appleUserId.length(), 10)) + + "@kindergarten.system"; + } else if (providedEmail != null) { + // 실제 이메일인 경우 그대로 사용 + systemEmail = providedEmail; + } else { + // 이메일이 없는 경우 시스템 이메일 생성 + systemEmail = + "apple_user_" + + appleUserId.substring(0, Math.min(appleUserId.length(), 10)) + + "@kindergarten.system"; + } - EmailCertification emailCert = EmailCertification.builder() - .email(request.getEmail()) - .type(EmailCertificationType.EMAIL) - .code(certification) - .isCertificated(false) - .build(); + // 활성 사용자 확인 + Optional activeUser = userRepository.findByEmailAndDeletedAtIsNull(systemEmail); + if (activeUser.isPresent()) { + return activeUser.get(); + } - emailCertificationRepository.save(emailCert); + // 탈퇴된 사용자 확인 및 복구 + Optional deletedUser = userRepository.findByEmailAndDeletedAtIsNotNull(systemEmail); + if (deletedUser.isPresent()) { + User user = deletedUser.get(); + user.restore(); + return user; } - @Transactional - public void savePasswordCertification(EmailCertificationRequestDTO request, String certification) { - if (!userRepository.existsByEmail(request.getEmail())) { - throw new BusinessException(ErrorCodes.NOT_FOUND_USER); - } + // 새로운 사용자 생성 + String dummyPassword = encodePassword("apple_" + appleUserId); + String nickname; + if (userResponse.getName() != null && !userResponse.getName().trim().isEmpty()) { + String originalName = userResponse.getName().trim(); + nickname = originalName.length() > 10 ? originalName.substring(0, 10) : originalName; + } else { + // "애플" (2글자) + 사용자 ID 마지막 6자리 = 최대 8글자 + String idSuffix = + appleUserId.length() > 6 ? appleUserId.substring(appleUserId.length() - 6) : appleUserId; + nickname = "애플" + idSuffix; + } - EmailCertification passwordCert = EmailCertification.builder() - .email(request.getEmail()) - .type(EmailCertificationType.TEMPORARY_PASSWORD) - .code(certification) - .isCertificated(false) - .build(); + User user = + User.registerApple(systemEmail, dummyPassword, appleUserId, nickname, UserRole.GENERAL); - emailCertificationRepository.save(passwordCert); - } + userRepository.save(user); - @Transactional - public void checkEmailCertification(CheckEmailCertificationRequestDTO request) { - EmailCertification emailCertification = emailCertificationRepository - .findByEmailAndCodeAndTypeAndDeletedAtIsNull(request.getEmail(), request.getCertification(), EmailCertificationType.EMAIL); + return user; + } - if (emailCertification == null) { - throw new BusinessException(ErrorCodes.NOT_FOUND_EMAIL); - } + @Transactional + public void updateHomeShortcut(Long userId, HomeShortcutsDto homeShortcutsDto) { + User user = getUserById(userId); + user.updateHomeShortcut(homeShortcutsDto.toJson()); + } - emailCertification.completeCertification(); + @Transactional + public void saveSignUpCertification(EmailCertificationRequestDTO request, String certification) { + if (userRepository.existsByEmail(request.getEmail())) { + throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); } - @Transactional - public void updateUserRole(Long userId, UpdateUserRoleRequestDTO request) { - User user = getUserById(userId); - user.updateUserRole(request.getRole()); + EmailCertification emailCert = + EmailCertification.builder() + .email(request.getEmail()) + .type(EmailCertificationType.EMAIL) + .code(certification) + .isCertificated(false) + .build(); + + emailCertificationRepository.save(emailCert); + } + + @Transactional + public void savePasswordCertification( + EmailCertificationRequestDTO request, String certification) { + if (!userRepository.existsByEmail(request.getEmail())) { + throw new BusinessException(ErrorCodes.NOT_FOUND_USER); } - public void updateTemporaryPassword(String email, String number) { - User user = getUserByEmail(email); - user.changePassword(passwordEncoder.encode(number)); + EmailCertification passwordCert = + EmailCertification.builder() + .email(request.getEmail()) + .type(EmailCertificationType.TEMPORARY_PASSWORD) + .code(certification) + .isCertificated(false) + .build(); + + emailCertificationRepository.save(passwordCert); + } + + @Transactional + public void checkEmailCertification(CheckEmailCertificationRequestDTO request) { + EmailCertification emailCertification = + emailCertificationRepository.findByEmailAndCodeAndTypeAndDeletedAtIsNull( + request.getEmail(), request.getCertification(), EmailCertificationType.EMAIL); + + if (emailCertification == null) { + throw new BusinessException(ErrorCodes.NOT_FOUND_EMAIL); } - public void checkEmailCertificationByTemporaryPassword(String email, String code) { - EmailCertification emailCertification = emailCertificationRepository - .findByEmailAndCodeAndTypeAndDeletedAtIsNull(email, code, EmailCertificationType.TEMPORARY_PASSWORD); + emailCertification.completeCertification(); + } - if (emailCertification == null) { - throw new BusinessException(ErrorCodes.NOT_FOUND_CERTIFICATION); - } + @Transactional + public void updateUserRole(Long userId, UpdateUserRoleRequestDTO request) { + User user = getUserById(userId); + user.updateUserRole(request.getRole()); + } - if (!emailCertification.getCode().equals(code)) { - throw new BusinessException(ErrorCodes.CERTIFICATION_CODE_MISMATCH); - } - } + public void updateTemporaryPassword(String email, String number) { + User user = getUserByEmail(email); + user.changePassword(passwordEncoder.encode(number)); + } - @Transactional(readOnly = true) - public NotificationSettingsDTO getNotificationSettings(Long userId) { - User user = getUserById(userId); - - return NotificationSettingsDTO.builder() - .allNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) - .communityNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS)) - .eventNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS)) - .build(); - } + public void checkEmailCertificationByTemporaryPassword(String email, String code) { + EmailCertification emailCertification = + emailCertificationRepository.findByEmailAndCodeAndTypeAndDeletedAtIsNull( + email, code, EmailCertificationType.TEMPORARY_PASSWORD); - @Transactional - public NotificationSettingsDTO updateNotificationSettings(Long userId, NotificationSettingsDTO request) { - User user = getUserById(userId); - - Set enabledSettings = new HashSet<>(); - if (request.isAllNotificationsEnabled()) { - enabledSettings.add(NotificationSetting.ALL_NOTIFICATIONS); - } - if (request.isCommunityNotificationsEnabled()) { - enabledSettings.add(NotificationSetting.COMMUNITY_NOTIFICATIONS); - } - if (request.isEventNotificationsEnabled()) { - enabledSettings.add(NotificationSetting.EVENT_NOTIFICATIONS); - } - - user.setNotificationSettings(enabledSettings); - - return request; + if (emailCertification == null) { + throw new BusinessException(ErrorCodes.NOT_FOUND_CERTIFICATION); } - @Transactional - public void markUserAsReviewWriter(Long userId) { - User user = getUserById(userId); - if (!user.hasWrittenReview()) { - user.markAsReviewWriter(); - } + if (!emailCertification.getCode().equals(code)) { + throw new BusinessException(ErrorCodes.CERTIFICATION_CODE_MISMATCH); } - - /// 관리자용 - 전체 유저 조회 - @Transactional(readOnly = true) - public Page getAllUsers(Pageable pageable) { - Page users = userRepository.findAllUsersWithKindergarten(pageable); - return users.map(AdminUserResponseDTO::from); + } + + @Transactional(readOnly = true) + public NotificationSettingsDTO getNotificationSettings(Long userId) { + User user = getUserById(userId); + + return NotificationSettingsDTO.builder() + .allNotificationsEnabled(user.hasNotificationEnabled(NotificationSetting.ALL_NOTIFICATIONS)) + .communityNotificationsEnabled( + user.hasNotificationEnabled(NotificationSetting.COMMUNITY_NOTIFICATIONS)) + .eventNotificationsEnabled( + user.hasNotificationEnabled(NotificationSetting.EVENT_NOTIFICATIONS)) + .build(); + } + + @Transactional + public NotificationSettingsDTO updateNotificationSettings( + Long userId, NotificationSettingsDTO request) { + User user = getUserById(userId); + + Set enabledSettings = new HashSet<>(); + if (request.isAllNotificationsEnabled()) { + enabledSettings.add(NotificationSetting.ALL_NOTIFICATIONS); } - - /// 관리자용 - 유저 검색 - @Transactional(readOnly = true) - public Page searchUsers(UserSearchDTO searchDTO, Pageable pageable) { - Page users = userRepository.findUsersWithFilters( - searchDTO.getEmail(), - searchDTO.getNickname(), - searchDTO.getRole(), - searchDTO.getProvider(), - searchDTO.getStatus(), - searchDTO.getKindergartenName(), - searchDTO.getHasWrittenReview(), - searchDTO.getIsRestoredUser(), - pageable - ); - return users.map(AdminUserResponseDTO::from); + if (request.isCommunityNotificationsEnabled()) { + enabledSettings.add(NotificationSetting.COMMUNITY_NOTIFICATIONS); } - - @Transactional(readOnly = true) - public AdminUserResponseDTO getUserToAdminDTO(Long userId) { - User user = userRepository.findByIdWithKindergarten(userId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); - return AdminUserResponseDTO.from(user); + if (request.isEventNotificationsEnabled()) { + enabledSettings.add(NotificationSetting.EVENT_NOTIFICATIONS); } - /// 관리자용 - 유저 상태 변경 - @Transactional - public void updateUserStatus(Long userId, UpdateUserStatusRequestDTO request) { - // 관리자 권한 확인 - User admin = getUserById(userId); - if (!admin.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - // 대상 유저 조회 - User targetUser = userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); - - // 관리자는 자신의 상태를 변경할 수 없음 - if (targetUser.getRole().equals(UserRole.ADMIN)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - // 상태 변경 - targetUser.updateStatus(request.getStatus()); + user.setNotificationSettings(enabledSettings); + + return request; + } + + @Transactional + public void markUserAsReviewWriter(Long userId) { + User user = getUserById(userId); + if (!user.hasWrittenReview()) { + user.markAsReviewWriter(); + } + } + + /// 관리자용 - 전체 유저 조회 + @Transactional(readOnly = true) + public Page getAllUsers(Pageable pageable) { + Page users = userRepository.findAllUsersWithKindergarten(pageable); + return users.map(AdminUserResponseDTO::from); + } + + /// 관리자용 - 유저 검색 + @Transactional(readOnly = true) + public Page searchUsers(UserSearchDTO searchDTO, Pageable pageable) { + Page users = + userRepository.findUsersWithFilters( + searchDTO.getEmail(), + searchDTO.getNickname(), + searchDTO.getRole(), + searchDTO.getProvider(), + searchDTO.getStatus(), + searchDTO.getKindergartenName(), + searchDTO.getHasWrittenReview(), + searchDTO.getIsRestoredUser(), + pageable); + return users.map(AdminUserResponseDTO::from); + } + + @Transactional(readOnly = true) + public AdminUserResponseDTO getUserToAdminDTO(Long userId) { + User user = + userRepository + .findByIdWithKindergarten(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_USER)); + return AdminUserResponseDTO.from(user); + } + + /// 관리자용 - 유저 상태 변경 + @Transactional + public void updateUserStatus(Long userId, UpdateUserStatusRequestDTO request) { + // 관리자 권한 확인 + User admin = getUserById(userId); + if (!admin.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } - /// 경력 개월 수 계산 - private int calculateCareerMonths(User user, LocalDate startDate, LocalDate endDate, boolean isAdding) { - int currentCareerMonths = user.getCareer() == null ? 0 : Integer.parseInt(user.getCareer()); - long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate); - return isAdding ? - currentCareerMonths + (int)monthsBetween : - currentCareerMonths - (int)monthsBetween; + // 대상 유저 조회 + User targetUser = + userRepository + .findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_EMAIL)); + + // 관리자는 자신의 상태를 변경할 수 없음 + if (targetUser.getRole().equals(UserRole.ADMIN)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } + + // 상태 변경 + targetUser.updateStatus(request.getStatus()); + } + + /// 경력 개월 수 계산 + private int calculateCareerMonths( + User user, LocalDate startDate, LocalDate endDate, boolean isAdding) { + int currentCareerMonths = user.getCareer() == null ? 0 : Integer.parseInt(user.getCareer()); + long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate); + return isAdding + ? currentCareerMonths + (int) monthsBetween + : currentCareerMonths - (int) monthsBetween; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java index 99f397a..f8977a8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/controller/UserBlockController.java @@ -6,46 +6,41 @@ import com.onebyone.kindergarten.global.common.ResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/blocks") @Tag(name = "유저 차단", description = "유저 차단 관련 API") public class UserBlockController { - private final UserBlockService userBlockService; - - @Operation(summary = "유저 차단-01 : 유저 차단", description = "로그인한 유저가 특정 유저를 차단합니다.") - @PostMapping - public ResponseDto blockUser( - @AuthenticationPrincipal UserDetails userDetails, - @RequestBody UserBlockRequestDto request - ) { - userBlockService.blockUser(Long.valueOf(userDetails.getUsername()), request.getTargetUserEmail()); - return ResponseDto.success(null); - } + private final UserBlockService userBlockService; - @Operation(summary = "유저 차단-02 : 유저 차단 해제", description = "로그인한 유저가 차단한 특정 유저를 차단 해제합니다.") - @DeleteMapping("/{targetUserEmail}") - public ResponseDto unblockUser( - @AuthenticationPrincipal UserDetails userDetails, - @PathVariable String targetUserEmail - ) { - userBlockService.unblockUser(Long.valueOf(userDetails.getUsername()), targetUserEmail); - return ResponseDto.success(null); - } + @Operation(summary = "유저 차단-01 : 유저 차단", description = "로그인한 유저가 특정 유저를 차단합니다.") + @PostMapping + public ResponseDto blockUser( + @AuthenticationPrincipal UserDetails userDetails, @RequestBody UserBlockRequestDto request) { + userBlockService.blockUser( + Long.valueOf(userDetails.getUsername()), request.getTargetUserEmail()); + return ResponseDto.success(null); + } - @Operation(summary = "유저 차단-03 : 차단된 유저 목록 조회", description = "로그인한 유저가 차단한 유저들의 목록을 조회합니다.") - @GetMapping("/list") - public ResponseDto> getBlockedUsers( - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success(userBlockService.getBlockedUsers(Long.valueOf(userDetails.getUsername()))); - } + @Operation(summary = "유저 차단-02 : 유저 차단 해제", description = "로그인한 유저가 차단한 특정 유저를 차단 해제합니다.") + @DeleteMapping("/{targetUserEmail}") + public ResponseDto unblockUser( + @AuthenticationPrincipal UserDetails userDetails, @PathVariable String targetUserEmail) { + userBlockService.unblockUser(Long.valueOf(userDetails.getUsername()), targetUserEmail); + return ResponseDto.success(null); + } -} \ No newline at end of file + @Operation(summary = "유저 차단-03 : 차단된 유저 목록 조회", description = "로그인한 유저가 차단한 유저들의 목록을 조회합니다.") + @GetMapping("/list") + public ResponseDto> getBlockedUsers( + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + userBlockService.getBlockedUsers(Long.valueOf(userDetails.getUsername()))); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/request/UserBlockRequestDto.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/request/UserBlockRequestDto.java index 4e099e7..223ef40 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/request/UserBlockRequestDto.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/request/UserBlockRequestDto.java @@ -7,5 +7,5 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserBlockRequestDto { - private String targetUserEmail; -} \ No newline at end of file + private String targetUserEmail; +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/response/BlockedUserResponseDto.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/response/BlockedUserResponseDto.java index eadcb78..bfd5521 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/response/BlockedUserResponseDto.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/dto/response/BlockedUserResponseDto.java @@ -1,25 +1,25 @@ package com.onebyone.kindergarten.domain.userBlock.dto.response; import com.onebyone.kindergarten.domain.user.enums.UserRole; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Getter @Builder public class BlockedUserResponseDto { - private final String email; - private final String nickname; - private final UserRole userRole; - private final String career; - private final LocalDateTime blockedAt; + private final String email; + private final String nickname; + private final UserRole userRole; + private final String career; + private final LocalDateTime blockedAt; - public BlockedUserResponseDto(String email, String nickname, UserRole userRole, String career, LocalDateTime blockedAt) { - this.email = email; - this.nickname = nickname; - this.userRole = userRole; - this.career = career; - this.blockedAt = blockedAt; - } -} \ No newline at end of file + public BlockedUserResponseDto( + String email, String nickname, UserRole userRole, String career, LocalDateTime blockedAt) { + this.email = email; + this.nickname = nickname; + this.userRole = userRole; + this.career = career; + this.blockedAt = blockedAt; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/entity/UserBlock.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/entity/UserBlock.java index f3049c9..3183803 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/entity/UserBlock.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/entity/UserBlock.java @@ -12,21 +12,21 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserBlock extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "blocked_user_id") - private User blockedUser; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "blocked_user_id") + private User blockedUser; - @Builder - public UserBlock(User user, User blockedUser) { - this.user = user; - this.blockedUser = blockedUser; - } -} \ No newline at end of file + @Builder + public UserBlock(User user, User blockedUser) { + this.user = user; + this.blockedUser = blockedUser; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/repository/UserBlockRepository.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/repository/UserBlockRepository.java index effdde2..690aa84 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/repository/UserBlockRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/repository/UserBlockRepository.java @@ -2,27 +2,27 @@ import com.onebyone.kindergarten.domain.userBlock.dto.response.BlockedUserResponseDto; import com.onebyone.kindergarten.domain.userBlock.entity.UserBlock; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public interface UserBlockRepository extends JpaRepository { - @Query("SELECT ub.blockedUser.id FROM UserBlock ub WHERE ub.user.id = :userId") - List findBlockedUserIdsByUserId(@Param("userId") Long userId); + @Query("SELECT ub.blockedUser.id FROM UserBlock ub WHERE ub.user.id = :userId") + List findBlockedUserIdsByUserId(@Param("userId") Long userId); - boolean existsByUserIdAndBlockedUserId(Long userId, Long blockedUserId); + boolean existsByUserIdAndBlockedUserId(Long userId, Long blockedUserId); - void deleteByUserIdAndBlockedUserId(Long userId, Long blockedUserId); + void deleteByUserIdAndBlockedUserId(Long userId, Long blockedUserId); - @Query("SELECT new com.onebyone.kindergarten.domain.userBlock.dto.response.BlockedUserResponseDto(" + - "bu.email, bu.nickname, bu.role, bu.career, ub.createdAt) " + - "FROM UserBlock ub " + - "JOIN ub.blockedUser bu " + - "WHERE ub.user.id = :userId") - List findBlockedUsersByUserId(@Param("userId") Long userId); -} \ No newline at end of file + @Query( + "SELECT new com.onebyone.kindergarten.domain.userBlock.dto.response.BlockedUserResponseDto(" + + "bu.email, bu.nickname, bu.role, bu.career, ub.createdAt) " + + "FROM UserBlock ub " + + "JOIN ub.blockedUser bu " + + "WHERE ub.user.id = :userId") + List findBlockedUsersByUserId(@Param("userId") Long userId); +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java b/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java index ac2cc7a..d1685d8 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userBlock/service/UserBlockService.java @@ -3,11 +3,11 @@ import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.domain.userBlock.dto.response.BlockedUserResponseDto; -import java.util.List; import com.onebyone.kindergarten.domain.userBlock.entity.UserBlock; import com.onebyone.kindergarten.domain.userBlock.repository.UserBlockRepository; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,43 +16,40 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class UserBlockService { - private final UserBlockRepository userBlockRepository; - private final UserService userService; - - @Transactional - public void blockUser(Long userId, String targetUserEmail) { - User user = userService.getUserById(userId); - - User targetUser = userService.getUserByEmail(targetUserEmail); - validateBlockRequest(user.getId(), targetUser.getId()); - - UserBlock userBlock = UserBlock.builder() - .user(user) - .blockedUser(targetUser) - .build(); - - userBlockRepository.save(userBlock); - } + private final UserBlockRepository userBlockRepository; + private final UserService userService; - @Transactional - public void unblockUser(Long userId, String targetUserEmail) { - User user = userService.getUserById(userId); + @Transactional + public void blockUser(Long userId, String targetUserEmail) { + User user = userService.getUserById(userId); - User targetUser = userService.getUserByEmail(targetUserEmail); - userBlockRepository.deleteByUserIdAndBlockedUserId(user.getId(), targetUser.getId()); - } + User targetUser = userService.getUserByEmail(targetUserEmail); + validateBlockRequest(user.getId(), targetUser.getId()); - private void validateBlockRequest(Long userId, Long targetUserId) { - if (userId.equals(targetUserId)) { - throw new BusinessException(ErrorCodes.SELF_BLOCK_NOT_ALLOWED); - } - if (userBlockRepository.existsByUserIdAndBlockedUserId(userId, targetUserId)) { - throw new BusinessException(ErrorCodes.ALREADY_BLOCK_USER); - } - } + UserBlock userBlock = UserBlock.builder().user(user).blockedUser(targetUser).build(); - public List getBlockedUsers(Long userId) { - User user = userService.getUserById(userId); - return userBlockRepository.findBlockedUsersByUserId(user.getId()); + userBlockRepository.save(userBlock); + } + + @Transactional + public void unblockUser(Long userId, String targetUserEmail) { + User user = userService.getUserById(userId); + + User targetUser = userService.getUserByEmail(targetUserEmail); + userBlockRepository.deleteByUserIdAndBlockedUserId(user.getId(), targetUser.getId()); + } + + private void validateBlockRequest(Long userId, Long targetUserId) { + if (userId.equals(targetUserId)) { + throw new BusinessException(ErrorCodes.SELF_BLOCK_NOT_ALLOWED); } -} \ No newline at end of file + if (userBlockRepository.existsByUserIdAndBlockedUserId(userId, targetUserId)) { + throw new BusinessException(ErrorCodes.ALREADY_BLOCK_USER); + } + } + + public List getBlockedUsers(Long userId) { + User user = userService.getUserById(userId); + return userBlockRepository.findBlockedUsersByUserId(user.getId()); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java index ca893d3..bdf0c03 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/controller/UserFavoriteKindergartenController.java @@ -1,61 +1,54 @@ package com.onebyone.kindergarten.domain.userFavoriteKindergartens.controller; import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenResponseDTO; +import com.onebyone.kindergarten.domain.userFavoriteKindergartens.dto.request.ToggleFavoriteRequestDTO; import com.onebyone.kindergarten.domain.userFavoriteKindergartens.dto.response.FavoriteToggleResponseDTO; +import com.onebyone.kindergarten.domain.userFavoriteKindergartens.service.UserFavoriteKindergartenService; import com.onebyone.kindergarten.global.common.ResponseDto; +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 org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import com.onebyone.kindergarten.domain.userFavoriteKindergartens.dto.request.ToggleFavoriteRequestDTO; -import com.onebyone.kindergarten.domain.userFavoriteKindergartens.service.UserFavoriteKindergartenService; - -import java.util.List; @RestController @RequestMapping("/favorite-kindergartens") @Tag(name = "유치원 즐겨찾기", description = "유치원 즐겨찾기 API") @RequiredArgsConstructor public class UserFavoriteKindergartenController { - private final UserFavoriteKindergartenService favoriteService; + private final UserFavoriteKindergartenService favoriteService; - @PostMapping - @Operation(summary = "유치원 즐겨찾기 토글", description = "유치원을 즐겨찾기에 추가하거나 제거합니다.") - public ResponseDto toggleFavorite( - @Valid @RequestBody ToggleFavoriteRequestDTO request, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success( - favoriteService.toggleFavorite(Long.valueOf(userDetails.getUsername()), request.getKindergartenId()) - ); - } + @PostMapping + @Operation(summary = "유치원 즐겨찾기 토글", description = "유치원을 즐겨찾기에 추가하거나 제거합니다.") + public ResponseDto toggleFavorite( + @Valid @RequestBody ToggleFavoriteRequestDTO request, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + favoriteService.toggleFavorite( + Long.valueOf(userDetails.getUsername()), request.getKindergartenId())); + } - @GetMapping - @Operation(summary = "즐겨찾기 목록 조회", description = "자신의 즐겨찾기 유치원 목록을 조회합니다.") - public ResponseDto> getMyFavorites( - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success( - favoriteService.getMyFavorites(Long.valueOf(userDetails.getUsername())) - ); - } + @GetMapping + @Operation(summary = "즐겨찾기 목록 조회", description = "자신의 즐겨찾기 유치원 목록을 조회합니다.") + public ResponseDto> getMyFavorites( + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + favoriteService.getMyFavorites(Long.valueOf(userDetails.getUsername()))); + } - @GetMapping("/status") - @Operation(summary = "즐겨찾기 상태 확인", description = "특정 유치원의 즐겨찾기 상태를 확인합니다.") - public ResponseDto getFavoriteStatus( - @RequestParam Long kindergartenId, - @AuthenticationPrincipal UserDetails userDetails - ) { - return ResponseDto.success( - favoriteService.isFavorite(Long.valueOf(userDetails.getUsername()), kindergartenId) - ); - } + @GetMapping("/status") + @Operation(summary = "즐겨찾기 상태 확인", description = "특정 유치원의 즐겨찾기 상태를 확인합니다.") + public ResponseDto getFavoriteStatus( + @RequestParam Long kindergartenId, @AuthenticationPrincipal UserDetails userDetails) { + return ResponseDto.success( + favoriteService.isFavorite(Long.valueOf(userDetails.getUsername()), kindergartenId)); + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/request/ToggleFavoriteRequestDTO.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/request/ToggleFavoriteRequestDTO.java index b494e7c..328ecd3 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/request/ToggleFavoriteRequestDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/request/ToggleFavoriteRequestDTO.java @@ -4,5 +4,5 @@ @Getter public class ToggleFavoriteRequestDTO { - private Long kindergartenId; + private Long kindergartenId; } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/response/FavoriteToggleResponseDTO.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/response/FavoriteToggleResponseDTO.java index 1c8a54c..f456511 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/response/FavoriteToggleResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/dto/response/FavoriteToggleResponseDTO.java @@ -4,9 +4,9 @@ @Getter public class FavoriteToggleResponseDTO { - private final boolean isFavorite; + private final boolean isFavorite; - public FavoriteToggleResponseDTO(boolean isFavorite) { - this.isFavorite = isFavorite; - } -} \ No newline at end of file + public FavoriteToggleResponseDTO(boolean isFavorite) { + this.isFavorite = isFavorite; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/entity/UserFavoriteKindergarten.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/entity/UserFavoriteKindergarten.java index 7b39c59..adf2fad 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/entity/UserFavoriteKindergarten.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/entity/UserFavoriteKindergarten.java @@ -12,21 +12,21 @@ @Getter @NoArgsConstructor public class UserFavoriteKindergarten extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // 즐겨찾기 코드 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // 즐겨찾기 코드 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; // 사용자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 사용자 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "kindergarten_id", nullable = false) - private Kindergarten kindergarten; // 유치원 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "kindergarten_id", nullable = false) + private Kindergarten kindergarten; // 유치원 - @Builder - public UserFavoriteKindergarten(User user, Kindergarten kindergarten) { - this.user = user; - this.kindergarten = kindergarten; - } + @Builder + public UserFavoriteKindergarten(User user, Kindergarten kindergarten) { + this.user = user; + this.kindergarten = kindergarten; + } } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/repository/UserFavoriteKindergartenRepository.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/repository/UserFavoriteKindergartenRepository.java index 64f400a..8f842e9 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/repository/UserFavoriteKindergartenRepository.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/repository/UserFavoriteKindergartenRepository.java @@ -3,39 +3,49 @@ import com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenResponseDTO; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.userFavoriteKindergartens.entity.UserFavoriteKindergarten; +import java.util.List; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository -public interface UserFavoriteKindergartenRepository extends JpaRepository { +public interface UserFavoriteKindergartenRepository + extends JpaRepository { - @Query("SELECT new com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenResponseDTO(" + - " k.id, k.name, k.establishment, k.establishmentDate, k.openDate ,k.address, " + - " k.homepage, k.phoneNumber, k.classCount3, k.classCount4, k.classCount5, " + - " k.pupilCount3, k.pupilCount4, k.pupilCount5, k.mixPupilCount, " + - " k.specialPupilCount, k.latitude, k.longitude) " + - "FROM user_favorite_kindergarten uf " + - "JOIN uf.kindergarten k " + - "WHERE uf.user = :user") - List findDtosByUser(@Param("user") User user); + @Query( + "SELECT new com.onebyone.kindergarten.domain.kindergatens.dto.KindergartenResponseDTO(" + + " k.id, k.name, k.establishment, k.establishmentDate, k.openDate ,k.address, " + + " k.homepage, k.phoneNumber, k.classCount3, k.classCount4, k.classCount5, " + + " k.pupilCount3, k.pupilCount4, k.pupilCount5, k.mixPupilCount, " + + " k.specialPupilCount, k.latitude, k.longitude) " + + "FROM user_favorite_kindergarten uf " + + "JOIN uf.kindergarten k " + + "WHERE uf.user = :user") + List findDtosByUser(@Param("user") User user); - @Modifying - @Query("DELETE FROM user_favorite_kindergarten uf " + - "WHERE uf.user = :user AND uf.kindergarten.id = :kindergartenId") - void deleteByUserAndKindergartenId(@Param("user") User user, @Param("kindergartenId") Long kindergartenId); + @Modifying + @Query( + "DELETE FROM user_favorite_kindergarten uf " + + "WHERE uf.user = :user AND uf.kindergarten.id = :kindergartenId") + void deleteByUserAndKindergartenId( + @Param("user") User user, @Param("kindergartenId") Long kindergartenId); - @Query("SELECT COUNT(uf) > 0 FROM user_favorite_kindergarten uf " + - "WHERE uf.user = :user AND uf.kindergarten.id = :kindergartenId") - boolean existsByUserAndKindergartenId(@Param("user") User user, @Param("kindergartenId") Long kindergartenId); + @Query( + "SELECT COUNT(uf) > 0 FROM user_favorite_kindergarten uf " + + "WHERE uf.user = :user AND uf.kindergarten.id = :kindergartenId") + boolean existsByUserAndKindergartenId( + @Param("user") User user, @Param("kindergartenId") Long kindergartenId); - /// 사용자의 즐겨찾기 엔티티 목록 조회 - @EntityGraph(attributePaths = {"kindergarten", "kindergarten.kindergartenInternshipReviewAggregate", "kindergarten.kindergartenWorkReviewAggregate"}) - @Query("SELECT uf FROM user_favorite_kindergarten uf WHERE uf.user = :user") - List findByUser(@Param("user") User user); + /// 사용자의 즐겨찾기 엔티티 목록 조회 + @EntityGraph( + attributePaths = { + "kindergarten", + "kindergarten.kindergartenInternshipReviewAggregate", + "kindergarten.kindergartenWorkReviewAggregate" + }) + @Query("SELECT uf FROM user_favorite_kindergarten uf WHERE uf.user = :user") + List findByUser(@Param("user") User user); } diff --git a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java index c049c41..fcc3810 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/userFavoriteKindergartens/service/UserFavoriteKindergartenService.java @@ -8,66 +8,66 @@ import com.onebyone.kindergarten.domain.userFavoriteKindergartens.dto.response.FavoriteToggleResponseDTO; import com.onebyone.kindergarten.domain.userFavoriteKindergartens.entity.UserFavoriteKindergarten; import com.onebyone.kindergarten.domain.userFavoriteKindergartens.repository.UserFavoriteKindergartenRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class UserFavoriteKindergartenService { - private final UserFavoriteKindergartenRepository favoriteRepository; - private final UserService userService; - private final KindergartenRepository kindergartenRepository; + private final UserFavoriteKindergartenRepository favoriteRepository; + private final UserService userService; + private final KindergartenRepository kindergartenRepository; + + /// 유치원 즐겨찾기 토글 + @Transactional + public FavoriteToggleResponseDTO toggleFavorite(Long userId, Long kindergartenId) { - /// 유치원 즐겨찾기 토글 - @Transactional - public FavoriteToggleResponseDTO toggleFavorite(Long userId, Long kindergartenId) { + // 사용자 조회 + User user = userService.getUserById(userId); - // 사용자 조회 - User user = userService.getUserById(userId); - - // 즐겨찾기 존재 여부 확인 및 삭제 시도 - boolean existed = favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); - - if (existed) { - // 존재 - 삭제 - favoriteRepository.deleteByUserAndKindergartenId(user, kindergartenId); - return new FavoriteToggleResponseDTO(false); - } else { - // 존재 하지 않음 - 추가 - UserFavoriteKindergarten favorite = UserFavoriteKindergarten.builder() - .user(user) - .kindergarten(Kindergarten.builder().id(kindergartenId).build()) - .build(); - favoriteRepository.save(favorite); - return new FavoriteToggleResponseDTO(true); - } + // 즐겨찾기 존재 여부 확인 및 삭제 시도 + boolean existed = favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); + + if (existed) { + // 존재 - 삭제 + favoriteRepository.deleteByUserAndKindergartenId(user, kindergartenId); + return new FavoriteToggleResponseDTO(false); + } else { + // 존재 하지 않음 - 추가 + UserFavoriteKindergarten favorite = + UserFavoriteKindergarten.builder() + .user(user) + .kindergarten(Kindergarten.builder().id(kindergartenId).build()) + .build(); + favoriteRepository.save(favorite); + return new FavoriteToggleResponseDTO(true); } + } - /// 즐겨찾기 목록 조회 - public List getMyFavorites(Long userId) { + /// 즐겨찾기 목록 조회 + public List getMyFavorites(Long userId) { - // 사용자 조회 - User user = userService.getUserById(userId); + // 사용자 조회 + User user = userService.getUserById(userId); - // 즐겨찾기 목록 조회 - List favorites = favoriteRepository.findByUser(user); - return favorites.stream() - .map(favorite -> KindergartenResponseDTO.from(favorite.getKindergarten())) - .toList(); - } + // 즐겨찾기 목록 조회 + List favorites = favoriteRepository.findByUser(user); + return favorites.stream() + .map(favorite -> KindergartenResponseDTO.from(favorite.getKindergarten())) + .toList(); + } - /// 즐겨찾기 상태 확인 - public boolean isFavorite(Long userId, Long kindergartenId) { + /// 즐겨찾기 상태 확인 + public boolean isFavorite(Long userId, Long kindergartenId) { - // 사용자 조회 - User user = userService.getUserById(userId); + // 사용자 조회 + User user = userService.getUserById(userId); - // 유치원 존재 여부 확인 - return favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); - } -} \ No newline at end of file + // 유치원 존재 여부 확인 + return favoriteRepository.existsByUserAndKindergartenId(user, kindergartenId); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/config/BatchConfig.java b/src/main/java/com/onebyone/kindergarten/global/batch/config/BatchConfig.java index e98cda9..4d9ae24 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/config/BatchConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/config/BatchConfig.java @@ -6,6 +6,8 @@ import com.onebyone.kindergarten.domain.kindergatens.repository.KindergartenRepository; import com.onebyone.kindergarten.global.batch.processor.KindergartenRegionProcessor; import com.onebyone.kindergarten.global.batch.processor.KindergartenSubRegionProcessor; +import java.util.HashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; @@ -21,108 +23,105 @@ import org.springframework.data.domain.Sort; import org.springframework.transaction.PlatformTransactionManager; -import java.util.HashMap; -import java.util.Map; - @Configuration @EnableBatchProcessing @RequiredArgsConstructor public class BatchConfig { - private final JobRepository jobRepository; - private final PlatformTransactionManager transactionManager; - private final KindergartenRepository kindergartenRepository; - private final RegionRepository regionRepository; - private final SubRegionRepository subRegionRepository; - - @Bean - public KindergartenRegionProcessor regionProcessor() { - return KindergartenRegionProcessor.create(regionRepository); - } - - @Bean - public RepositoryItemReader kindergartenRegionReader() { - RepositoryItemReader reader = new RepositoryItemReader<>(); - reader.setRepository(kindergartenRepository); - reader.setMethodName("findAll"); - reader.setPageSize(100); - - Map sorts = new HashMap<>(); - sorts.put("id", Sort.Direction.ASC); - reader.setSort(sorts); - - return reader; - } - - @Bean - public RepositoryItemWriter kindergartenRegionWriter() { - RepositoryItemWriter writer = new RepositoryItemWriter<>(); - writer.setRepository(kindergartenRepository); - return writer; - } - - @Bean Step regionStep() { - return new StepBuilder("RegionStep", jobRepository) - .chunk(100, transactionManager) - .reader(kindergartenSubRegionReader()) - .processor(regionProcessor()) - .writer(kindergartenSubRegionWriter()) - .build(); - } - - @Bean - public Job regionJob() { - return new JobBuilder("regionJob", jobRepository) - .listener(new RunIdIncrementer()) - .start(regionStep()) - .build(); - } - - @Bean - public KindergartenSubRegionProcessor subRegionProcessor() { -// return new KindergartenAddressProcessor(); - return KindergartenSubRegionProcessor.create(regionRepository, subRegionRepository); - } - - @Bean - public RepositoryItemReader kindergartenSubRegionReader() { -// JpaPagingItemReader reader = new JpaPagingItemReader<>(); -// reader.setEntityManagerFactory(entityManagerFactory); -// reader.setMet - RepositoryItemReader reader = new RepositoryItemReader<>(); - reader.setRepository(kindergartenRepository); - reader.setMethodName("findAll"); - reader.setPageSize(100); - - Map sorts = new HashMap<>(); - sorts.put("id", Sort.Direction.ASC); - reader.setSort(sorts); - - return reader; - } - - @Bean - public RepositoryItemWriter kindergartenSubRegionWriter() { - RepositoryItemWriter writer = new RepositoryItemWriter<>(); - writer.setRepository(kindergartenRepository); - return writer; - } - - @Bean - public Step subRegionStep() { - return new StepBuilder("SubRegionStep", jobRepository) - .chunk(100, transactionManager) - .reader(kindergartenSubRegionReader()) - .processor(subRegionProcessor()) - .writer(kindergartenSubRegionWriter()) - .build(); - } - - @Bean - public Job subRegionJob() { - return new JobBuilder("SubRegionJob", jobRepository) - .listener(new RunIdIncrementer()) - .start(subRegionStep()) - .build(); - } - + private final JobRepository jobRepository; + private final PlatformTransactionManager transactionManager; + private final KindergartenRepository kindergartenRepository; + private final RegionRepository regionRepository; + private final SubRegionRepository subRegionRepository; + + @Bean + public KindergartenRegionProcessor regionProcessor() { + return KindergartenRegionProcessor.create(regionRepository); + } + + @Bean + public RepositoryItemReader kindergartenRegionReader() { + RepositoryItemReader reader = new RepositoryItemReader<>(); + reader.setRepository(kindergartenRepository); + reader.setMethodName("findAll"); + reader.setPageSize(100); + + Map sorts = new HashMap<>(); + sorts.put("id", Sort.Direction.ASC); + reader.setSort(sorts); + + return reader; + } + + @Bean + public RepositoryItemWriter kindergartenRegionWriter() { + RepositoryItemWriter writer = new RepositoryItemWriter<>(); + writer.setRepository(kindergartenRepository); + return writer; + } + + @Bean + Step regionStep() { + return new StepBuilder("RegionStep", jobRepository) + .chunk(100, transactionManager) + .reader(kindergartenSubRegionReader()) + .processor(regionProcessor()) + .writer(kindergartenSubRegionWriter()) + .build(); + } + + @Bean + public Job regionJob() { + return new JobBuilder("regionJob", jobRepository) + .listener(new RunIdIncrementer()) + .start(regionStep()) + .build(); + } + + @Bean + public KindergartenSubRegionProcessor subRegionProcessor() { + // return new KindergartenAddressProcessor(); + return KindergartenSubRegionProcessor.create(regionRepository, subRegionRepository); + } + + @Bean + public RepositoryItemReader kindergartenSubRegionReader() { + // JpaPagingItemReader reader = new JpaPagingItemReader<>(); + // reader.setEntityManagerFactory(entityManagerFactory); + // reader.setMet + RepositoryItemReader reader = new RepositoryItemReader<>(); + reader.setRepository(kindergartenRepository); + reader.setMethodName("findAll"); + reader.setPageSize(100); + + Map sorts = new HashMap<>(); + sorts.put("id", Sort.Direction.ASC); + reader.setSort(sorts); + + return reader; + } + + @Bean + public RepositoryItemWriter kindergartenSubRegionWriter() { + RepositoryItemWriter writer = new RepositoryItemWriter<>(); + writer.setRepository(kindergartenRepository); + return writer; + } + + @Bean + public Step subRegionStep() { + return new StepBuilder("SubRegionStep", jobRepository) + .chunk(100, transactionManager) + .reader(kindergartenSubRegionReader()) + .processor(subRegionProcessor()) + .writer(kindergartenSubRegionWriter()) + .build(); + } + + @Bean + public Job subRegionJob() { + return new JobBuilder("SubRegionJob", jobRepository) + .listener(new RunIdIncrementer()) + .start(subRegionStep()) + .build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/config/QuartzSchedulerConfig.java b/src/main/java/com/onebyone/kindergarten/global/batch/config/QuartzSchedulerConfig.java index d054dd4..830dfe9 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/config/QuartzSchedulerConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/config/QuartzSchedulerConfig.java @@ -8,81 +8,79 @@ @Configuration public class QuartzSchedulerConfig { -// // 테스트용 Job -// @Bean -// public JobDetail instantDetail() { -// return JobBuilder.newJob( -// InstantJob.class -// ).storeDurably() -// .build(); -// } -// -// @Bean -// public Trigger instantTrigger() { -// return TriggerBuilder.newTrigger() -// .forJob(instantDetail()) -// .startNow() -// .withSchedule(instantCronScheduler()) -// .build(); -// } -// -// public CronScheduleBuilder instantCronScheduler() { -// return CronScheduleBuilder.cronSchedule("0 * * * * ?"); -// } + // // 테스트용 Job + // @Bean + // public JobDetail instantDetail() { + // return JobBuilder.newJob( + // InstantJob.class + // ).storeDurably() + // .build(); + // } + // + // @Bean + // public Trigger instantTrigger() { + // return TriggerBuilder.newTrigger() + // .forJob(instantDetail()) + // .startNow() + // .withSchedule(instantCronScheduler()) + // .build(); + // } + // + // public CronScheduleBuilder instantCronScheduler() { + // return CronScheduleBuilder.cronSchedule("0 * * * * ?"); + // } - /// 푸시 알림 Job 설정 - @Bean - public JobDetail pushNotificationJobDetail() { - return JobBuilder.newJob( - PushNotificationJob.class - ).storeDurably() - .withIdentity("pushNotificationJob") - .withDescription("푸시 알림 발송 작업") - .build(); - } + /// 푸시 알림 Job 설정 + @Bean + public JobDetail pushNotificationJobDetail() { + return JobBuilder.newJob(PushNotificationJob.class) + .storeDurably() + .withIdentity("pushNotificationJob") + .withDescription("푸시 알림 발송 작업") + .build(); + } - /// 푸시 알림 Trigger 설정 - @Bean - public Trigger pushNotificationTrigger() { - return TriggerBuilder.newTrigger() - .forJob(pushNotificationJobDetail()) - .withIdentity("pushNotificationTrigger") - .withDescription("15분마다 미발송 푸시 알림 발송") - .startNow() - .withSchedule(pushNotificationCronScheduler()) - .build(); - } + /// 푸시 알림 Trigger 설정 + @Bean + public Trigger pushNotificationTrigger() { + return TriggerBuilder.newTrigger() + .forJob(pushNotificationJobDetail()) + .withIdentity("pushNotificationTrigger") + .withDescription("15분마다 미발송 푸시 알림 발송") + .startNow() + .withSchedule(pushNotificationCronScheduler()) + .build(); + } - /// 20분마다 실행되는 크론 표현식 - public CronScheduleBuilder pushNotificationCronScheduler() { - return CronScheduleBuilder.cronSchedule("0 0/20 * * * ?"); - } + /// 20분마다 실행되는 크론 표현식 + public CronScheduleBuilder pushNotificationCronScheduler() { + return CronScheduleBuilder.cronSchedule("0 0/20 * * * ?"); + } - /// 인기 게시글 캐시 갱신 Job 설정 - @Bean - public JobDetail topPostsCacheRefreshJobDetail() { - return JobBuilder.newJob( - TopPostsCacheRefreshJob.class - ).storeDurably() - .withIdentity("topPostsCacheRefreshJob") - .withDescription("인기 게시글 캐시 갱신 작업") - .build(); - } + /// 인기 게시글 캐시 갱신 Job 설정 + @Bean + public JobDetail topPostsCacheRefreshJobDetail() { + return JobBuilder.newJob(TopPostsCacheRefreshJob.class) + .storeDurably() + .withIdentity("topPostsCacheRefreshJob") + .withDescription("인기 게시글 캐시 갱신 작업") + .build(); + } - /// 인기 게시글 캐시 갱신 Trigger 설정 (매일 새벽 6시에 실행) - @Bean - public Trigger topPostsCacheRefreshTrigger() { - return TriggerBuilder.newTrigger() - .forJob(topPostsCacheRefreshJobDetail()) - .withIdentity("topPostsCacheRefreshTrigger") - .withDescription("매일 새벽 6시에 인기 게시글 캐시 갱신") - .startNow() - .withSchedule(topPostsCacheRefreshCronScheduler()) - .build(); - } + /// 인기 게시글 캐시 갱신 Trigger 설정 (매일 새벽 6시에 실행) + @Bean + public Trigger topPostsCacheRefreshTrigger() { + return TriggerBuilder.newTrigger() + .forJob(topPostsCacheRefreshJobDetail()) + .withIdentity("topPostsCacheRefreshTrigger") + .withDescription("매일 새벽 6시에 인기 게시글 캐시 갱신") + .startNow() + .withSchedule(topPostsCacheRefreshCronScheduler()) + .build(); + } - /// 매일 새벽 6시에 실행되는 크론 표현식 - public CronScheduleBuilder topPostsCacheRefreshCronScheduler() { - return CronScheduleBuilder.cronSchedule("0 0 6 * * ?"); - } + /// 매일 새벽 6시에 실행되는 크론 표현식 + public CronScheduleBuilder topPostsCacheRefreshCronScheduler() { + return CronScheduleBuilder.cronSchedule("0 0 6 * * ?"); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/job/InstantJob.java b/src/main/java/com/onebyone/kindergarten/global/batch/job/InstantJob.java index bfa4a36..5f11fe8 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/job/InstantJob.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/job/InstantJob.java @@ -1,12 +1,12 @@ -//package com.onebyone.kindergarten.global.batch.job; +// package com.onebyone.kindergarten.global.batch.job; // -//import lombok.extern.slf4j.Slf4j; -//import org.quartz.JobExecutionContext; -//import org.quartz.JobExecutionException; -//import org.springframework.scheduling.quartz.QuartzJobBean; +// import lombok.extern.slf4j.Slf4j; +// import org.quartz.JobExecutionContext; +// import org.quartz.JobExecutionException; +// import org.springframework.scheduling.quartz.QuartzJobBean; // -//@Slf4j -//public class InstantJob extends QuartzJobBean { +// @Slf4j +// public class InstantJob extends QuartzJobBean { // // @Override // protected void executeInternal(JobExecutionContext context) throws JobExecutionException { @@ -14,4 +14,4 @@ // log.info("instantJob"); // log.info("=====Instant job completed====="); // } -//} +// } diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/job/PushNotificationJob.java b/src/main/java/com/onebyone/kindergarten/global/batch/job/PushNotificationJob.java index 3cf658d..76a6a74 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/job/PushNotificationJob.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/job/PushNotificationJob.java @@ -2,57 +2,55 @@ import com.onebyone.kindergarten.domain.pushNotification.entity.PushNotification; import com.onebyone.kindergarten.domain.pushNotification.service.PushNotificationService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class PushNotificationJob extends QuartzJobBean { - private final PushNotificationService pushNotificationService; + private final PushNotificationService pushNotificationService; - /// 15분마다 미전송 알림을 조회하여 FCM 발송 - /// 현재 시간 기준으로 미전송 알림을 조회하여 발송 - @Override - protected void executeInternal(JobExecutionContext context) throws JobExecutionException { - log.info("===== 푸시 알림 Job 실행 시작: {} =====", LocalDateTime.now()); + /// 15분마다 미전송 알림을 조회하여 FCM 발송 + /// 현재 시간 기준으로 미전송 알림을 조회하여 발송 + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + log.info("===== 푸시 알림 Job 실행 시작: {} =====", LocalDateTime.now()); - try { - // 현재 시간을 커서로 설정 - LocalDateTime cursorTime = LocalDateTime.now(); + try { + // 현재 시간을 커서로 설정 + LocalDateTime cursorTime = LocalDateTime.now(); - // 미전송 알림 조회 (트랜잭션 범위 내에서 수행) - List unsentNotifications = getUnsentNotifications(cursorTime); - log.info("미전송 알림 수: {}", unsentNotifications.size()); + // 미전송 알림 조회 (트랜잭션 범위 내에서 수행) + List unsentNotifications = getUnsentNotifications(cursorTime); + log.info("미전송 알림 수: {}", unsentNotifications.size()); - // 미전송 알림이 없는 경우 종료 - if (unsentNotifications.isEmpty()) { - log.info("미전송 알림이 없습니다."); - return; - } + // 미전송 알림이 없는 경우 종료 + if (unsentNotifications.isEmpty()) { + log.info("미전송 알림이 없습니다."); + return; + } - // 푸시 알림 전송 - pushNotificationService.sendAllFCMNotificationsByAsync(unsentNotifications); + // 푸시 알림 전송 + pushNotificationService.sendAllFCMNotificationsByAsync(unsentNotifications); - } catch (Exception e) { - log.error("푸시 알림 Job 실행 중 오류 발생: {}", e.getMessage(), e); - throw new JobExecutionException(e); - } - - log.info("===== 푸시 알림 Job 실행 완료: {} =====", LocalDateTime.now()); - } - - /// 미전송 알림 조회 (트랜잭션 범위 내에서 수행) - protected List getUnsentNotifications(LocalDateTime cursorTime) { - return pushNotificationService.getUnsentNotificationsBeforeTime(cursorTime); + } catch (Exception e) { + log.error("푸시 알림 Job 실행 중 오류 발생: {}", e.getMessage(), e); + throw new JobExecutionException(e); } -} \ No newline at end of file + + log.info("===== 푸시 알림 Job 실행 완료: {} =====", LocalDateTime.now()); + } + + /// 미전송 알림 조회 (트랜잭션 범위 내에서 수행) + protected List getUnsentNotifications(LocalDateTime cursorTime) { + return pushNotificationService.getUnsentNotificationsBeforeTime(cursorTime); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/job/TopPostsCacheRefreshJob.java b/src/main/java/com/onebyone/kindergarten/global/batch/job/TopPostsCacheRefreshJob.java index de9c299..0ca72aa 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/job/TopPostsCacheRefreshJob.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/job/TopPostsCacheRefreshJob.java @@ -3,38 +3,36 @@ import com.onebyone.kindergarten.domain.communityPosts.service.CommunityService; import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; - @Slf4j @Component @RequiredArgsConstructor public class TopPostsCacheRefreshJob extends QuartzJobBean { - private final CommunityService communityService; + private final CommunityService communityService; - @Override - protected void executeInternal(JobExecutionContext context) { - log.info("===== 인기 게시글 캐시 갱신 Job 실행 시작: {} =====", LocalDateTime.now()); + @Override + protected void executeInternal(JobExecutionContext context) { + log.info("===== 인기 게시글 캐시 갱신 Job 실행 시작: {} =====", LocalDateTime.now()); - try { - /// 캐시 갱신 - communityService.refreshTopPostsCache(); + try { + /// 캐시 갱신 + communityService.refreshTopPostsCache(); - /// 데이터 로드 - communityService.getTopPosts(); + /// 데이터 로드 + communityService.getTopPosts(); - log.info("인기 게시글 캐시가 성공적으로 갱신되었습니다."); - } catch (Exception e) { - throw new BusinessException(ErrorCodes.FAILED_TOP_POST_CACHE_EXCEPTION); - } - - log.info("===== 인기 게시글 캐시 갱신 Job 실행 완료: {} =====", LocalDateTime.now()); + log.info("인기 게시글 캐시가 성공적으로 갱신되었습니다."); + } catch (Exception e) { + throw new BusinessException(ErrorCodes.FAILED_TOP_POST_CACHE_EXCEPTION); } -} \ No newline at end of file + + log.info("===== 인기 게시글 캐시 갱신 Job 실행 완료: {} =====", LocalDateTime.now()); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenRegionProcessor.java b/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenRegionProcessor.java index 790c3a8..acc01c4 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenRegionProcessor.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenRegionProcessor.java @@ -3,47 +3,45 @@ import com.onebyone.kindergarten.domain.address.entity.Region; import com.onebyone.kindergarten.domain.address.repository.RegionRepository; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; -import lombok.RequiredArgsConstructor; -import org.springframework.batch.item.ItemProcessor; - import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.item.ItemProcessor; @RequiredArgsConstructor public class KindergartenRegionProcessor implements ItemProcessor { - private final RegionRepository regionRepository; + private final RegionRepository regionRepository; - // Batch 시작 시 캐시 - private final Map cityMap; + // Batch 시작 시 캐시 + private final Map cityMap; - @Override - public Kindergarten process(Kindergarten item){ - String[] splitAddress = item.getAddress().split(" "); + @Override + public Kindergarten process(Kindergarten item) { + String[] splitAddress = item.getAddress().split(" "); - if(splitAddress.length < 3){ - System.out.println("주소 형식 이상: " + item.getId() + " / " + item.getAddress()); - return item; - } + if (splitAddress.length < 3) { + System.out.println("주소 형식 이상: " + item.getId() + " / " + item.getAddress()); + return item; + } - String city = splitAddress[0]; + String city = splitAddress[0]; - if("전북특별자치도".equals(city)) { - city = "전라북도"; - } else if ("경기".equals(city)) { - city = "경기도"; - } + if ("전북특별자치도".equals(city)) { + city = "전라북도"; + } else if ("경기".equals(city)) { + city = "경기도"; + } - Region region = cityMap.get(city); + Region region = cityMap.get(city); - item.updateRegion(region.getRegionId()); - return item; - } + item.updateRegion(region.getRegionId()); + return item; + } - public static KindergartenRegionProcessor create(RegionRepository regionRepository) { - Map cityMap = regionRepository.findAll() - .stream() - .collect(Collectors.toMap(Region::getName, r -> r)); + public static KindergartenRegionProcessor create(RegionRepository regionRepository) { + Map cityMap = + regionRepository.findAll().stream().collect(Collectors.toMap(Region::getName, r -> r)); - return new KindergartenRegionProcessor(regionRepository, cityMap); - } + return new KindergartenRegionProcessor(regionRepository, cityMap); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenSubRegionProcessor.java b/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenSubRegionProcessor.java index 87913ca..b5b40fe 100644 --- a/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenSubRegionProcessor.java +++ b/src/main/java/com/onebyone/kindergarten/global/batch/processor/KindergartenSubRegionProcessor.java @@ -4,84 +4,87 @@ import com.onebyone.kindergarten.domain.address.repository.RegionRepository; import com.onebyone.kindergarten.domain.address.repository.SubRegionRepository; import com.onebyone.kindergarten.domain.kindergatens.entity.Kindergarten; -import lombok.RequiredArgsConstructor; -import org.springframework.batch.item.ItemProcessor; - import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.item.ItemProcessor; @RequiredArgsConstructor public class KindergartenSubRegionProcessor implements ItemProcessor { - private final RegionRepository regionRepository; - private final SubRegionRepository subRegionRepository; - - // Batch 시작 시 캐시 - private final Map cityMap; - private final Map> subRegionMap; - - @Override - public Kindergarten process(Kindergarten item){ - String[] splitAddress = item.getAddress().split(" "); - - if(splitAddress.length < 3){ - System.out.println("주소 형식 이상: " + item.getId() + " / " + item.getAddress()); - return item; - } - - String city = splitAddress[0]; - String region = splitAddress[1]; - String subRegion = splitAddress[2]; - - Long cityId = cityMap.get(city); - - if(cityId == null){ - if("전북특별자치도".equals(city)) { - cityId = cityMap.get("전라북도"); - } else if ("경기".equals(city)) { - cityId = cityMap.get("경기도"); - } else { - System.out.println("등록되지 않은 city: " + item.getId() + " / " + city + " / " + item.getAddress()); - return item; - } - } - - Map subMap = subRegionMap.get(cityId); - - if(subMap == null){ - System.out.println("서브리전 없음: " + item.getId() + " / cityId=" + cityId + " / " + item.getAddress()); - return item; - } - - char lastChar = subRegion.charAt(subRegion.length() - 1); - SubRegion subRegionEntity; - - if ('시' == lastChar || '군' == lastChar || '구' == lastChar) { - subRegionEntity = subMap.get(subRegion); - } else { - subRegionEntity = subMap.get(region); - } - - if(subRegionEntity == null){ - System.out.println("해당 subRegion 없음: " + item.getId() + " / " + subRegion + " / " + item.getAddress()); - return item; - } - - item.updateSubRegion(subRegionEntity.getSubRegionId()); + private final RegionRepository regionRepository; + private final SubRegionRepository subRegionRepository; + + // Batch 시작 시 캐시 + private final Map cityMap; + private final Map> subRegionMap; + + @Override + public Kindergarten process(Kindergarten item) { + String[] splitAddress = item.getAddress().split(" "); + + if (splitAddress.length < 3) { + System.out.println("주소 형식 이상: " + item.getId() + " / " + item.getAddress()); + return item; + } + + String city = splitAddress[0]; + String region = splitAddress[1]; + String subRegion = splitAddress[2]; + + Long cityId = cityMap.get(city); + + if (cityId == null) { + if ("전북특별자치도".equals(city)) { + cityId = cityMap.get("전라북도"); + } else if ("경기".equals(city)) { + cityId = cityMap.get("경기도"); + } else { + System.out.println( + "등록되지 않은 city: " + item.getId() + " / " + city + " / " + item.getAddress()); return item; + } + } + + Map subMap = subRegionMap.get(cityId); + + if (subMap == null) { + System.out.println( + "서브리전 없음: " + item.getId() + " / cityId=" + cityId + " / " + item.getAddress()); + return item; } - public static KindergartenSubRegionProcessor create(RegionRepository regionRepository, SubRegionRepository subRegionRepository) { - Map cityMap = regionRepository.findAll() - .stream() - .collect(Collectors.toMap(r -> r.getName(), r -> r.getRegionId())); + char lastChar = subRegion.charAt(subRegion.length() - 1); + SubRegion subRegionEntity; - Map> subRegionMap = subRegionRepository.findAll() - .stream() - .collect(Collectors.groupingBy( - SubRegion::getRegionId, - Collectors.toMap(SubRegion::getName, s -> s) - )); + if ('시' == lastChar || '군' == lastChar || '구' == lastChar) { + subRegionEntity = subMap.get(subRegion); + } else { + subRegionEntity = subMap.get(region); + } - return new KindergartenSubRegionProcessor(regionRepository, subRegionRepository, cityMap, subRegionMap); + if (subRegionEntity == null) { + System.out.println( + "해당 subRegion 없음: " + item.getId() + " / " + subRegion + " / " + item.getAddress()); + return item; } + + item.updateSubRegion(subRegionEntity.getSubRegionId()); + return item; + } + + public static KindergartenSubRegionProcessor create( + RegionRepository regionRepository, SubRegionRepository subRegionRepository) { + Map cityMap = + regionRepository.findAll().stream() + .collect(Collectors.toMap(r -> r.getName(), r -> r.getRegionId())); + + Map> subRegionMap = + subRegionRepository.findAll().stream() + .collect( + Collectors.groupingBy( + SubRegion::getRegionId, Collectors.toMap(SubRegion::getName, s -> s))); + + return new KindergartenSubRegionProcessor( + regionRepository, subRegionRepository, cityMap, subRegionMap); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/common/BaseEntity.java b/src/main/java/com/onebyone/kindergarten/global/common/BaseEntity.java index b4a5bd2..fb8163c 100644 --- a/src/main/java/com/onebyone/kindergarten/global/common/BaseEntity.java +++ b/src/main/java/com/onebyone/kindergarten/global/common/BaseEntity.java @@ -3,23 +3,22 @@ import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; @Getter @EntityListeners(AuditingEntityListener.class) @MappedSuperclass public class BaseEntity { - @Column(name = "created_at", updatable = false) - @CreatedDate - protected LocalDateTime createdAt; + @Column(name = "created_at", updatable = false) + @CreatedDate + protected LocalDateTime createdAt; - @Column(name = "updated_at") - protected LocalDateTime updatedAt; + @Column(name = "updated_at") + protected LocalDateTime updatedAt; - @Column(name = "deleted_at") - protected LocalDateTime deletedAt; + @Column(name = "deleted_at") + protected LocalDateTime deletedAt; } diff --git a/src/main/java/com/onebyone/kindergarten/global/common/PageResponseDTO.java b/src/main/java/com/onebyone/kindergarten/global/common/PageResponseDTO.java index 94e048e..ba46363 100644 --- a/src/main/java/com/onebyone/kindergarten/global/common/PageResponseDTO.java +++ b/src/main/java/com/onebyone/kindergarten/global/common/PageResponseDTO.java @@ -1,27 +1,26 @@ package com.onebyone.kindergarten.global.common; +import java.util.List; import lombok.Getter; import org.springframework.data.domain.Page; -import java.util.List; - @Getter public class PageResponseDTO { - private List content; - private int pageNumber; - private int pageSize; - private long totalElements; - private int totalPages; - private boolean first; - private boolean last; + private List content; + private int pageNumber; + private int pageSize; + private long totalElements; + private int totalPages; + private boolean first; + private boolean last; - public PageResponseDTO(Page page) { - this.content = page.getContent(); - this.pageNumber = page.getNumber(); - this.pageSize = page.getSize(); - this.totalElements = page.getTotalElements(); - this.totalPages = page.getTotalPages(); - this.first = page.isFirst(); - this.last = page.isLast(); - } -} \ No newline at end of file + public PageResponseDTO(Page page) { + this.content = page.getContent(); + this.pageNumber = page.getNumber(); + this.pageSize = page.getSize(); + this.totalElements = page.getTotalElements(); + this.totalPages = page.getTotalPages(); + this.first = page.isFirst(); + this.last = page.isLast(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/common/ResponseDto.java b/src/main/java/com/onebyone/kindergarten/global/common/ResponseDto.java index b1f526a..f87efdc 100644 --- a/src/main/java/com/onebyone/kindergarten/global/common/ResponseDto.java +++ b/src/main/java/com/onebyone/kindergarten/global/common/ResponseDto.java @@ -8,23 +8,23 @@ @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseDto { - private final Boolean success; - private final T data; - private final String message; + private final Boolean success; + private final T data; + private final String message; - public static ResponseDto success(T data) { - return new ResponseDto<>(true, data, null); - } + public static ResponseDto success(T data) { + return new ResponseDto<>(true, data, null); + } - public static ResponseDto success(T data, String message) { - return new ResponseDto<>(true, data, message); - } + public static ResponseDto success(T data, String message) { + return new ResponseDto<>(true, data, message); + } - public static ResponseDto error(String message) { - return new ResponseDto<>(false, null, message); - } + public static ResponseDto error(String message) { + return new ResponseDto<>(false, null, message); + } - public static ResponseDto error(T data, String message) { - return new ResponseDto<>(false, data, message); - } -} \ No newline at end of file + public static ResponseDto error(T data, String message) { + return new ResponseDto<>(false, data, message); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/config/AsyncConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/AsyncConfig.java index 32f716a..3ddcad3 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/AsyncConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/AsyncConfig.java @@ -1,30 +1,30 @@ package com.onebyone.kindergarten.global.config; +import java.util.concurrent.Executor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import java.util.concurrent.Executor; - /// 푸시 알림과 같은 비동기 처리가 필요한 작업을 위한 스레드 풀 구성 @Configuration @EnableAsync public class AsyncConfig { - /// 비동기 작업을 위한 스레드 풀 설정 - /// - corePoolSize: 기본적으로 유지하는 스레드 (5개) - /// - maxPoolSize: 최대 스레드 수 (10개) - /// - queueCapacity: 작업 큐 크기 (25개) - @Bean(name = "taskExecutor") - public Executor taskExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(5); - executor.setMaxPoolSize(10); - executor.setQueueCapacity(25); - executor.setThreadNamePrefix("PushNotification-"); - executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); - executor.initialize(); - return executor; - } -} \ No newline at end of file + /// 비동기 작업을 위한 스레드 풀 설정 + /// - corePoolSize: 기본적으로 유지하는 스레드 (5개) + /// - maxPoolSize: 최대 스레드 수 (10개) + /// - queueCapacity: 작업 큐 크기 (25개) + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(25); + executor.setThreadNamePrefix("PushNotification-"); + executor.setRejectedExecutionHandler( + new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/config/CacheConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/CacheConfig.java index 18d764b..7ddde7a 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/CacheConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/CacheConfig.java @@ -10,11 +10,11 @@ @EnableCaching public class CacheConfig { - /// TOP10 게시물 캐시 - public static final String TOP_POSTS_CACHE = "topPostsCache"; + /// TOP10 게시물 캐시 + public static final String TOP_POSTS_CACHE = "topPostsCache"; - @Bean - public CacheManager cacheManager() { - return new ConcurrentMapCacheManager(TOP_POSTS_CACHE); - } -} \ No newline at end of file + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(TOP_POSTS_CACHE); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/config/FirebaseConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/FirebaseConfig.java index 72f30d2..1fea66d 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/FirebaseConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/FirebaseConfig.java @@ -5,47 +5,49 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.messaging.FirebaseMessaging; import jakarta.annotation.PostConstruct; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - @Configuration public class FirebaseConfig { - @Value("${firebase.service-account}") - private String serviceAccountBase64; - - @PostConstruct - public void init(){ - try{ -// InputStream serviceAccount = new ClassPathResource("OnebyoneFirebaseKey.json").getInputStream(); - - // 파이어베이스 인증키 파일 - InputStream serviceAccount = new ByteArrayInputStream(getBase64DecodeBytes(serviceAccountBase64)); - - /// 파이어베이스 초기화 - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(serviceAccount)) - .build(); - - /// 파이어베이스 앱 초기화 - if (FirebaseApp.getApps().isEmpty()) { - FirebaseApp.initializeApp(options); - } - } catch (Exception e){ - throw new RuntimeException("Failed to initialize Firebase", e); - } + @Value("${firebase.service-account}") + private String serviceAccountBase64; + + @PostConstruct + public void init() { + try { + // InputStream serviceAccount = new + // ClassPathResource("OnebyoneFirebaseKey.json").getInputStream(); + + // 파이어베이스 인증키 파일 + InputStream serviceAccount = + new ByteArrayInputStream(getBase64DecodeBytes(serviceAccountBase64)); + + /// 파이어베이스 초기화 + FirebaseOptions options = + FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(serviceAccount)) + .build(); + + /// 파이어베이스 앱 초기화 + if (FirebaseApp.getApps().isEmpty()) { + FirebaseApp.initializeApp(options); + } + } catch (Exception e) { + throw new RuntimeException("Failed to initialize Firebase", e); } + } - private static byte[] getBase64DecodeBytes(String input) { - return Base64.decodeBase64(input); - } + private static byte[] getBase64DecodeBytes(String input) { + return Base64.decodeBase64(input); + } - @Bean - public FirebaseMessaging firebaseMessaging() { - return FirebaseMessaging.getInstance(); - } -} \ No newline at end of file + @Bean + public FirebaseMessaging firebaseMessaging() { + return FirebaseMessaging.getInstance(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java index a435b02..b6c4c29 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/SecurityConfig.java @@ -2,8 +2,10 @@ import com.onebyone.kindergarten.global.jwt.JwtEntryPoint; import com.onebyone.kindergarten.global.jwt.JwtFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import lombok.RequiredArgsConstructor; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -15,65 +17,71 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { - private final JwtEntryPoint jwtEntryPoint; - private final JwtFilter jwtFilter; - - private final List permitOriginList = new ArrayList<>(Arrays.asList( - "/users/sign-up", "/users/sign-in", "/users/reissue", - "/users/email-certification", "/users/temporary-password", "/users/check-email-certification", - "/users/kakao/callback", "/users/naver/callback", "/users/apple/callback", - "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", - "/kindergarten/*/simple", - "/address", - "/community/**", - "/notice/**" - )); - - private final List adminOriginList = new ArrayList<>(Arrays.asList( - "/admin/**", - "/address/batch/**" - )); - - @Bean - public BCryptPasswordEncoder bCryptPasswordEncoder() { - return new BCryptPasswordEncoder(); - } + private final JwtEntryPoint jwtEntryPoint; + private final JwtFilter jwtFilter; - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - // .cors(Customizer.withDefaults()) - .csrf(csrf -> csrf.disable()) // Non-Browser Clients만을 위한 API 서버이므로, CSRF 보호 기능 해제 - .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin())) // h2-console 사용을 - // 위한 설정 + private final List permitOriginList = + new ArrayList<>( + Arrays.asList( + "/users/sign-up", + "/users/sign-in", + "/users/reissue", + "/users/email-certification", + "/users/temporary-password", + "/users/check-email-certification", + "/users/kakao/callback", + "/users/naver/callback", + "/users/apple/callback", + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**", + "/kindergarten/*/simple", + "/address", + "/community/**", + "/notice/**")); - .authorizeHttpRequests(auth -> auth - .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() - .requestMatchers(permitOriginList.toArray(new String[0])) - .permitAll() - .requestMatchers(adminOriginList.toArray(new String[0])).hasRole("ADMIN") // 관리자 전용 API - .anyRequest().authenticated() // 나머지 요청은 인증된 사용자만 접근 가능 - ) - .exceptionHandling(ex -> ex - .authenticationEntryPoint(jwtEntryPoint) // 인증 실패 시 처리 - ) + private final List adminOriginList = + new ArrayList<>(Arrays.asList("/admin/**", "/address/batch/**")); - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 X - ) + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); // JwtFilter 추가 + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // .cors(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()) // Non-Browser Clients만을 위한 API 서버이므로, CSRF 보호 기능 해제 + .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin())) // h2-console 사용을 + // 위한 설정 - return http.build(); - } + .authorizeHttpRequests( + auth -> + auth.requestMatchers(CorsUtils::isPreFlightRequest) + .permitAll() + .requestMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() + .requestMatchers(permitOriginList.toArray(new String[0])) + .permitAll() + .requestMatchers(adminOriginList.toArray(new String[0])) + .hasRole("ADMIN") // 관리자 전용 API + .anyRequest() + .authenticated() // 나머지 요청은 인증된 사용자만 접근 가능 + ) + .exceptionHandling( + ex -> ex.authenticationEntryPoint(jwtEntryPoint) // 인증 실패 시 처리 + ) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 X + ) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); // JwtFilter 추가 + return http.build(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/config/SwaggerConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/SwaggerConfig.java index 945a962..092d59d 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/SwaggerConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/SwaggerConfig.java @@ -7,36 +7,35 @@ import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.beans.factory.annotation.Value; @Configuration @RequiredArgsConstructor public class SwaggerConfig { - @Value("${swagger.description}") - private String description; + @Value("${swagger.description}") + private String description; - @Value("${swagger.server-url}") - private String serverUrl; + @Value("${swagger.server-url}") + private String serverUrl; - @Bean - public OpenAPI openAPI() { - return new OpenAPI() - .info(new Info() - .title("원바원 API") - .version("v1") - .description(description)) - .addServersItem(new Server().url(serverUrl)) - .addSecurityItem(new SecurityRequirement().addList("JWT Authentication")) - .components(new Components().addSecuritySchemes("JWT Authentication", - new SecurityScheme() - .name("JWT Authentication") - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - .in(SecurityScheme.In.HEADER) - )); - } + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info().title("원바원 API").version("v1").description(description)) + .addServersItem(new Server().url(serverUrl)) + .addSecurityItem(new SecurityRequirement().addList("JWT Authentication")) + .components( + new Components() + .addSecuritySchemes( + "JWT Authentication", + new SecurityScheme() + .name("JWT Authentication") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER))); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/config/WebMvcConfig.java b/src/main/java/com/onebyone/kindergarten/global/config/WebMvcConfig.java index 4bb31de..6726fa1 100644 --- a/src/main/java/com/onebyone/kindergarten/global/config/WebMvcConfig.java +++ b/src/main/java/com/onebyone/kindergarten/global/config/WebMvcConfig.java @@ -7,9 +7,10 @@ @Configuration public class WebMvcConfig implements WebMvcConfigurer { - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new LoggerInterceptor()) - .excludePathPatterns("/swagger-ui/**", "/v3/**"); - } + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry + .addInterceptor(new LoggerInterceptor()) + .excludePathPatterns("/swagger-ui/**", "/v3/**"); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/enums/ReportStatus.java b/src/main/java/com/onebyone/kindergarten/global/enums/ReportStatus.java index a006d1c..311bcbf 100644 --- a/src/main/java/com/onebyone/kindergarten/global/enums/ReportStatus.java +++ b/src/main/java/com/onebyone/kindergarten/global/enums/ReportStatus.java @@ -1,3 +1,8 @@ package com.onebyone.kindergarten.global.enums; -public enum ReportStatus { PENDING, PROCESSED, REJECTED, YET } \ No newline at end of file +public enum ReportStatus { + PENDING, + PROCESSED, + REJECTED, + YET +} diff --git a/src/main/java/com/onebyone/kindergarten/global/enums/ReviewStatus.java b/src/main/java/com/onebyone/kindergarten/global/enums/ReviewStatus.java index ef5634a..c940d56 100644 --- a/src/main/java/com/onebyone/kindergarten/global/enums/ReviewStatus.java +++ b/src/main/java/com/onebyone/kindergarten/global/enums/ReviewStatus.java @@ -1,5 +1,7 @@ package com.onebyone.kindergarten.global.enums; public enum ReviewStatus { - ACCEPTED, DELETED, REJECTED + ACCEPTED, + DELETED, + REJECTED } diff --git a/src/main/java/com/onebyone/kindergarten/global/enums/ReviewType.java b/src/main/java/com/onebyone/kindergarten/global/enums/ReviewType.java index 06b6590..a389f96 100644 --- a/src/main/java/com/onebyone/kindergarten/global/enums/ReviewType.java +++ b/src/main/java/com/onebyone/kindergarten/global/enums/ReviewType.java @@ -1,3 +1,6 @@ package com.onebyone.kindergarten.global.enums; -public enum ReviewType { WORK, INTERNSHIP } \ No newline at end of file +public enum ReviewType { + WORK, + INTERNSHIP +} diff --git a/src/main/java/com/onebyone/kindergarten/global/exception/BusinessException.java b/src/main/java/com/onebyone/kindergarten/global/exception/BusinessException.java index 4b97215..f64bb66 100644 --- a/src/main/java/com/onebyone/kindergarten/global/exception/BusinessException.java +++ b/src/main/java/com/onebyone/kindergarten/global/exception/BusinessException.java @@ -1,14 +1,14 @@ package com.onebyone.kindergarten.global.exception; public class BusinessException extends RuntimeException { - private final ErrorCodes errorCodes; + private final ErrorCodes errorCodes; - public BusinessException(ErrorCodes errorCodes) { - super(errorCodes.getCode()); - this.errorCodes = errorCodes; - } + public BusinessException(ErrorCodes errorCodes) { + super(errorCodes.getCode()); + this.errorCodes = errorCodes; + } - public ErrorCodes getErrorCode() { - return errorCodes; - } + public ErrorCodes getErrorCode() { + return errorCodes; + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorCodes.java b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorCodes.java index 7c794d2..9bb2679 100644 --- a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorCodes.java +++ b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorCodes.java @@ -4,70 +4,70 @@ @Getter public enum ErrorCodes { - INVALID_PASSWORD_ERROR("E0001","비밀번호가 일치하지 않습니다."), - NOT_FOUND_EMAIL("E0002", "이메일이 존재하지 않습니다."), - PASSWORD_MISMATCH_ERROR("E0003", "비밀번호가 일치하지 않습니다."), - NOT_FOUND_INQUIRY("E0004","문의를 찾을 수 없습니다."), - INQUIRY_NOT_ADMIN_CANNOT_WRITE("E0005","관리자가 아니면 문의를 작성할 수 없습니다."), - INQUIRY_NOT_ADMIN_CANNOT_READ("E0006","관리자가 아니면 문의를 읽을 수 없습니다."), - POST_NOT_FOUND("E0007", "게시글을 찾을 수 없습니다."), - KINDERGARTEN_NOT_FOUND("E0008","유치원을 찾을 수 없습니다."), - WORK_HISTORY_NOT_FOUND("E0009","이력을 찾을 수 없습니다."), - UNAUTHORIZED_DELETE("E0010","삭제 권한이 없습니다."), - NOT_FOUND_REPORT("E0011","신고를 찾을 수 없습니다."), - INVALID_REPORT_STATUS("E0012", "잘못된 신고 상태입니다."), - INVALID_REPORT_POST_TARGET("E0013", "존재하지 않는 신고 게시글입니다."), - NOT_FOUND_NOTICE("E0014", "존재하지 않는 공지사항입니다."), - ALREADY_EXIST_INTERNSHIP_REVIEW("E0015", "이미 등록된 실습 리뷰가 존재합니다."), - NOT_FOUND_INTERNSHIP_REVIEW("E0016", "실습 리뷰가 존재하지 않습니다."), - ALREADY_EXIST_WORK_REVIEW("E0017","이미 등록된 근무 리뷰가 존재합니다."), - NOT_FOUND_WORK_REVIEW("E0018", "근무 리뷰가 존재하지 않습니다."), - INCORRECT_USER_EXCEPTION("E0019", "유저가 일치하지 않습니다."), - INVALID_TOKEN_EXPIRED("E0020", "만료된 토큰입니다."), - INVALID_TOKEN_UNSUPPORTED("E0021","지원되지 않는 토큰 형식입니다."), - INVALID_TOKEN_MALFORMED("E0022","구조가 잘못된 토큰입니다."), - INVALID_TOKEN_SIGNATURE("E0023","서명이 올바르지 않은 토큰입니다."), - INVALID_TOKEN_ILLEGAL("E0024","잘못 생성된 토큰입니다."), - NOTIFICATION_ERROR("E0025", "알림 전송 중 오류가 발생했습니다."), - ENTITY_NOT_FOUND_EXCEPTION("E0026","유치원을 찾을 수 없습니다."), - HTTP_MESSAGE_NOT_REDABLE_EXCEPTION("E0027","잘못된 요청 형식입니다."), - ALREADY_EXIST_EMAIL("E0028","이미 존재하는 이메일입니다."), - NOT_FOUND_EXCEPTION_BY_TEMPORARY_PASSWORD_EXCEPTION("E0029","이메일이 인증되지 않았습니다."), - ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION("E0030","starRating은 1부터 5 사이의 값이어야 합니다."), - ALREADY_BLOCK_USER("E0031","이미 차단한 사용자입니다."), - SELF_BLOCK_NOT_ALLOWED("E0032","자기 자신을 차단할 수 없습니다."), - NOT_FOUND_POST("E0033","게시글을 찾을 수 없습니다."), - NOT_FOUND_PARENT_COMMENT("E0034", "원 댓글을 찾을 수 없습니다."), - PARENT_POST_MISMATCH("E0035", "원 댓글의 게시글이 일치하지 않습니다."), - REPLY_TO_REPLY_NOT_ALLOWED("E0036", "대댓글에는 답글을 작성할 수 없습니다."), - REPLY_TO_DELETED_COMMENT_NOT_ALLOWED("E0037", "삭제된 댓글에는 답글을 작성할 수 없습니다."), - NOT_FOUND_COMMENT("E0038", "댓글이 존재하지 않습니다."), - REVIEW_EDIT_NOT_OWNER("E0039", "본인의 리뷰만 수정할 수 있습니다."), - NOT_FOUND_USER("E0040", "사용자가 존재하지 않습니다."), - INVALID_REPORT_COMMENT_TARGET("E0041", "존재하지 않는 신고 댓글입니다."), - REVIEW_REPORT_NOT_IMPLEMENTED("E0042", "리뷰 처리는 아직 구현되지 않았습니다."), - INVALID_REPORT_TARGET_TYPE("E0043", "지원하지 않는 신고 대상 타입입니다."), - FAILED_TOP_POST_CACHE_EXCEPTION("E0044", "인기 게시글 캐시 갱신에 실패했습니다."), - FAILED_SEND_MAIL_EXCEPTION("E0045", "메일 발송에 실패했습니다"), - ALREADY_EXIST_EMAIL_CERTIFICATION("E0046", "기존에 발급된 인증번호가 존재합니다."), - FAILED_EMAIL_CERTIFICATION_EXCEPTION("E0047", "인증되지 않은 이메일입니다."), - FAILED_AUTHORIZATION_EXCEPTION("E0048", "인증되지 않은 요청입니다."), - INCORRECT_KINDERGARTEN_EXCEPTION("E0049", "유치원이 일치하지 않습니다."), - INVALID_REPORT_INTERNSHIP_REVIEW_TARGET("E0050", "존재하지 않는 실습 신고 리뷰입니다."), - INVALID_REPORT_WORK_REVIEW_TARGET("E0051", "존재하지 않는 근무 신고 리뷰입니다."), - BATCH_NOT_ADMIN_CANNOT_USE("E0052", "관리자가 아니면 배치를 사용할 수 없습니다."), - REGION_NOT_FOUND_EXCEPTION("E0053", "해당 행정구역 데이터가 존재하지 않습니다."), - SUB_REGION_NOT_FOUND_EXCEPTION("E0054", "해당 시군구 데이터가 존재하지 않습니다."), - REGION_NOT_MATCHED_WITH_SUB_REGION("E0055", "해당 행정구역에 연결될 시군구를 찾을 수 없습니다."), - NOT_FOUND_CERTIFICATION("E0056", "인증 내역이 존재하지 않습니다."), - CERTIFICATION_CODE_MISMATCH("E0057", "인증 코드가 일치하지 않습니다."), - INTERNAL_SERVER_ERROR("E9999","알 수 없는 에러 발생"); + INVALID_PASSWORD_ERROR("E0001", "비밀번호가 일치하지 않습니다."), + NOT_FOUND_EMAIL("E0002", "이메일이 존재하지 않습니다."), + PASSWORD_MISMATCH_ERROR("E0003", "비밀번호가 일치하지 않습니다."), + NOT_FOUND_INQUIRY("E0004", "문의를 찾을 수 없습니다."), + INQUIRY_NOT_ADMIN_CANNOT_WRITE("E0005", "관리자가 아니면 문의를 작성할 수 없습니다."), + INQUIRY_NOT_ADMIN_CANNOT_READ("E0006", "관리자가 아니면 문의를 읽을 수 없습니다."), + POST_NOT_FOUND("E0007", "게시글을 찾을 수 없습니다."), + KINDERGARTEN_NOT_FOUND("E0008", "유치원을 찾을 수 없습니다."), + WORK_HISTORY_NOT_FOUND("E0009", "이력을 찾을 수 없습니다."), + UNAUTHORIZED_DELETE("E0010", "삭제 권한이 없습니다."), + NOT_FOUND_REPORT("E0011", "신고를 찾을 수 없습니다."), + INVALID_REPORT_STATUS("E0012", "잘못된 신고 상태입니다."), + INVALID_REPORT_POST_TARGET("E0013", "존재하지 않는 신고 게시글입니다."), + NOT_FOUND_NOTICE("E0014", "존재하지 않는 공지사항입니다."), + ALREADY_EXIST_INTERNSHIP_REVIEW("E0015", "이미 등록된 실습 리뷰가 존재합니다."), + NOT_FOUND_INTERNSHIP_REVIEW("E0016", "실습 리뷰가 존재하지 않습니다."), + ALREADY_EXIST_WORK_REVIEW("E0017", "이미 등록된 근무 리뷰가 존재합니다."), + NOT_FOUND_WORK_REVIEW("E0018", "근무 리뷰가 존재하지 않습니다."), + INCORRECT_USER_EXCEPTION("E0019", "유저가 일치하지 않습니다."), + INVALID_TOKEN_EXPIRED("E0020", "만료된 토큰입니다."), + INVALID_TOKEN_UNSUPPORTED("E0021", "지원되지 않는 토큰 형식입니다."), + INVALID_TOKEN_MALFORMED("E0022", "구조가 잘못된 토큰입니다."), + INVALID_TOKEN_SIGNATURE("E0023", "서명이 올바르지 않은 토큰입니다."), + INVALID_TOKEN_ILLEGAL("E0024", "잘못 생성된 토큰입니다."), + NOTIFICATION_ERROR("E0025", "알림 전송 중 오류가 발생했습니다."), + ENTITY_NOT_FOUND_EXCEPTION("E0026", "유치원을 찾을 수 없습니다."), + HTTP_MESSAGE_NOT_REDABLE_EXCEPTION("E0027", "잘못된 요청 형식입니다."), + ALREADY_EXIST_EMAIL("E0028", "이미 존재하는 이메일입니다."), + NOT_FOUND_EXCEPTION_BY_TEMPORARY_PASSWORD_EXCEPTION("E0029", "이메일이 인증되지 않았습니다."), + ILLEGAL_ARGUMENT_STAR_RATING_EXCEPTION("E0030", "starRating은 1부터 5 사이의 값이어야 합니다."), + ALREADY_BLOCK_USER("E0031", "이미 차단한 사용자입니다."), + SELF_BLOCK_NOT_ALLOWED("E0032", "자기 자신을 차단할 수 없습니다."), + NOT_FOUND_POST("E0033", "게시글을 찾을 수 없습니다."), + NOT_FOUND_PARENT_COMMENT("E0034", "원 댓글을 찾을 수 없습니다."), + PARENT_POST_MISMATCH("E0035", "원 댓글의 게시글이 일치하지 않습니다."), + REPLY_TO_REPLY_NOT_ALLOWED("E0036", "대댓글에는 답글을 작성할 수 없습니다."), + REPLY_TO_DELETED_COMMENT_NOT_ALLOWED("E0037", "삭제된 댓글에는 답글을 작성할 수 없습니다."), + NOT_FOUND_COMMENT("E0038", "댓글이 존재하지 않습니다."), + REVIEW_EDIT_NOT_OWNER("E0039", "본인의 리뷰만 수정할 수 있습니다."), + NOT_FOUND_USER("E0040", "사용자가 존재하지 않습니다."), + INVALID_REPORT_COMMENT_TARGET("E0041", "존재하지 않는 신고 댓글입니다."), + REVIEW_REPORT_NOT_IMPLEMENTED("E0042", "리뷰 처리는 아직 구현되지 않았습니다."), + INVALID_REPORT_TARGET_TYPE("E0043", "지원하지 않는 신고 대상 타입입니다."), + FAILED_TOP_POST_CACHE_EXCEPTION("E0044", "인기 게시글 캐시 갱신에 실패했습니다."), + FAILED_SEND_MAIL_EXCEPTION("E0045", "메일 발송에 실패했습니다"), + ALREADY_EXIST_EMAIL_CERTIFICATION("E0046", "기존에 발급된 인증번호가 존재합니다."), + FAILED_EMAIL_CERTIFICATION_EXCEPTION("E0047", "인증되지 않은 이메일입니다."), + FAILED_AUTHORIZATION_EXCEPTION("E0048", "인증되지 않은 요청입니다."), + INCORRECT_KINDERGARTEN_EXCEPTION("E0049", "유치원이 일치하지 않습니다."), + INVALID_REPORT_INTERNSHIP_REVIEW_TARGET("E0050", "존재하지 않는 실습 신고 리뷰입니다."), + INVALID_REPORT_WORK_REVIEW_TARGET("E0051", "존재하지 않는 근무 신고 리뷰입니다."), + BATCH_NOT_ADMIN_CANNOT_USE("E0052", "관리자가 아니면 배치를 사용할 수 없습니다."), + REGION_NOT_FOUND_EXCEPTION("E0053", "해당 행정구역 데이터가 존재하지 않습니다."), + SUB_REGION_NOT_FOUND_EXCEPTION("E0054", "해당 시군구 데이터가 존재하지 않습니다."), + REGION_NOT_MATCHED_WITH_SUB_REGION("E0055", "해당 행정구역에 연결될 시군구를 찾을 수 없습니다."), + NOT_FOUND_CERTIFICATION("E0056", "인증 내역이 존재하지 않습니다."), + CERTIFICATION_CODE_MISMATCH("E0057", "인증 코드가 일치하지 않습니다."), + INTERNAL_SERVER_ERROR("E9999", "알 수 없는 에러 발생"); - private final String code; - private final String message; + private final String code; + private final String message; - ErrorCodes(String code, String message) { - this.code = code; - this.message = message; - } + ErrorCodes(String code, String message) { + this.code = code; + this.message = message; + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorHandler.java b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorHandler.java index 4bfbaf4..a75aee3 100644 --- a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorHandler.java +++ b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorHandler.java @@ -8,19 +8,18 @@ @Slf4j @ControllerAdvice public class ErrorHandler { - @ExceptionHandler(BusinessException.class) - @ResponseBody - public ErrorResponse handleBusinessException(BusinessException e) { - ErrorCodes errorCodes = e.getErrorCode(); - log.error("BusinessException: 발생: 에러코드:{} 에러 메시지:{}", e.getMessage(), e.getErrorCode()); - return ErrorResponse.buildError(errorCodes); - } + @ExceptionHandler(BusinessException.class) + @ResponseBody + public ErrorResponse handleBusinessException(BusinessException e) { + ErrorCodes errorCodes = e.getErrorCode(); + log.error("BusinessException: 발생: 에러코드:{} 에러 메시지:{}", e.getMessage(), e.getErrorCode()); + return ErrorResponse.buildError(errorCodes); + } - - @ExceptionHandler(Exception.class) - @ResponseBody - public ErrorResponse handleAllExceptions(Exception e) { - log.error("알 수 없는 예외 발생: {}", e.getMessage(), e); - return ErrorResponse.buildError(ErrorCodes.INTERNAL_SERVER_ERROR); - } -} \ No newline at end of file + @ExceptionHandler(Exception.class) + @ResponseBody + public ErrorResponse handleAllExceptions(Exception e) { + log.error("알 수 없는 예외 발생: {}", e.getMessage(), e); + return ErrorResponse.buildError(ErrorCodes.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorResponse.java b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorResponse.java index 1e04f11..0c9e048 100644 --- a/src/main/java/com/onebyone/kindergarten/global/exception/ErrorResponse.java +++ b/src/main/java/com/onebyone/kindergarten/global/exception/ErrorResponse.java @@ -5,19 +5,19 @@ @Getter public class ErrorResponse { - private final String code; - private final String message; + private final String code; + private final String message; - @Builder - public ErrorResponse(String code, String message) { - this.code = code; - this.message = message; - } + @Builder + public ErrorResponse(String code, String message) { + this.code = code; + this.message = message; + } - public static ErrorResponse buildError(ErrorCodes errorCodes) { - return ErrorResponse.builder() - .code(errorCodes.getCode()) - .message(errorCodes.getMessage()) - .build(); - } -} \ No newline at end of file + public static ErrorResponse buildError(ErrorCodes errorCodes) { + return ErrorResponse.builder() + .code(errorCodes.getCode()) + .message(errorCodes.getMessage()) + .build(); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java index c6f6dd4..841def3 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/AddressFacade.java @@ -19,48 +19,50 @@ @Slf4j @RequiredArgsConstructor public class AddressFacade { - Logger logger = LoggerFactory.getLogger(AddressFacade.class); + Logger logger = LoggerFactory.getLogger(AddressFacade.class); - private final UserService userService; - private final JobLauncher jobLauncher; - private final Job subRegionJob; - private final Job regionJob; + private final UserService userService; + private final JobLauncher jobLauncher; + private final Job subRegionJob; + private final Job regionJob; - public void regionBatch(Long userId){ - UserDTO user = userService.getUserToDTO(userId); + public void regionBatch(Long userId) { + UserDTO user = userService.getUserToDTO(userId); - if (!UserRole.ADMIN.name().equals(user.getRole())) { - throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); - } + if (!UserRole.ADMIN.name().equals(user.getRole())) { + throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); + } - try{ - JobParameters params = new JobParametersBuilder() - .addLong("time", System.currentTimeMillis()) // 중복 실행 방지 - .toJobParameters(); + try { + JobParameters params = + new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) // 중복 실행 방지 + .toJobParameters(); - jobLauncher.run(regionJob, params); - } catch (Exception e) { - logger.error(e.getMessage()); - throw new BusinessException(ErrorCodes.INTERNAL_SERVER_ERROR); - } + jobLauncher.run(regionJob, params); + } catch (Exception e) { + logger.error(e.getMessage()); + throw new BusinessException(ErrorCodes.INTERNAL_SERVER_ERROR); } + } - public void subRegionBatch(Long userId){ - UserDTO user = userService.getUserToDTO(userId); + public void subRegionBatch(Long userId) { + UserDTO user = userService.getUserToDTO(userId); - if (!UserRole.ADMIN.name().equals(user.getRole())) { - throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); - } + if (!UserRole.ADMIN.name().equals(user.getRole())) { + throw new BusinessException(ErrorCodes.BATCH_NOT_ADMIN_CANNOT_USE); + } - try{ - JobParameters params = new JobParametersBuilder() - .addLong("time", System.currentTimeMillis()) // 중복 실행 방지 - .toJobParameters(); + try { + JobParameters params = + new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) // 중복 실행 방지 + .toJobParameters(); - jobLauncher.run(subRegionJob, params); - } catch (Exception e) { - logger.error(e.getMessage()); - throw new BusinessException(ErrorCodes.INTERNAL_SERVER_ERROR); - } + jobLauncher.run(subRegionJob, params); + } catch (Exception e) { + logger.error(e.getMessage()); + throw new BusinessException(ErrorCodes.INTERNAL_SERVER_ERROR); } + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java index efd0225..0e09fa7 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/CommunityFacade.java @@ -8,12 +8,11 @@ @Component @RequiredArgsConstructor public class CommunityFacade { - private final CommunityService communityService; - - @Transactional - public void deletePost(Long id, Long userId) { - communityService.deletePost(id, userId); - communityService.refreshTopPostsCache(); - } + private final CommunityService communityService; + @Transactional + public void deletePost(Long id, Long userId) { + communityService.deletePost(id, userId); + communityService.refreshTopPostsCache(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java index b3372f9..2389199 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenFacade.java @@ -19,67 +19,78 @@ @Component @RequiredArgsConstructor public class KindergartenFacade { - private final KindergartenInternshipReviewService kindergartenInternshipReviewService; - private final KindergartenInternshipReviewAggregateService kindergartenInternshipReviewAggregateService; - private final KindergartenWorkReviewService kindergartenWorkReviewService; - private final KindergartenWorkReviewAggregateService kindergartenWorkReviewAggregateService; - private final UserService userService; + private final KindergartenInternshipReviewService kindergartenInternshipReviewService; + private final KindergartenInternshipReviewAggregateService + kindergartenInternshipReviewAggregateService; + private final KindergartenWorkReviewService kindergartenWorkReviewService; + private final KindergartenWorkReviewAggregateService kindergartenWorkReviewAggregateService; + private final UserService userService; + @Transactional + public void createInternshipReview(CreateInternshipReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = + kindergartenInternshipReviewService.createInternshipReview(request, userId); - @Transactional - public void createInternshipReview(CreateInternshipReviewRequestDTO request, Long userId) { - Kindergarten kindergarten = kindergartenInternshipReviewService.createInternshipReview(request, userId); + kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); - kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); - - /// 사용자 리뷰 작성 플래그 업데이트 - userService.markUserAsReviewWriter(userId); - } + /// 사용자 리뷰 작성 플래그 업데이트 + userService.markUserAsReviewWriter(userId); + } - @Transactional - public void modifyInternshipReview(ModifyInternshipReviewRequestDTO request, Long userId) { - Kindergarten kindergarten = kindergartenInternshipReviewService.modifyInternshipReview(request, userId); + @Transactional + public void modifyInternshipReview(ModifyInternshipReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = + kindergartenInternshipReviewService.modifyInternshipReview(request, userId); - kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); - } + kindergartenInternshipReviewAggregateService.updateOrCreateAggregate(kindergarten); + } - @Transactional - public void createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { - Kindergarten kindergarten = kindergartenWorkReviewService.createWorkReview(request, userId); + @Transactional + public void createWorkReview(CreateWorkReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenWorkReviewService.createWorkReview(request, userId); - kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); - - /// 사용자 리뷰 작성 플래그 업데이트 - userService.markUserAsReviewWriter(userId); - } + kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); - @Transactional - public void modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { - Kindergarten kindergarten = kindergartenWorkReviewService.modifyWorkReview(request, userId); + /// 사용자 리뷰 작성 플래그 업데이트 + userService.markUserAsReviewWriter(userId); + } - kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); - } + @Transactional + public void modifyWorkReview(ModifyWorkReviewRequestDTO request, Long userId) { + Kindergarten kindergarten = kindergartenWorkReviewService.modifyWorkReview(request, userId); + + kindergartenWorkReviewAggregateService.updateOrCreateAggregate(kindergarten); + } - @Transactional - public void deleteWorkReview(Long reviewId, Long userId) { - User currentUser = userService.getUserById(userId); - kindergartenWorkReviewService.deleteWorkReview(reviewId, currentUser.getId(), currentUser.getRole()); - int workReviewCount = kindergartenWorkReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); - int internshipReviewCount = kindergartenInternshipReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); - if (workReviewCount + internshipReviewCount == 0) { - currentUser.unMarkAsReviewWriter(); - } + @Transactional + public void deleteWorkReview(Long reviewId, Long userId) { + User currentUser = userService.getUserById(userId); + kindergartenWorkReviewService.deleteWorkReview( + reviewId, currentUser.getId(), currentUser.getRole()); + int workReviewCount = + kindergartenWorkReviewService.countReviewsByUser( + currentUser.getId(), ReviewStatus.ACCEPTED); + int internshipReviewCount = + kindergartenInternshipReviewService.countReviewsByUser( + currentUser.getId(), ReviewStatus.ACCEPTED); + if (workReviewCount + internshipReviewCount == 0) { + currentUser.unMarkAsReviewWriter(); } + } - @Transactional - public void deleteInternshipReview(Long reviewId, Long userId) { - User currentUser = userService.getUserById(userId); - kindergartenInternshipReviewService.deleteWorkReview(reviewId, currentUser.getId(), currentUser.getRole()); - int workReviewCount = kindergartenWorkReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); - int internshipReviewCount = kindergartenInternshipReviewService.countReviewsByUser(currentUser.getId(), ReviewStatus.ACCEPTED); - if (workReviewCount + internshipReviewCount == 0) { - currentUser.unMarkAsReviewWriter(); - } + @Transactional + public void deleteInternshipReview(Long reviewId, Long userId) { + User currentUser = userService.getUserById(userId); + kindergartenInternshipReviewService.deleteWorkReview( + reviewId, currentUser.getId(), currentUser.getRole()); + int workReviewCount = + kindergartenWorkReviewService.countReviewsByUser( + currentUser.getId(), ReviewStatus.ACCEPTED); + int internshipReviewCount = + kindergartenInternshipReviewService.countReviewsByUser( + currentUser.getId(), ReviewStatus.ACCEPTED); + if (workReviewCount + internshipReviewCount == 0) { + currentUser.unMarkAsReviewWriter(); } + } } - diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java index 684c7f3..6dae172 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/KindergartenWorkHistoryFacade.java @@ -17,33 +17,36 @@ @Component @RequiredArgsConstructor public class KindergartenWorkHistoryFacade { - private final UserService userService; - private final KindergartenService kindergartenService; - private final KindergartenWorkHistoryService kindergartenWorkHistoryService; - - @Transactional - public KindergartenWorkHistoryResponse addCertification(Long userId, KindergartenWorkHistoryRequest request) { - // 사용자 조회 - User user = userService.getUserById(userId); - - // 유치원 이름으로 유치원 조회 - Kindergarten kindergarten = kindergartenService.getKindergartenByName(request.getKindergartenName()); - - userService.addCareer(user, request.getStartDate(), request.getEndDate()); - return kindergartenWorkHistoryService.addCertification(user, kindergarten, request); + private final UserService userService; + private final KindergartenService kindergartenService; + private final KindergartenWorkHistoryService kindergartenWorkHistoryService; + + @Transactional + public KindergartenWorkHistoryResponse addCertification( + Long userId, KindergartenWorkHistoryRequest request) { + // 사용자 조회 + User user = userService.getUserById(userId); + + // 유치원 이름으로 유치원 조회 + Kindergarten kindergarten = + kindergartenService.getKindergartenByName(request.getKindergartenName()); + + userService.addCareer(user, request.getStartDate(), request.getEndDate()); + return kindergartenWorkHistoryService.addCertification(user, kindergarten, request); + } + + @Transactional + public void deleteCertification(Long userId, Long certificationId) { + User user = userService.getUserById(userId); + KindergartenWorkHistory workHistory = + kindergartenWorkHistoryService.getKindergartenWorkHistory(certificationId); + + // 유치원 근무 이력 소유자 확인 + if (!workHistory.getUser().equals(user)) { + throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); } - @Transactional - public void deleteCertification(Long userId, Long certificationId) { - User user = userService.getUserById(userId); - KindergartenWorkHistory workHistory = kindergartenWorkHistoryService.getKindergartenWorkHistory(certificationId); - - // 유치원 근무 이력 소유자 확인 - if (!workHistory.getUser().equals(user)) { - throw new BusinessException(ErrorCodes.UNAUTHORIZED_DELETE); - } - - userService.removeCareer(user, workHistory.getStartDate(), workHistory.getEndDate()); - kindergartenWorkHistoryService.deleteCertification(workHistory); - } + userService.removeCareer(user, workHistory.getStartDate(), workHistory.getEndDate()); + kindergartenWorkHistoryService.deleteCertification(workHistory); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/NoticeFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/NoticeFacade.java index 1feb502..a0b401c 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/NoticeFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/NoticeFacade.java @@ -13,27 +13,22 @@ @Component @RequiredArgsConstructor public class NoticeFacade { - - private final NoticeService noticeService; - private final NotificationTemplateService notificationTemplateService; - /** - * 공지사항을 생성하고 푸시 알림 전송 여부에 따라 알림을 발송합니다. - */ - @Transactional - public NoticeResponseDTO createNotice(NoticeCreateRequestDTO dto) { - /// 공지사항 생성 - NoticeResponseDTO notice = noticeService.createNotice(dto); - - /// 푸시 알림 전송 여부 확인 후 전송 - if (dto.getIsPushSend() != null && dto.getIsPushSend()) { - notificationTemplateService.sendNoticeNotificationToAllUsers( - notice.getTitle(), - notice.getContent(), - notice.getId() - ); - } - - return notice; + private final NoticeService noticeService; + private final NotificationTemplateService notificationTemplateService; + + /** 공지사항을 생성하고 푸시 알림 전송 여부에 따라 알림을 발송합니다. */ + @Transactional + public NoticeResponseDTO createNotice(NoticeCreateRequestDTO dto) { + /// 공지사항 생성 + NoticeResponseDTO notice = noticeService.createNotice(dto); + + /// 푸시 알림 전송 여부 확인 후 전송 + if (dto.getIsPushSend() != null && dto.getIsPushSend()) { + notificationTemplateService.sendNoticeNotificationToAllUsers( + notice.getTitle(), notice.getContent(), notice.getId()); } + + return notice; + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/SampleFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/SampleFacade.java index fba1cf4..1afec76 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/SampleFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/SampleFacade.java @@ -4,10 +4,10 @@ @Component public class SampleFacade { -/* - 도메인간 서비스끼리 순환참조 발생을 막기 위한 facade 패키지 - 예시) boardService에서 boardReviewService를 참조하여 트랜잭션을 유발 방지 - 참조해야 할 상황이 발생한다면 controller에서 facade 패키지를 의존성을 추가하여 - facade 클래스에서 각 서비스를 참조 - */ + /* + 도메인간 서비스끼리 순환참조 발생을 막기 위한 facade 패키지 + 예시) boardService에서 boardReviewService를 참조하여 트랜잭션을 유발 방지 + 참조해야 할 상황이 발생한다면 controller에서 facade 패키지를 의존성을 추가하여 + facade 클래스에서 각 서비스를 참조 + */ } diff --git a/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java b/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java index 110858b..0d94e2b 100644 --- a/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java +++ b/src/main/java/com/onebyone/kindergarten/global/facade/UserFacade.java @@ -2,238 +2,227 @@ import com.onebyone.kindergarten.domain.communityComments.dto.response.PageCommunityCommentsResponseDTO; import com.onebyone.kindergarten.domain.communityComments.service.CommunityCommentService; +import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; +import com.onebyone.kindergarten.domain.kindergartenInternshipReview.service.KindergartenInternshipReviewService; +import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewPagedResponseDTO; +import com.onebyone.kindergarten.domain.kindergartenWorkReview.service.KindergartenWorkReviewService; import com.onebyone.kindergarten.domain.user.dto.JwtUserInfoDto; +import com.onebyone.kindergarten.domain.user.dto.UserDTO; import com.onebyone.kindergarten.domain.user.dto.request.EmailCertificationRequestDTO; +import com.onebyone.kindergarten.domain.user.dto.request.SignInRequestDTO; +import com.onebyone.kindergarten.domain.user.dto.request.SignUpRequestDTO; import com.onebyone.kindergarten.domain.user.dto.request.UpdateTemporaryPasswordRequestDTO; +import com.onebyone.kindergarten.domain.user.dto.response.*; import com.onebyone.kindergarten.domain.user.entity.User; import com.onebyone.kindergarten.domain.user.enums.EmailCertificationType; +import com.onebyone.kindergarten.domain.user.service.AppleAuthService; +import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.feignClient.KakaoApiClient; import com.onebyone.kindergarten.global.feignClient.KakaoAuthClient; import com.onebyone.kindergarten.global.feignClient.NaverApiClient; import com.onebyone.kindergarten.global.feignClient.NaverAuthClient; -import com.onebyone.kindergarten.global.provider.EmailProvider; -import com.onebyone.kindergarten.domain.user.dto.UserDTO; -import com.onebyone.kindergarten.domain.user.service.AppleAuthService; -import com.onebyone.kindergarten.domain.user.dto.response.*; -import com.onebyone.kindergarten.domain.user.dto.request.SignInRequestDTO; -import com.onebyone.kindergarten.domain.user.dto.request.SignUpRequestDTO; -import com.onebyone.kindergarten.domain.user.service.UserService; import com.onebyone.kindergarten.global.jwt.JwtProvider; -import com.onebyone.kindergarten.domain.kindergartenInternshipReview.dto.InternshipReviewPagedResponseDTO; -import com.onebyone.kindergarten.domain.kindergartenInternshipReview.service.KindergartenInternshipReviewService; -import com.onebyone.kindergarten.domain.kindergartenWorkReview.dto.WorkReviewPagedResponseDTO; -import com.onebyone.kindergarten.domain.kindergartenWorkReview.service.KindergartenWorkReviewService; +import com.onebyone.kindergarten.global.provider.EmailProvider; import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; import java.util.Random; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import lombok.extern.slf4j.Slf4j; @Component @RequiredArgsConstructor @Slf4j public class UserFacade { - private final UserService userService; - private final CommunityCommentService communityCommentService; - private final KindergartenInternshipReviewService kindergartenInternshipReviewService; - private final KindergartenWorkReviewService kindergartenWorkReviewService; - private final JwtProvider jwtProvider; - private final KakaoApiClient kakaoApiClient; - private final KakaoAuthClient kakaoAuthClient; - private final NaverAuthClient naverAuthClient; - private final NaverApiClient naverApiClient; - private final AppleAuthService appleAuthService; - private final EmailProvider emailProvider; - @Value("${oauth.kakao.secret-key}") - private String kakaoApiKey; - @Value("${oauth.kakao.url.redirect-uri}") - private String kakaoRedirectUrl; - @Value("${oauth.naver.client-id}") - private String naverClientId; - @Value("${oauth.naver.client-secret}") - private String naverClientSecret; - - public SignUpResponseDTO signUp(SignUpRequestDTO request) { - JwtUserInfoDto dto = userService.signUp(request); - - String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); - String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); - - return SignUpResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } + private final UserService userService; + private final CommunityCommentService communityCommentService; + private final KindergartenInternshipReviewService kindergartenInternshipReviewService; + private final KindergartenWorkReviewService kindergartenWorkReviewService; + private final JwtProvider jwtProvider; + private final KakaoApiClient kakaoApiClient; + private final KakaoAuthClient kakaoAuthClient; + private final NaverAuthClient naverAuthClient; + private final NaverApiClient naverApiClient; + private final AppleAuthService appleAuthService; + private final EmailProvider emailProvider; - public SignInResponseDTO signIn(SignInRequestDTO request) { - JwtUserInfoDto dto = userService.signIn(request); + @Value("${oauth.kakao.secret-key}") + private String kakaoApiKey; - String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); - String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); + @Value("${oauth.kakao.url.redirect-uri}") + private String kakaoRedirectUrl; - return SignInResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } + @Value("${oauth.naver.client-id}") + private String naverClientId; - @Transactional - public SignInResponseDTO kakaoLogin(String code, String fcmToken) { - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "authorization_code"); - params.add("client_id", kakaoApiKey); - params.add("redirect_uri", kakaoRedirectUrl); - params.add("code", code); + @Value("${oauth.naver.client-secret}") + private String naverClientSecret; - log.info("=== 카카오 토큰 요청 파라미터 ==="); - log.info("client_id: {}", kakaoApiKey); - log.info("redirect_uri: {}", kakaoRedirectUrl); - log.info("code: {}", code); - log.info("fcmToken 존재: {}", fcmToken != null); - log.info("============================"); + public SignUpResponseDTO signUp(SignUpRequestDTO request) { + JwtUserInfoDto dto = userService.signUp(request); - KakaoTokenResponse tokenResponse = kakaoAuthClient.getAccessToken(params); - String kakaoAccessToken = tokenResponse.getAccess_token(); + String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); - KakaoUserResponse userResponse = kakaoApiClient.getUserInfo("Bearer " + kakaoAccessToken); + return SignUpResponseDTO.builder().accessToken(accessToken).refreshToken(refreshToken).build(); + } - User user = userService.signUpByKakao(userResponse); + public SignInResponseDTO signIn(SignInRequestDTO request) { + JwtUserInfoDto dto = userService.signIn(request); - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - user.updateFcmToken(fcmToken); - } + String accessToken = jwtProvider.generateAccessToken(dto.getUserId(), dto.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(dto.getUserId(), dto.getRole()); - String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); - String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); + return SignInResponseDTO.builder().accessToken(accessToken).refreshToken(refreshToken).build(); + } - return SignInResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } + @Transactional + public SignInResponseDTO kakaoLogin(String code, String fcmToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", kakaoApiKey); + params.add("redirect_uri", kakaoRedirectUrl); + params.add("code", code); - @Transactional - public SignInResponseDTO naverLogin(String code, String state, String fcmToken) { - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "authorization_code"); - params.add("client_id", naverClientId); - params.add("client_secret", naverClientSecret); - params.add("code", code); - params.add("state", state); - - log.info("=== 네이버 토큰 요청 파라미터 ==="); - log.info("client_id: {}", naverClientId); - log.info("code: {}", code); - log.info("state: {}", state); - log.info("fcmToken 존재: {}", fcmToken != null); - log.info("============================"); - - NaverTokenResponse response = naverAuthClient.getAccessToken(params); - - NaverUserResponse userResponse = naverApiClient.getUserInfo("Bearer " + response.getAccess_token()); - User user = userService.signUpByNaver(userResponse); - - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - user.updateFcmToken(fcmToken); - } - - String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); - String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); - - return SignInResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } + log.info("=== 카카오 토큰 요청 파라미터 ==="); + log.info("client_id: {}", kakaoApiKey); + log.info("redirect_uri: {}", kakaoRedirectUrl); + log.info("code: {}", code); + log.info("fcmToken 존재: {}", fcmToken != null); + log.info("============================"); + KakaoTokenResponse tokenResponse = kakaoAuthClient.getAccessToken(params); + String kakaoAccessToken = tokenResponse.getAccess_token(); + KakaoUserResponse userResponse = kakaoApiClient.getUserInfo("Bearer " + kakaoAccessToken); - public SignInResponseDTO appleLogin(String idToken, String fcmToken) { + User user = userService.signUpByKakao(userResponse); - log.info("=== 애플 로그인 요청 ==="); - log.info("idToken 존재: {}", idToken != null); - log.info("fcmToken 존재: {}", fcmToken != null); - log.info("====================="); + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); + } - // Apple ID Token 검증 및 사용자 정보 추출 - AppleUserResponse userResponse = appleAuthService.verifyIdToken(idToken); - User user = userService.signUpByApple(userResponse); + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); - if (fcmToken != null && !fcmToken.trim().isEmpty()) { - user.updateFcmToken(fcmToken); - } + return SignInResponseDTO.builder().accessToken(accessToken).refreshToken(refreshToken).build(); + } - String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); - String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); + @Transactional + public SignInResponseDTO naverLogin(String code, String state, String fcmToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", naverClientId); + params.add("client_secret", naverClientSecret); + params.add("code", code); + params.add("state", state); - return SignInResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); - } + log.info("=== 네이버 토큰 요청 파라미터 ==="); + log.info("client_id: {}", naverClientId); + log.info("code: {}", code); + log.info("state: {}", state); + log.info("fcmToken 존재: {}", fcmToken != null); + log.info("============================"); - public PageCommunityCommentsResponseDTO getWroteMyCommunityComments(Long userId, int page, int size) { - UserDTO user = userService.getUserToDTO(userId); - return communityCommentService.getWroteMyCommunityComments(user.getUserId(), page, size); - } + NaverTokenResponse response = naverAuthClient.getAccessToken(params); - /// 내가 작성한 실습 리뷰 조회 - public InternshipReviewPagedResponseDTO getMyInternshipReviews(Long userId, int page, int size) { - return kindergartenInternshipReviewService.getMyReviews(userId, page, size); - } + NaverUserResponse userResponse = + naverApiClient.getUserInfo("Bearer " + response.getAccess_token()); + User user = userService.signUpByNaver(userResponse); - /// 내가 작성한 근무 리뷰 조회 - public WorkReviewPagedResponseDTO getMyWorkReviews(Long userId, int page, int size) { - AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); - return kindergartenWorkReviewService.getMyReviews(user.getId(), page, size); + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); } - @Transactional - public boolean emailCertification(EmailCertificationRequestDTO request) { - String code = createNumber(); + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); + + return SignInResponseDTO.builder().accessToken(accessToken).refreshToken(refreshToken).build(); + } + + public SignInResponseDTO appleLogin(String idToken, String fcmToken) { + + log.info("=== 애플 로그인 요청 ==="); + log.info("idToken 존재: {}", idToken != null); + log.info("fcmToken 존재: {}", fcmToken != null); + log.info("====================="); - EmailCertificationType certificationType = request.getCertificationType(); - if (EmailCertificationType.EMAIL.equals(certificationType)) { - userService.saveSignUpCertification(request, code); - } else if (EmailCertificationType.TEMPORARY_PASSWORD.equals(certificationType)){ - userService.savePasswordCertification(request, code); - } else { - userService.saveSignUpCertification(request, code); - } + // Apple ID Token 검증 및 사용자 정보 추출 + AppleUserResponse userResponse = appleAuthService.verifyIdToken(idToken); + User user = userService.signUpByApple(userResponse); - return emailProvider.sendCertificationMail(request.getEmail(), code); + if (fcmToken != null && !fcmToken.trim().isEmpty()) { + user.updateFcmToken(fcmToken); } - @Transactional - public boolean updateTemporaryPasswordCertification(UpdateTemporaryPasswordRequestDTO request) { - String number = createNumber(); - userService.checkEmailCertificationByTemporaryPassword(request.getEmail(), request.getCode()); - userService.updateTemporaryPassword(request.getEmail(), number); - return emailProvider.sendTemporaryPasswordMail(request.getEmail(), number); + String accessToken = jwtProvider.generateAccessToken(user.getId(), user.getRole()); + String refreshToken = jwtProvider.generateRefreshToken(user.getId(), user.getRole()); + + return SignInResponseDTO.builder().accessToken(accessToken).refreshToken(refreshToken).build(); + } + + public PageCommunityCommentsResponseDTO getWroteMyCommunityComments( + Long userId, int page, int size) { + UserDTO user = userService.getUserToDTO(userId); + return communityCommentService.getWroteMyCommunityComments(user.getUserId(), page, size); + } + + /// 내가 작성한 실습 리뷰 조회 + public InternshipReviewPagedResponseDTO getMyInternshipReviews(Long userId, int page, int size) { + return kindergartenInternshipReviewService.getMyReviews(userId, page, size); + } + + /// 내가 작성한 근무 리뷰 조회 + public WorkReviewPagedResponseDTO getMyWorkReviews(Long userId, int page, int size) { + AdminUserResponseDTO user = userService.getUserToAdminDTO(userId); + return kindergartenWorkReviewService.getMyReviews(user.getId(), page, size); + } + + @Transactional + public boolean emailCertification(EmailCertificationRequestDTO request) { + String code = createNumber(); + + EmailCertificationType certificationType = request.getCertificationType(); + if (EmailCertificationType.EMAIL.equals(certificationType)) { + userService.saveSignUpCertification(request, code); + } else if (EmailCertificationType.TEMPORARY_PASSWORD.equals(certificationType)) { + userService.savePasswordCertification(request, code); + } else { + userService.saveSignUpCertification(request, code); } - public String createNumber() { - Random random = new Random(); - StringBuilder key = new StringBuilder(); - - for (int i = 0; i < 8; i++) { - int idx = random.nextInt(3); - - switch (idx) { - case 0: - key.append((char) (random.nextInt(26) + 97)); - break; - case 1: - key.append((char) (random.nextInt(26) + 65)); - break; - case 2: - key.append(random.nextInt(9)); - break; - } - } - return key.toString(); + return emailProvider.sendCertificationMail(request.getEmail(), code); + } + + @Transactional + public boolean updateTemporaryPasswordCertification(UpdateTemporaryPasswordRequestDTO request) { + String number = createNumber(); + userService.checkEmailCertificationByTemporaryPassword(request.getEmail(), request.getCode()); + userService.updateTemporaryPassword(request.getEmail(), number); + return emailProvider.sendTemporaryPasswordMail(request.getEmail(), number); + } + + public String createNumber() { + Random random = new Random(); + StringBuilder key = new StringBuilder(); + + for (int i = 0; i < 8; i++) { + int idx = random.nextInt(3); + + switch (idx) { + case 0: + key.append((char) (random.nextInt(26) + 97)); + break; + case 1: + key.append((char) (random.nextInt(26) + 65)); + break; + case 2: + key.append(random.nextInt(9)); + break; + } } + return key.toString(); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/feignClient/AppleAuthClient.java b/src/main/java/com/onebyone/kindergarten/global/feignClient/AppleAuthClient.java index 8e18b90..c451820 100644 --- a/src/main/java/com/onebyone/kindergarten/global/feignClient/AppleAuthClient.java +++ b/src/main/java/com/onebyone/kindergarten/global/feignClient/AppleAuthClient.java @@ -6,6 +6,6 @@ @FeignClient(name = "appleAuthClient", url = "https://appleid.apple.com") public interface AppleAuthClient { - @GetMapping("/auth/keys") - ApplePublicKeyResponse getPublicKeys(); -} \ No newline at end of file + @GetMapping("/auth/keys") + ApplePublicKeyResponse getPublicKeys(); +} diff --git a/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoApiClient.java b/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoApiClient.java index da3b0ad..3e7053c 100644 --- a/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoApiClient.java +++ b/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoApiClient.java @@ -7,6 +7,6 @@ @FeignClient(name = "kakaoApiClient", url = "${oauth.kakao.url.api}") public interface KakaoApiClient { - @GetMapping("/v2/user/me") - KakaoUserResponse getUserInfo(@RequestHeader("Authorization") String bearerToken); + @GetMapping("/v2/user/me") + KakaoUserResponse getUserInfo(@RequestHeader("Authorization") String bearerToken); } diff --git a/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoAuthClient.java b/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoAuthClient.java index 02e1f09..8335a53 100644 --- a/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoAuthClient.java +++ b/src/main/java/com/onebyone/kindergarten/global/feignClient/KakaoAuthClient.java @@ -9,6 +9,6 @@ @FeignClient(name = "kakaoAuthClient", url = "${oauth.kakao.url.auth}") public interface KakaoAuthClient { - @PostMapping(value = "/oauth/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - KakaoTokenResponse getAccessToken(@RequestBody MultiValueMap request); -} \ No newline at end of file + @PostMapping(value = "/oauth/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + KakaoTokenResponse getAccessToken(@RequestBody MultiValueMap request); +} diff --git a/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverApiClient.java b/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverApiClient.java index 229f23d..0a21e60 100644 --- a/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverApiClient.java +++ b/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverApiClient.java @@ -7,6 +7,6 @@ @FeignClient(name = "NaverApiClient", url = "${oauth.naver.url.api}") public interface NaverApiClient { - @GetMapping("/v1/nid/me") - NaverUserResponse getUserInfo(@RequestHeader("Authorization") String bearerToken); + @GetMapping("/v1/nid/me") + NaverUserResponse getUserInfo(@RequestHeader("Authorization") String bearerToken); } diff --git a/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverAuthClient.java b/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverAuthClient.java index 54d1fca..48c79e9 100644 --- a/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverAuthClient.java +++ b/src/main/java/com/onebyone/kindergarten/global/feignClient/NaverAuthClient.java @@ -9,6 +9,6 @@ @FeignClient(name = "naverApiClinet", url = "${oauth.naver.url.auth}") public interface NaverAuthClient { - @PostMapping(value = "/oauth2.0/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - NaverTokenResponse getAccessToken(@RequestParam MultiValueMap request); + @PostMapping(value = "/oauth2.0/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + NaverTokenResponse getAccessToken(@RequestParam MultiValueMap request); } diff --git a/src/main/java/com/onebyone/kindergarten/global/interceptor/LoggerInterceptor.java b/src/main/java/com/onebyone/kindergarten/global/interceptor/LoggerInterceptor.java index 0520a32..c5176d0 100644 --- a/src/main/java/com/onebyone/kindergarten/global/interceptor/LoggerInterceptor.java +++ b/src/main/java/com/onebyone/kindergarten/global/interceptor/LoggerInterceptor.java @@ -2,49 +2,61 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -import java.util.UUID; - @Slf4j public class LoggerInterceptor implements HandlerInterceptor { - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String uuid = UUID.randomUUID().toString().substring(0, 8); - request.setAttribute("UUID", uuid); - - log.debug("============================================ start ============================================"); - log.debug("[{}] request Uri : {}", uuid, request.getRequestURI()); - log.debug("[{}] method : {}", uuid, request.getMethod()); - log.debug("[{}] userAgent : {}", uuid, request.getHeader("User-Agent")); - - String query = request.getQueryString(); - if (query != null) { - log.debug("[{}] query : {}", uuid, query); - } - - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) { - log.debug("[{}] email : {}", uuid, auth.getName()); - } - - log.debug("[{}] Client IP : {}", uuid, request.getRemoteAddr()); - request.setAttribute("startTime", System.currentTimeMillis()); - - return true; + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String uuid = UUID.randomUUID().toString().substring(0, 8); + request.setAttribute("UUID", uuid); + + log.debug( + "============================================ start ============================================"); + log.debug("[{}] request Uri : {}", uuid, request.getRequestURI()); + log.debug("[{}] method : {}", uuid, request.getMethod()); + log.debug("[{}] userAgent : {}", uuid, request.getHeader("User-Agent")); + + String query = request.getQueryString(); + if (query != null) { + log.debug("[{}] query : {}", uuid, query); } - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - log.debug("[{}] execute time : {} ms", request.getAttribute("UUID"), (System.currentTimeMillis() - (Long) request.getAttribute("startTime"))); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) { + log.debug("[{}] email : {}", uuid, auth.getName()); } - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { - log.debug("============================================ End ============================================"); - } + log.debug("[{}] Client IP : {}", uuid, request.getRemoteAddr()); + request.setAttribute("startTime", System.currentTimeMillis()); + + return true; + } + + @Override + public void postHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + ModelAndView modelAndView) + throws Exception { + log.debug( + "[{}] execute time : {} ms", + request.getAttribute("UUID"), + (System.currentTimeMillis() - (Long) request.getAttribute("startTime"))); + } + + @Override + public void afterCompletion( + HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + log.debug( + "============================================ End ============================================"); + } } diff --git a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtEntryPoint.java b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtEntryPoint.java index cef422d..b6046e6 100644 --- a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtEntryPoint.java +++ b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtEntryPoint.java @@ -3,36 +3,36 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.onebyone.kindergarten.global.exception.ErrorCodes; import com.onebyone.kindergarten.global.exception.ErrorResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - @Component @RequiredArgsConstructor public class JwtEntryPoint implements AuthenticationEntryPoint { - private final ObjectMapper om; + private final ObjectMapper om; + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + throws IOException { - @Override - public void commence(HttpServletRequest request, - HttpServletResponse response, - AuthenticationException authException) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - - try { - ErrorResponse body = ErrorResponse.buildError(ErrorCodes.FAILED_AUTHORIZATION_EXCEPTION); - om.writeValue(response.getWriter(), body); - } catch (Exception e) { - ErrorResponse body = ErrorResponse.buildError(ErrorCodes.INTERNAL_SERVER_ERROR); - om.writeValue(response.getWriter(), body); - } + try { + ErrorResponse body = ErrorResponse.buildError(ErrorCodes.FAILED_AUTHORIZATION_EXCEPTION); + om.writeValue(response.getWriter(), body); + } catch (Exception e) { + ErrorResponse body = ErrorResponse.buildError(ErrorCodes.INTERNAL_SERVER_ERROR); + om.writeValue(response.getWriter(), body); } -} \ No newline at end of file + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java index bba517e..84673ad 100644 --- a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java +++ b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtFilter.java @@ -2,85 +2,95 @@ import com.onebyone.kindergarten.global.exception.ErrorCodes; import com.onebyone.kindergarten.global.exception.ErrorResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; - +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; @Component @RequiredArgsConstructor public class JwtFilter extends OncePerRequestFilter { - private final JwtProvider jwtProvider; - -// private final RedisService redisService; + private final JwtProvider jwtProvider; - @Override - protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + // private final RedisService redisService; - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - filterChain.doFilter(request, response); - return; - } - - String jwt = resolveToken(request); - - if (StringUtils.hasText(jwt) ) { - Map map = jwtProvider.validateTokenWithError(jwt); + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { - if ((boolean) map.get("isValid")) { - Map authenticationMap = jwtProvider.getAuthentication(jwt); + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + filterChain.doFilter(request, response); + return; + } - if ((boolean) authenticationMap.get("isValid")) { - Authentication authentication = (Authentication) authenticationMap.get("authentication"); - SecurityContextHolder.getContext().setAuthentication(authentication); - filterChain.doFilter(request, response); - } else { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); + String jwt = resolveToken(request); - ErrorResponse errorResponse = ErrorResponse.buildError((ErrorCodes) authenticationMap.get("errorCode")); - response.getWriter().write( - "{\"code\": \"" + errorResponse.getCode() + "\", \"message\": \"" + errorResponse.getMessage() + "\"}" - ); - } - } else { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); + if (StringUtils.hasText(jwt)) { + Map map = jwtProvider.validateTokenWithError(jwt); - ErrorResponse errorResponse = ErrorResponse.buildError((ErrorCodes) map.get("errorCode")); - response.getWriter().write( - "{\"code\": \"" + errorResponse.getCode() + "\", \"message\": \"" + errorResponse.getMessage() + "\"}"); - } + if ((boolean) map.get("isValid")) { + Map authenticationMap = jwtProvider.getAuthentication(jwt); -// TODO: BlackList에 존재하는 토큰으로 요청이 온 경우. -// Optional isBlackList = redisService.getBlackList(jwt); -// isBlackList.ifPresent(t -> { -// throw new RuntimeException("이미 로그아웃된 토큰입니다."); -// }); + if ((boolean) authenticationMap.get("isValid")) { + Authentication authentication = (Authentication) authenticationMap.get("authentication"); + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); } else { - filterChain.doFilter(request, response); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + + ErrorResponse errorResponse = + ErrorResponse.buildError((ErrorCodes) authenticationMap.get("errorCode")); + response + .getWriter() + .write( + "{\"code\": \"" + + errorResponse.getCode() + + "\", \"message\": \"" + + errorResponse.getMessage() + + "\"}"); } + } else { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + + ErrorResponse errorResponse = ErrorResponse.buildError((ErrorCodes) map.get("errorCode")); + response + .getWriter() + .write( + "{\"code\": \"" + + errorResponse.getCode() + + "\", \"message\": \"" + + errorResponse.getMessage() + + "\"}"); + } + + // TODO: BlackList에 존재하는 토큰으로 요청이 온 경우. + // Optional isBlackList = redisService.getBlackList(jwt); + // isBlackList.ifPresent(t -> { + // throw new RuntimeException("이미 로그아웃된 토큰입니다."); + // }); + } else { + filterChain.doFilter(request, response); } + } - public String resolveToken(HttpServletRequest request) { - String authorization = request.getHeader("Authorization"); + public String resolveToken(HttpServletRequest request) { + String authorization = request.getHeader("Authorization"); - if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) { - return authorization.substring(7); - } - return null; + if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) { + return authorization.substring(7); } -} \ No newline at end of file + return null; + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java index 442343d..6eb6a66 100644 --- a/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java +++ b/src/main/java/com/onebyone/kindergarten/global/jwt/JwtProvider.java @@ -5,6 +5,9 @@ import com.onebyone.kindergarten.global.exception.*; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -16,146 +19,139 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.*; - @Slf4j @Component @RequiredArgsConstructor public class JwtProvider { - private final CustomUserDetailService customUserDetailService; - @Value("${spring.jwt.secret}") - private String secretKey; - - private final Long accessTokenValidationMs = 30 * 60 * 1000L; - private final Long refreshTokenValidationMs = 15 * 24 * 60 * 60 * 1000L; - - public String generateAccessToken(Long userId, UserRole role) { - - Claims claims = Jwts.claims() - .setSubject(String.valueOf(userId)) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + accessTokenValidationMs)); - - claims.put("role", String.valueOf(role)); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) - .setClaims(claims) - .signWith(getSignKey(secretKey), SignatureAlgorithm.HS256) - .compact(); + private final CustomUserDetailService customUserDetailService; + + @Value("${spring.jwt.secret}") + private String secretKey; + + private final Long accessTokenValidationMs = 30 * 60 * 1000L; + private final Long refreshTokenValidationMs = 15 * 24 * 60 * 60 * 1000L; + + public String generateAccessToken(Long userId, UserRole role) { + + Claims claims = + Jwts.claims() + .setSubject(String.valueOf(userId)) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + accessTokenValidationMs)); + + claims.put("role", String.valueOf(role)); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(secretKey), SignatureAlgorithm.HS256) + .compact(); + } + + // RefreshToken 생성 + public String generateRefreshToken(Long userId, UserRole role) { + + Claims claims = + Jwts.claims() + .setSubject(String.valueOf(userId)) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + refreshTokenValidationMs)); + + claims.put("role", String.valueOf(role)); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(secretKey), SignatureAlgorithm.HS256) + .compact(); + } + + private Key getSignKey(String secretKey) { + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + // JWT 유효성 검증 + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSignKey(secretKey)).build().parseClaimsJws(token); + return true; + } catch (JwtException e) { + return false; } - - // RefreshToken 생성 - public String generateRefreshToken(Long userId, UserRole role) { - - Claims claims = Jwts.claims() - .setSubject(String.valueOf(userId)) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + refreshTokenValidationMs)); - - claims.put("role", String.valueOf(role)); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) - .setClaims(claims) - .signWith(getSignKey(secretKey), SignatureAlgorithm.HS256) - .compact(); + } + + public Map validateTokenWithError(String token) { + Map result = new HashMap<>(); + try { + Jwts.parserBuilder().setSigningKey(getSignKey(secretKey)).build().parseClaimsJws(token); + result.put("isValid", true); + } catch (ExpiredJwtException e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_EXPIRED); + } catch (UnsupportedJwtException e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_UNSUPPORTED); + } catch (MalformedJwtException e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_MALFORMED); + } catch (IllegalArgumentException e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); + } catch (Exception e) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INTERNAL_SERVER_ERROR); } - private Key getSignKey(String secretKey) { - return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + return result; + } + + // JWT payload를 복호화해서 반환 + private Claims getClaims(String token) { + try { + return Jwts.parserBuilder() // JwtParserBuilder 인스턴스 생성 + .setSigningKey(getSignKey(secretKey)) // JWT Signature 검증을 위한 SecretKey 설정 + .build() // Thread-Safe한 JwtParser를 반환하기 위해 build 호출 + .parseClaimsJws(token) // Claim(Payload) 파싱 + .getBody(); + } catch (Exception e) { + throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); } + } - // JWT 유효성 검증 - public boolean validateToken(String token) { - try { - Jwts.parserBuilder() - .setSigningKey(getSignKey(secretKey)) - .build() - .parseClaimsJws(token); - return true; - } catch (JwtException e) { - return false; - } - } + // JWT Claims으로 User 객체를 생성하여 Authentication 객체를 반환 + public Map getAuthentication(String token) { + Map result = new HashMap<>(); - public Map validateTokenWithError(String token) { - Map result = new HashMap<>(); - try { - Jwts.parserBuilder() - .setSigningKey(getSignKey(secretKey)) - .build() - .parseClaimsJws(token); - result.put("isValid", true); - } catch (ExpiredJwtException e) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INVALID_TOKEN_EXPIRED); - } catch (UnsupportedJwtException e) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INVALID_TOKEN_UNSUPPORTED); - } catch (MalformedJwtException e) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INVALID_TOKEN_MALFORMED); - } catch (IllegalArgumentException e) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); - } catch (Exception e) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INTERNAL_SERVER_ERROR); - } - - return result; - } + // JWT에서 Claims 가져오기 + Claims claims = getClaims(token); + String userId = claims.getSubject(); + String role = claims.get("role", String.class); - // JWT payload를 복호화해서 반환 - private Claims getClaims(String token) { - try { - return Jwts.parserBuilder() // JwtParserBuilder 인스턴스 생성 - .setSigningKey(getSignKey(secretKey)) // JWT Signature 검증을 위한 SecretKey 설정 - .build() // Thread-Safe한 JwtParser를 반환하기 위해 build 호출 - .parseClaimsJws(token) // Claim(Payload) 파싱 - .getBody(); - } catch (Exception e) { - throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); - } + if (userId == null || role == null) { + result.put("isValid", false); + result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); + return result; } - // JWT Claims으로 User 객체를 생성하여 Authentication 객체를 반환 - public Map getAuthentication(String token) { - Map result = new HashMap<>(); - - // JWT에서 Claims 가져오기 - Claims claims = getClaims(token); - String userId = claims.getSubject(); - String role = claims.get("role", String.class); - - if (userId == null || role == null) { - result.put("isValid", false); - result.put("errorCode", ErrorCodes.INVALID_TOKEN_ILLEGAL); - return result; } + List grantedAuthorities = + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role)); - List grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role)); + UserDetails userDetails = + User.withUsername(userId).password("").authorities(grantedAuthorities).build(); - UserDetails userDetails = User - .withUsername(userId) - .password("") - .authorities(grantedAuthorities) - .build(); + Authentication authentication = + new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); - Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + result.put("isValid", true); + result.put("authentication", authentication); + return result; + } - result.put("isValid", true); - result.put("authentication", authentication); - return result; + public Claims getClaimFromRefreshToken(String refreshToken) { + if (!validateToken(refreshToken)) { + throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); } - public Claims getClaimFromRefreshToken(String refreshToken) { - if (!validateToken(refreshToken)) { - throw new BusinessException(ErrorCodes.INVALID_TOKEN_ILLEGAL); - } - - return getClaims(refreshToken); - } -} \ No newline at end of file + return getClaims(refreshToken); + } +} diff --git a/src/main/java/com/onebyone/kindergarten/global/provider/EmailProvider.java b/src/main/java/com/onebyone/kindergarten/global/provider/EmailProvider.java index 2e889fc..896de4e 100644 --- a/src/main/java/com/onebyone/kindergarten/global/provider/EmailProvider.java +++ b/src/main/java/com/onebyone/kindergarten/global/provider/EmailProvider.java @@ -2,92 +2,93 @@ import com.onebyone.kindergarten.global.exception.BusinessException; import com.onebyone.kindergarten.global.exception.ErrorCodes; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; - @Component @RequiredArgsConstructor public class EmailProvider { - private final JavaMailSender javaMailSender; - private final String certificationSubject = "[원바원] 인증메일입니다."; - private final String temporaryPasswordSubject = "[원바원] 비밀번호 재설정 메일입니다."; + private final JavaMailSender javaMailSender; + private final String certificationSubject = "[원바원] 인증메일입니다."; + private final String temporaryPasswordSubject = "[원바원] 비밀번호 재설정 메일입니다."; - public boolean sendCertificationMail(String email, String number) { - try { - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper messageHelper = new MimeMessageHelper(message); + public boolean sendCertificationMail(String email, String number) { + try { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper messageHelper = new MimeMessageHelper(message); - String htmlContent = getCertificationMessage(number); - messageHelper.setTo(email); - messageHelper.setSubject(certificationSubject); - messageHelper.setText(htmlContent, true); + String htmlContent = getCertificationMessage(number); + messageHelper.setTo(email); + messageHelper.setSubject(certificationSubject); + messageHelper.setText(htmlContent, true); - javaMailSender.send(message); - } catch (Exception e) { - throw new BusinessException(ErrorCodes.FAILED_SEND_MAIL_EXCEPTION); - } - return true; + javaMailSender.send(message); + } catch (Exception e) { + throw new BusinessException(ErrorCodes.FAILED_SEND_MAIL_EXCEPTION); } + return true; + } - public boolean sendTemporaryPasswordMail(String email, String number) { - try { - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper messageHelper = new MimeMessageHelper(message); + public boolean sendTemporaryPasswordMail(String email, String number) { + try { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper messageHelper = new MimeMessageHelper(message); - String htmlContent = getTemporaryPasswordMessage(number); - messageHelper.setTo(email); - messageHelper.setSubject(temporaryPasswordSubject); - messageHelper.setText(htmlContent, true); + String htmlContent = getTemporaryPasswordMessage(number); + messageHelper.setTo(email); + messageHelper.setSubject(temporaryPasswordSubject); + messageHelper.setText(htmlContent, true); - javaMailSender.send(message); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - return true; + javaMailSender.send(message); + } catch (Exception e) { + e.printStackTrace(); + return false; } + return true; + } - private String getCertificationMessage(String certificationNumber) { - StringBuilder sb = new StringBuilder(); + private String getCertificationMessage(String certificationNumber) { + StringBuilder sb = new StringBuilder(); - sb.append( - "
") - .append("

원바원 이메일 본인인증

") - .append("
") - .append("") - .append(certificationNumber) - .append("") - .append("
") - .append("

") - .append("해당 인증번호를 인증 화면에 입력하여 주십시오.
") - .append("원바원을 이용해 주셔서 감사합니다.") - .append("

") - .append("
"); + sb.append( + "
") + .append("

원바원 이메일 본인인증

") + .append("
") + .append( + "") + .append(certificationNumber) + .append("") + .append("
") + .append("

") + .append("해당 인증번호를 인증 화면에 입력하여 주십시오.
") + .append("원바원을 이용해 주셔서 감사합니다.") + .append("

") + .append("
"); - return sb.toString(); - } + return sb.toString(); + } - private String getTemporaryPasswordMessage(String temporaryPassword) { - StringBuilder sb = new StringBuilder(); + private String getTemporaryPasswordMessage(String temporaryPassword) { + StringBuilder sb = new StringBuilder(); - sb.append( - "
") - .append("

원바원 임시 비밀번호 발급

") - .append("
") - .append("") - .append(temporaryPassword) - .append("") - .append("
") - .append("

") - .append("해당 비밀번호로 로그인 후 비밀번호를 변경해주십시오.
") - .append("원바원을 이용해 주셔서 감사합니다.") - .append("

") - .append("
"); + sb.append( + "
") + .append("

원바원 임시 비밀번호 발급

") + .append("
") + .append( + "") + .append(temporaryPassword) + .append("") + .append("
") + .append("

") + .append("해당 비밀번호로 로그인 후 비밀번호를 변경해주십시오.
") + .append("원바원을 이용해 주셔서 감사합니다.") + .append("

") + .append("
"); - return sb.toString(); - } + return sb.toString(); + } } diff --git a/src/test/java/com/onebyone/kindergarten/KindergartenApplicationTests.java b/src/test/java/com/onebyone/kindergarten/KindergartenApplicationTests.java index c535265..747b01b 100644 --- a/src/test/java/com/onebyone/kindergarten/KindergartenApplicationTests.java +++ b/src/test/java/com/onebyone/kindergarten/KindergartenApplicationTests.java @@ -2,13 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; @SpringBootTest class KindergartenApplicationTests { - @Test - void contextLoads() { - } - + @Test + void contextLoads() {} } From 6e3180014a59d878c6982d9fe69b058503e25e67 Mon Sep 17 00:00:00 2001 From: juhoon Date: Fri, 12 Dec 2025 10:17:48 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix(api):=20=EC=A3=BC=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A3=BC=EC=84=9D=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kindergarten/domain/user/service/UserService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java index c087aad..7a96543 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/user/service/UserService.java @@ -41,11 +41,11 @@ public JwtUserInfoDto signUp(SignUpRequestDTO request) { throw new BusinessException(ErrorCodes.ALREADY_EXIST_EMAIL); } - // EmailCertification emailCertification = - // emailCertificationRepository.findByEmail(request.getEmail()); - // if (emailCertification == null || !emailCertification.isCertificated()) { - // throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); - // } + EmailCertification emailCertification = + emailCertificationRepository.findByEmail(request.getEmail()); + if (emailCertification == null || !emailCertification.isCertificated()) { + throw new BusinessException(ErrorCodes.FAILED_EMAIL_CERTIFICATION_EXCEPTION); + } String encodedPassword = encodePassword(request.getPassword()); User user = userRepository.save(request.toEntity(encodedPassword)); From e578366f975f15c4cee898a4efb701a175f85b40 Mon Sep 17 00:00:00 2001 From: juhoon Date: Fri, 12 Dec 2025 11:26:26 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix(api):=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20=EC=8B=9C=EC=97=90=20-1=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20formatting=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../communityPosts/entity/CommunityPost.java | 4 +++- .../service/CommunityLikeService.java | 20 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java index 28cc483..67d9f58 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/entity/CommunityPost.java @@ -72,7 +72,9 @@ public void increaseLikeCount() { // 좋아요 감소 public void decreaseLikeCount() { - this.likeCount--; + if (this.likeCount > 0) { + this.likeCount--; + } } /// 게시물 신고 상태 변경 diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java index 526b805..0e43e8c 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java @@ -42,6 +42,12 @@ public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { // 사용자 조회 User user = userService.getUserById(userId); + // 게시글 조회 + CommunityPost post = + communityRepository + .findById(postId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + // 게시글 존재 여부와 좋아요 여부를 한 번에 확인 return communityLikeRepository .findByUserAndPostId(user, postId) @@ -49,28 +55,22 @@ public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { like -> { // 좋아요 취소 communityLikeRepository.delete(like); - communityRepository.updateLikeCount(postId, -1); - return new CommunityLikeResponseDTO(false, like.getPost().getLikeCount() - 1); + post.decreaseLikeCount(); + return new CommunityLikeResponseDTO(false, post.getLikeCount()); }) .orElseGet( () -> { // 게시글 존재 여부 확인 및 좋아요 추가 - CommunityPost post = - communityRepository - .findById(postId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); - CommunityLike newLike = CommunityLike.builder().user(user).post(post).build(); communityLikeRepository.save(newLike); - communityRepository.updateLikeCount(postId, 1); - + post.increaseLikeCount(); // 알림 발송 - 본인 글이 아니고 삭제된 게시글이 아닌 경우 if (!post.getUser().getId().equals(user.getId()) && post.getDeletedAt() == null) { notificationTemplateService.sendLikeNotification( post.getUser().getId(), user, post.getTitle(), post.getId()); } - return new CommunityLikeResponseDTO(true, post.getLikeCount() + 1); + return new CommunityLikeResponseDTO(true, post.getLikeCount()); }); } } From c939ef83b79b75498ec5cdd8837dcac087260f7a Mon Sep 17 00:00:00 2001 From: juhoon Date: Fri, 12 Dec 2025 11:29:31 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor(api):=20formatter=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/communityPosts/service/CommunityLikeService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java index 0e43e8c..6119b5d 100644 --- a/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java +++ b/src/main/java/com/onebyone/kindergarten/domain/communityPosts/service/CommunityLikeService.java @@ -44,9 +44,9 @@ public CommunityLikeResponseDTO toggleLike(Long postId, Long userId) { // 게시글 조회 CommunityPost post = - communityRepository - .findById(postId) - .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); + communityRepository + .findById(postId) + .orElseThrow(() -> new BusinessException(ErrorCodes.NOT_FOUND_POST)); // 게시글 존재 여부와 좋아요 여부를 한 번에 확인 return communityLikeRepository