From 0539ba383ff8a138bdb44232b24683b6c268d5b5 Mon Sep 17 00:00:00 2001 From: KyungMin Lee Date: Tue, 20 Feb 2024 15:08:14 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20Pull=20Notification=20Pagination=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swagger/template/NotificationSwagger.java | 17 ++++++ .../OAuth2AuthenticationSuccessHandler.java | 2 +- .../application/NotificationService.java | 14 ++++- .../dto/req/NotificationCreateRequest.java | 12 ++-- .../dto/res/NotificationCreateResponse.java | 6 +- .../notification/domain/Notification.java | 19 ++++-- .../NotificationCustomRepository.java | 11 ++++ .../NotificationCustomRepositoryImpl.java | 60 +++++++++++++++++++ .../NotificationJpaRepository.java | 3 +- .../presentation/NotificationController.java | 13 +++- .../dto/res/NotificationPublicApiRes.java | 22 +++++++ .../presentation/vo/NotificationDataVo.java | 16 +++++ .../presentation/vo/NotificationSenderVo.java | 19 ++++++ 13 files changed, 196 insertions(+), 18 deletions(-) create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepository.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepositoryImpl.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/presentation/dto/res/NotificationPublicApiRes.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationDataVo.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationSenderVo.java diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/NotificationSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/NotificationSwagger.java index c6a1defb..235197ff 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/NotificationSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/NotificationSwagger.java @@ -2,8 +2,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples; import io.oeid.mogakgo.domain.notification.presentation.dto.req.FCMTokenApiRequest; +import io.oeid.mogakgo.domain.notification.presentation.dto.res.NotificationPublicApiRes; import io.oeid.mogakgo.exception.dto.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -29,4 +32,18 @@ public interface NotificationSwagger { ) ResponseEntity manageFCMToken(@Parameter(hidden = true) Long userId, FCMTokenApiRequest request); + + @Operation(summary = "알림 조회", description = "회원의 알림을 조회할 때 사용하는 API") + @ApiResponse(responseCode = "200", description = "알림 조회 성공", + content = @Content(schema = @Schema(implementation = NotificationPublicApiRes.class))) + @ApiResponse(responseCode = "404", description = "요청한 유저가 존재하지 않음", content = @Content( + mediaType = APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + }) + ) + ResponseEntity> getByUserId( + @Parameter(hidden = true) Long id, + CursorPaginationInfoReq pageable); } diff --git a/src/main/java/io/oeid/mogakgo/domain/auth/oauth/OAuth2AuthenticationSuccessHandler.java b/src/main/java/io/oeid/mogakgo/domain/auth/oauth/OAuth2AuthenticationSuccessHandler.java index fe14cc3f..e089f4e6 100644 --- a/src/main/java/io/oeid/mogakgo/domain/auth/oauth/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/io/oeid/mogakgo/domain/auth/oauth/OAuth2AuthenticationSuccessHandler.java @@ -43,6 +43,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo authentication = new JwtAuthenticationToken(oAuth2User, null, oAuth2User.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); - response.sendRedirect("/oauth/login/success"); + response.sendRedirect("/oauth2/login/success"); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/application/NotificationService.java b/src/main/java/io/oeid/mogakgo/domain/notification/application/NotificationService.java index 7f3a7338..6a863886 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/application/NotificationService.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/application/NotificationService.java @@ -1,9 +1,12 @@ package io.oeid.mogakgo.domain.notification.application; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.notification.application.dto.req.NotificationCreateRequest; import io.oeid.mogakgo.domain.notification.application.dto.res.NotificationCreateResponse; import io.oeid.mogakgo.domain.notification.domain.Notification; import io.oeid.mogakgo.domain.notification.infrastructure.NotificationJpaRepository; +import io.oeid.mogakgo.domain.notification.presentation.dto.res.NotificationPublicApiRes; import io.oeid.mogakgo.domain.user.application.UserCommonService; import io.oeid.mogakgo.domain.user.domain.User; import lombok.RequiredArgsConstructor; @@ -23,8 +26,15 @@ public class NotificationService { @Transactional public NotificationCreateResponse createNotification(NotificationCreateRequest request) { log.info("createNotification request: {}", request); - User user = userCommonService.getUserById(request.getUserId()); - Notification notification = notificationRepository.save(request.toEntity(user)); + User sender = userCommonService.getUserById(request.getSenderId()); + User receiver = userCommonService.getUserById(request.getReceiverId()); + Notification notification = notificationRepository.save(request.toEntity(sender, receiver)); return NotificationCreateResponse.from(notification); } + + public CursorPaginationResult getNotifications(Long userId, + CursorPaginationInfoReq pageable) { + User user = userCommonService.getUserById(userId); + return notificationRepository.findByUserIdWithPagination(user.getId(), pageable); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/req/NotificationCreateRequest.java b/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/req/NotificationCreateRequest.java index 6992d4d2..63cf6be5 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/req/NotificationCreateRequest.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/req/NotificationCreateRequest.java @@ -11,16 +11,18 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class NotificationCreateRequest { - private final Long userId; + private final Long senderId; + private final Long receiverId; private final NotificationTag notificationTag; private final String detailData; - public static NotificationCreateRequest of(Long userId, NotificationTag notificationTag, + public static NotificationCreateRequest of(Long senderId, Long receiverId, + NotificationTag notificationTag, String detailData) { - return new NotificationCreateRequest(userId, notificationTag, detailData); + return new NotificationCreateRequest(senderId, receiverId, notificationTag, detailData); } - public Notification toEntity(User user) { - return Notification.of(user, notificationTag, detailData); + public Notification toEntity(User sender, User receiver) { + return Notification.of(sender, receiver, notificationTag, detailData); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/res/NotificationCreateResponse.java b/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/res/NotificationCreateResponse.java index c5b597ae..82849c67 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/res/NotificationCreateResponse.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/application/dto/res/NotificationCreateResponse.java @@ -12,7 +12,8 @@ public class NotificationCreateResponse { private final Long id; - private final Long userId; + private final Long senderId; + private final Long receiverId; private final NotificationTag notificationTag; private final String detailData; private final LocalDateTime createdAt; @@ -21,7 +22,8 @@ public class NotificationCreateResponse { public static NotificationCreateResponse from(Notification notification) { return new NotificationCreateResponse( notification.getId(), - notification.getUser().getId(), + notification.getSender().getId(), + notification.getReceiver().getId(), notification.getNotificationTag(), notification.getDetailData(), notification.getCreatedAt(), diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java b/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java index 5f39cd8c..10488682 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/domain/Notification.java @@ -33,8 +33,12 @@ public class Notification { private Long id; @ManyToOne - @JoinColumn(name = "user_id") - private User user; + @JoinColumn(name = "sender_id") + private User sender; + + @ManyToOne + @JoinColumn(name = "receiver_id") + private User receiver; @Enumerated(EnumType.STRING) @Column(name = "tag") @@ -50,15 +54,18 @@ public class Notification { @Column(name = "created_at") private LocalDateTime createdAt; - private Notification(User user, NotificationTag notificationTag, String detailData) { - this.user = user; + private Notification(User sender, User receiver, NotificationTag notificationTag, + String detailData) { + this.sender = sender; + this.receiver = receiver; this.notificationTag = validateNotificationTag(notificationTag); this.detailData = validateDetailData(detailData); this.checkedYn = false; } - public static Notification of(User user, NotificationTag notificationTag, String detail) { - return new Notification(user, notificationTag, detail); + public static Notification of(User sender, User receiver, NotificationTag notificationTag, + String detail) { + return new Notification(sender, receiver, notificationTag, detail); } private NotificationTag validateNotificationTag(NotificationTag notificationTag) { diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepository.java b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepository.java new file mode 100644 index 00000000..9d5d0271 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepository.java @@ -0,0 +1,11 @@ +package io.oeid.mogakgo.domain.notification.infrastructure; + + +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.notification.presentation.dto.res.NotificationPublicApiRes; + +public interface NotificationCustomRepository { + + CursorPaginationResult findByUserIdWithPagination(Long userId, CursorPaginationInfoReq pageable); +} diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepositoryImpl.java b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepositoryImpl.java new file mode 100644 index 00000000..b1ffb028 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationCustomRepositoryImpl.java @@ -0,0 +1,60 @@ +package io.oeid.mogakgo.domain.notification.infrastructure; + + +import static io.oeid.mogakgo.domain.notification.domain.QNotification.notification; +import static io.oeid.mogakgo.domain.user.domain.QUser.user; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.notification.presentation.dto.res.NotificationPublicApiRes; +import io.oeid.mogakgo.domain.notification.presentation.vo.NotificationDataVo; +import io.oeid.mogakgo.domain.notification.presentation.vo.NotificationSenderVo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class NotificationCustomRepositoryImpl implements NotificationCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public CursorPaginationResult findByUserIdWithPagination(Long userId, + CursorPaginationInfoReq pageable) { + List result = jpaQueryFactory.select( + Projections.constructor( + NotificationPublicApiRes.class, + notification.id, + notification.notificationTag, + Projections.constructor( + NotificationSenderVo.class, + notification.sender.username, + notification.sender.id, + notification.sender.avatarUrl + ), + Projections.constructor( + NotificationDataVo.class, + notification.detailData, + notification.createdAt + ) + ) + ) + .from(notification) + .join(notification.sender, user) + .where( + cursorIdCondition(pageable.getCursorId()), + notification.receiver.id.eq(userId)) + .orderBy(notification.id.desc()) + .limit(pageable.getPageSize() + 1L) + .fetch(); + return CursorPaginationResult.fromDataWithExtraItemForNextCheck(result, + pageable.getPageSize()); + } + + private BooleanExpression cursorIdCondition(Long cursorId) { + return cursorId != null ? notification.id.lt(cursorId) : null; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationJpaRepository.java index 99d885e9..261771f8 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/infrastructure/NotificationJpaRepository.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface NotificationJpaRepository extends JpaRepository { +public interface NotificationJpaRepository extends JpaRepository, + NotificationCustomRepository { } \ No newline at end of file diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/presentation/NotificationController.java b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/NotificationController.java index b98098b6..49fac334 100644 --- a/src/main/java/io/oeid/mogakgo/domain/notification/presentation/NotificationController.java +++ b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/NotificationController.java @@ -1,12 +1,18 @@ package io.oeid.mogakgo.domain.notification.presentation; import io.oeid.mogakgo.common.annotation.UserId; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.common.swagger.template.NotificationSwagger; import io.oeid.mogakgo.domain.notification.application.FCMNotificationService; +import io.oeid.mogakgo.domain.notification.application.NotificationService; import io.oeid.mogakgo.domain.notification.presentation.dto.req.FCMTokenApiRequest; +import io.oeid.mogakgo.domain.notification.presentation.dto.res.NotificationPublicApiRes; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,6 +24,7 @@ public class NotificationController implements NotificationSwagger { private final FCMNotificationService fcmNotificationService; + private final NotificationService notificationService; @PostMapping("/fcm") public ResponseEntity manageFCMToken(@UserId Long userId, @RequestBody @Valid @@ -26,5 +33,9 @@ public ResponseEntity manageFCMToken(@UserId Long userId, @RequestBody @Va return ResponseEntity.ok().build(); } - + @GetMapping + public ResponseEntity> getByUserId( + @UserId Long id, @Valid @ModelAttribute CursorPaginationInfoReq pageable) { + return ResponseEntity.ok().body(notificationService.getNotifications(id, pageable)); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/presentation/dto/res/NotificationPublicApiRes.java b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/dto/res/NotificationPublicApiRes.java new file mode 100644 index 00000000..4183e597 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/dto/res/NotificationPublicApiRes.java @@ -0,0 +1,22 @@ +package io.oeid.mogakgo.domain.notification.presentation.dto.res; + +import io.oeid.mogakgo.domain.notification.domain.enums.NotificationTag; +import io.oeid.mogakgo.domain.notification.presentation.vo.NotificationDataVo; +import io.oeid.mogakgo.domain.notification.presentation.vo.NotificationSenderVo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "알림 API 응답") +@Getter +@AllArgsConstructor +public class NotificationPublicApiRes { + + @Schema(description = "알림 ID") + private Long id; + @Schema(description = "알림 형식") + private NotificationTag tag; + private NotificationSenderVo sender; + private NotificationDataVo data; + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationDataVo.java b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationDataVo.java new file mode 100644 index 00000000..29e3bb0a --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationDataVo.java @@ -0,0 +1,16 @@ +package io.oeid.mogakgo.domain.notification.presentation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "알림 데이터") +@Getter +@AllArgsConstructor +public class NotificationDataVo { + + private final String detail; + private final LocalDateTime createdAt; + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationSenderVo.java b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationSenderVo.java new file mode 100644 index 00000000..0c3fe247 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/notification/presentation/vo/NotificationSenderVo.java @@ -0,0 +1,19 @@ +package io.oeid.mogakgo.domain.notification.presentation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "알림 발신자 정보") +@Getter +@AllArgsConstructor(access = AccessLevel.PUBLIC) +public class NotificationSenderVo { + @Schema(description = "알림 전송자 이름", defaultValue = "mogakgo") + private String name; + @Schema(description = "알림 전송자 아이디", nullable = true) + private Long id; + @Schema(description = "알림 전송자 프로필 이미지 URL", nullable = true) + private String profileImageUrl; + +}