diff --git a/src/main/java/cmc/peerna/apiResponse/code/ResponseStatus.java b/src/main/java/cmc/peerna/apiResponse/code/ResponseStatus.java index a0f9e3b..cb8fa33 100644 --- a/src/main/java/cmc/peerna/apiResponse/code/ResponseStatus.java +++ b/src/main/java/cmc/peerna/apiResponse/code/ResponseStatus.java @@ -38,6 +38,10 @@ public enum ResponseStatus implements BaseCode { ALREADY_EXIST_PROJECT_MEMBER(OK, 2302, "이미 해당 프로젝트에 참여중입니다."), PROJECT_SELF_INVITE(OK, 2303, "자신이 만든 프로젝트엔 참여할 수 없습니다."), + // Notice 에러 + NOTICE_COUNT_ZERO(OK, 2350, "조회된 알림이 0개입니다."), + + // 400번대 에러 diff --git a/src/main/java/cmc/peerna/converter/NoticeConverter.java b/src/main/java/cmc/peerna/converter/NoticeConverter.java new file mode 100644 index 0000000..ca0b80a --- /dev/null +++ b/src/main/java/cmc/peerna/converter/NoticeConverter.java @@ -0,0 +1,36 @@ +package cmc.peerna.converter; + +import cmc.peerna.domain.Notice; +import cmc.peerna.web.dto.responseDto.NoticeResponseDto; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + +public class NoticeConverter { + + public static NoticeResponseDto.NoticeSimpleInfoDto toNoticeSimpleProfile(Notice notice) { + return NoticeResponseDto.NoticeSimpleInfoDto.builder() + .targetId(notice.getTargetId()) + .noticeType(notice.getNoticeType()) + .contents(notice.getContents()) + .createdTime(notice.getCreatedAt()) + .build(); + } + + public static NoticeResponseDto.NoticePageDto toNoticePageDto(Page noticePage) { + if(noticePage.getTotalElements()==0L) return null; + List noticeSimpleInfoDtoList = noticePage.stream() + .map(notice -> toNoticeSimpleProfile(notice)) + .collect(Collectors.toList()); + + return NoticeResponseDto.NoticePageDto.builder() + .noticeList(noticeSimpleInfoDtoList) + .isFirst(noticePage.isFirst()) + .isLast(noticePage.isLast()) + .totalPage(noticePage.getTotalPages()) + .totalElements(noticePage.getTotalElements()) + .currentPageElements(noticePage.getNumberOfElements()) + .build(); + } +} diff --git a/src/main/java/cmc/peerna/domain/Notice.java b/src/main/java/cmc/peerna/domain/Notice.java index b44f717..78fcaf3 100644 --- a/src/main/java/cmc/peerna/domain/Notice.java +++ b/src/main/java/cmc/peerna/domain/Notice.java @@ -1,6 +1,7 @@ package cmc.peerna.domain; import cmc.peerna.domain.common.BaseEntity; +import cmc.peerna.domain.enums.NoticeGroup; import cmc.peerna.domain.enums.NoticeType; import jakarta.persistence.*; import lombok.*; @@ -23,8 +24,12 @@ public class Notice extends BaseEntity { @JoinColumn(name = "receiver_id") private Member receiver; + @Enumerated(EnumType.STRING) + private NoticeGroup noticeGroup; + @Enumerated(EnumType.STRING) private NoticeType noticeType; + private Long targetId; private String contents; diff --git a/src/main/java/cmc/peerna/domain/enums/NoticeGroup.java b/src/main/java/cmc/peerna/domain/enums/NoticeGroup.java new file mode 100644 index 0000000..8325c89 --- /dev/null +++ b/src/main/java/cmc/peerna/domain/enums/NoticeGroup.java @@ -0,0 +1,13 @@ +package cmc.peerna.domain.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum NoticeGroup { + PROJECT("프로젝트 관련 알림"), + PEER_TEST("피어테스트 관련 알림"); + + private final String description; +} diff --git a/src/main/java/cmc/peerna/domain/enums/NoticeType.java b/src/main/java/cmc/peerna/domain/enums/NoticeType.java index 6ac0b3c..f0ec097 100644 --- a/src/main/java/cmc/peerna/domain/enums/NoticeType.java +++ b/src/main/java/cmc/peerna/domain/enums/NoticeType.java @@ -1,5 +1,17 @@ package cmc.peerna.domain.enums; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum NoticeType { - PROJECT_SUGGEST, PROJECT_INVITE + INVITED_TO_OTHER_PROJECT("남의 프로젝트에 참여 제안 받음"), + OTHER_USER_REQUESTED_MY_PROJECT("남이 내 프로젝트에 참여 신청함."), + OTHER_PROJECT_MY_REQUEST_ACCEPTED("남의 프로젝트에 대한 내 참여 요청이 수락됨."), + OTHER_PROJECT_MY_REQUEST_DECLINED("남의 프로젝트에 대한 내 참여 요청이 거절됨."), + MY_PROJECT_MY_INVITE_ACCEPTED("나의 프로젝트에 대한 내 참여 제안이 수락됨."), + MY_PROJECT_MY_INVITE_DECLINED("나의 프로젝트에 대한 내 참여 제안이 거절됨."); + + private final String description; } diff --git a/src/main/java/cmc/peerna/domain/enums/NotificationType.java b/src/main/java/cmc/peerna/domain/enums/NotificationType.java deleted file mode 100644 index 6f66caf..0000000 --- a/src/main/java/cmc/peerna/domain/enums/NotificationType.java +++ /dev/null @@ -1,13 +0,0 @@ -package cmc.peerna.domain.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Getter -public enum NotificationType { - COMMENT("comment"), - EVENT("event"); - - private final String type; -} diff --git a/src/main/java/cmc/peerna/repository/NoticeRepository.java b/src/main/java/cmc/peerna/repository/NoticeRepository.java new file mode 100644 index 0000000..787e360 --- /dev/null +++ b/src/main/java/cmc/peerna/repository/NoticeRepository.java @@ -0,0 +1,13 @@ +package cmc.peerna.repository; + +import cmc.peerna.domain.Member; +import cmc.peerna.domain.Notice; +import cmc.peerna.domain.enums.NoticeGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoticeRepository extends JpaRepository { + + Page findAllByNoticeGroupAndReceiver(NoticeGroup noticeGroup, Member receiver, PageRequest pageRequest); +} diff --git a/src/main/java/cmc/peerna/service/NoticeService.java b/src/main/java/cmc/peerna/service/NoticeService.java new file mode 100644 index 0000000..41abd6e --- /dev/null +++ b/src/main/java/cmc/peerna/service/NoticeService.java @@ -0,0 +1,15 @@ +package cmc.peerna.service; + +import cmc.peerna.domain.Member; +import cmc.peerna.domain.enums.NoticeGroup; +import cmc.peerna.domain.enums.NoticeType; +import cmc.peerna.web.dto.responseDto.NoticeResponseDto; + +public interface NoticeService { + void createNotice(Member sender, Long receiverId, NoticeType noticeType); + + NoticeResponseDto.NoticePageDto getNoticePageByNoticeGroup(Member receiver, NoticeGroup noticeGroup, Integer page); + + NoticeResponseDto.NoticePageDto getProjectNoticePage(Member receiver, Integer page); + NoticeResponseDto.NoticePageDto getPeerTestNoticePage(Member receiver, Integer page); +} diff --git a/src/main/java/cmc/peerna/service/serviceImpl/NoticeServiceImpl.java b/src/main/java/cmc/peerna/service/serviceImpl/NoticeServiceImpl.java new file mode 100644 index 0000000..820c536 --- /dev/null +++ b/src/main/java/cmc/peerna/service/serviceImpl/NoticeServiceImpl.java @@ -0,0 +1,72 @@ +package cmc.peerna.service.serviceImpl; + +import cmc.peerna.apiResponse.code.ResponseStatus; +import cmc.peerna.apiResponse.exception.handler.MemberException; +import cmc.peerna.apiResponse.exception.handler.RootException; +import cmc.peerna.converter.NoticeConverter; +import cmc.peerna.domain.Member; +import cmc.peerna.domain.Notice; +import cmc.peerna.domain.enums.NoticeGroup; +import cmc.peerna.domain.enums.NoticeType; +import cmc.peerna.repository.MemberRepository; +import cmc.peerna.repository.NoticeRepository; +import cmc.peerna.service.NoticeService; +import cmc.peerna.web.dto.responseDto.NoticeResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@RequiredArgsConstructor +public class NoticeServiceImpl implements NoticeService { + + private final NoticeRepository noticeRepository; + private final MemberRepository memberRepository; + + @Value("${paging.size}") + private Integer pageSize; + + @Override + @Transactional + public void createNotice(Member sender, Long receiverId, NoticeType noticeType) { + Member receiver = memberRepository.findById(receiverId).orElseThrow(() -> new MemberException(ResponseStatus.MEMBER_NOT_FOUND)); + noticeRepository.save(Notice.builder() + .sender(sender) + .receiver(receiver) + .noticeType(noticeType) + .build() + ); + } + + @Override + public NoticeResponseDto.NoticePageDto getNoticePageByNoticeGroup(Member receiver, NoticeGroup noticeGroup, Integer page) { + + PageRequest pageRequest = PageRequest.of(page, pageSize, Sort.by(Sort.Order.desc("createdAt"))); + Page noticeByNoticeGroup = noticeRepository.findAllByNoticeGroupAndReceiver(noticeGroup, receiver, pageRequest); + if (noticeByNoticeGroup.getTotalElements() == 0L) { + throw new RootException(ResponseStatus.NOTICE_COUNT_ZERO); + } + if (noticeByNoticeGroup.getTotalPages() <= page) + throw new MemberException(ResponseStatus.OVER_PAGE_INDEX_ERROR); + + NoticeResponseDto.NoticePageDto noticePageDto = NoticeConverter.toNoticePageDto(noticeByNoticeGroup); + return noticePageDto; + } + + @Override + public NoticeResponseDto.NoticePageDto getProjectNoticePage(Member receiver, Integer page) { + return getNoticePageByNoticeGroup(receiver, NoticeGroup.PROJECT, page); + } + + @Override + public NoticeResponseDto.NoticePageDto getPeerTestNoticePage(Member receiver, Integer page) { + return getNoticePageByNoticeGroup(receiver, NoticeGroup.PEER_TEST, page); + } + +} diff --git a/src/main/java/cmc/peerna/web/controller/HomeController.java b/src/main/java/cmc/peerna/web/controller/HomeController.java index 29352e3..6fd3838 100644 --- a/src/main/java/cmc/peerna/web/controller/HomeController.java +++ b/src/main/java/cmc/peerna/web/controller/HomeController.java @@ -2,14 +2,18 @@ import cmc.peerna.apiResponse.code.ResponseStatus; import cmc.peerna.apiResponse.exception.handler.MemberException; +import cmc.peerna.apiResponse.response.PageResponseDto; import cmc.peerna.apiResponse.response.ResponseDto; import cmc.peerna.domain.Member; -import cmc.peerna.fcm.service.FcmService; import cmc.peerna.jwt.handler.annotation.AuthMember; import cmc.peerna.service.MemberService; +import cmc.peerna.service.NoticeService; import cmc.peerna.service.RootService; import cmc.peerna.validation.annotation.CheckPage; +import cmc.peerna.web.dto.requestDto.RootRequestDto; import cmc.peerna.web.dto.responseDto.HomeResponseDto; +import cmc.peerna.web.dto.responseDto.NoticeResponseDto; +import cmc.peerna.web.dto.responseDto.ProjectResponseDto; import cmc.peerna.web.dto.responseDto.RootResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -26,6 +30,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @Slf4j @RequiredArgsConstructor @@ -44,6 +50,7 @@ public class HomeController { private final MemberService memberService; private final RootService rootService; + private final NoticeService noticeService; @Operation(summary = "피어 유형으로 동료 찾기 API ✔️🔑", description = "피어 유형으로 동료 찾기 API입니다.") @@ -127,4 +134,74 @@ else if (page < 1) RootResponseDto.AllFeedbackDto feedbackList = rootService.getFeedbackList(peer, page); return ResponseDto.of(feedbackList); } + + @Operation(summary = "알림 - 피어테스트 알림 조회 API ✔️🔑", description = "알림 - 피어테스트 알림 조회 API입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "2200", description = "BAD_REQUEST, 존재하지 않는 유저를 조회한 경우."), + @ApiResponse(responseCode = "2350", description = "BAD_REQUEST, 조회된 알림이 0개입니다."), + @ApiResponse(responseCode = "4012", description = "BAD_REQUEST , 페이지 번호는 1 이상이여야 합니다.", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4013", description = "BAD_REQUEST , 페이지 번호가 페이징 범위를 초과했습니다.", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + @Parameters({ + @Parameter(name = "member", hidden = true) + }) + @GetMapping("/home/notice/peer-test") + public PageResponseDto> getPeerTestNotice(@AuthMember Member member, @CheckPage @RequestParam(name = "page") Integer page) { + if (page == null) + page = 1; + else if (page < 1) + throw new MemberException(ResponseStatus.UNDER_PAGE_INDEX_ERROR); + page -= 1; + + NoticeResponseDto.NoticePageDto peerTestNoticePage = noticeService.getPeerTestNoticePage(member, page); + + List peerTestNoticeList; + peerTestNoticeList = peerTestNoticePage.getNoticeList(); + + RootRequestDto.PageRequestDto pageRequestDto = RootRequestDto.PageRequestDto.builder() + .totalElements(peerTestNoticePage.getTotalElements()) + .currentPageElements(peerTestNoticePage.getCurrentPageElements()) + .totalPage(peerTestNoticePage.getTotalPage()) + .isFirst(peerTestNoticePage.getIsFirst()) + .isLast(peerTestNoticePage.getIsLast()) + .build(); + + return PageResponseDto.of(peerTestNoticeList, pageRequestDto); + } + + + @Operation(summary = "알림 - 프로젝트 알림 조회 API ✔️🔑", description = "알림 - 프로젝트 알림 조회 API입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "2200", description = "BAD_REQUEST, 존재하지 않는 유저를 조회한 경우."), + @ApiResponse(responseCode = "2350", description = "BAD_REQUEST, 조회된 알림이 0개입니다."), + @ApiResponse(responseCode = "4012", description = "BAD_REQUEST , 페이지 번호는 1 이상이여야 합니다.", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4013", description = "BAD_REQUEST , 페이지 번호가 페이징 범위를 초과했습니다.", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + @Parameters({ + @Parameter(name = "member", hidden = true) + }) + @GetMapping("/home/notice/project") + public PageResponseDto> getProjectNotice(@AuthMember Member member, @CheckPage @RequestParam(name = "page") Integer page) { + if (page == null) + page = 1; + else if (page < 1) + throw new MemberException(ResponseStatus.UNDER_PAGE_INDEX_ERROR); + page -= 1; + + NoticeResponseDto.NoticePageDto peerTestNoticePage = noticeService.getProjectNoticePage(member, page); + + List projectNoticeList; + projectNoticeList = peerTestNoticePage.getNoticeList(); + + RootRequestDto.PageRequestDto pageRequestDto = RootRequestDto.PageRequestDto.builder() + .totalElements(peerTestNoticePage.getTotalElements()) + .currentPageElements(peerTestNoticePage.getCurrentPageElements()) + .totalPage(peerTestNoticePage.getTotalPage()) + .isFirst(peerTestNoticePage.getIsFirst()) + .isLast(peerTestNoticePage.getIsLast()) + .build(); + + return PageResponseDto.of(projectNoticeList, pageRequestDto); + } + } diff --git a/src/main/java/cmc/peerna/web/dto/responseDto/NoticeResponseDto.java b/src/main/java/cmc/peerna/web/dto/responseDto/NoticeResponseDto.java new file mode 100644 index 0000000..749afd1 --- /dev/null +++ b/src/main/java/cmc/peerna/web/dto/responseDto/NoticeResponseDto.java @@ -0,0 +1,36 @@ +package cmc.peerna.web.dto.responseDto; + +import cmc.peerna.domain.enums.NoticeType; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +public class NoticeResponseDto { + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class NoticeSimpleInfoDto { + private Long targetId; + + // 알림의 좌측 아이콘 구분 위한 필드 + private NoticeType noticeType; + private String contents; + private LocalDateTime createdTime; + + } + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class NoticePageDto{ + List noticeList; + Long totalElements; + Integer currentPageElements; + Integer totalPage; + Boolean isFirst; + Boolean isLast; + } +}