Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6951407
[feat] 내 모임방 목록 조회 api controller 개발 (#89)
seongjunnoh Jul 21, 2025
89cb069
[feat] 내 모임방 목록 조회 api use case 개발 (#89)
seongjunnoh Jul 21, 2025
ae89b3c
[feat] 내 모임방 목록 조회 api의 request param 검증을 위한 enum 도입 (#89)
seongjunnoh Jul 21, 2025
73cf3f6
[feat] error code 추가 (#89)
seongjunnoh Jul 21, 2025
e36a8ef
[feat] next cursor 값을 위해 custom SliceImpl 추가 (#89)
seongjunnoh Jul 21, 2025
87b1861
[feat] 내 모임방 목록 조회 api 영속성 adapter 구현 (#89)
seongjunnoh Jul 21, 2025
d8e5b1a
[feat] QueryDSL + 커서 기반 페이징 처리를 통해 데이터 조회 (#89)
seongjunnoh Jul 21, 2025
a767a9e
[test] 내 모임방 목록 조회 api 통합 테스트 코드 작성 (#89)
seongjunnoh Jul 21, 2025
03fc6bf
develop merge (#89)
seongjunnoh Jul 21, 2025
21d31ca
[fix] develop merge 후 코드 수정 (#89)
seongjunnoh Jul 21, 2025
5376d0e
[refactor] 커서 관련 변수 네이밍 수정 (#89)
seongjunnoh Jul 21, 2025
a65a1ea
[refactor] RoomQueryRepositoryImpl 에서 중복되는 코드 private 메서드로 추출 (#89)
seongjunnoh Jul 22, 2025
7f40f4c
Merge remote-tracking branch 'origin' into feat/#89-get-room-mine
seongjunnoh Jul 22, 2025
9d9b3b8
Merge remote-tracking branch 'origin' into feat/#89-get-room-mine
seongjunnoh Jul 26, 2025
1a3f220
develop merge (#89)
seongjunnoh Jul 28, 2025
1f35fde
[refactor] controller request param의 cursor 수정 (#89)
seongjunnoh Jul 28, 2025
4852a13
[refactor] response dto 수정 (#89)
seongjunnoh Jul 28, 2025
d3f4f93
[refactor] usecase 메서드 파라미터 수정 (#89)
seongjunnoh Jul 28, 2025
65f8c9c
[refactor] "내가 참여한 방 목록 조회" service 코드 수정 (#89)
seongjunnoh Jul 28, 2025
7b7dd91
[refactor] 조회용 dto 선언 및 dto -> response 의 mapper 선언 (#89)
seongjunnoh Jul 28, 2025
2d934ff
[refactor] CursorBasedList, Cursor 반영하여 room query adapter 코드 수정 (#89)
seongjunnoh Jul 28, 2025
12c305d
[refactor] CursorBasedList, Cursor 반영하여 query repository 메서드 시그니처 수정 …
seongjunnoh Jul 28, 2025
472710f
[refactor] "내가 참여한 방 목록 조회" api QueryDSL 코드 수정 (#89)
seongjunnoh Jul 28, 2025
3d39ed4
[refactor] "내가 참여한 방 목록 조회" api 통합 테스트 코드 수정 (#89)
seongjunnoh Jul 28, 2025
49ff254
[refactor] 중복되는 코드를 위한 헬퍼 메서드 도입 (#89)
seongjunnoh Jul 28, 2025
8da1411
[refactor] inner class -> var 로 타입 수정 (#89)
seongjunnoh Jul 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ public enum ErrorCode implements ResponseCode {
USER_NOT_PARTICIPATED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 140006, "사용자가 방에 참여하지 않은 상태에서 취소하기는 불가합니다."),
HOST_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 140007, "방장은 참여 취소를 할 수 없습니다."),
ROOM_RECRUIT_CANNOT_CLOSED(HttpStatus.BAD_REQUEST, 140008, "방 모집 마감을 할 수 없습니다."),
ROOM_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 140009, "방 접근 권한이 없습니다."),
INVALID_MY_ROOM_TYPE(HttpStatus.BAD_REQUEST, 140009, "유저가 참가한 방 목록 검색 요청에 유효하지 않은 MY ROOM type 이 있습니다."),
INVALID_MY_ROOM_CURSOR(HttpStatus.BAD_REQUEST, 140010, "유저가 참가한 방 목록 검색 요청에 유효하지 않은 cursor 가 있습니다"),
ROOM_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 140011, "방 접근 권한이 없습니다."),

/**
* 150000 : Category error
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/konkuk/thip/common/util/Cursor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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<RoomSearchResponse> searchRooms(
Expand Down Expand Up @@ -82,4 +81,12 @@ public BaseResponse<RoomPlayingDetailViewResponse> getPlayingRoomDetailView(
return BaseResponse.ok(roomShowPlayingDetailViewUseCase.getPlayingRoomDetailView(userId, roomId));
}

// 내 모임방 리스트 조회
@GetMapping("/rooms/my")
public BaseResponse<RoomShowMineResponse> getMyRooms(
@UserId final Long userId,
@RequestParam(value = "type", required = false, defaultValue = "playingAndRecruiting") final String type,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@RequestParam(value = "cursor", required = false) final String cursor) {
return BaseResponse.ok(roomShowMineUseCase.getMyRooms(userId, type, cursor));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package konkuk.thip.room.adapter.in.web.response;

import java.util.List;

public record RoomShowMineResponse(
List<MyRoom> roomList,
String nextCursor,
boolean isLast
) {
public record MyRoom(
Long roomId,
String bookImageUrl,
String roomName,
int memberCount,
String endDate // 방 진행 마감일 or 방 모집 마감일 (~ 뒤 형식)
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,9 @@ public RoomJpaEntity updateFrom(Room room) {
this.memberCount = room.getMemberCount();
return this;
}
}

// 테스트 메서드 편의용
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3: 이건 나즁에,, 리펙토링하는게 좋아보여욥

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buzz0331 @hd0rable 저희가 RoomJpaEntity 에 memberCount 값을 추가하여서 기존 테스트 코드 작성시 특정 방에 어떤 유저가 속해있다 라는 상황을 가정할때 더이상 RoomParticipantJpaEntity 를 DB에 save 하지 않고, room의 memberCount 값을 수정함으로써 테스트 코드를 간결하게 작성할 수 있는 이점이 있지 않을까 생각하긴 합니다

이런 이유로 RoomJpaEntity에 updateMemberCount 라는 메서드를 추가하였고, jpa entity 는 외부로 노출되는 메서드가 아니니 테스트 메서드 편의용으로 괜찮지 않나라는 생각이긴 합니다

관련해서 어떻게 생각하시나요?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateMemberCount()를 통해 테스트 코드가 간결해질 수 있다는 점에는 공감합니다. 또한 테스트를 준비하는 데 필요한 반복적인 작업들을 줄일 수 있다는 이점도 분명하다고 생각했습니다.

다만 JPA 엔티티는 도메인-DB 매핑과 정합성을 책임지는 계층이기 때문에,
테스트를 위한 조작용 메서드를 엔티티 내부에 추가하는 방식은 설계적으로 다소 책임이 분산될 수 있다고 느꼈습니다.
가능하다면 테스트 전용 팩토리나 빌더 객체를 활용해서 memberCount를 설정하거나,
RoomParticipant를 실제로 생성해서 상태를 만드는 쪽이 더 명확한 구조가 될 것 같습니다.

그래도 간편성 차원에선 고려 해볼법한 방법이라 생각하며, 유지한다면 명확히 @VisibleForTesting 또는 JavaDoc 주석을 명시해두는 것도 좋을 것 같습니다.

Copy link
Copy Markdown
Collaborator Author

@seongjunnoh seongjunnoh Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VisibleForTesting 어노테이션을 통한 문서화도 좋은 방법인것 같습니다!!

@buzz0331 현준님 생각은 어떠신가요?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 편의를 위해 update 메서드 뚫어놓는 것은 좋은 것 같아요! 특정 테스트 환경을 만들기 위해서는 불가피할 것 같습니다.

저도 저번에 방 참여 api의 테스트코드를 작성하기 위해서 @VisibleForTesting을 도입해서 updateStartDate 메서드를 구현해두었는데 테스트에서도 접근이 가능하고, 서비스 코드에서도 접근이 가능하더라구요,, @VisibleForTesting이 하는 역할이 정확히 어떤건지 아시나요??

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VisibleForTesting도 사실상 주석처럼 "테스트에서만 사용하겠다"라는걸 명시하는 어노테이션으로 사용한다고 하네요

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 접근 제한자를 실제로 변경해주는 것은 아니고 주석 정도의 역할이군요! 주석을 사용하는 것보다 조금더 명시적일 것 같아서 좋을 것 같네요!!

public void updateMemberCount(int memberCount) {
this.memberCount = memberCount;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +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.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.RoomShowMineQueryDto;
import konkuk.thip.room.domain.Room;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -41,4 +44,44 @@ public List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingR
public Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate date, Pageable pageable) {
return roomJpaRepository.searchHomeJoinedRooms(userId, date, pageable);
}

@Override
public CursorBasedList<RoomShowMineQueryDto> findRecruitingRoomsUserParticipated(Long userId, Cursor cursor) {
return findRooms(userId, cursor, roomJpaRepository::findRecruitingRoomsUserParticipated);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 엄청 간단해지네여 👍🏻

}

@Override
public CursorBasedList<RoomShowMineQueryDto> findPlayingRoomsUserParticipated(Long userId, Cursor cursor) {
return findRooms(userId, cursor, roomJpaRepository::findPlayingRoomsUserParticipated);
}

@Override
public CursorBasedList<RoomShowMineQueryDto> findPlayingAndRecruitingRoomsUserParticipated(Long userId, Cursor cursor) {
return findRooms(userId, cursor, roomJpaRepository::findPlayingAndRecruitingRoomsUserParticipated);
}

@Override
public CursorBasedList<RoomShowMineQueryDto> findExpiredRoomsUserParticipated(Long userId, Cursor cursor) {
return findRooms(userId, cursor, roomJpaRepository::findExpiredRoomsUserParticipated);
}

@FunctionalInterface
private interface RoomQueryFunction {
List<RoomShowMineQueryDto> apply(Long userId, LocalDate lastLocalDate, Long lastId, int pageSize);
}

private CursorBasedList<RoomShowMineQueryDto> 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<RoomShowMineQueryDto> 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()
));
return nextCursor.toEncodedString();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +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.application.port.out.dto.RoomShowMineQueryDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Expand All @@ -15,5 +16,14 @@ public interface RoomQueryRepository {
Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable);

List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count);

Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable);

List<RoomShowMineQueryDto> findRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize);

List<RoomShowMineQueryDto> findPlayingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize);

List<RoomShowMineQueryDto> findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize);

List<RoomShowMineQueryDto> findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize);
}
Loading