diff --git a/src/main/java/doldol_server/doldol/invite/controller/InviteController.java b/src/main/java/doldol_server/doldol/invite/controller/InviteController.java index 50f2e5a..b4f7ab3 100644 --- a/src/main/java/doldol_server/doldol/invite/controller/InviteController.java +++ b/src/main/java/doldol_server/doldol/invite/controller/InviteController.java @@ -1,5 +1,7 @@ package doldol_server.doldol.invite.controller; +import doldol_server.doldol.common.dto.CursorPage; +import doldol_server.doldol.common.request.CursorPageRequest; import doldol_server.doldol.common.response.ApiResponse; import doldol_server.doldol.auth.dto.CustomUserDetails; import doldol_server.doldol.invite.dto.request.InviteCommentCreateRequest; @@ -13,11 +15,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import java.util.List; - @Tag(name = "초대장", description = "파티/모임 초대장 API") @RestController @RequestMapping("/invites") @@ -38,38 +39,53 @@ public ApiResponse createInvite( return ApiResponse.created(inviteService.createInvite(request, userDetails.getUserId())); } + @Operation( + summary = "내 초대장 목록 조회", + security = {@SecurityRequirement(name = "jwt")} + ) + @GetMapping + public ApiResponse> getMyInvites( + @ParameterObject @Valid CursorPageRequest request, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + return ApiResponse.ok(inviteService.getMyInvites(userDetails.getUserId(), request)); + } + @Operation(summary = "초대장 상세 조회") - @GetMapping("/{inviteId}") - public ApiResponse getInvite(@PathVariable Long inviteId) { - return ApiResponse.ok(inviteService.getInvite(inviteId)); + @GetMapping("/{inviteCode}") + public ApiResponse getInvite(@PathVariable String inviteCode) { + return ApiResponse.ok(inviteService.getInvite(inviteCode)); } @Operation(summary = "초대장 댓글 등록") - @PostMapping("/{inviteId}/comments") + @PostMapping("/{inviteCode}/comments") public ApiResponse addComment( - @PathVariable Long inviteId, + @PathVariable String inviteCode, @Valid @RequestBody InviteCommentCreateRequest request ) { - return ApiResponse.created(inviteService.addComment(inviteId, request)); + return ApiResponse.created(inviteService.addComment(inviteCode, request)); } @Operation(summary = "초대장 댓글 목록 조회") - @GetMapping("/{inviteId}/comments") - public ApiResponse> getComments(@PathVariable Long inviteId) { - return ApiResponse.ok(inviteService.getComments(inviteId)); + @GetMapping("/{inviteCode}/comments") + public ApiResponse> getComments( + @PathVariable String inviteCode, + @ParameterObject @Valid CursorPageRequest request + ) { + return ApiResponse.ok(inviteService.getComments(inviteCode, request)); } @Operation( summary = "초대장 수정", security = {@SecurityRequirement(name = "jwt")} ) - @PutMapping("/{inviteId}") + @PutMapping("/{inviteCode}") public ApiResponse updateInvite( - @PathVariable Long inviteId, + @PathVariable String inviteCode, @Valid @RequestBody InviteUpdateRequest request, @AuthenticationPrincipal CustomUserDetails userDetails ) { - inviteService.updateInvite(inviteId, request, userDetails.getUserId()); + inviteService.updateInvite(inviteCode, request, userDetails.getUserId()); return ApiResponse.noContent(); } @@ -77,12 +93,12 @@ public ApiResponse updateInvite( summary = "초대장 삭제", security = {@SecurityRequirement(name = "jwt")} ) - @DeleteMapping("/{inviteId}") + @DeleteMapping("/{inviteCode}") public ApiResponse deleteInvite( - @PathVariable Long inviteId, + @PathVariable String inviteCode, @AuthenticationPrincipal CustomUserDetails userDetails ) { - inviteService.deleteInvite(inviteId, userDetails.getUserId()); + inviteService.deleteInvite(inviteCode, userDetails.getUserId()); return ApiResponse.noContent(); } } diff --git a/src/main/java/doldol_server/doldol/invite/repository/InviteCommentRepository.java b/src/main/java/doldol_server/doldol/invite/repository/InviteCommentRepository.java index 84dbde4..c69ca73 100644 --- a/src/main/java/doldol_server/doldol/invite/repository/InviteCommentRepository.java +++ b/src/main/java/doldol_server/doldol/invite/repository/InviteCommentRepository.java @@ -1,8 +1,9 @@ package doldol_server.doldol.invite.repository; import doldol_server.doldol.invite.entity.InviteComment; +import doldol_server.doldol.invite.repository.custom.InviteCommentRepositoryCustom; import org.springframework.data.jpa.repository.JpaRepository; -public interface InviteCommentRepository extends JpaRepository { +public interface InviteCommentRepository extends JpaRepository, InviteCommentRepositoryCustom { } diff --git a/src/main/java/doldol_server/doldol/invite/repository/InviteRepository.java b/src/main/java/doldol_server/doldol/invite/repository/InviteRepository.java index 77c1eae..810a26f 100644 --- a/src/main/java/doldol_server/doldol/invite/repository/InviteRepository.java +++ b/src/main/java/doldol_server/doldol/invite/repository/InviteRepository.java @@ -1,14 +1,17 @@ package doldol_server.doldol.invite.repository; import doldol_server.doldol.invite.entity.Invite; +import doldol_server.doldol.invite.repository.custom.InviteRepositoryCustom; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -public interface InviteRepository extends JpaRepository { +public interface InviteRepository extends JpaRepository, InviteRepositoryCustom { @EntityGraph(attributePaths = {"comments"}) - Optional findWithCommentsByInviteId(Long inviteId); + Optional findWithCommentsByInviteCode(String inviteCode); + + Optional findByInviteCode(String inviteCode); } diff --git a/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustom.java b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustom.java new file mode 100644 index 0000000..bb97703 --- /dev/null +++ b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustom.java @@ -0,0 +1,12 @@ +package doldol_server.doldol.invite.repository.custom; + +import doldol_server.doldol.common.request.CursorPageRequest; +import doldol_server.doldol.invite.dto.response.InviteCommentResponse; + +import java.util.List; + +public interface InviteCommentRepositoryCustom { + + List findCommentsByInviteCode(String inviteCode, CursorPageRequest request); +} + diff --git a/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustomImpl.java b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustomImpl.java new file mode 100644 index 0000000..3a3f705 --- /dev/null +++ b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteCommentRepositoryCustomImpl.java @@ -0,0 +1,45 @@ +package doldol_server.doldol.invite.repository.custom; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import doldol_server.doldol.common.request.CursorPageRequest; +import doldol_server.doldol.invite.dto.response.InviteCommentResponse; +import doldol_server.doldol.invite.entity.InviteComment; +import doldol_server.doldol.invite.entity.QInvite; +import doldol_server.doldol.invite.entity.QInviteComment; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +public class InviteCommentRepositoryCustomImpl implements InviteCommentRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findCommentsByInviteCode(String inviteCode, CursorPageRequest request) { + QInviteComment comment = QInviteComment.inviteComment; + QInvite invite = QInvite.invite; + + BooleanExpression cursorCondition = null; + if (request.cursorId() != null) { + cursorCondition = comment.commentId.lt(request.cursorId()); + } + + List comments = queryFactory + .selectFrom(comment) + .join(comment.invite, invite) + .where( + invite.inviteCode.eq(inviteCode), + cursorCondition + ) + .orderBy(comment.commentId.desc()) + .limit(request.size() + 1L) + .fetch(); + + return comments.stream() + .map(InviteCommentResponse::from) + .toList(); + } +} + diff --git a/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustom.java b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustom.java new file mode 100644 index 0000000..9720180 --- /dev/null +++ b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustom.java @@ -0,0 +1,12 @@ +package doldol_server.doldol.invite.repository.custom; + +import doldol_server.doldol.common.request.CursorPageRequest; +import doldol_server.doldol.invite.dto.response.InviteResponse; + +import java.util.List; + +public interface InviteRepositoryCustom { + + List findInvitesByUserId(Long userId, CursorPageRequest request); +} + diff --git a/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustomImpl.java b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustomImpl.java new file mode 100644 index 0000000..c7c6277 --- /dev/null +++ b/src/main/java/doldol_server/doldol/invite/repository/custom/InviteRepositoryCustomImpl.java @@ -0,0 +1,42 @@ +package doldol_server.doldol.invite.repository.custom; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import doldol_server.doldol.common.request.CursorPageRequest; +import doldol_server.doldol.invite.dto.response.InviteResponse; +import doldol_server.doldol.invite.entity.Invite; +import doldol_server.doldol.invite.entity.QInvite; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +public class InviteRepositoryCustomImpl implements InviteRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findInvitesByUserId(Long userId, CursorPageRequest request) { + QInvite invite = QInvite.invite; + + BooleanExpression cursorCondition = null; + if (request.cursorId() != null) { + cursorCondition = invite.inviteId.lt(request.cursorId()); + } + + List invites = queryFactory + .selectFrom(invite) + .where( + invite.user.id.eq(userId), + cursorCondition + ) + .orderBy(invite.inviteId.desc()) + .limit(request.size() + 1L) + .fetch(); + + return invites.stream() + .map(InviteResponse::from) + .toList(); + } +} + diff --git a/src/main/java/doldol_server/doldol/invite/service/InviteService.java b/src/main/java/doldol_server/doldol/invite/service/InviteService.java index 0d307a6..0474a9b 100644 --- a/src/main/java/doldol_server/doldol/invite/service/InviteService.java +++ b/src/main/java/doldol_server/doldol/invite/service/InviteService.java @@ -1,6 +1,8 @@ package doldol_server.doldol.invite.service; +import doldol_server.doldol.common.dto.CursorPage; import doldol_server.doldol.common.exception.CustomException; +import doldol_server.doldol.common.request.CursorPageRequest; import doldol_server.doldol.invite.dto.request.InviteCommentCreateRequest; import doldol_server.doldol.invite.dto.request.InviteCreateRequest; import doldol_server.doldol.invite.dto.request.InviteUpdateRequest; @@ -52,15 +54,15 @@ public InviteResponse createInvite(InviteCreateRequest request, Long userId) { } @Transactional(readOnly = true) - public InviteResponse getInvite(Long inviteId) { - Invite invite = inviteRepository.findWithCommentsByInviteId(inviteId) + public InviteResponse getInvite(String inviteCode) { + Invite invite = inviteRepository.findWithCommentsByInviteCode(inviteCode) .orElseThrow(() -> new CustomException(InviteErrorCode.INVITE_NOT_FOUND)); return InviteResponse.from(invite); } @Transactional - public InviteCommentResponse addComment(Long inviteId, InviteCommentCreateRequest request) { - Invite invite = inviteRepository.findById(inviteId) + public InviteCommentResponse addComment(String inviteCode, InviteCommentCreateRequest request) { + Invite invite = inviteRepository.findByInviteCode(inviteCode) .orElseThrow(() -> new CustomException(InviteErrorCode.INVITE_NOT_FOUND)); InviteComment comment = InviteComment.builder() @@ -74,18 +76,18 @@ public InviteCommentResponse addComment(Long inviteId, InviteCommentCreateReques } @Transactional(readOnly = true) - public List getComments(Long inviteId) { - Invite invite = inviteRepository.findWithCommentsByInviteId(inviteId) + public CursorPage getComments(String inviteCode, CursorPageRequest request) { + // 초대장 존재 여부 확인 + inviteRepository.findByInviteCode(inviteCode) .orElseThrow(() -> new CustomException(InviteErrorCode.INVITE_NOT_FOUND)); - return invite.getComments() - .stream() - .map(InviteCommentResponse::from) - .toList(); + + List comments = inviteCommentRepository.findCommentsByInviteCode(inviteCode, request); + return CursorPage.of(comments, request.size(), InviteCommentResponse::getCommentId); } @Transactional - public void updateInvite(Long inviteId, InviteUpdateRequest request, Long userId) { - Invite invite = inviteRepository.findById(inviteId) + public void updateInvite(String inviteCode, InviteUpdateRequest request, Long userId) { + Invite invite = inviteRepository.findByInviteCode(inviteCode) .orElseThrow(() -> new CustomException(InviteErrorCode.INVITE_NOT_FOUND)); if (!invite.getUser().getId().equals(userId)) { @@ -105,8 +107,8 @@ public void updateInvite(Long inviteId, InviteUpdateRequest request, Long userId } @Transactional - public void deleteInvite(Long inviteId, Long userId) { - Invite invite = inviteRepository.findById(inviteId) + public void deleteInvite(String inviteCode, Long userId) { + Invite invite = inviteRepository.findByInviteCode(inviteCode) .orElseThrow(() -> new CustomException(InviteErrorCode.INVITE_NOT_FOUND)); if (!invite.getUser().getId().equals(userId)) { @@ -115,5 +117,11 @@ public void deleteInvite(Long inviteId, Long userId) { inviteRepository.delete(invite); } + + @Transactional(readOnly = true) + public CursorPage getMyInvites(Long userId, CursorPageRequest request) { + List invites = inviteRepository.findInvitesByUserId(userId, request); + return CursorPage.of(invites, request.size(), InviteResponse::getInviteId); + } }