diff --git a/nginx.conf b/nginx.conf index b0b49bcb..49536fb2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -48,4 +48,17 @@ server { proxy_set_header Connection "Upgrade"; proxy_set_header Origin ""; } + + location /api/notifications { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_read_timeout 120s; + proxy_pass_request_headers on; + proxy_set_header Connection ""; + proxy_set_header Cache-Control "no-cache"; + proxy_set_header X-Accel-Buffering "no"; + proxy_set_header Content-Type "text/event-stream"; + proxy_buffering off; + chunked_transfer_encoding on; + } } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java b/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java index a328e2dc..1b3be8c4 100644 --- a/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java +++ b/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java @@ -30,8 +30,9 @@ public void addInterceptors(InterceptorRegistry registry) { "/api/login/oauth2/code/**", "/api/rankings", "/api/auth/locations/search", - "/api/ws-stomp/**" - )); + "/api/ws-stomp/**", + "/api/notifications/**" + )); WebMvcConfigurer.super.addInterceptors(registry); } diff --git a/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java b/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java index 6a172bf8..6fa48cdb 100644 --- a/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java +++ b/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java @@ -13,6 +13,7 @@ import sumcoda.boardbuddy.service.ChatMessageService; import sumcoda.boardbuddy.service.ChatRoomService; import sumcoda.boardbuddy.service.GatherArticleService; +import sumcoda.boardbuddy.service.NotificationService; import java.util.List; import java.util.Map; @@ -31,6 +32,8 @@ public class GatherArticleController { private final ChatMessageService chatMessageService; + private final NotificationService notificationService; + /** * 모집글 작성 컨트롤러 * @@ -56,6 +59,8 @@ public ResponseEntity>> // 채팅방 입장 메세지 전송 chatMessageService.publishEnterOrExitChatMessage(chatRoomAndNicknamePair, MessageType.ENTER); + notificationService.notifyGatherArticle(gatherArticleId, username); + return buildSuccessResponseWithPairKeyData("post", createResponse, "모집글이 업로드 되었습니다", HttpStatus.CREATED); } diff --git a/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java b/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java index 46639609..c1989244 100644 --- a/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java +++ b/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java @@ -7,7 +7,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import sumcoda.boardbuddy.dto.NotificationResponse; @@ -29,30 +29,34 @@ public class NotificationController { /** * SSE Emitter 구독 요청 * - * @param username 유저 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 **/ - @GetMapping(value = "/api/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public SseEmitter subscribe( - @RequestAttribute String username + @GetMapping(value = "/api/notifications/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public ResponseEntity subscribe( + @RequestParam String nickname ) { - log.info("User {} subscribed for notifications", username); - SseEmitter sseEmitter = notificationService.subscribe(username); + log.info("User {} subscribed for notifications", nickname); + SseEmitter sseEmitter = notificationService.subscribe(nickname); - return sseEmitter; + HttpHeaders headers = new HttpHeaders(); + headers.add("Cache-Control", "no-cache"); + headers.add("X-Accel-Buffering", "no"); + + return new ResponseEntity<>(sseEmitter, headers, HttpStatus.OK); } /** * 알림 목록 조회 요청 * -// * @param username 유저 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 * @return 알림 목록 조회 성공 시 약속된 SuccessResponse 반환 **/ @GetMapping(value = "/api/notifications") public ResponseEntity>>> getNotifications( - @RequestAttribute String username + @RequestParam String nickname ) { - List notificationDTOs = notificationService.getNotifications(username); + List notificationDTOs = notificationService.getNotifications(nickname); return buildSuccessResponseWithPairKeyData("notifications", notificationDTOs, "알림이 조회되었습니다.", HttpStatus.OK); } diff --git a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java index 94f5d6f9..ea0aab88 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java @@ -279,9 +279,6 @@ public SimpleInfoDTO(Long gatherArticleId, String title, String meetingLocation, } } - - - @Getter @NoArgsConstructor public static class TitleDTO { @@ -292,4 +289,23 @@ public TitleDTO(String title) { this.title = title; } } + + @Getter + @NoArgsConstructor + public static class LocationInfoDTO { + private String sido; + + private String sgg; + + private String emd; + + @Builder + public LocationInfoDTO(String sido, String sgg, String emd) { + this.sido = sido; + this.sgg = sgg; + this.emd = emd; + } + } + + } diff --git a/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java b/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java index 2dca4aec..d4cc8bb6 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java @@ -106,11 +106,11 @@ public LocationWithRadiusDTO(String sido, String sgg, String emd, Integer radius @Getter @NoArgsConstructor - public static class UserNameDTO { + public static class UsernameDTO { private String username; @Builder - public UserNameDTO(String username) { + public UsernameDTO(String username) { this.username = username; } } diff --git a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java index 6fe6044f..d30bf6f9 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java @@ -17,5 +17,5 @@ public interface CommentRepositoryCustom { Optional findCommentByCommentId(Long commentId); - MemberResponse.UserNameDTO findCommentAuthorByCommentId(Long commentId); + MemberResponse.UsernameDTO findCommentAuthorByCommentId(Long commentId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java index c26cbf40..b14ec1ec 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java @@ -60,9 +60,9 @@ public Optional findCommentByCommentId(Long commentId) { } @Override - public MemberResponse.UserNameDTO findCommentAuthorByCommentId(Long commentId) { + public MemberResponse.UsernameDTO findCommentAuthorByCommentId(Long commentId) { return jpaQueryFactory. - select(Projections.fields(MemberResponse.UserNameDTO.class, + select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(comment) .join(comment.member, member) diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java index 5631b702..29b49ec9 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java @@ -29,4 +29,6 @@ Slice findReadSliceDTOByLocationAndStatusAnd GatherArticleResponse.ReadDTO findGatherArticleReadDTOByGatherArticleId(Long gatherArticleId, Long memberId); Optional findTitleDTOById(Long gatherArticleId); + + Optional findLocationInfoDTOById(Long gatherArticleId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java index 4ca8208f..0b72d485 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java @@ -253,4 +253,17 @@ public Optional findTitleDTOById(Long gatherArti .where(gatherArticle.id.eq(gatherArticleId)) .fetchOne()); } + + @Override + public Optional findLocationInfoDTOById(Long gatherArticleId) { + return Optional.ofNullable(jpaQueryFactory + .select(Projections.fields(GatherArticleResponse.LocationInfoDTO.class, + gatherArticle.sido, + gatherArticle.sgg, + gatherArticle.emd + )) + .from(gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId)) + .fetchOne()); + } } diff --git a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java index 6c9acd52..8bd4909a 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java @@ -21,11 +21,15 @@ public interface MemberRepositoryCustom { Optional findLocationWithRadiusDTOByUsername(String username); - Optional findUserNameDTOByUsername(String username); + Optional findUserNameDTOByUsername(String username); Optional findIdDTOByUsername(String username); - Optional findUsernameDTOByNickname(String nickname); + Optional findUsernameDTOByNickname(String nickname); Optional findNicknameDTOByUsername(String username); + + List findUsernamesWithGatherArticleInRange(String username, String sido, String sgg, String emd); + + } diff --git a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java index 349eaa83..5733fa40 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java @@ -1,6 +1,7 @@ package sumcoda.boardbuddy.repository.member; import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import sumcoda.boardbuddy.dto.AuthResponse; @@ -12,7 +13,9 @@ import static sumcoda.boardbuddy.entity.QBadgeImage.badgeImage; import static sumcoda.boardbuddy.entity.QMember.*; +import static sumcoda.boardbuddy.entity.QNearPublicDistrict.*; import static sumcoda.boardbuddy.entity.QProfileImage.*; +import static sumcoda.boardbuddy.entity.QPublicDistrict.*; @RequiredArgsConstructor public class MemberRepositoryCustomImpl implements MemberRepositoryCustom { @@ -112,9 +115,9 @@ public Optional findLocationWithRadiusDTOB } @Override - public Optional findUserNameDTOByUsername(String username) { + public Optional findUserNameDTOByUsername(String username) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(member) .where(member.username.eq(username)) @@ -132,9 +135,9 @@ public Optional findIdDTOByUsername(String username) { } @Override - public Optional findUsernameDTOByNickname(String nickname) { + public Optional findUsernameDTOByNickname(String nickname) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(member) .where(member.nickname.eq(nickname)) @@ -150,4 +153,29 @@ public Optional findNicknameDTOByUsername(String use .where(member.username.eq(username)) .fetchOne()); } + + @Override + public List findUsernamesWithGatherArticleInRange(String username, String sido, String sgg, String emd) { + return jpaQueryFactory + .select(member.username) + .from(member) + .where( + member.username.ne(username) + .and( + JPAExpressions + .selectOne() + .from(publicDistrict) + .join(nearPublicDistrict).on(publicDistrict.id.eq(nearPublicDistrict.publicDistrict.id)) + .where( + nearPublicDistrict.sido.eq(sido) + .and(nearPublicDistrict.sgg.eq(sgg)) + .and(nearPublicDistrict.emd.eq(emd)) + .and(member.radius.goe(nearPublicDistrict.radius)) // 반경 조건 + ) + .exists() + ) + ) + .fetch(); + } + } diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java index 0e2840e7..ad093157 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java @@ -12,7 +12,7 @@ public interface MemberGatherArticleRepositoryCustom { boolean isHasRole(Long gatherArticleId, String username); - Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId); + Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId); - List findParticipantsByGatherArticleId(Long gatherArticleId); + List findParticipantsByGatherArticleId(Long gatherArticleId); } diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java index 5d71eb55..cd1a8e81 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java @@ -49,9 +49,9 @@ public boolean isHasRole(Long gatherArticleId, String username) { // 모집글의 작성자를 찾는 메서드 @Override - public Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId) { + public Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(memberGatherArticle) .join(memberGatherArticle.member, member) @@ -62,9 +62,9 @@ public Optional findAuthorUsernameByGatherArticleId( //모집글의 모든 참가자를 찾는 메서드 @Override - public List findParticipantsByGatherArticleId(Long gatherArticleId) { + public List findParticipantsByGatherArticleId(Long gatherArticleId) { return jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(memberGatherArticle) .join(memberGatherArticle.member, member) diff --git a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java index 697e4d80..1043e863 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java @@ -1,10 +1,10 @@ package sumcoda.boardbuddy.repository.notification; -import sumcoda.boardbuddy.entity.Notification; +import sumcoda.boardbuddy.dto.NotificationResponse; import java.util.List; public interface NotificationRepositoryCustom { - List findNotificationByMemberUsername(String username); + List< NotificationResponse.NotificationDTO> findNotificationByMemberUsername(String username); } diff --git a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java index 172b44f9..69a3ebd6 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java @@ -1,8 +1,9 @@ package sumcoda.boardbuddy.repository.notification; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import sumcoda.boardbuddy.entity.Notification; +import sumcoda.boardbuddy.dto.NotificationResponse; import java.util.List; @@ -22,8 +23,10 @@ public class NotificationRepositoryCustomImpl implements NotificationRepositoryC * @return 최신순으로 정렬된 알림 내역 **/ @Override - public List findNotificationByMemberUsername(String username) { - return jpaQueryFactory.selectFrom(notification) + public List< NotificationResponse.NotificationDTO> findNotificationByMemberUsername(String username) { + return jpaQueryFactory.select(Projections.fields(NotificationResponse.NotificationDTO.class, + notification.message, + notification.createdAt)) .join(notification.member, member) .where(member.username.eq(username)) .orderBy(notification.createdAt.desc()) diff --git a/src/main/java/sumcoda/boardbuddy/service/CommentService.java b/src/main/java/sumcoda/boardbuddy/service/CommentService.java index be4e5718..4e36bb34 100644 --- a/src/main/java/sumcoda/boardbuddy/service/CommentService.java +++ b/src/main/java/sumcoda/boardbuddy/service/CommentService.java @@ -114,7 +114,7 @@ public List getComments(Long gatherArticleId, String us public void updateComment(Long gatherArticleId, Long commentId, CommentRequest.UpdateDTO updateDTO, String username) { // 사용자 검증 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유효하지 않은 사용자입니다.")); // 모집글 검증 @@ -158,7 +158,7 @@ public void updateComment(Long gatherArticleId, Long commentId, CommentRequest.U public void deleteComment(Long gatherArticleId, Long commentId, String username) { // 사용자 검증 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유효하지 않은 사용자입니다.")); // 모집글 검증 diff --git a/src/main/java/sumcoda/boardbuddy/service/NotificationService.java b/src/main/java/sumcoda/boardbuddy/service/NotificationService.java index ccd5c170..d84e68ab 100644 --- a/src/main/java/sumcoda/boardbuddy/service/NotificationService.java +++ b/src/main/java/sumcoda/boardbuddy/service/NotificationService.java @@ -11,6 +11,7 @@ import sumcoda.boardbuddy.entity.Member; import sumcoda.boardbuddy.entity.Notification; import sumcoda.boardbuddy.exception.gatherArticle.GatherArticleNotFoundException; +import sumcoda.boardbuddy.exception.gatherArticle.GatherArticleRetrievalException; import sumcoda.boardbuddy.exception.member.MemberNotFoundException; import sumcoda.boardbuddy.exception.member.MemberRetrievalException; import sumcoda.boardbuddy.exception.sseEmitter.SseEmitterSendErrorException; @@ -20,6 +21,7 @@ import sumcoda.boardbuddy.repository.notification.NotificationRepository; import sumcoda.boardbuddy.repository.gatherArticle.GatherArticleRepository; import sumcoda.boardbuddy.repository.memberGatherArticle.MemberGatherArticleRepository; +import sumcoda.boardbuddy.repository.publicDistrict.PublicDistrictRepository; import sumcoda.boardbuddy.repository.sseEmitter.SseEmitterRepository; import java.io.IOException; @@ -46,6 +48,8 @@ public class NotificationService { private final CommentRepository commentRepository; + private final PublicDistrictRepository publicDistrictRepository; + // SSE Emitter와 이벤트 캐시를 위한 저장소 private static final Map emitters = new ConcurrentHashMap<>(); @@ -54,21 +58,22 @@ public class NotificationService { /** * 유저 로그인 시 SSE Emitter 구독 요청 캐치 * - * @param username 로그인 사용자 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 **/ @Transactional - public SseEmitter subscribe(String username) { + public SseEmitter subscribe(String nickname) { + + MemberResponse.UsernameDTO usernameDTO = memberRepository.findUsernameDTOByNickname(nickname) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 유저입니다.")); + + String username = usernameDTO.getUsername(); + // 매 연결마다 고유 Id 부여 String emitterId = username + "_" + System.currentTimeMillis(); // SseEmitter 인스턴스 생성 SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); - // 유저 검증 로직 - boolean memberExist = memberRepository.existsByUsername(username); - if (!memberExist) { - throw new MemberNotFoundException("존재하지 않는 유저입니다."); - } // 503 Service Unavailable 방지용 dummy 이벤트 전송 try { @@ -96,7 +101,7 @@ public SseEmitter subscribe(String username) { @Transactional public void notifyApplyParticipation(Long gatherArticleId, String appliedUsername) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -150,7 +155,7 @@ public void notifyRejectParticipation(String appliedNickname, Long gatherArticle @Transactional public void notifyCancelParticipation(Long gatherArticleId, String canceledUsername) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -172,7 +177,7 @@ public void notifyReviewRequest(Long gatherArticleId) { String message = String.format("모집글 '%s'에 대한 리뷰를 남겨주세요!", formattingTitle(gatherArticleId)); // 모든 참가자들의 아이디 조회 - List participants = memberGatherArticleRepository.findParticipantsByGatherArticleId(gatherArticleId); + List participants = memberGatherArticleRepository.findParticipantsByGatherArticleId(gatherArticleId); // 모든 참가자에게 알림 전송 participants.forEach(userNameDTO -> saveNotification(userNameDTO.getUsername(), message, "reviewRequest")); @@ -188,7 +193,7 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt if (parentId == null) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -199,7 +204,7 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt saveNotification(authorUsername, message, "writeComment"); } else { // 원댓글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = commentRepository.findCommentAuthorByCommentId(parentId); + MemberResponse.UsernameDTO authorUsernameDTO = commentRepository.findCommentAuthorByCommentId(parentId); String authorUsername = authorUsernameDTO.getUsername(); @@ -210,26 +215,50 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt } } + /** + * 모집글이 작성되면 해당 모집글 주변에 위치한 사용자에게 알림 + * + * @param gatherArticleId 해당 모집글 Id + **/ + @Transactional + public void notifyGatherArticle(Long gatherArticleId, String writtenUsername) { + + GatherArticleResponse.LocationInfoDTO locationInfoDTO = gatherArticleRepository.findLocationInfoDTOById(gatherArticleId) + .orElseThrow(() -> new GatherArticleRetrievalException("서버 문제로 해당 모집글의 정보를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + String sido = locationInfoDTO.getSido(); + String sgg = locationInfoDTO.getSgg(); + String emd = locationInfoDTO.getEmd(); + + List usernamesWithGatherArticleInRange = memberRepository.findUsernamesWithGatherArticleInRange(writtenUsername, sido, sgg, emd); + + for (String username : usernamesWithGatherArticleInRange) { + // 리뷰 요청 메시지를 포맷하여 생성 + String message = String.format("%s님의 주변에 '%s' 모집글이 작성되었습니다.", getNickname(username), formattingTitle(gatherArticleId)); + + log.info(message); + + saveNotification(username, message, "writeComment"); + } + + } + /** * 유저의 알림을 조회하여 최신순으로 반환하는 메서드 * - * @param username 알림을 조회할 유저의 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 * @return NotificationResponse 알림 응답 DTO **/ - public List getNotifications(String username) { - // 유저 검증 - if (Boolean.FALSE.equals(memberRepository.existsByUsername(username))) { - throw new MemberNotFoundException("해당 유저를 찾을 수 없습니다."); - } + public List getNotifications(String nickname) { + + MemberResponse.UsernameDTO usernameDTO = memberRepository.findUsernameDTOByNickname(nickname) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 유저입니다.")); + + String username = usernameDTO.getUsername(); //DB에서 해당 유저의 알림을 최신순으로 조회 - return notificationRepository.findNotificationByMemberUsername(username).stream() - .map(notification -> NotificationResponse.NotificationDTO.builder() - .message(notification.getMessage()) - .createdAt(notification.getCreatedAt()) - .build()) - .toList(); + return notificationRepository.findNotificationByMemberUsername(username); } /** @@ -239,7 +268,6 @@ public List getNotifications(String userna * @param message 알림 메세지 * @param eventName 알림 이벤트 이름 **/ - @Transactional public void saveNotification(String username, String message, String eventName) { Member member = memberRepository.findByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); @@ -322,7 +350,7 @@ private String getNickname(String username) { private String getUsername(String nickname) { // 유저 닉네임으로 유저 아이디 조회 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(nickname) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(nickname) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); return userNameDTO.getUsername(); diff --git a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java index 88178c05..e955fa70 100644 --- a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java +++ b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java @@ -174,7 +174,7 @@ public String approveParticipationApplication(Long gatherArticleId, Long partici // 모집글 상태 확인, 업데이트 updateGatherArticleStatusBasedOnParticipants(gatherArticle, newParticipantsCount); - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(applicantNickname).orElseThrow(() -> new MemberNotFoundException("참가 승인할 사용자의 정보를 찾을 수 없습니다.")); + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(applicantNickname).orElseThrow(() -> new MemberNotFoundException("참가 승인할 사용자의 정보를 찾을 수 없습니다.")); return userNameDTO.getUsername(); }