Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e57dc01
[refactor] 특정 책과 관련된 방 쿼리 필터링 조건을 정적으로 변경 (#289)
buzz0331 Sep 12, 2025
edc58dd
[fix] FIrebaseAdapter 프로필 조건 수정 (#289)
buzz0331 Sep 12, 2025
a41646a
[refactor] 방 도메인 내부 방 기간 유효성 검증 정적을 변경 (#289)
buzz0331 Sep 12, 2025
4b21c69
[refactor] 방 조회 쿼리 기간 필터링 조건 모두 정적으로 수정 (#289)
buzz0331 Sep 12, 2025
231c256
[refactor] 기간이 만료된 방 내부 게시물들은 조회만 가능하도록 예외처리 (#289)
buzz0331 Sep 12, 2025
1d8a507
[refactor] RoomStatus 도입에 따른 테스트 케이스 수정 (#289)
buzz0331 Sep 12, 2025
a2297a1
[refactor] 투표하기, 투표삭제하기 api에서 방 만료 검증 (#289)
buzz0331 Sep 12, 2025
0414562
[fix] 투표하기 단위테스트에 RoomCommandPort Mock 추가 (#295)
buzz0331 Sep 12, 2025
a99e536
[fix] 투표하기 단위테스트에 Room 스텁 객체 추가 (#295)
buzz0331 Sep 12, 2025
1f07fef
[refactor] RoomQueryDto에 roomStatus 추가 (#295)
buzz0331 Sep 13, 2025
a06af47
[refactor] 진행중인 방이 아닌경우의 에러코드 추가 (#295)
buzz0331 Sep 13, 2025
ce1956d
[refactor] 진행중인 방이 아닌 경우(모집 중인 방)의 예외처리 추가 (#295)
buzz0331 Sep 13, 2025
5e43d56
[docs] 에러메시지 스웨거 명세 추가 (#295)
buzz0331 Sep 13, 2025
ed6d55f
[merge] 머지 (#295)
buzz0331 Sep 13, 2025
d87aa32
[test] TestEntityFactory에서 방 생성시 기본 진행중인 방이 생성되도록 수정 (#295)
buzz0331 Sep 13, 2025
fbaef2e
[chore] 주석 제거 (#295)
buzz0331 Sep 13, 2025
f25d4f2
[refactor] validateRoom 메서드 추출 해제 (#295)
buzz0331 Sep 13, 2025
a4819f3
[refactor] RoomStatus not null assertion 추가 (#295)
buzz0331 Sep 13, 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 @@ -11,8 +11,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;

@Service
@RequiredArgsConstructor
public class BookRecruitingRoomsService implements BookRecruitingRoomsUseCase {
Expand All @@ -26,7 +24,7 @@ public class BookRecruitingRoomsService implements BookRecruitingRoomsUseCase {
@Transactional(readOnly = true)
public BookRecruitingRoomsResponse getRecruitingRoomsWithBook(String isbn, String cursorStr) {
Integer totalRoomCount = (cursorStr == null || cursorStr.isBlank()) ? // 첫 요청 여부 판단
roomQueryPort.countRecruitingRoomsByBookAndStartDateAfter(isbn, LocalDate.now()) : null;
roomQueryPort.countRecruitingRoomsByBookIsbn(isbn) : null;

Cursor cursor = Cursor.from(cursorStr, DEFAULT_PAGE_SIZE);
CursorBasedList<RoomQueryDto> roomDtos = roomQueryPort.findRoomsByIsbnOrderByDeadline(isbn, cursor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ public BookDetailSearchResult searchDetailBooks(String isbn,Long userId) {
}

private int getRecruitingRoomCount(Book book) {
//오늘 날짜 기준으로 방 활동 시작 기간이 이후인 방 찾기(모집중인 방)
LocalDate today = LocalDate.now();
return roomQueryPort.countRecruitingRoomsByBookAndStartDateAfter(book.getIsbn(), today);
// 모집 중인 방 개수
return roomQueryPort.countRecruitingRoomsByBookIsbn(book.getIsbn());
}

private int getReadCount(Book book) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import konkuk.thip.post.domain.CountUpdatable;
import konkuk.thip.room.application.service.validator.RoomParticipantValidator;
import konkuk.thip.room.application.service.validator.RoomValidator;
import konkuk.thip.roompost.domain.RoomPost;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
Expand All @@ -11,11 +12,13 @@
public class RoomPostCommentAccessPolicy implements CommentAccessPolicy {

private final RoomParticipantValidator roomParticipantValidator;
private final RoomValidator roomValidator;

@Override
public void validateCommentAccess(CountUpdatable post, Long userId) {
RoomPost roomPost = (RoomPost) post;
roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId);
roomValidator.validateRoomExpired(roomPost.getRoomId());
Copy link
Member

Choose a reason for hiding this comment

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

붐업

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public enum SwaggerResponseDescription {
RECORD_DELETE(new LinkedHashSet<>(Set.of(
ROOM_ACCESS_FORBIDDEN,
RECORD_NOT_FOUND,
RECORD_ACCESS_FORBIDDEN
RECORD_ACCESS_FORBIDDEN,
ROOM_IS_EXPIRED
))),
RECORD_PIN(new LinkedHashSet<>(Set.of(
ROOM_ACCESS_FORBIDDEN,
Expand All @@ -158,7 +159,8 @@ public enum SwaggerResponseDescription {
RECORD_UPDATE(new LinkedHashSet<>(Set.of(
ROOM_ACCESS_FORBIDDEN,
RECORD_NOT_FOUND,
RECORD_ACCESS_FORBIDDEN
RECORD_ACCESS_FORBIDDEN,
ROOM_IS_EXPIRED
))),

// Vote
Expand All @@ -175,7 +177,8 @@ public enum SwaggerResponseDescription {
VOTE_ITEM_NOT_FOUND,
VOTE_ITEM_ALREADY_VOTED,
VOTE_ITEM_NOT_VOTED_CANNOT_CANCEL,
VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE
VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE,
ROOM_IS_EXPIRED
))),
VOTE_DELETE(new LinkedHashSet<>(Set.of(
ROOM_ACCESS_FORBIDDEN,
Expand Down Expand Up @@ -251,8 +254,8 @@ public enum SwaggerResponseDescription {
VOTE_NOT_FOUND,
INVALID_COMMENT_CREATE,
FEED_ACCESS_FORBIDDEN,
ROOM_ACCESS_FORBIDDEN

ROOM_ACCESS_FORBIDDEN,
ROOM_IS_EXPIRED
))),
CHANGE_COMMENT_LIKE_STATE(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND,
Expand All @@ -264,7 +267,8 @@ public enum SwaggerResponseDescription {
COMMENT_NOT_LIKED_CANNOT_CANCEL,
COMMENT_LIKE_COUNT_UNDERFLOW,
FEED_ACCESS_FORBIDDEN,
ROOM_ACCESS_FORBIDDEN
ROOM_ACCESS_FORBIDDEN,
ROOM_IS_EXPIRED
))),
COMMENT_DELETE(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND,
Expand All @@ -275,7 +279,8 @@ public enum SwaggerResponseDescription {
COMMENT_DELETE_FORBIDDEN,
COMMENT_COUNT_UNDERFLOW,
FEED_ACCESS_FORBIDDEN,
ROOM_ACCESS_FORBIDDEN
ROOM_ACCESS_FORBIDDEN,
ROOM_IS_EXPIRED
))),

// Book
Expand Down Expand Up @@ -321,7 +326,8 @@ public enum SwaggerResponseDescription {
ROOM_NOT_FOUND,
USER_NOT_FOUND,
ATTENDANCE_CHECK_WRITE_LIMIT_EXCEEDED,
ATTENDANCE_CHECK_NOT_FOUND
ATTENDANCE_CHECK_NOT_FOUND,
ROOM_IS_EXPIRED
))),

ATTENDANCE_CHECK_SHOW(new LinkedHashSet<>(Set.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

@Slf4j
@Component
@Profile({"!test", "!local"})
@Profile("!test & !local")
@RequiredArgsConstructor
public class FirebaseAdapter implements FirebaseMessagingPort {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,7 @@ public void updateIsPublic(boolean isPublic) {

@VisibleForTesting
public void updateRoomPercentage(double roomPercentage) {this.roomPercentage = roomPercentage;}

@VisibleForTesting
public void updateRoomStatus(RoomStatus roomStatus) {this.roomStatus = roomStatus;}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public class RoomQueryPersistenceAdapter implements RoomQueryPort {
private final RoomJpaRepository roomJpaRepository;

@Override
public int countRecruitingRoomsByBookAndStartDateAfter(String isbn, LocalDate currentDate) {
return roomJpaRepository.countActiveRoomsByBookIdAndStartDateAfter(isbn, currentDate);
public int countRecruitingRoomsByBookIsbn(String isbn) {
return roomJpaRepository.countRecruitingRoomsByBookIsbn(isbn);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

Expand All @@ -19,8 +18,8 @@ public interface RoomJpaRepository extends JpaRepository<RoomJpaEntity, Long>, R

@Query("SELECT COUNT(r) FROM RoomJpaEntity r " +
"WHERE r.bookJpaEntity.isbn = :isbn " +
"AND r.startDate > :currentDate")
int countActiveRoomsByBookIdAndStartDateAfter(@Param("isbn") String isbn, @Param("currentDate") LocalDate currentDate);
"AND r.roomStatus = 'RECRUITING'")
int countRecruitingRoomsByBookIsbn(@Param("isbn") String isbn);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import konkuk.thip.room.application.port.out.dto.RoomParticipantQueryDto;
import konkuk.thip.room.application.port.out.dto.RoomQueryDto;
import konkuk.thip.room.domain.value.Category;
import konkuk.thip.room.domain.value.RoomStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand All @@ -35,9 +36,9 @@ public class RoomQueryRepositoryImpl implements RoomQueryRepository {
private final QRoomParticipantJpaEntity participant = QRoomParticipantJpaEntity.roomParticipantJpaEntity;

/** 모집중 + ACTIVE 공통 where */
private BooleanBuilder recruitingActiveWhere(LocalDate today) {
private BooleanBuilder recruitingActiveWhere() {
BooleanBuilder where = new BooleanBuilder();
where.and(room.startDate.after(today));
where.and(room.roomStatus.eq(RoomStatus.RECRUITING));
return where;
}

Expand Down Expand Up @@ -91,10 +92,9 @@ private QRoomQueryDto projectionForRecruitingRoomSearch() {
*/
@Override
public List<RoomQueryDto> findRecruitingRoomsOrderByStartDateAsc(String keyword, LocalDate lastStartDate, Long roomId, int pageSize) {
final LocalDate today = LocalDate.now();
DateExpression<LocalDate> cursorExpr = room.startDate; // 커서 비교는 startDate

BooleanBuilder where = recruitingActiveWhere(today);
BooleanBuilder where = recruitingActiveWhere();
applyKeyword(where, keyword);
applyCursorStartDateAsc(where, cursorExpr, lastStartDate, roomId);

Expand All @@ -110,10 +110,9 @@ public List<RoomQueryDto> findRecruitingRoomsOrderByStartDateAsc(String keyword,

@Override
public List<RoomQueryDto> findRecruitingRoomsWithCategoryOrderByStartDateAsc(String keyword, Category category, LocalDate lastStartDate, Long roomId, int pageSize) {
final LocalDate today = LocalDate.now();
DateExpression<LocalDate> cursorExpr = room.startDate;

BooleanBuilder where = recruitingActiveWhere(today);
BooleanBuilder where = recruitingActiveWhere();
applyCategory(where, category);
applyKeyword(where, keyword);
applyCursorStartDateAsc(where, cursorExpr, lastStartDate, roomId);
Expand All @@ -130,9 +129,7 @@ public List<RoomQueryDto> findRecruitingRoomsWithCategoryOrderByStartDateAsc(Str

@Override
public List<RoomQueryDto> findRecruitingRoomsOrderByMemberCountDesc(String keyword, Integer lastMemberCount, Long roomId, int pageSize) {
final LocalDate today = LocalDate.now();

BooleanBuilder where = recruitingActiveWhere(today);
BooleanBuilder where = recruitingActiveWhere();
applyKeyword(where, keyword);
applyCursorMemberCountDesc(where, lastMemberCount, roomId);

Expand All @@ -148,9 +145,7 @@ public List<RoomQueryDto> findRecruitingRoomsOrderByMemberCountDesc(String keywo

@Override
public List<RoomQueryDto> findRecruitingRoomsWithCategoryOrderByMemberCountDesc(String keyword, Category category, Integer lastMemberCount, Long roomId, int pageSize) {
final LocalDate today = LocalDate.now();

BooleanBuilder where = recruitingActiveWhere(today);
BooleanBuilder where = recruitingActiveWhere();
applyCategory(where, category);
applyKeyword(where, keyword);
applyCursorMemberCountDesc(where, lastMemberCount, roomId);
Expand All @@ -174,7 +169,7 @@ public List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingR
.join(room.bookJpaEntity, book)
.where(
room.category.eq(category)
.and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각
.and(room.roomStatus.eq(RoomStatus.RECRUITING)) // 모집 중인 방
.and(room.roomId.ne(roomId))// 현재 방 제외
.and(room.isPublic.isTrue()) // 공개방 만
)
Expand Down Expand Up @@ -207,8 +202,9 @@ public List<RoomParticipantQueryDto> findHomeJoinedRoomsByUserPercentage(
// 활동 기간 중인 방만: startDate ≤ today ≤ endDate
BooleanBuilder where = new BooleanBuilder();
where.and(participant.userJpaEntity.userId.eq(userId));
where.and(room.startDate.loe(LocalDate.now()));
where.and(room.endDate.goe(LocalDate.now()));
// where.and(room.startDate.loe(LocalDate.now()));
// where.and(room.endDate.goe(LocalDate.now()));
where.and(room.roomStatus.eq(RoomStatus.IN_PROGRESS)); // 활동 기간 중인 방만: IN_PROGRESS 상태
Copy link
Collaborator

Choose a reason for hiding this comment

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

이것도 기존 코드를 주석이 아니라 삭제하는건 어떤가요??


// 커서 기반 추가 조건
if (userPercentageCursor != null && startDateCursor != null && roomIdCursor != null) {
Expand Down Expand Up @@ -252,7 +248,8 @@ public List<RoomQueryDto> findRecruitingRoomsUserParticipated(
) {
LocalDate today = LocalDate.now();
BooleanExpression base = participant.userJpaEntity.userId.eq(userId)
.and(room.startDate.after(today)); // 유저가 참여한 방 && 모집중인 방
// .and(room.startDate.after(today)); // 유저가 참여한 방 && 모집중인 방
.and(room.roomStatus.eq(RoomStatus.RECRUITING)); // 유저가 참여한 방 && 모집중인 방
DateExpression<LocalDate> cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일)
OrderSpecifier<?>[] orders = new OrderSpecifier<?>[]{
cursorExpr.asc(), room.roomId.asc()
Expand All @@ -268,8 +265,9 @@ public List<RoomQueryDto> findPlayingRoomsUserParticipated(
) {
LocalDate today = LocalDate.now();
BooleanExpression base = participant.userJpaEntity.userId.eq(userId)
.and(room.startDate.loe(today))
.and(room.endDate.goe(today)); // 유저가 참여한 방 && 현재 진행중인 방
// .and(room.startDate.loe(today))
// .and(room.endDate.goe(today)); // 유저가 참여한 방 && 현재 진행중인 방
.and(room.roomStatus.eq(RoomStatus.IN_PROGRESS)); // 유저가 참여한 방 && 현재 진행중인 방
DateExpression<LocalDate> cursorExpr = room.endDate; // 커서 비교는 endDate(= 진행 마감일)
OrderSpecifier<?>[] orders = new OrderSpecifier<?>[]{
cursorExpr.asc(), room.roomId.asc()
Expand All @@ -284,8 +282,10 @@ public List<RoomQueryDto> findPlayingAndRecruitingRoomsUserParticipated(
Long userId, Integer priorityCursor, 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 playing = room.startDate.loe(today).and(room.endDate.goe(today));
// BooleanExpression recruiting = room.startDate.after(today);
BooleanExpression playing = room.roomStatus.eq(RoomStatus.IN_PROGRESS);
BooleanExpression recruiting = room.roomStatus.eq(RoomStatus.RECRUITING);
BooleanExpression base = participant.userJpaEntity.userId.eq(userId)
.and(playing.or(recruiting)); // 유저가 참여한 방 && 현재 진행중인 방 + 모집중인 방

Expand All @@ -309,7 +309,8 @@ public List<RoomQueryDto> findExpiredRoomsUserParticipated(
) {
LocalDate today = LocalDate.now();
BooleanExpression base = participant.userJpaEntity.userId.eq(userId)
.and(room.endDate.before(today)); // 유저가 참여한 방 && 만료된 방
// .and(room.endDate.before(today)); // 유저가 참여한 방 && 만료된 방
.and(room.roomStatus.eq(RoomStatus.EXPIRED)); // 유저가 참여한 방 && 만료된 방

DateExpression<LocalDate> cursorExpr = room.endDate;
OrderSpecifier<?>[] orders = new OrderSpecifier<?>[]{
Expand Down Expand Up @@ -361,7 +362,8 @@ public List<RoomQueryDto> findRoomsByCategoryOrderByMemberCount(Category categor
public List<RoomQueryDto> findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize) {
DateExpression<LocalDate> cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일)
BooleanExpression baseCondition = room.bookJpaEntity.isbn.eq(isbn)
.and(room.startDate.after(LocalDate.now())); // 모집 마감 시각 > 현재 시각
// .and(room.startDate.after(LocalDate.now())); // 모집 마감 시각 > 현재 시각
.and(room.roomStatus.eq(RoomStatus.RECRUITING)); // 모집중인 방


if (dateCursor != null && roomIdCursor != null) { // 첫 페이지가 아닌 경우
Expand Down Expand Up @@ -389,7 +391,8 @@ public List<RoomQueryDto> findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalD

private BooleanExpression findDeadlinePopularRoomCondition(Category category, Long userId) {
return room.category.eq(category)
.and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각
// .and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각
.and(room.roomStatus.eq(RoomStatus.RECRUITING)) // 모집중인 방
.and(room.isPublic.isTrue()) // 공개 방만 조회
.and(userJoinedRoom(userId).not()); // 유저가 참여하지 않은 방만 조회
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse;
import konkuk.thip.room.application.port.out.dto.RoomParticipantQueryDto;
import konkuk.thip.room.application.port.out.dto.RoomQueryDto;
import konkuk.thip.room.domain.value.Category;
import konkuk.thip.room.domain.Room;
import konkuk.thip.room.domain.value.Category;

import java.time.LocalDate;
import java.util.List;

public interface RoomQueryPort {

int countRecruitingRoomsByBookAndStartDateAfter(String isbn, LocalDate currentDate);
int countRecruitingRoomsByBookIsbn(String isbn);

/**
* 방 검색
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public class RoomJoinService implements RoomJoinUseCase {
public RoomJoinResult changeJoinState(RoomJoinCommand roomJoinCommand) {
RoomJoinType type = roomJoinCommand.type();

// 방이 존재하지 않거나 만료된 경우
// 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리
Room room = roomCommandPort.findById(roomJoinCommand.roomId())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL));

validateRoom(room);
room.validateRoomRecruitExpired();

Comment on lines +37 to 42
Copy link

@coderabbitai coderabbitai bot Sep 12, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

JOIN/CANCEL 공통 선검증이 모집기간 만료 체크로 고정되어 취소 흐름을 막을 수 있습니다

현재 changeJoinState 초반의 room.validateRoomRecruitExpired()는 IN_PROGRESS 상태에서의 취소(CANCEL)까지 차단할 위험이 있습니다. 만료(EXPIRED) 차단은 공통, 모집기간(RECRUITING) 여부는 JOIN에만 적용하는 편이 안전합니다.

- // 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리
+ // 방이 존재하지 않거나 만료된 경우 예외 처리
 Room room = roomCommandPort.findById(roomJoinCommand.roomId())
         .orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL));
-
-room.validateRoomRecruitExpired();
+// 공통: 만료(EXPIRED) 차단
+room.validateRoomExpired();
 
 switch (type) {
-    case JOIN -> handleJoin(roomJoinCommand, roomParticipantOptional, room);
+    case JOIN -> handleJoin(roomJoinCommand, roomParticipantOptional, room); // JOIN 내에서 모집기간 검증
     case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, roomParticipantOptional, room);
 }

그리고 handleJoin 내부에서 모집기간 만료 검증을 수행하세요.

 private void handleJoin(RoomJoinCommand roomJoinCommand, Optional<RoomParticipant> participantOptional, Room room) {
+    // JOIN 전용: 모집기간(RECRUITING) 상태 검증
+    room.validateRoomRecruitExpired();
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/service/RoomJoinService.java
around lines 37-42, the current common pre-check always calls
room.validateRoomRecruitExpired(), which can incorrectly block CANCEL flows;
remove that expired check from the shared pre-validation and keep only
existence/other neutral checks there, then add a call to
room.validateRoomRecruitExpired() inside the handleJoin method so that EXPIRED
is enforced only for JOIN operations; ensure CANCEL flows do not invoke the
expired check.

Copy link
Collaborator

Choose a reason for hiding this comment

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

p3 : 현재 api 는 방 참여 상태 변경 api 이므로 아직 모집중인 방에 대해서 유저가 참여신청 or 참여신청 취소 하는 것이 맞는거죠??

코래의 답변은 저희 요구사항에 부합하지 않은 내용이 맞는지 확인차 여쭤봅니다!

Copy link

Choose a reason for hiding this comment

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

@seongjunnoh 맞습니다! 제가 요구사항을 잘못 이해했네요.

현재 방 참여 상태 변경 API는 모집중인 방(RECRUITING)에서만 JOIN과 CANCEL이 모두 가능해야 하는 것이 맞습니다. 따라서 현재 구현이 올바릅니다:

// 방이 존재하지 않거나 모집기간이 만료된 경우 예외 처리
Room room = roomCommandPort.findById(roomJoinCommand.roomId())
        .orElseThrow(() -> new BusinessException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL));

room.validateRoomRecruitExpired(); // JOIN, CANCEL 모두 모집중인 방에서만 가능

비즈니스 로직상 이유:

  • JOIN: 당연히 모집중일 때만 참여 가능
  • CANCEL: 모집중일 때 참여신청을 취소하는 것이므로, 이미 진행중(IN_PROGRESS)이거나 만료된(EXPIRED) 방에서는 취소할 이유가 없음

제가 이전에 제안한 내용은 틀렸으니 무시해 주세요. 현재 구현이 요구사항에 정확히 부합합니다.


✏️ Learnings added
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#300
File: src/main/java/konkuk/thip/room/application/service/RoomJoinService.java:37-42
Timestamp: 2025-09-13T06:46:25.061Z
Learning: seongjunnoh 프로젝트에서 방 참여 상태 변경 API(RoomJoinService.changeJoinState)는 모집중인 방(RECRUITING)에서만 JOIN과 CANCEL이 모두 가능하며, 공통 검증에서 room.validateRoomRecruitExpired()를 사용하는 것이 올바른 구현이다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 모집중인 방에 한해서만 참여, 참여취소가 가능합니다.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Optional<RoomParticipant> roomParticipantOptional = roomParticipantCommandPort.findByUserIdAndRoomIdOptional(roomJoinCommand.userId(), roomJoinCommand.roomId());

Expand Down Expand Up @@ -93,11 +93,6 @@ private void handleJoin(RoomJoinCommand roomJoinCommand, Optional<RoomParticipan
room.increaseMemberCount();
}

private void validateRoom(Room room) {
room.validateRoomRecruitExpired();
room.validateRoomExpired();
}

// 방장이 참여 취소를 요청한 경우
private void validateCancelable(RoomParticipant roomParticipant) {
if (roomParticipant.isHost()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package konkuk.thip.room.application.service.validator;

import konkuk.thip.common.annotation.application.HelperService;
import konkuk.thip.room.application.port.out.RoomCommandPort;
import konkuk.thip.room.domain.Room;
import lombok.RequiredArgsConstructor;

@HelperService
@RequiredArgsConstructor
public class RoomValidator {

private final RoomCommandPort roomCommandPort;

public void validateRoomExpired(Long roomId) {
Room room = roomCommandPort.getByIdOrThrow(roomId);
room.validateRoomExpired();
}
Comment on lines 8 to 17
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM


}
Loading