From 695140711ab302d2bd14abdd9e922cf83f79ff59 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:26:33 +0900 Subject: [PATCH 01/22] =?UTF-8?q?[feat]=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20con?= =?UTF-8?q?troller=20=EA=B0=9C=EB=B0=9C=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomQueryController.java | 18 ++++++++++++----- .../in/web/response/RoomShowMineResponse.java | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java index d185ed994..6197041c7 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java @@ -2,11 +2,7 @@ import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.UserId; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; -import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; -import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; -import konkuk.thip.room.adapter.in.web.response.RoomGetMemberListResponse; -import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; +import konkuk.thip.room.adapter.in.web.response.*; import konkuk.thip.room.application.port.in.*; import konkuk.thip.room.application.port.in.RoomGetHomeJoinedListUseCase; import konkuk.thip.room.application.port.in.RoomGetMemberListUseCase; @@ -20,6 +16,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; + @RestController @RequiredArgsConstructor public class RoomQueryController { @@ -30,6 +28,7 @@ public class RoomQueryController { private final RoomShowRecruitingDetailViewUseCase roomShowRecruitingDetailViewUseCase; private final RoomGetMemberListUseCase roomGetMemberListUseCase; private final RoomShowPlayingDetailViewUseCase roomShowPlayingDetailViewUseCase; + private final RoomShowMineUseCase roomShowMineUseCase; @GetMapping("/rooms/search") public BaseResponse searchRooms( @@ -82,4 +81,13 @@ public BaseResponse getPlayingRoomDetailView( return BaseResponse.ok(roomShowPlayingDetailViewUseCase.getPlayingRoomDetailView(userId, roomId)); } + // 내 모임방 리스트 조회 + @GetMapping("/rooms/my") + public BaseResponse getMyRooms( + @UserId final Long userId, + @RequestParam(value = "type", required = false, defaultValue = "playingAndRecruiting") final String type, + @RequestParam(value = "cursorDate", required = false) final LocalDate cursorDate, + @RequestParam(value = "cursorId", required = false) final Long cursorId) { + return BaseResponse.ok(roomShowMineUseCase.getMyRooms(userId, type, cursorDate, cursorId)); + } } diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java new file mode 100644 index 000000000..86b046f4c --- /dev/null +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java @@ -0,0 +1,20 @@ +package konkuk.thip.room.adapter.in.web.response; + +import java.time.LocalDate; +import java.util.List; + +public record RoomShowMineResponse( + List roomList, + int size, // 현제 페이지에 포함된 데이터 수 + boolean last, + LocalDate nextCursorDate, // 다음 페이지 시작 커서의 endDate 값 + Long nextCursorId // 다음 페이지 시작 커서의 roomId 값 +) { + public record MyRoom( + Long roomId, + String bookImageUrl, + String roomName, + int memberCount, + String endDate // 방 진행 마감일 or 방 모집 마감일 (~ 뒤 형식) + ) {} +} From 89cb0698f3ee824f719c85373282a8957b734666 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:28:50 +0900 Subject: [PATCH 02/22] =?UTF-8?q?[feat]=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20use?= =?UTF-8?q?=20case=20=EA=B0=9C=EB=B0=9C=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/RoomShowMineUseCase.java | 10 ++++ .../service/RoomShowMineService.java | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java create mode 100644 src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java new file mode 100644 index 000000000..577f32362 --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java @@ -0,0 +1,10 @@ +package konkuk.thip.room.application.port.in; + +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; + +import java.time.LocalDate; + +public interface RoomShowMineUseCase { + + RoomShowMineResponse getMyRooms(Long userId, String type, LocalDate cursorDate, Long cursorId); +} diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java new file mode 100644 index 000000000..3c9c5bc8e --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java @@ -0,0 +1,54 @@ +package konkuk.thip.room.application.service; + +import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; +import konkuk.thip.room.application.port.in.RoomShowMineUseCase; +import konkuk.thip.room.application.port.out.RoomQueryPort; +import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.domain.MyRoomType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_MY_ROOM_CURSOR; + +@Service +@RequiredArgsConstructor +public class RoomShowMineService implements RoomShowMineUseCase { + + private final static int PAGE_SIZE = 10; + + private final RoomQueryPort roomQueryPort; + + @Override + @Transactional(readOnly = true) + public RoomShowMineResponse getMyRooms(Long userId, String type, LocalDate cursorDate, Long cursorId) { + // 1. cursor xor 연산 검증 + if (cursorDate == null ^ cursorId == null) { + throw new BusinessException(INVALID_MY_ROOM_CURSOR, new IllegalArgumentException("cursorDate, cursorId는 하나만 null 일 수 없습니다.")); + } + + // 2. type 검증 및 커서 기반 조회 + CursorSliceOfMyRoomView slice = switch (MyRoomType.from(type)) { + case RECRUITING -> roomQueryPort + .findRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + case PLAYING -> roomQueryPort + .findPlayingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + case PLAYING_AND_RECRUITING -> roomQueryPort + .findPlayingAndRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + case EXPIRED -> roomQueryPort + .findExpiredRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + }; + + // 3. return + return new RoomShowMineResponse( + slice.getContent(), + slice.getContent().size(), + slice.isLast(), + slice.getNextCursorDate(), + slice.getNextCursorId() + ); + } +} From ae89b3c024cab29c7581bfca7870ed0382579159 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:29:41 +0900 Subject: [PATCH 03/22] =?UTF-8?q?[feat]=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api?= =?UTF-8?q?=EC=9D=98=20request=20param=20=EA=B2=80=EC=A6=9D=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20enum=20=EB=8F=84=EC=9E=85=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/room/domain/MyRoomType.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/domain/MyRoomType.java diff --git a/src/main/java/konkuk/thip/room/domain/MyRoomType.java b/src/main/java/konkuk/thip/room/domain/MyRoomType.java new file mode 100644 index 000000000..7660c5491 --- /dev/null +++ b/src/main/java/konkuk/thip/room/domain/MyRoomType.java @@ -0,0 +1,28 @@ +package konkuk.thip.room.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.exception.code.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor +public enum MyRoomType { + PLAYING("playing"), + RECRUITING("recruiting"), + PLAYING_AND_RECRUITING("playingAndRecruiting"), + EXPIRED("expired"); + + private final String type; + + public static MyRoomType from(String type) { + return Arrays.stream(MyRoomType.values()) + .filter(param -> param.getType().equals(type)) + .findFirst() + .orElseThrow( + () -> new InvalidStateException(ErrorCode.INVALID_MY_ROOM_TYPE) + ); + } +} From 73cf3f674e7bdccbdd7e627a1c4fab71f75b04ef Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:29:53 +0900 Subject: [PATCH 04/22] =?UTF-8?q?[feat]=20error=20code=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index f39c53cb7..224197988 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -116,6 +116,8 @@ public enum ErrorCode implements ResponseCode { USER_ALREADY_PARTICIPATE(HttpStatus.BAD_REQUEST, 140005, "사용자가 이미 방에 참여한 상태입니다."), USER_NOT_PARTICIPATED(HttpStatus.BAD_REQUEST, 140006, "사용자가 방에 참여하지 않은 상태에서 취소하기는 불가합니다."), HOST_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 140007, "방장은 참여 취소를 할 수 없습니다."), + INVALID_MY_ROOM_TYPE(HttpStatus.BAD_REQUEST, 140008, "유저가 참가한 방 목록 검색 요청에 유효하지 않은 MY ROOM type 이 있습니다."), + INVALID_MY_ROOM_CURSOR(HttpStatus.BAD_REQUEST, 140009, "유저가 참가한 방 목록 검색 요청에 유효하지 않은 cursor 가 있습니다"), /** * 150000 : Category error From e36a8ef937b9956cf2a94f4473f61b804e3b4d98 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:30:35 +0900 Subject: [PATCH 05/22] =?UTF-8?q?[feat]=20next=20cursor=20=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20custom=20SliceImpl=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/out/dto/CursorSliceOfMyRoomView.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java diff --git a/src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java b/src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java new file mode 100644 index 000000000..1bc2af9cd --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java @@ -0,0 +1,22 @@ +package konkuk.thip.room.application.port.out.dto; + +import lombok.Getter; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.SliceImpl; + +import java.time.LocalDate; +import java.util.List; + +@Getter +public class CursorSliceOfMyRoomView extends SliceImpl { + + private final LocalDate nextCursorDate; + private final Long nextCursorId; + + public CursorSliceOfMyRoomView(List content, Pageable pageable, boolean hasNext, + LocalDate nextCursorDate, Long nextCursorId) { + super(content, pageable, hasNext); + this.nextCursorDate = nextCursorDate; + this.nextCursorId = nextCursorId; + } +} From 87b1861cba314960c5159a6bc24b2dc4b5ec5f54 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:33:09 +0900 Subject: [PATCH 06/22] =?UTF-8?q?[feat]=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EC=98=81=EC=86=8D=EC=84=B1=20adapter=20=EA=B5=AC=ED=98=84=20(#?= =?UTF-8?q?89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RoomQueryPersistenceAdapter.java | 23 +++++++++++++++++++ .../application/port/out/RoomQueryPort.java | 13 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index 72291caf6..c7d9109b7 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -3,9 +3,11 @@ import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; import konkuk.thip.room.adapter.out.mapper.RoomMapper; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; import konkuk.thip.room.application.port.out.RoomQueryPort; +import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; import konkuk.thip.room.domain.Room; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -41,4 +43,25 @@ public List findOtherRecruitingR public Page searchHomeJoinedRooms(Long userId, LocalDate date, Pageable pageable) { return roomJpaRepository.searchHomeJoinedRooms(userId, date, pageable); } + + @Override + public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastRoomStartDateCursor, Long lastRoomIdCursor, int pageSize) { + return roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastRoomStartDateCursor, lastRoomIdCursor, pageSize); + } + + @Override + public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize) { + return roomJpaRepository.findPlayingRoomsUserParticipated(userId, lastRoomEndDateCursor, lastRoomIdCursor, pageSize); + } + + @Override + public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long lastRoomIdCursor, int pageSize) { + return roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, dateCursor, lastRoomIdCursor, pageSize); + } + + @Override + public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize) { + return roomJpaRepository.findExpiredRoomsUserParticipated(userId, lastRoomEndDateCursor, lastRoomIdCursor, pageSize); + } + } diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index b5032e3de..7cca93023 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -3,6 +3,8 @@ import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; +import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; import konkuk.thip.room.domain.Room; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -11,9 +13,20 @@ import java.util.List; public interface RoomQueryPort { + int countRecruitingRoomsByBookAndStartDateAfter(Long bookId, LocalDate currentDate); + Page searchRoom(String keyword, String category, Pageable pageable); List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Room currentRoom, int count); + Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); + + CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastRoomStartDateCursor, Long lastRoomIdCursor, int pageSize); + + CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize); + + CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long lastRoomIdCursor, int pageSize); + + CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize); } From d8e5b1a28f184a39c75e7a9326285783555b7a69 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:34:39 +0900 Subject: [PATCH 07/22] =?UTF-8?q?[feat]=20QueryDSL=20+=20=EC=BB=A4?= =?UTF-8?q?=EC=84=9C=20=EA=B8=B0=EB=B0=98=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RoomQueryRepository.java | 11 + .../repository/RoomQueryRepositoryImpl.java | 214 ++++++++++++++++-- 2 files changed, 206 insertions(+), 19 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 7580ae787..301a4208f 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -3,6 +3,8 @@ import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; +import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,5 +17,14 @@ public interface RoomQueryRepository { Page searchRoom(String keyword, String category, Pageable pageable); List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count); + Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); + + CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + + CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + + CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + + CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index 84894eb6f..d23fb1b02 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -4,7 +4,9 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.DateExpression; import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPQLQuery; @@ -14,18 +16,20 @@ import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; import konkuk.thip.room.adapter.out.jpa.QRoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.QRoomParticipantJpaEntity; +import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.domain.MyRoomType; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Function; @Repository @RequiredArgsConstructor @@ -34,8 +38,7 @@ public class RoomQueryRepositoryImpl implements RoomQueryRepository { private final JPAQueryFactory queryFactory; private final QRoomJpaEntity room = QRoomJpaEntity.roomJpaEntity; private final QBookJpaEntity book = QBookJpaEntity.bookJpaEntity; - private final QRoomParticipantJpaEntity userRoom = QRoomParticipantJpaEntity.roomParticipantJpaEntity; - + private final QRoomParticipantJpaEntity participant = QRoomParticipantJpaEntity.roomParticipantJpaEntity; @Override public Page searchRoom(String keyword, String category, Pageable pageable) { @@ -58,7 +61,7 @@ public Page searchRoom(String keyword, Stri .when(room.title.containsIgnoreCase(keyword)).then(1) .otherwise(0); - NumberExpression memberCountExpr = userRoom.roomParticipantId.count(); // 방 별 멤버수 표현식 + NumberExpression memberCountExpr = participant.roomParticipantId.count(); // 방 별 멤버수 표현식 List tuples = queryFactory .select( @@ -72,7 +75,7 @@ public Page searchRoom(String keyword, Stri ) .from(room) .join(room.bookJpaEntity, book) - .leftJoin(userRoom).on(userRoom.roomJpaEntity.eq(room)) + .leftJoin(participant).on(participant.roomJpaEntity.eq(room)) .where(where) .groupBy( room.roomId, @@ -146,11 +149,11 @@ private OrderSpecifier toOrderSpecifier(Sort sort, QRoomJpaEntity room, Numbe @Override public List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count) { - NumberExpression memberCountExpr = userRoom.roomParticipantId.count(); + NumberExpression memberCountExpr = participant.roomParticipantId.count(); List tuples = queryFactory .select(room.roomId, room.title, memberCountExpr, room.recruitCount, room.startDate) .from(room) - .leftJoin(userRoom).on(userRoom.roomJpaEntity.eq(room)) + .leftJoin(participant).on(participant.roomJpaEntity.eq(room)) .where( room.categoryJpaEntity.value.eq(category) .and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각 @@ -181,7 +184,7 @@ public Page searchHomeJoinedRoom // 유저가 참여한 방만: userId 조건 // 활동 기간 중인 방만: startDate ≤ today ≤ endDate BooleanBuilder where = new BooleanBuilder(); - where.and(userRoom.userJpaEntity.userId.eq(userId)); + where.and(participant.userJpaEntity.userId.eq(userId)); where.and(room.startDate.loe(date)); where.and(room.endDate.goe(date)); @@ -202,14 +205,14 @@ public Page searchHomeJoinedRoom room.recruitCount, room.startDate, book.title, - userRoom.userPercentage + participant.userPercentage ) - .from(userRoom) - .join(userRoom.roomJpaEntity, room) + .from(participant) + .join(participant.roomJpaEntity, room) .join(room.bookJpaEntity, book) .where(where) .orderBy( - userRoom.userPercentage.desc(), // 진행률 높은 순(내림차순) + participant.userPercentage.desc(), // 진행률 높은 순(내림차순) room.startDate.asc() // 진행률 같으면 활동 시작일 빠른 순 (오름차순) ) .offset(pageable.getOffset()) @@ -224,7 +227,7 @@ public Page searchHomeJoinedRoom .bookImageUrl(t.get(book.imageUrl)) .bookTitle(t.get(book.title)) .memberCount(Optional.ofNullable(t.get(memberCountSubQuery)).map(Number::intValue).orElse(1)) - .userPercentage(Optional.ofNullable(t.get(userRoom.userPercentage)) + .userPercentage(Optional.ofNullable(t.get(participant.userPercentage)) .map(val -> ((Number) val).doubleValue()) .map(Math::round) .map(Long::intValue) @@ -235,9 +238,9 @@ public Page searchHomeJoinedRoom // 4. 전체 개수 조회 (페이징 정보 계산용) Long totalCount = queryFactory - .select(userRoom.count()) - .from(userRoom) - .join(userRoom.roomJpaEntity, room) + .select(participant.count()) + .from(participant) + .join(participant.roomJpaEntity, room) .where(where) .fetchOne(); long total = (totalCount != null) ? totalCount : 0L; @@ -245,4 +248,177 @@ public Page searchHomeJoinedRoom // 5. PageImpl 생성하여 반환 return new PageImpl<>(content, pageable, total); } + + // 1) 모집중인 방 + @Override + public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated( + Long userId, LocalDate lastDate, Long lastId, int pageSize + ) { + LocalDate today = LocalDate.now(); + BooleanExpression base = participant.userJpaEntity.userId.eq(userId) + .and(room.startDate.after(today)); // 유저가 참여한 방 && 모집중인 방 + DateExpression cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일) + OrderSpecifier[] orders = new OrderSpecifier[]{ + cursorExpr.asc(), room.roomId.asc() + }; + Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 + t.get(room.roomId), + t.get(book.imageUrl), + t.get(room.title), + t.get(room.memberCount), + DateUtil.formatAfterTime(t.get(cursorExpr)) + ); + return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + } + + // 2) 진행중인 방 + @Override + public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated( + Long userId, LocalDate lastDate, Long lastId, int pageSize + ) { + LocalDate today = LocalDate.now(); + BooleanExpression base = participant.userJpaEntity.userId.eq(userId) + .and(room.startDate.loe(today)) + .and(room.endDate.goe(today)); // 유저가 참여한 방 && 현재 진행중인 방 + DateExpression cursorExpr = room.endDate; // 커서 비교는 endDate(= 진행 마감일) + OrderSpecifier[] orders = new OrderSpecifier[]{ + cursorExpr.asc(), room.roomId.asc() + }; + Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 + t.get(room.roomId), + t.get(book.imageUrl), + t.get(room.title), + t.get(room.memberCount), + DateUtil.formatAfterTime(t.get(cursorExpr)) + ); + return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + } + + // 3) 진행+모집 통합 + @Override + public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated( + Long userId, LocalDate lastDate, Long lastId, int pageSize + ) { + LocalDate today = LocalDate.now(); + BooleanExpression playing = room.startDate.loe(today).and(room.endDate.goe(today)); + BooleanExpression recruiting = room.startDate.after(today); + BooleanExpression base = participant.userJpaEntity.userId.eq(userId) + .and( playing.or(recruiting) ); // 유저가 참여한 방 && 현재 진행중인 방 + 모집중인 방 + + // 진행중: cursor=endDate, 모집중: cursor=startDate + DateExpression cursorExpr = new CaseBuilder() + .when(playing).then(room.endDate) + .otherwise(room.startDate); + + // 진행중 먼저(0), 모집중 다음(1) -> 조회 우선순위 반영 + NumberExpression priority = new CaseBuilder() + .when(playing).then(0) + .otherwise(1); + + OrderSpecifier[] orders = new OrderSpecifier[]{ + priority.asc(), + cursorExpr.asc(), + room.roomId.asc() + }; + Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 + t.get(room.roomId), + t.get(book.imageUrl), + t.get(room.title), + t.get(room.memberCount), + DateUtil.formatAfterTime(t.get(cursorExpr)) + ); + return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + } + + // 4) 만료된 방 + @Override + public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated( + Long userId, LocalDate lastDate, Long lastId, int pageSize + ) { + LocalDate today = LocalDate.now(); + BooleanExpression base = participant.userJpaEntity.userId.eq(userId) + .and(room.endDate.before(today)); // 유저가 참여한 방 && 만료된 방 + + DateExpression cursorExpr = room.endDate; + OrderSpecifier[] orders = new OrderSpecifier[]{ + cursorExpr.desc(), room.roomId.desc() // 만료된 방은 가장 최근에 만료된 방부터 반환 + }; + Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 + t.get(room.roomId), + t.get(book.imageUrl), + t.get(room.title), + t.get(room.memberCount), + null // 만료된 방은 endDate=null + ); + return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, false, orders); + } + + /** + * 공통 커서+페이징 처리 + */ + private CursorSliceOfMyRoomView sliceQuery( + BooleanExpression baseCondition, + DateExpression cursorDateExpr, + Function mapper, + LocalDate lastCursorDate, + Long lastCursorId, + int pageSize, + boolean ascending, + OrderSpecifier... orderSpecs + ) { + BooleanBuilder where = new BooleanBuilder(baseCondition); // baseCondition + 커서 기반으로 where 절 구성 + if (lastCursorDate != null && lastCursorId != null) { + if (ascending) { // 진행중, 모집중, 통합 + where.and( + cursorDateExpr.gt(lastCursorDate) // lastCursorDate 보다 크거나 + .or( + cursorDateExpr.eq(lastCursorDate) + .and(room.roomId.goe(lastCursorId)) // 같으면 id가 lastCursorId 보다 크거나 같은 것 + ) + ); + } else { // 내림차순일 때는 반대로 (만료된 방) + where.and( + cursorDateExpr.lt(lastCursorDate) // lastCursorDate 보다 작거나 + .or( + cursorDateExpr.eq(lastCursorDate) + .and(room.roomId.loe(lastCursorId)) // 같으면 id가 lastCursorId 보다 작거나 같은 것 + ) + ); + } + } + + int fetchSize = pageSize + 1; + List tuples = queryFactory + .select(room.roomId, book.imageUrl, room.title, room.memberCount, cursorDateExpr) + .from(participant) + .join(participant.roomJpaEntity, room) + .join(room.bookJpaEntity, book) + .where(where) + .orderBy(orderSpecs) + .limit(fetchSize) + .fetch(); // 직접 tuple 결과를 조회하므로 lazy 로딩 적용 대상 X + + boolean hasNext = tuples.size() == fetchSize; + List content = tuples.stream() + .limit(pageSize) + .map(mapper) + .toList(); // pageSize 만큼만 dto로 매핑 + + // 커서 값 세팅 + LocalDate nextDate = null; + Long nextId = null; + if (hasNext) { + Tuple next = tuples.get(pageSize); + nextDate = next.get(cursorDateExpr); + nextId = next.get(room.roomId); + } + + return new CursorSliceOfMyRoomView<>( + content, + PageRequest.of(0, pageSize), + hasNext, + nextDate, + nextId + ); + } } From a767a9e54e858f8a2bc62e2807c260a78a1f2033 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:34:56 +0900 Subject: [PATCH 08/22] =?UTF-8?q?[test]=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomShowMineApiTest.java | 542 ++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java new file mode 100644 index 000000000..da68ee426 --- /dev/null +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java @@ -0,0 +1,542 @@ +package konkuk.thip.room.adapter.in.web; + +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import java.time.LocalDate; + +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_MY_ROOM_CURSOR; +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_MY_ROOM_TYPE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 내 방 목록 조회 api 통합 테스트") +class RoomShowMineApiTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private CategoryJpaRepository categoryJpaRepository; + + @Autowired + private BookJpaRepository bookJpaRepository; + + @Autowired + private RoomJpaRepository roomJpaRepository; + + @Autowired + private RoomParticipantJpaRepository roomParticipantJpaRepository; + + @AfterEach + void tearDown() { + roomParticipantJpaRepository.deleteAllInBatch(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + categoryJpaRepository.deleteAllInBatch(); + aliasJpaRepository.deleteAllInBatch(); + } + + private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, LocalDate endDate, int recruitCount) { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + + BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() + .title(bookTitle) + .isbn(isbn) + .authorName("한강") + .bestSeller(false) + .publisher("문학동네") + .imageUrl("https://image1.jpg") + .pageCount(300) + .description("한강의 소설") + .build()); + + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias)); + + return roomJpaRepository.save(RoomJpaEntity.builder() + .title(roomName) + .description("한강 작품 읽기 모임") + .isPublic(true) + .roomPercentage(0.0) + .startDate(startDate) + .endDate(endDate) + .recruitCount(recruitCount) + .bookJpaEntity(book) + .categoryJpaEntity(category) + .build()); + } + + private void changeRoomMemberCount(RoomJpaEntity roomJpaEntity, int count) { + roomJpaEntity.updateMemberCount(count); + roomJpaRepository.save(roomJpaEntity); + } + + private void saveSingleUserToRoom(RoomJpaEntity roomJpaEntity, UserJpaEntity userJpaEntity) { + RoomParticipantJpaEntity roomParticipantJpaEntity = RoomParticipantJpaEntity.builder() + .userJpaEntity(userJpaEntity) + .roomJpaEntity(roomJpaEntity) + .roomParticipantRole(RoomParticipantRole.MEMBER) + .build(); + roomParticipantJpaRepository.save(roomParticipantJpaEntity); + + roomJpaEntity.updateMemberCount(roomJpaEntity.getMemberCount() + 1); + roomJpaRepository.save(roomJpaEntity); // room의 memberCount 값도 업데이트 해줘야 한다 + } + + @Test + @DisplayName("type 으로 playing 을 받을 경우, 해당 유저가 참여중인 방 중 [현재 진행중인 모임방]의 정보를 [활동 마감일 임박순] 으로 반환한다.") + void get_my_playing_rooms() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn2", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity playingRoom2 = saveScienceRoom("진행중인방-책-2", "isbn3", "과학-방-10일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(10), 10); + changeRoomMemberCount(playingRoom2, 3); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(playingRoom2, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "playing")); // 진행중인 방 + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-5일뒤-활동마감"))) + .andExpect(jsonPath("$.data.roomList[0].memberCount", is(7))) // 기존 6명 + user + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-10일뒤-활동마감"))) + .andExpect(jsonPath("$.data.roomList[1].memberCount", is(4))); // 기존 3명 + user + } + + @Test + @DisplayName("type 으로 recruiting 을 받을 경우, 해당 유저가 참여중인 방 중 [모집중인 모임방]의 정보를 [모집 마감일 임박순] 으로 반환한다.") + void get_my_recruiting_rooms() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "recruiting")); // 모집중인 방 + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[0].memberCount", is(6))) // 기존 5명 + user + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-5일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].memberCount", is(9))); // 기존 8명 + user + } + + @Test + @DisplayName("type 이 주어지지 않을 경우, 해당 유저가 참여중인 방 중 [현재 진행중 + 모집중인 모임방]의 정보를 [현재 진행중 -> 모집중] 순 으로 반환한다.") + void get_my_playing_and_recruiting_rooms() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId())); // type request param 없는 경우 + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(3))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-5일뒤-활동마감"))) + .andExpect(jsonPath("$.data.roomList[0].memberCount", is(7))) // 기존 6명 + user + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].memberCount", is(6))) // 기존 5명 + user + .andExpect(jsonPath("$.data.roomList[2].roomName", is("과학-방-5일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[2].memberCount", is(9))); // 기존 8명 + user + } + + @Test + @DisplayName("type 으로 expired 을 받을 경우, 해당 유저가 참여중인 방 중 [만료된 모임방]의 정보를 [활동 마감일 최신순] 으로 반환한다.") + void get_my_expired_rooms() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn2", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn3", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + RoomJpaEntity expiredRoom2 = saveScienceRoom("만료된방-책-2", "isbn4", "과학-방-10일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(10), 10); + changeRoomMemberCount(expiredRoom2, 1); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + saveSingleUserToRoom(expiredRoom2, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "expired")); // 만료된 방 + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-5일전-활동마감"))) + .andExpect(jsonPath("$.data.roomList[0].memberCount", is(8))) // 기존 7명 + user + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-10일전-활동마감"))) + .andExpect(jsonPath("$.data.roomList[1].memberCount", is(2))); // 기존 1명 + user + } + + @Test + @DisplayName("유효하지 않은 type 을 받을 경우, 400 error 를 반환한다.") + void get_my_rooms_wrong_type() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "wrongType")); // 이상한 type request param + + //then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess", is(false))) + .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_TYPE.getCode()))); + } + + @Test + @DisplayName("cursorDate 만 받고 cursorId 는 받지 않은 경우, 400 error를 반환한다.") + void get_my_rooms_only_cursor_date_exist() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "playing") + .param("cursorDate", String.valueOf(LocalDate.now()))); // cursor 중 cursorDate 만 request param 으로 넘어온 경우 + + //then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess", is(false))) + .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_CURSOR.getCode()))); + } + + @Test + @DisplayName("cursorId 만 받고 cursorDate 는 받지 않은 경우, 400 error를 반환한다.") + void get_my_rooms_only_cursor_id_exist() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); + changeRoomMemberCount(playingRoom1, 6); + + RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); + changeRoomMemberCount(expiredRoom1, 7); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(playingRoom1, user); + saveSingleUserToRoom(expiredRoom1, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "playing") + .param("cursorId", String.valueOf(10L))); // cursor 중 cursorId 만 request param 으로 넘어온 경우 + + //then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess", is(false))) + .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_CURSOR.getCode()))); + } + + @Test + @DisplayName("한번에 최대 10개의 데이터만을 반환한다. 다음 페이지에 해당하는 데이터가 있을 경우, 다음 페이지의 cursor 값을 반환한다.") + void get_my_rooms_page_1() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-2일뒤-활동시작", LocalDate.now().plusDays(2), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity recruitingRoom3 = saveScienceRoom("모집중인방-책-3", "isbn3", "과학-방-3일뒤-활동시작", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom3, 8); + + RoomJpaEntity recruitingRoom4 = saveScienceRoom("모집중인방-책-4", "isbn4", "과학-방-4일뒤-활동시작", LocalDate.now().plusDays(4), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom4, 8); + + RoomJpaEntity recruitingRoom5 = saveScienceRoom("모집중인방-책-5", "isbn5", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom5, 8); + + RoomJpaEntity recruitingRoom6 = saveScienceRoom("모집중인방-책-6", "isbn6", "과학-방-6일뒤-활동시작", LocalDate.now().plusDays(6), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom6, 8); + + RoomJpaEntity recruitingRoom7 = saveScienceRoom("모집중인방-책-7", "isbn7", "과학-방-7일뒤-활동시작", LocalDate.now().plusDays(7), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom7, 8); + + RoomJpaEntity recruitingRoom8 = saveScienceRoom("모집중인방-책-8", "isbn8", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom8, 8); + + RoomJpaEntity recruitingRoom9 = saveScienceRoom("모집중인방-책-9", "isbn9", "과학-방-9일뒤-활동시작", LocalDate.now().plusDays(9), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom9, 8); + + RoomJpaEntity recruitingRoom10 = saveScienceRoom("모집중인방-책-10", "isbn10", "과학-방-10일뒤-활동시작", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom10, 8); + + RoomJpaEntity recruitingRoom11 = saveScienceRoom("모집중인방-책-11", "isbn11", "과학-방-11일뒤-활동시작", LocalDate.now().plusDays(11), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom11, 8); + + RoomJpaEntity recruitingRoom12 = saveScienceRoom("모집중인방-책-12", "isbn12", "과학-방-12일뒤-활동시작", LocalDate.now().plusDays(12), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom12, 8); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(recruitingRoom3, user); + saveSingleUserToRoom(recruitingRoom4, user); + saveSingleUserToRoom(recruitingRoom5, user); + saveSingleUserToRoom(recruitingRoom6, user); + saveSingleUserToRoom(recruitingRoom7, user); + saveSingleUserToRoom(recruitingRoom8, user); + saveSingleUserToRoom(recruitingRoom9, user); + saveSingleUserToRoom(recruitingRoom10, user); + saveSingleUserToRoom(recruitingRoom11, user); + saveSingleUserToRoom(recruitingRoom12, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "recruiting")); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.size", is(10))) + .andExpect(jsonPath("$.data.last", is(false))) + .andExpect(jsonPath("$.data.nextCursorDate", is(recruitingRoom11.getStartDate().toString()))) // 11번째 데이터의 startDate가 nextCursorDate 이다 + .andExpect(jsonPath("$.data.nextCursorId", is(recruitingRoom11.getRoomId().intValue()))) // 11번째 데이터의 id가 nextCursorId 이다 + .andExpect(jsonPath("$.data.roomList", hasSize(10))) + // 정렬 조건 : 모집중인 방 == 방 활동 시작일 임박 순 + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-2일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[2].roomName", is("과학-방-3일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[3].roomName", is("과학-방-4일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[4].roomName", is("과학-방-5일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[5].roomName", is("과학-방-6일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[6].roomName", is("과학-방-7일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[7].roomName", is("과학-방-8일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[8].roomName", is("과학-방-9일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[9].roomName", is("과학-방-10일뒤-활동시작"))); + } + + @Test + @DisplayName("cursor 값을 기준으로 해당 페이지의 데이터를 반환한다.") + void get_my_rooms_page_2() throws Exception { + //given + RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom1, 5); + + RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-2일뒤-활동시작", LocalDate.now().plusDays(2), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom2, 8); + + RoomJpaEntity recruitingRoom3 = saveScienceRoom("모집중인방-책-3", "isbn3", "과학-방-3일뒤-활동시작", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom3, 8); + + RoomJpaEntity recruitingRoom4 = saveScienceRoom("모집중인방-책-4", "isbn4", "과학-방-4일뒤-활동시작", LocalDate.now().plusDays(4), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom4, 8); + + RoomJpaEntity recruitingRoom5 = saveScienceRoom("모집중인방-책-5", "isbn5", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom5, 8); + + RoomJpaEntity recruitingRoom6 = saveScienceRoom("모집중인방-책-6", "isbn6", "과학-방-6일뒤-활동시작", LocalDate.now().plusDays(6), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom6, 8); + + RoomJpaEntity recruitingRoom7 = saveScienceRoom("모집중인방-책-7", "isbn7", "과학-방-7일뒤-활동시작", LocalDate.now().plusDays(7), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom7, 8); + + RoomJpaEntity recruitingRoom8 = saveScienceRoom("모집중인방-책-8", "isbn8", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom8, 8); + + RoomJpaEntity recruitingRoom9 = saveScienceRoom("모집중인방-책-9", "isbn9", "과학-방-9일뒤-활동시작", LocalDate.now().plusDays(9), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom9, 8); + + RoomJpaEntity recruitingRoom10 = saveScienceRoom("모집중인방-책-10", "isbn10", "과학-방-10일뒤-활동시작", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom10, 8); + + RoomJpaEntity recruitingRoom11 = saveScienceRoom("모집중인방-책-11", "isbn11", "과학-방-11일뒤-활동시작", LocalDate.now().plusDays(11), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom11, 8); + + RoomJpaEntity recruitingRoom12 = saveScienceRoom("모집중인방-책-12", "isbn12", "과학-방-12일뒤-활동시작", LocalDate.now().plusDays(12), LocalDate.now().plusDays(30), 10); + changeRoomMemberCount(recruitingRoom12, 8); + + AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); + + // user가 생성한 방에 참여한 상황 가정 + saveSingleUserToRoom(recruitingRoom1, user); + saveSingleUserToRoom(recruitingRoom2, user); + saveSingleUserToRoom(recruitingRoom3, user); + saveSingleUserToRoom(recruitingRoom4, user); + saveSingleUserToRoom(recruitingRoom5, user); + saveSingleUserToRoom(recruitingRoom6, user); + saveSingleUserToRoom(recruitingRoom7, user); + saveSingleUserToRoom(recruitingRoom8, user); + saveSingleUserToRoom(recruitingRoom9, user); + saveSingleUserToRoom(recruitingRoom10, user); + saveSingleUserToRoom(recruitingRoom11, user); + saveSingleUserToRoom(recruitingRoom12, user); + + //when + ResultActions result = mockMvc.perform(get("/rooms/my") + .requestAttr("userId", user.getUserId()) + .param("type", "recruiting") + .param("cursorDate", recruitingRoom11.getStartDate().toString()) + .param("cursorId", recruitingRoom11.getRoomId().toString())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.size", is(2))) + .andExpect(jsonPath("$.data.last", is(true))) + .andExpect(jsonPath("$.data.nextCursorDate", nullValue())) // 다음 페이지가 없으므로 cursor 값은 null + .andExpect(jsonPath("$.data.nextCursorId", nullValue())) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-11일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-12일뒤-활동시작"))); + } +} From 21d31ca2ef2570b7f3bfa41ff148c6921d8d1464 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 16:47:09 +0900 Subject: [PATCH 09/22] =?UTF-8?q?[fix]=20develop=20merge=20=ED=9B=84=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트를 위해 RoomJpaEntity 에 updateMemberCount 메서드 추가 --- .../konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java b/src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java index 8c7128b06..11f418b28 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java +++ b/src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java @@ -69,4 +69,9 @@ public RoomJpaEntity updateFrom(Room room) { this.memberCount = room.getMemberCount(); return this; } -} \ No newline at end of file + + // 테스트 메서드 편의용 + public void updateMemberCount(int memberCount) { + this.memberCount = memberCount; + } +} From 5376d0e632c7427a60d629a74e028995409454bb Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 21 Jul 2025 17:17:37 +0900 Subject: [PATCH 10/22] =?UTF-8?q?[refactor]=20=EC=BB=A4=EC=84=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=B3=80=EC=88=98=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 클라이언트와 주고받는 커서는 현재 조회할 페이지의 첫번째 레코드의 정보를 의미하므로, 변수 네이밍도 의미를 잘 나타내도록 수정 --- .../RoomQueryPersistenceAdapter.java | 16 ++++----- .../repository/RoomQueryRepository.java | 8 ++--- .../repository/RoomQueryRepositoryImpl.java | 36 +++++++++---------- .../application/port/out/RoomQueryPort.java | 8 ++--- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index c7d9109b7..521bb911b 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -45,23 +45,23 @@ public Page searchHomeJoinedRoom } @Override - public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastRoomStartDateCursor, Long lastRoomIdCursor, int pageSize) { - return roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastRoomStartDateCursor, lastRoomIdCursor, pageSize); + public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { + return roomJpaRepository.findRecruitingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); } @Override - public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize) { - return roomJpaRepository.findPlayingRoomsUserParticipated(userId, lastRoomEndDateCursor, lastRoomIdCursor, pageSize); + public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { + return roomJpaRepository.findPlayingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); } @Override - public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long lastRoomIdCursor, int pageSize) { - return roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, dateCursor, lastRoomIdCursor, pageSize); + public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { + return roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); } @Override - public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize) { - return roomJpaRepository.findExpiredRoomsUserParticipated(userId, lastRoomEndDateCursor, lastRoomIdCursor, pageSize); + public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { + return roomJpaRepository.findExpiredRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); } } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 301a4208f..6b38495c0 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -20,11 +20,11 @@ public interface RoomQueryRepository { Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); - CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastDate, Long lastId, int pageSize); + CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index d23fb1b02..fe5893643 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -252,7 +252,7 @@ public Page searchHomeJoinedRoom // 1) 모집중인 방 @Override public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated( - Long userId, LocalDate lastDate, Long lastId, int pageSize + Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) @@ -268,13 +268,13 @@ public CursorSliceOfMyRoomView findRecruitingRoomsU t.get(room.memberCount), DateUtil.formatAfterTime(t.get(cursorExpr)) ); - return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } // 2) 진행중인 방 @Override public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated( - Long userId, LocalDate lastDate, Long lastId, int pageSize + Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) @@ -291,13 +291,13 @@ public CursorSliceOfMyRoomView findPlayingRoomsUser t.get(room.memberCount), DateUtil.formatAfterTime(t.get(cursorExpr)) ); - return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } // 3) 진행+모집 통합 @Override public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated( - Long userId, LocalDate lastDate, Long lastId, int pageSize + Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression playing = room.startDate.loe(today).and(room.endDate.goe(today)); @@ -327,13 +327,13 @@ public CursorSliceOfMyRoomView findPlayingAndRecrui t.get(room.memberCount), DateUtil.formatAfterTime(t.get(cursorExpr)) ); - return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, true, orders); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } // 4) 만료된 방 @Override public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated( - Long userId, LocalDate lastDate, Long lastId, int pageSize + Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) @@ -350,7 +350,7 @@ public CursorSliceOfMyRoomView findExpiredRoomsUser t.get(room.memberCount), null // 만료된 방은 endDate=null ); - return sliceQuery(base, cursorExpr, mapper, lastDate, lastId, pageSize, false, orders); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, false, orders); } /** @@ -360,28 +360,28 @@ private CursorSliceOfMyRoomView sliceQuery( BooleanExpression baseCondition, DateExpression cursorDateExpr, Function mapper, - LocalDate lastCursorDate, - Long lastCursorId, + LocalDate dateCursor, + Long roomIdCursor, int pageSize, boolean ascending, OrderSpecifier... orderSpecs ) { BooleanBuilder where = new BooleanBuilder(baseCondition); // baseCondition + 커서 기반으로 where 절 구성 - if (lastCursorDate != null && lastCursorId != null) { + if (dateCursor != null && roomIdCursor != null) { if (ascending) { // 진행중, 모집중, 통합 where.and( - cursorDateExpr.gt(lastCursorDate) // lastCursorDate 보다 크거나 + cursorDateExpr.gt(dateCursor) // dateCursor 보다 크거나 .or( - cursorDateExpr.eq(lastCursorDate) - .and(room.roomId.goe(lastCursorId)) // 같으면 id가 lastCursorId 보다 크거나 같은 것 + cursorDateExpr.eq(dateCursor) + .and(room.roomId.goe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 크거나 같은 것 ) ); } else { // 내림차순일 때는 반대로 (만료된 방) where.and( - cursorDateExpr.lt(lastCursorDate) // lastCursorDate 보다 작거나 + cursorDateExpr.lt(dateCursor) // dateCursor 보다 작거나 .or( - cursorDateExpr.eq(lastCursorDate) - .and(room.roomId.loe(lastCursorId)) // 같으면 id가 lastCursorId 보다 작거나 같은 것 + cursorDateExpr.eq(dateCursor) + .and(room.roomId.loe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 작거나 같은 것 ) ); } @@ -408,7 +408,7 @@ private CursorSliceOfMyRoomView sliceQuery( LocalDate nextDate = null; Long nextId = null; if (hasNext) { - Tuple next = tuples.get(pageSize); + Tuple next = tuples.get(pageSize); // 다음 페이지의 첫번째 레코드 nextDate = next.get(cursorDateExpr); nextId = next.get(room.roomId); } diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index 7cca93023..52bf05d36 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -22,11 +22,11 @@ public interface RoomQueryPort { Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); - CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate lastRoomStartDateCursor, Long lastRoomIdCursor, int pageSize); + CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize); + CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long lastRoomIdCursor, int pageSize); + CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate lastRoomEndDateCursor, Long lastRoomIdCursor, int pageSize); + CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); } From a65a1ea27c4d2dfdf067a70a15f9f179670ee032 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Tue, 22 Jul 2025 23:44:46 +0900 Subject: [PATCH 11/22] =?UTF-8?q?[refactor]=20RoomQueryRepositoryImpl=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A4=91=EB=B3=B5=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20private=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RoomQueryRepositoryImpl.java | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index fe5893643..05cdfc549 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -261,13 +261,8 @@ public CursorSliceOfMyRoomView findRecruitingRoomsU OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() }; - Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 - t.get(room.roomId), - t.get(book.imageUrl), - t.get(room.title), - t.get(room.memberCount), - DateUtil.formatAfterTime(t.get(cursorExpr)) - ); + Function mapper = createMyRoomMapper(cursorExpr, true); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } @@ -284,13 +279,8 @@ public CursorSliceOfMyRoomView findPlayingRoomsUser OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() }; - Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 - t.get(room.roomId), - t.get(book.imageUrl), - t.get(room.title), - t.get(room.memberCount), - DateUtil.formatAfterTime(t.get(cursorExpr)) - ); + Function mapper = createMyRoomMapper(cursorExpr, true); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } @@ -320,13 +310,8 @@ public CursorSliceOfMyRoomView findPlayingAndRecrui cursorExpr.asc(), room.roomId.asc() }; - Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 - t.get(room.roomId), - t.get(book.imageUrl), - t.get(room.title), - t.get(room.memberCount), - DateUtil.formatAfterTime(t.get(cursorExpr)) - ); + Function mapper = createMyRoomMapper(cursorExpr, true); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); } @@ -343,16 +328,26 @@ public CursorSliceOfMyRoomView findExpiredRoomsUser OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.desc(), room.roomId.desc() // 만료된 방은 가장 최근에 만료된 방부터 반환 }; - Function mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 - t.get(room.roomId), - t.get(book.imageUrl), - t.get(room.title), - t.get(room.memberCount), - null // 만료된 방은 endDate=null - ); + Function mapper = createMyRoomMapper(cursorExpr, false); + return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, false, orders); } + /** + * t 에서 RoomShowMineResponse.MyRoom 으로 매핑하는 함수 생성 + * @param cursorExpr 커서 기준 날짜 표현식 + * @param formatCursor true 면 DateUtil.formatAfterTime, false 면 null 반환 + */ + private Function createMyRoomMapper(DateExpression cursorExpr, boolean formatCursor) { + return t -> new RoomShowMineResponse.MyRoom( + t.get(room.roomId), + t.get(book.imageUrl), + t.get(room.title), + t.get(room.memberCount), + formatCursor ? DateUtil.formatAfterTime(t.get(cursorExpr)) : null + ); + } + /** * 공통 커서+페이징 처리 */ From 1f35fde4dae44f1226bd342604b1012fcb9dc9b8 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:20:11 +0900 Subject: [PATCH 12/22] =?UTF-8?q?[refactor]=20controller=20request=20param?= =?UTF-8?q?=EC=9D=98=20cursor=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - String 타입의 cursor 로 수정 (내부에서 파싱) --- .../konkuk/thip/room/adapter/in/web/RoomQueryController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java index 6197041c7..dd36b6f7e 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java @@ -86,8 +86,7 @@ public BaseResponse getPlayingRoomDetailView( public BaseResponse getMyRooms( @UserId final Long userId, @RequestParam(value = "type", required = false, defaultValue = "playingAndRecruiting") final String type, - @RequestParam(value = "cursorDate", required = false) final LocalDate cursorDate, - @RequestParam(value = "cursorId", required = false) final Long cursorId) { - return BaseResponse.ok(roomShowMineUseCase.getMyRooms(userId, type, cursorDate, cursorId)); + @RequestParam(value = "cursor", required = false) final String cursor) { + return BaseResponse.ok(roomShowMineUseCase.getMyRooms(userId, type, cursor)); } } From 4852a131b42a44d1cb5506d729d80c211a5cb3c3 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:20:45 +0900 Subject: [PATCH 13/22] =?UTF-8?q?[refactor]=20response=20dto=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정된 페이징 처리 데이터 반영 --- .../room/adapter/in/web/response/RoomShowMineResponse.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java index 86b046f4c..110292c61 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java @@ -1,14 +1,11 @@ package konkuk.thip.room.adapter.in.web.response; -import java.time.LocalDate; import java.util.List; public record RoomShowMineResponse( List roomList, - int size, // 현제 페이지에 포함된 데이터 수 - boolean last, - LocalDate nextCursorDate, // 다음 페이지 시작 커서의 endDate 값 - Long nextCursorId // 다음 페이지 시작 커서의 roomId 값 + String nextCursor, + boolean isLast ) { public record MyRoom( Long roomId, From d3f4f939ccc677724089580efe74bc08d6e160a1 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:25:48 +0900 Subject: [PATCH 14/22] =?UTF-8?q?[refactor]=20usecase=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - String cursor 를 주입받도록 수정 --- .../thip/room/application/port/in/RoomShowMineUseCase.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java index 577f32362..4a7bbffed 100644 --- a/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java +++ b/src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java @@ -2,9 +2,7 @@ import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; -import java.time.LocalDate; - public interface RoomShowMineUseCase { - RoomShowMineResponse getMyRooms(Long userId, String type, LocalDate cursorDate, Long cursorId); + RoomShowMineResponse getMyRooms(Long userId, String type, String cursor); } From 65f8c9c68f1136bfd58f8e45c3fa0b1c363a0d92 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:26:16 +0900 Subject: [PATCH 15/22] =?UTF-8?q?[refactor]=20"=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=9C=20=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C"=20service=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RoomShowMineService.java | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java index 3c9c5bc8e..398a34fe1 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java @@ -1,18 +1,18 @@ package konkuk.thip.room.application.service; -import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.common.util.Cursor; +import konkuk.thip.common.util.CursorBasedList; import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; +import konkuk.thip.room.application.mapper.RoomQueryMapper; import konkuk.thip.room.application.port.in.RoomShowMineUseCase; import konkuk.thip.room.application.port.out.RoomQueryPort; -import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; import konkuk.thip.room.domain.MyRoomType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; - -import static konkuk.thip.common.exception.code.ErrorCode.INVALID_MY_ROOM_CURSOR; +import java.util.List; @Service @RequiredArgsConstructor @@ -21,34 +21,49 @@ public class RoomShowMineService implements RoomShowMineUseCase { private final static int PAGE_SIZE = 10; private final RoomQueryPort roomQueryPort; + private final RoomQueryMapper roomQueryMapper; @Override @Transactional(readOnly = true) - public RoomShowMineResponse getMyRooms(Long userId, String type, LocalDate cursorDate, Long cursorId) { - // 1. cursor xor 연산 검증 - if (cursorDate == null ^ cursorId == null) { - throw new BusinessException(INVALID_MY_ROOM_CURSOR, new IllegalArgumentException("cursorDate, cursorId는 하나만 null 일 수 없습니다.")); - } + public RoomShowMineResponse getMyRooms(Long userId, String type, String cursor) { + // 1. cursor 생성 + Cursor nextCursor = Cursor.from(cursor, PAGE_SIZE); // 2. type 검증 및 커서 기반 조회 - CursorSliceOfMyRoomView slice = switch (MyRoomType.from(type)) { + MyRoomType myRoomType = MyRoomType.from(type); + CursorBasedList result = switch (myRoomType) { case RECRUITING -> roomQueryPort - .findRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + .findRecruitingRoomsUserParticipated(userId, nextCursor); case PLAYING -> roomQueryPort - .findPlayingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + .findPlayingRoomsUserParticipated(userId, nextCursor); case PLAYING_AND_RECRUITING -> roomQueryPort - .findPlayingAndRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + .findPlayingAndRecruitingRoomsUserParticipated(userId, nextCursor); case EXPIRED -> roomQueryPort - .findExpiredRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); + .findExpiredRoomsUserParticipated(userId, nextCursor); }; - // 3. return + // 3. dto -> response로 매핑 (EXPIRED 타입인 경우 endDate를 null로 처리) + boolean isExpiredType = myRoomType == MyRoomType.EXPIRED; + List myRooms = result.contents().stream() + .map(dto -> { + RoomShowMineResponse.MyRoom r = roomQueryMapper.toShowMyRoomResponse(dto); + if (isExpiredType) { + return new RoomShowMineResponse.MyRoom( + r.roomId(), + r.bookImageUrl(), + r.roomName(), + r.memberCount(), + null + ); + } + return r; + }) + .toList(); + return new RoomShowMineResponse( - slice.getContent(), - slice.getContent().size(), - slice.isLast(), - slice.getNextCursorDate(), - slice.getNextCursorId() + myRooms, + result.nextCursor(), + !result.hasNext() ); } } From 7b7dd91e6884ad6da896988bc0e5a041aba7d5d7 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:26:37 +0900 Subject: [PATCH 16/22] =?UTF-8?q?[refactor]=20=EC=A1=B0=ED=9A=8C=EC=9A=A9?= =?UTF-8?q?=20dto=20=EC=84=A0=EC=96=B8=20=EB=B0=8F=20dto=20->=20response?= =?UTF-8?q?=20=EC=9D=98=20mapper=20=EC=84=A0=EC=96=B8=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/RoomQueryMapper.java | 22 +++++++++++++++++++ .../port/out/dto/RoomShowMineQueryDto.java | 18 +++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java create mode 100644 src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java diff --git a/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java b/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java new file mode 100644 index 000000000..0c80e94a8 --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java @@ -0,0 +1,22 @@ +package konkuk.thip.room.application.mapper; + +import konkuk.thip.common.util.DateUtil; +import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( + componentModel = "spring", + imports = DateUtil.class, + unmappedTargetPolicy = ReportingPolicy.IGNORE // 명시적으로 매핑하지 않은 필드를 무시하도록 설정 +) +public interface RoomQueryMapper { + + @Mapping( + target = "endDate", + expression = "java(DateUtil.formatAfterTime(dto.endDate()))" + ) + RoomShowMineResponse.MyRoom toShowMyRoomResponse(RoomShowMineQueryDto dto); +} diff --git a/src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java b/src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java new file mode 100644 index 000000000..3d9bf21be --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java @@ -0,0 +1,18 @@ +package konkuk.thip.room.application.port.out.dto; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Builder; + +import java.time.LocalDate; + +@Builder +public record RoomShowMineQueryDto( + Long roomId, + String bookImageUrl, + String roomName, + int memberCount, + LocalDate endDate // 방 진행 마감일 or 방 모집 마감일 +) { + @QueryProjection + public RoomShowMineQueryDto {} +} From 2d934fff35829afb629df5edc6073b8a436fe2b9 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:27:01 +0900 Subject: [PATCH 17/22] =?UTF-8?q?[refactor]=20CursorBasedList,=20Cursor=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20room=20query=20adapter?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/util/Cursor.java | 5 ++ .../RoomQueryPersistenceAdapter.java | 69 ++++++++++++++++--- .../application/port/out/RoomQueryPort.java | 13 ++-- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/main/java/konkuk/thip/common/util/Cursor.java b/src/main/java/konkuk/thip/common/util/Cursor.java index 25b92273f..1ddbfa55e 100644 --- a/src/main/java/konkuk/thip/common/util/Cursor.java +++ b/src/main/java/konkuk/thip/common/util/Cursor.java @@ -5,6 +5,7 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @@ -51,6 +52,10 @@ public LocalDateTime getLocalDateTime(int index) { return getAs(index, LocalDateTime::parse, "LocalDateTime"); } + public LocalDate getLocalDate(int index) { + return getAs(index, LocalDate::parse, "LocalDate"); + } + public Long getLong(int index) { return getAs(index, Long::parseLong, "Long"); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index 521bb911b..add068d31 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -1,13 +1,14 @@ package konkuk.thip.room.adapter.out.persistence; +import konkuk.thip.common.util.Cursor; +import konkuk.thip.common.util.CursorBasedList; import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; -import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; import konkuk.thip.room.adapter.out.mapper.RoomMapper; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; import konkuk.thip.room.application.port.out.RoomQueryPort; -import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; import konkuk.thip.room.domain.Room; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -45,23 +46,71 @@ public Page searchHomeJoinedRoom } @Override - public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { - return roomJpaRepository.findRecruitingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); + public CursorBasedList findRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { + LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); + Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + int pageSize = cursor.getPageSize(); + + List roomShowMineQueryDtos = roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); + + return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { + Cursor nextCursor = new Cursor(List.of( + roomShowMineQueryDto.endDate().toString(), + roomShowMineQueryDto.roomId().toString() + )); + return nextCursor.toEncodedString(); + }); } @Override - public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { - return roomJpaRepository.findPlayingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); + public CursorBasedList findPlayingRoomsUserParticipated(Long userId, Cursor cursor) { + LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); + Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + int pageSize = cursor.getPageSize(); + + List roomShowMineQueryDtos = roomJpaRepository.findPlayingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); + + return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { + Cursor nextCursor = new Cursor(List.of( + roomShowMineQueryDto.endDate().toString(), + roomShowMineQueryDto.roomId().toString() + )); + return nextCursor.toEncodedString(); + }); } @Override - public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { - return roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); + public CursorBasedList findPlayingAndRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { + LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); + Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + int pageSize = cursor.getPageSize(); + + List roomShowMineQueryDtos = roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); + + return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { + Cursor nextCursor = new Cursor(List.of( + roomShowMineQueryDto.endDate().toString(), + roomShowMineQueryDto.roomId().toString() + )); + return nextCursor.toEncodedString(); + }); } @Override - public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize) { - return roomJpaRepository.findExpiredRoomsUserParticipated(userId, dateCursor, roomIdCursor, pageSize); + public CursorBasedList findExpiredRoomsUserParticipated(Long userId, Cursor cursor) { + LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); + Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + int pageSize = cursor.getPageSize(); + + List roomShowMineQueryDtos = roomJpaRepository.findExpiredRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); + + return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { + Cursor nextCursor = new Cursor(List.of( + roomShowMineQueryDto.endDate().toString(), + roomShowMineQueryDto.roomId().toString() + )); + return nextCursor.toEncodedString(); + }); } } diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index 52bf05d36..85276c896 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -1,10 +1,11 @@ package konkuk.thip.room.application.port.out; +import konkuk.thip.common.util.Cursor; +import konkuk.thip.common.util.CursorBasedList; import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; -import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; -import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; import konkuk.thip.room.domain.Room; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -22,11 +23,11 @@ public interface RoomQueryPort { Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); - CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + CursorBasedList findRecruitingRoomsUserParticipated(Long userId, Cursor cursor); - CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + CursorBasedList findPlayingRoomsUserParticipated(Long userId, Cursor cursor); - CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + CursorBasedList findPlayingAndRecruitingRoomsUserParticipated(Long userId, Cursor cursor); - CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + CursorBasedList findExpiredRoomsUserParticipated(Long userId, Cursor cursor); } From 12c305d91dc06b517a1da6d10244f5ca2db15911 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:27:23 +0900 Subject: [PATCH 18/22] =?UTF-8?q?[refactor]=20CursorBasedList,=20Cursor=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20query=20repository=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=8B=9C=EA=B7=B8=EB=8B=88?= =?UTF-8?q?=EC=B2=98=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/repository/RoomQueryRepository.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 6b38495c0..097d8dbef 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -3,8 +3,7 @@ import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; -import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; -import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -20,11 +19,11 @@ public interface RoomQueryRepository { Page searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable); - CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + List findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + List findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + List findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - CursorSliceOfMyRoomView findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); + List findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); } From 472710fad1dc5a7c47e4fad9859f8ccbe7381ebc Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:29:06 +0900 Subject: [PATCH 19/22] =?UTF-8?q?[refactor]=20"=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=9C=20=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C"=20api=20QueryDSL=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CursorBasedList, 조회용 dto 도입 - Q클래스의 조회용 dto를 사용하여 조회 및 반환을 한번에 수행하도록 수정 - 현재 커서의 값을 배제하는 exclusive 방식으로 페이지네이션 로직 수정 --- .../repository/RoomQueryRepositoryImpl.java | 139 ++++++------------ 1 file changed, 48 insertions(+), 91 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index 05cdfc549..42fccc107 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -12,24 +12,22 @@ import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import konkuk.thip.book.adapter.out.jpa.QBookJpaEntity; +import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.util.DateUtil; import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; -import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; import konkuk.thip.room.adapter.out.jpa.QRoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.QRoomParticipantJpaEntity; -import konkuk.thip.room.application.port.out.dto.CursorSliceOfMyRoomView; -import konkuk.thip.room.domain.MyRoomType; +import konkuk.thip.room.application.port.out.dto.QRoomShowMineQueryDto; +import konkuk.thip.room.application.port.out.dto.RoomShowMineQueryDto; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.*; import org.springframework.stereotype.Repository; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.function.Function; @Repository @RequiredArgsConstructor @@ -251,49 +249,50 @@ public Page searchHomeJoinedRoom // 1) 모집중인 방 @Override - public CursorSliceOfMyRoomView findRecruitingRoomsUserParticipated( + public List findRecruitingRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) - .and(room.startDate.after(today)); // 유저가 참여한 방 && 모집중인 방 + .and(room.startDate.after(today)) + .and(room.status.eq(StatusType.ACTIVE)); // 유저가 참여한 방 && 모집중인 방 DateExpression cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일) OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() }; - Function mapper = createMyRoomMapper(cursorExpr, true); - return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); + return fetchMyRooms(base, cursorExpr, orders, true, dateCursor, roomIdCursor, pageSize); } // 2) 진행중인 방 @Override - public CursorSliceOfMyRoomView findPlayingRoomsUserParticipated( + public List findPlayingRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) .and(room.startDate.loe(today)) - .and(room.endDate.goe(today)); // 유저가 참여한 방 && 현재 진행중인 방 + .and(room.endDate.goe(today)) + .and(room.status.eq(StatusType.ACTIVE)); // 유저가 참여한 방 && 현재 진행중인 방 DateExpression cursorExpr = room.endDate; // 커서 비교는 endDate(= 진행 마감일) OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() }; - Function mapper = createMyRoomMapper(cursorExpr, true); - return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); + return fetchMyRooms(base, cursorExpr, orders, true, dateCursor, roomIdCursor, pageSize); } // 3) 진행+모집 통합 @Override - public CursorSliceOfMyRoomView findPlayingAndRecruitingRoomsUserParticipated( + public List findPlayingAndRecruitingRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression playing = room.startDate.loe(today).and(room.endDate.goe(today)); BooleanExpression recruiting = room.startDate.after(today); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) - .and( playing.or(recruiting) ); // 유저가 참여한 방 && 현재 진행중인 방 + 모집중인 방 + .and(playing.or(recruiting)) + .and(room.status.eq(StatusType.ACTIVE)); // 유저가 참여한 방 && 현재 진행중인 방 + 모집중인 방 // 진행중: cursor=endDate, 모집중: cursor=startDate DateExpression cursorExpr = new CaseBuilder() @@ -310,110 +309,68 @@ public CursorSliceOfMyRoomView findPlayingAndRecrui cursorExpr.asc(), room.roomId.asc() }; - Function mapper = createMyRoomMapper(cursorExpr, true); - return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, true, orders); + return fetchMyRooms(base, cursorExpr, orders, true, dateCursor, roomIdCursor, pageSize); } // 4) 만료된 방 @Override - public CursorSliceOfMyRoomView findExpiredRoomsUserParticipated( + public List findExpiredRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { LocalDate today = LocalDate.now(); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) - .and(room.endDate.before(today)); // 유저가 참여한 방 && 만료된 방 + .and(room.endDate.before(today)) + .and(room.status.eq(StatusType.ACTIVE)); // 유저가 참여한 방 && 만료된 방 DateExpression cursorExpr = room.endDate; OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.desc(), room.roomId.desc() // 만료된 방은 가장 최근에 만료된 방부터 반환 }; - Function mapper = createMyRoomMapper(cursorExpr, false); - return sliceQuery(base, cursorExpr, mapper, dateCursor, roomIdCursor, pageSize, false, orders); + return fetchMyRooms(base, cursorExpr, orders, false, dateCursor, roomIdCursor, pageSize); } /** - * t 에서 RoomShowMineResponse.MyRoom 으로 매핑하는 함수 생성 - * @param cursorExpr 커서 기준 날짜 표현식 - * @param formatCursor true 면 DateUtil.formatAfterTime, false 면 null 반환 + * 공통 커서 + 2단계 조회 (IDs → entities) 처리 */ - private Function createMyRoomMapper(DateExpression cursorExpr, boolean formatCursor) { - return t -> new RoomShowMineResponse.MyRoom( - t.get(room.roomId), - t.get(book.imageUrl), - t.get(room.title), - t.get(room.memberCount), - formatCursor ? DateUtil.formatAfterTime(t.get(cursorExpr)) : null - ); - } - - /** - * 공통 커서+페이징 처리 - */ - private CursorSliceOfMyRoomView sliceQuery( + private List fetchMyRooms( BooleanExpression baseCondition, - DateExpression cursorDateExpr, - Function mapper, + DateExpression cursorExpr, + OrderSpecifier[] orders, + boolean ascending, LocalDate dateCursor, Long roomIdCursor, - int pageSize, - boolean ascending, - OrderSpecifier... orderSpecs + int pageSize ) { - BooleanBuilder where = new BooleanBuilder(baseCondition); // baseCondition + 커서 기반으로 where 절 구성 - if (dateCursor != null && roomIdCursor != null) { - if (ascending) { // 진행중, 모집중, 통합 - where.and( - cursorDateExpr.gt(dateCursor) // dateCursor 보다 크거나 - .or( - cursorDateExpr.eq(dateCursor) - .and(room.roomId.goe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 크거나 같은 것 - ) - ); - } else { // 내림차순일 때는 반대로 (만료된 방) - where.and( - cursorDateExpr.lt(dateCursor) // dateCursor 보다 작거나 - .or( - cursorDateExpr.eq(dateCursor) - .and(room.roomId.loe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 작거나 같은 것 - ) - ); + BooleanBuilder where = new BooleanBuilder(baseCondition); + if (dateCursor != null && roomIdCursor != null) { // 첫 페이지가 아닌 경우 + if (ascending) { + where.and(cursorExpr.gt(dateCursor) + .or(cursorExpr.eq(dateCursor) + .and(room.roomId.gt(roomIdCursor)))); + } else { + where.and(cursorExpr.lt(dateCursor) + .or(cursorExpr.eq(dateCursor) + .and(room.roomId.lt(roomIdCursor)))); } } - int fetchSize = pageSize + 1; - List tuples = queryFactory - .select(room.roomId, book.imageUrl, room.title, room.memberCount, cursorDateExpr) + // 2) DTO 프로젝션: 필요한 필드만 바로 조회 + return queryFactory + .select(new QRoomShowMineQueryDto( + room.roomId, + book.imageUrl, + room.title, + room.memberCount, + cursorExpr + )) .from(participant) .join(participant.roomJpaEntity, room) - .join(room.bookJpaEntity, book) + .leftJoin(room.bookJpaEntity, book) .where(where) - .orderBy(orderSpecs) - .limit(fetchSize) - .fetch(); // 직접 tuple 결과를 조회하므로 lazy 로딩 적용 대상 X - - boolean hasNext = tuples.size() == fetchSize; - List content = tuples.stream() - .limit(pageSize) - .map(mapper) - .toList(); // pageSize 만큼만 dto로 매핑 - - // 커서 값 세팅 - LocalDate nextDate = null; - Long nextId = null; - if (hasNext) { - Tuple next = tuples.get(pageSize); // 다음 페이지의 첫번째 레코드 - nextDate = next.get(cursorDateExpr); - nextId = next.get(room.roomId); - } - - return new CursorSliceOfMyRoomView<>( - content, - PageRequest.of(0, pageSize), - hasNext, - nextDate, - nextId - ); + .orderBy(orders) + .limit(pageSize + 1) + .fetch(); } } From 3d39ed4336d383b69c33ac4c08225fb6c15609b1 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Mon, 28 Jul 2025 20:29:22 +0900 Subject: [PATCH 20/22] =?UTF-8?q?[refactor]=20"=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=9C=20=EB=B0=A9=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C"=20api=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomShowMineApiTest.java | 89 ++----------------- 1 file changed, 5 insertions(+), 84 deletions(-) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java index da68ee426..97de3c849 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java @@ -309,80 +309,6 @@ void get_my_rooms_wrong_type() throws Exception { .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_TYPE.getCode()))); } - @Test - @DisplayName("cursorDate 만 받고 cursorId 는 받지 않은 경우, 400 error를 반환한다.") - void get_my_rooms_only_cursor_date_exist() throws Exception { - //given - RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); - changeRoomMemberCount(recruitingRoom1, 5); - - RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); - changeRoomMemberCount(recruitingRoom2, 8); - - RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); - changeRoomMemberCount(playingRoom1, 6); - - RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); - changeRoomMemberCount(expiredRoom1, 7); - - AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); - UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); - - // user가 생성한 방에 참여한 상황 가정 - saveSingleUserToRoom(recruitingRoom1, user); - saveSingleUserToRoom(recruitingRoom2, user); - saveSingleUserToRoom(playingRoom1, user); - saveSingleUserToRoom(expiredRoom1, user); - - //when - ResultActions result = mockMvc.perform(get("/rooms/my") - .requestAttr("userId", user.getUserId()) - .param("type", "playing") - .param("cursorDate", String.valueOf(LocalDate.now()))); // cursor 중 cursorDate 만 request param 으로 넘어온 경우 - - //then - result.andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.isSuccess", is(false))) - .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_CURSOR.getCode()))); - } - - @Test - @DisplayName("cursorId 만 받고 cursorDate 는 받지 않은 경우, 400 error를 반환한다.") - void get_my_rooms_only_cursor_id_exist() throws Exception { - //given - RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); - changeRoomMemberCount(recruitingRoom1, 5); - - RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); - changeRoomMemberCount(recruitingRoom2, 8); - - RoomJpaEntity playingRoom1 = saveScienceRoom("진행중인방-책-1", "isbn3", "과학-방-5일뒤-활동마감", LocalDate.now().minusDays(5), LocalDate.now().plusDays(5), 10); - changeRoomMemberCount(playingRoom1, 6); - - RoomJpaEntity expiredRoom1 = saveScienceRoom("만료된방-책-1", "isbn4", "과학-방-5일전-활동마감", LocalDate.now().minusDays(30), LocalDate.now().minusDays(5), 10); - changeRoomMemberCount(expiredRoom1, 7); - - AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); - UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); - - // user가 생성한 방에 참여한 상황 가정 - saveSingleUserToRoom(recruitingRoom1, user); - saveSingleUserToRoom(recruitingRoom2, user); - saveSingleUserToRoom(playingRoom1, user); - saveSingleUserToRoom(expiredRoom1, user); - - //when - ResultActions result = mockMvc.perform(get("/rooms/my") - .requestAttr("userId", user.getUserId()) - .param("type", "playing") - .param("cursorId", String.valueOf(10L))); // cursor 중 cursorId 만 request param 으로 넘어온 경우 - - //then - result.andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.isSuccess", is(false))) - .andExpect(jsonPath("$.code", is(INVALID_MY_ROOM_CURSOR.getCode()))); - } - @Test @DisplayName("한번에 최대 10개의 데이터만을 반환한다. 다음 페이지에 해당하는 데이터가 있을 경우, 다음 페이지의 cursor 값을 반환한다.") void get_my_rooms_page_1() throws Exception { @@ -447,10 +373,7 @@ void get_my_rooms_page_1() throws Exception { //then result.andExpect(status().isOk()) - .andExpect(jsonPath("$.data.size", is(10))) - .andExpect(jsonPath("$.data.last", is(false))) - .andExpect(jsonPath("$.data.nextCursorDate", is(recruitingRoom11.getStartDate().toString()))) // 11번째 데이터의 startDate가 nextCursorDate 이다 - .andExpect(jsonPath("$.data.nextCursorId", is(recruitingRoom11.getRoomId().intValue()))) // 11번째 데이터의 id가 nextCursorId 이다 + .andExpect(jsonPath("$.data.isLast", is(false))) .andExpect(jsonPath("$.data.roomList", hasSize(10))) // 정렬 조건 : 모집중인 방 == 방 활동 시작일 임박 순 .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) @@ -523,18 +446,16 @@ void get_my_rooms_page_2() throws Exception { saveSingleUserToRoom(recruitingRoom12, user); //when + String nextCursor = recruitingRoom10.getStartDate().toString() + "|" + recruitingRoom10.getRoomId().toString(); // 이전 페이지의 마지막 레코드인 room10이 nextCursor이다 + ResultActions result = mockMvc.perform(get("/rooms/my") .requestAttr("userId", user.getUserId()) .param("type", "recruiting") - .param("cursorDate", recruitingRoom11.getStartDate().toString()) - .param("cursorId", recruitingRoom11.getRoomId().toString())); + .param("cursor", nextCursor)); //then result.andExpect(status().isOk()) - .andExpect(jsonPath("$.data.size", is(2))) - .andExpect(jsonPath("$.data.last", is(true))) - .andExpect(jsonPath("$.data.nextCursorDate", nullValue())) // 다음 페이지가 없으므로 cursor 값은 null - .andExpect(jsonPath("$.data.nextCursorId", nullValue())) + .andExpect(jsonPath("$.data.isLast", is(true))) .andExpect(jsonPath("$.data.roomList", hasSize(2))) .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-11일뒤-활동시작"))) .andExpect(jsonPath("$.data.roomList[1].roomName", is("과학-방-12일뒤-활동시작"))); From 49ff25419ca4b0e477b3684befc674fd6e432011 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Tue, 29 Jul 2025 01:45:17 +0900 Subject: [PATCH 21/22] =?UTF-8?q?[refactor]=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=ED=97=AC=ED=8D=BC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 함수형 인터페이스를 도입하여 중복되는 로직 추상화 --- .../RoomQueryPersistenceAdapter.java | 57 +++++-------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index add068d31..7f3139f60 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -47,64 +47,36 @@ public Page searchHomeJoinedRoom @Override public CursorBasedList findRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { - LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); - Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); - int pageSize = cursor.getPageSize(); - - List roomShowMineQueryDtos = roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); - - return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { - Cursor nextCursor = new Cursor(List.of( - roomShowMineQueryDto.endDate().toString(), - roomShowMineQueryDto.roomId().toString() - )); - return nextCursor.toEncodedString(); - }); + return findRooms(userId, cursor, roomJpaRepository::findRecruitingRoomsUserParticipated); } @Override public CursorBasedList findPlayingRoomsUserParticipated(Long userId, Cursor cursor) { - LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); - Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); - int pageSize = cursor.getPageSize(); - - List roomShowMineQueryDtos = roomJpaRepository.findPlayingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); - - return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { - Cursor nextCursor = new Cursor(List.of( - roomShowMineQueryDto.endDate().toString(), - roomShowMineQueryDto.roomId().toString() - )); - return nextCursor.toEncodedString(); - }); + return findRooms(userId, cursor, roomJpaRepository::findPlayingRoomsUserParticipated); } @Override public CursorBasedList findPlayingAndRecruitingRoomsUserParticipated(Long userId, Cursor cursor) { - LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); - Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); - int pageSize = cursor.getPageSize(); - - List roomShowMineQueryDtos = roomJpaRepository.findPlayingAndRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); - - return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { - Cursor nextCursor = new Cursor(List.of( - roomShowMineQueryDto.endDate().toString(), - roomShowMineQueryDto.roomId().toString() - )); - return nextCursor.toEncodedString(); - }); + return findRooms(userId, cursor, roomJpaRepository::findPlayingAndRecruitingRoomsUserParticipated); } @Override public CursorBasedList findExpiredRoomsUserParticipated(Long userId, Cursor cursor) { + return findRooms(userId, cursor, roomJpaRepository::findExpiredRoomsUserParticipated); + } + + @FunctionalInterface + private interface RoomQueryFunction { + List apply(Long userId, LocalDate lastLocalDate, Long lastId, int pageSize); + } + + private CursorBasedList findRooms(Long userId, Cursor cursor, RoomQueryFunction queryFunction) { LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); int pageSize = cursor.getPageSize(); - List roomShowMineQueryDtos = roomJpaRepository.findExpiredRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); - - return CursorBasedList.of(roomShowMineQueryDtos, pageSize, roomShowMineQueryDto -> { + List dtos = queryFunction.apply(userId, lastLocalDate, lastId, pageSize); + return CursorBasedList.of(dtos, pageSize, roomShowMineQueryDto -> { Cursor nextCursor = new Cursor(List.of( roomShowMineQueryDto.endDate().toString(), roomShowMineQueryDto.roomId().toString() @@ -112,5 +84,4 @@ public CursorBasedList findExpiredRoomsUserParticipated(Lo return nextCursor.toEncodedString(); }); } - } From 8da1411466087f2911907c7a4deedde1edc641a4 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Tue, 29 Jul 2025 01:49:35 +0900 Subject: [PATCH 22/22] =?UTF-8?q?[refactor]=20inner=20class=20->=20var=20?= =?UTF-8?q?=EB=A1=9C=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/RoomShowMineService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java index 398a34fe1..4fb6f9aa7 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java @@ -46,17 +46,17 @@ public RoomShowMineResponse getMyRooms(Long userId, String type, String cursor) boolean isExpiredType = myRoomType == MyRoomType.EXPIRED; List myRooms = result.contents().stream() .map(dto -> { - RoomShowMineResponse.MyRoom r = roomQueryMapper.toShowMyRoomResponse(dto); + var myRoomResponse = roomQueryMapper.toShowMyRoomResponse(dto); if (isExpiredType) { return new RoomShowMineResponse.MyRoom( - r.roomId(), - r.bookImageUrl(), - r.roomName(), - r.memberCount(), + myRoomResponse.roomId(), + myRoomResponse.bookImageUrl(), + myRoomResponse.roomName(), + myRoomResponse.memberCount(), null ); } - return r; + return myRoomResponse; }) .toList();