Skip to content

Commit

Permalink
Feat: 알림 조회 no offset 페이징 API 구현 (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
versatile0010 authored Nov 21, 2023
1 parent 05b50db commit 2a3d488
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.numberone.backend.domain.notification.controller;

import com.numberone.backend.domain.notification.dto.request.NotificationSearchParameter;
import com.numberone.backend.domain.notification.dto.response.NotificationTabResponse;
import com.numberone.backend.domain.notification.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@RequestMapping("/api/notifications")
@RestController
@RequiredArgsConstructor
public class NotificationController {
private final NotificationService notificationService;

@Operation(summary = "알림 리스트 조회 no offset Paging API 입니다.", description = """
요청 예시 url 은 아래와 같습니다.
'http://localhost:8080/api/notifications?page=0&size=10`
size 는 페이지의 사이즈를 의미합니다.
정렬 순서는 생성 시간 순(빠른 순)입니다.
lastNotificationId 은 가장 직전에 조회한 페이지에서, 가장 마지막(작은) NotificationId 를 의미합니다.
- 첫 페이지를 요청하는 경우에는 null 로 보내야 합니다.
- 첫 페이지 이후에 대한 요청은, 직전 페이지에서 얻어온 페이지 리스트 중 가장 작은 NotificationId 를 담아서 보내면 다음 페이지가 요청됩니다.
""")
@GetMapping
public ResponseEntity<Slice<NotificationTabResponse>> getNotificationPage(
Pageable pageable,
@ModelAttribute NotificationSearchParameter param){
return ResponseEntity.ok(notificationService.getNotificationTabPagesByMember(param, pageable));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.numberone.backend.domain.notification.dto.request;

import lombok.*;

@ToString
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class NotificationSearchParameter {
Long lastNotificationId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.numberone.backend.domain.notification.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.numberone.backend.domain.notification.entity.NotificationEntity;
import com.numberone.backend.domain.notification.entity.NotificationTag;
import com.querydsl.core.annotations.QueryProjection;
import lombok.*;

import java.time.LocalDateTime;

@ToString
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class NotificationTabResponse {
private Long id;
private String tag;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul")
private LocalDateTime createdAt;
private String title;
private String body;


@QueryProjection
public NotificationTabResponse(NotificationEntity notification) {
this.id = notification.getId();
this.tag = switch (notification.getNotificationTag()) {
case FAMILY -> "가족";
case SUPPORT -> "후원";
case COMMUNITY -> "커뮤니티";
};
this.createdAt = notification.getCreatedAt();
this.title = notification.getTitle();
this.body = notification.getBody();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.numberone.backend.domain.notification.repository;

import com.numberone.backend.domain.notification.entity.NotificationEntity;
import com.numberone.backend.domain.notification.repository.custom.NotificationRepositoryCustom;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NotificationRepository extends JpaRepository<NotificationEntity, Long> {
public interface NotificationRepository extends JpaRepository<NotificationEntity, Long>, NotificationRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.numberone.backend.domain.notification.repository.custom;

import com.numberone.backend.domain.notification.dto.request.NotificationSearchParameter;
import com.numberone.backend.domain.notification.dto.response.NotificationTabResponse;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

public interface NotificationRepositoryCustom {
Slice<NotificationTabResponse> getNotificationTabPagesNoOffSetByMember(NotificationSearchParameter param, Long memberId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.numberone.backend.domain.notification.repository.custom;

import com.numberone.backend.domain.notification.dto.request.NotificationSearchParameter;
import com.numberone.backend.domain.notification.dto.response.NotificationTabResponse;
import com.numberone.backend.domain.notification.dto.response.QNotificationTabResponse;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

import java.util.List;
import java.util.Objects;

import static com.numberone.backend.domain.member.entity.QMember.member;
import static com.numberone.backend.domain.notification.entity.QNotificationEntity.notificationEntity;

public class NotificationRepositoryCustomImpl implements NotificationRepositoryCustom {
private final JPAQueryFactory queryFactory;

public NotificationRepositoryCustomImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}

@Override
public Slice<NotificationTabResponse> getNotificationTabPagesNoOffSetByMember(NotificationSearchParameter param, Long memberId, Pageable pageable) {
List<NotificationTabResponse> result = queryFactory.select(new QNotificationTabResponse(notificationEntity))
.from(notificationEntity)
.innerJoin(member)
.on(notificationEntity.receivedMemberId.eq(member.id))
.where(
ltNotificationEntityId(param.getLastNotificationId()),
member.id.eq(memberId)
)
.orderBy(notificationEntity.id.desc())
.limit(pageable.getPageSize() + 1)
.fetch();
return checkLastPage(pageable, result);
}

private BooleanExpression ltNotificationEntityId(Long notificationId) {
if (Objects.isNull(notificationId)) {
return null;
}
return notificationEntity.id.lt(notificationId);
}

private Slice<NotificationTabResponse> checkLastPage(Pageable pageable, List<NotificationTabResponse> result) {
boolean hasNext = false;

if (result.size() > pageable.getPageSize()) {
hasNext = true;
result.remove(pageable.getPageSize());
}
return new SliceImpl<>(result, pageable, hasNext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.numberone.backend.domain.notification.service;

import com.numberone.backend.domain.member.entity.Member;
import com.numberone.backend.domain.member.repository.MemberRepository;
import com.numberone.backend.domain.notification.dto.request.NotificationSearchParameter;
import com.numberone.backend.domain.notification.dto.response.NotificationTabResponse;
import com.numberone.backend.domain.notification.repository.NotificationRepository;
import com.numberone.backend.domain.token.util.SecurityContextProvider;
import com.numberone.backend.exception.notfound.NotFoundMemberException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class NotificationService {
private final MemberRepository memberRepository;
private final NotificationRepository notificationRepository;

public Slice<NotificationTabResponse> getNotificationTabPagesByMember(NotificationSearchParameter param, Pageable pageable) {
String principal = SecurityContextProvider.getAuthenticatedUserEmail();
Member member = memberRepository.findByEmail(principal)
.orElseThrow(NotFoundMemberException::new);
return notificationRepository.getNotificationTabPagesNoOffSetByMember(param, member.getId(), pageable);
}

}

0 comments on commit 2a3d488

Please sign in to comment.