Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -99,7 +99,7 @@ public enum ErrorCode implements ResponseCode {
INVALID_ROOM_SEARCH_SORT(HttpStatus.BAD_REQUEST, 100005, "방 검색 시 정렬 조건이 잘못되었습니다."),
ROOM_MEMBER_COUNT_EXCEEDED(HttpStatus.BAD_REQUEST, 100006, "방의 최대 인원 수를 초과했습니다."),
ROOM_MEMBER_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 100007, "방의 인원 수가 1 이하(방장 포함)입니다."),
ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, "방이 만료되었습니다."),
ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, " 완료된 모임방에서는 기존 기록에 대한 조회만 가능해요."),
ROOM_POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 100009, "일치하는 방 게시물 타입 이름이 없습니다. [RECORD, VOTE] 중 하나여야 합니다."),
ROOM_NOT_IN_PROGRESS(HttpStatus.BAD_REQUEST, 100010, "진행 중인 방이 아닙니다."),

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/konkuk/thip/common/security/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ public class JwtUtil {

private final SecretKey secretKey;

//todo 확정 후 환경변수로 변경
private final long tokenExpiredMs = 2592000000L; // 30일
private final long signupTokenExpiredMs = 2592000000L; // 30일
@Value("${jwt.access-token-expiration}")
private long tokenExpiredMs;
@Value("${jwt.signup-token-expiration}")
private long signupTokenExpiredMs;
Comment on lines +28 to +31
Copy link
Contributor

Choose a reason for hiding this comment

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

굿 감사합니다~


public JwtUtil(@Value("${jwt.secret}") String secret) {
secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public enum SwaggerResponseDescription {
ROOM_GET_MEMBER_LIST(new LinkedHashSet<>(Set.of(
ROOM_NOT_FOUND
))),
ROOM_PLAYING_DETAIL(new LinkedHashSet<>(Set.of(
ROOM_PLAYING_OR_EXPIRED_DETAIL(new LinkedHashSet<>(Set.of(
BOOK_NOT_FOUND,
ROOM_NOT_FOUND,
ROOM_ACCESS_FORBIDDEN
Expand All @@ -109,7 +109,7 @@ public enum SwaggerResponseDescription {
BOOK_NOT_FOUND,
ROOM_ACCESS_FORBIDDEN
))),
ROOM_GET_DEADLINE_POPULAR(new LinkedHashSet<>(Set.of(
ROOM_GET_DEADLINE_POPULAR_RECENT(new LinkedHashSet<>(Set.of(
CATEGORY_NOT_MATCH
))),
CHANGE_ROOM_LIKE_STATE(new LinkedHashSet<>(Set.of(
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/konkuk/thip/common/util/DateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static String formatAfterTime(LocalDate date) {
return minutes + "분 뒤";
}

public static String RecruitingRoomFormatAfterTime(LocalDate date) {
public static String recruitingRoomFormatAfterTime(LocalDate date) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime dateTime = date.atStartOfDay();
Duration d = Duration.between(now, dateTime);
Expand All @@ -68,6 +68,29 @@ public static String RecruitingRoomFormatAfterTime(LocalDate date) {
return "마감 임박";
}

public static String recruitingRoomFormatAfterTimeSimple(LocalDate date) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime dateTime = date.atStartOfDay();
Duration d = Duration.between(now, dateTime);

if (d.isNegative() || d.isZero()) {
return "??";
}

long days = d.toDays();
if (days > 0) {
return days + "일";
}

long hours = d.toHours();
if (hours >= 1) {
return hours + "시간";
}

long minutes = d.toMinutes();
return minutes + "분";
}


public static String formatDate(LocalDate date) {
return date.format(DATE_FORMATTER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ public class RecentSearchCreateManager {
private final RecentSearchQueryPort recentSearchQueryPort;

public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) {
// 검색완료일 경우에 최근검색어 추가
if (isFinalized) {
// 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
() -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장
);
}
if (!isFinalized) return; // 검색완료일 경우에 최근검색어 추가
if (keyword == null || keyword.trim().isEmpty()) return;
String normalized = keyword.trim();
Comment on lines +19 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

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

확인했습니다


// 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(normalized, userId, type)
.ifPresentOrElse(
recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신
() -> recentSearchCommandPort.save(RecentSearch.withoutId(normalized, type, userId)) // 없으면 새로 저장
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public class RoomQueryController {
private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase;
private final RoomShowRecruitingDetailViewUseCase roomShowRecruitingDetailViewUseCase;
private final RoomGetMemberListUseCase roomGetMemberListUseCase;
private final RoomShowPlayingDetailViewUseCase roomShowPlayingDetailViewUseCase;
private final RoomShowPlayingOrExpiredDetailViewUseCase roomShowPlayingOrExpiredDetailViewUseCase;
private final RoomShowMineUseCase roomShowMineUseCase;
private final RoomGetBookPageUseCase roomGetBookPageUseCase;
private final RoomGetDeadlinePopularUseCase roomGetDeadlinePopularUsecase;
private final RoomGetDeadlinePopularRecentUseCase roomGetDeadlinePopularRecentUseCase;

@Operation(
summary = "모집중인 방 검색",
Expand All @@ -41,14 +41,15 @@ public class RoomQueryController {
public BaseResponse<RoomSearchResponse> searchRecruitingRooms(
@Parameter(description = "검색 키워드 (책 이름 or 방 이름)", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword,
@Parameter(description = "모임방 카테고리", example = "문학") @RequestParam(value = "category", required = false, defaultValue = "") final String category,
@Parameter(description = "전체검색여부 (전체 검색에 해당할때만 true로 보내주세요) ", example = "true") @RequestParam(value = "isAllCategory", required = false, defaultValue = "false") final boolean isAllCategory,
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3 : 혹시 isAllCategory 라는 request param 은 FE 분들과 사전 상의가 된 네이밍일까요??

Copy link
Member Author

Choose a reason for hiding this comment

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

넵 희용띠니와 상의해서 전체 파라미터를 나타내는 변수로 정했습니다!!

@Parameter(description = "정렬 방식 (마감 임박 : deadline, 신청 인원 : memberCount)", example = "deadline") @RequestParam("sort") final String sort,
Comment on lines +44 to 45
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

isAllCategory 플래그: 파라미터-로직 규칙 명확화 필요

권장 규칙:

  • isAllCategory=true → 카테고리 무시, 키워드 없어도 전체검색 허용.
  • isAllCategory=false → keyword 또는 category 중 최소 하나는 필수. 둘 다 빈 값이면 400 반환.
  • category와 isAllCategory=true가 함께 온 경우 서버에서 category 무시(로그로 경고).

RoomSearchQuery.of(...)가 위 규칙을 강제하는지 확인 부탁드립니다. 필요 시 검증 실패 응답 및 스웨거 설명에 예시 추가 권장.

서명 변경 검증:

원하시면 유효성 매트릭스(키워드/카테고리/isAllCategory 조합별 동작) 테스트 코드도 작성해 드립니다.

Also applies to: 52-52


🏁 Script executed:

#!/bin/bash
rg -nP 'RoomSearchQuery\.of\(' -C2

Length of output: 570


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --hidden -S '\b(class|record|interface)\s+RoomSearchQuery\b' src || true
rg -n --hidden -S 'RoomSearchQuery\.of\(' src || true
files=$(rg -n --hidden -S '\b(class|record|interface)\s+RoomSearchQuery\b' src -l || true)
if [ -n "$files" ]; then
  for file in $files; do
    echo "---- $file (first 300 lines) ----"
    sed -n '1,300p' "$file" || true
  done
fi

Length of output: 984


isAllCategory 유효성 추가 — RoomSearchQuery.of가 현재 검증을 수행하지 않습니다

확인: src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java의 of(...)는 단순히 레코드 인스턴스만 생성합니다.

  • 필수 규칙(반드시 구현):
    • isAllCategory=true → category 무시, keyword 없어도 전체검색 허용.
    • isAllCategory=false → keyword 또는 category 중 최소 하나 필수; 둘 다 비었으면 400 반환.
    • 요청에 category와 isAllCategory=true 동시 포함 시 category 무시하고 경고 로그 남김.
  • 권장 구현 위치: RoomSearchQuery.of(...) 내부에서 검증 후 예외 발생(컨트롤러에서 400으로 매핑) 또는 컨트롤러(파라미터 바인딩 지점)에서 사전검증.
  • 수정 대상(정확한 포인터):
    • src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java (of 메서드)
    • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (파라미터 처리 — 약 44–52라인)
  • 추가: 스웨거 파라미터 설명에 동작 규칙/예시 명시 및 유효성 실패(400) 응답 문서화.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java
update the of(...) factory to perform the described validation: if isAllCategory
is true, ignore any provided category (do not drop keyword) and return the
record while logging a warning when category was supplied; if isAllCategory is
false, require that at least one of keyword or category is non-empty and throw
an IllegalArgumentException (or a custom validation exception) when both are
empty; ensure the exception will be translated to HTTP 400 by the controller
advice or exception handler. In
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java around
lines 44–52, adjust parameter handling/documentation: update Swagger @Parameter
descriptions to explain the validation rules/behavior and examples, remove
duplicate server-side validation (rely on RoomSearchQuery.of(...) for
enforcement) and ensure that any thrown validation exception is mapped to a 400
response in API docs.

@Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized,
@Parameter(description = "커서 (첫번째 요청시 : null, 다음 요청시 : 이전 요청에서 반환받은 nextCursor 값)")
@RequestParam(value = "cursor", required = false) final String cursor,
@Parameter(hidden = true) @UserId final Long userId
) {
return BaseResponse.ok(roomSearchUseCase.searchRecruitingRooms(
RoomSearchQuery.of(keyword, category, sort, isFinalized, cursor, userId)
RoomSearchQuery.of(keyword, category, sort, isFinalized, cursor, userId,isAllCategory)
));
}

Expand Down Expand Up @@ -79,7 +80,7 @@ public BaseResponse<RoomRecruitingDetailViewResponse> getRecruitingRoomDetailVie

@Operation(
summary = "[모임 홈] 참여중인 내 모임방 조회",
description = "사용자가 참여중인 모임방 목록을 조회합니다."
description = "사용자가 참여중인 (모집중/진행중인 방) 모임방 목록을 조회합니다."
)
@ExceptionDescription(ROOM_GET_HOME_JOINED_LIST)
@GetMapping("/rooms/home/joined")
Expand All @@ -105,18 +106,18 @@ public BaseResponse<RoomGetMemberListResponse> getRoomMemberList(
return BaseResponse.ok(roomGetMemberListUseCase.getRoomMemberList(userId, roomId));
}

// 진행중인 방 상세보기
// 진행중인/완료된 방 상세보기
@Operation(
summary = "진행중인 방 상세보기",
description = "진행중인 방의 상세 정보를 조회합니다."
summary = "진행중인/완료된 방 상세보기",
description = "진행중인/완료된 방의 상세 정보를 조회합니다."
)
@ExceptionDescription(ROOM_PLAYING_DETAIL)
@GetMapping("/rooms/{roomId}/playing")
public BaseResponse<RoomPlayingDetailViewResponse> getPlayingRoomDetailView(
@ExceptionDescription(ROOM_PLAYING_OR_EXPIRED_DETAIL)
@GetMapping("/rooms/{roomId}")
public BaseResponse<RoomPlayingOrExpiredDetailViewResponse> getPlayingOrExpiredRoomDetailView(
@Parameter(hidden = true) @UserId final Long userId,
@PathVariable("roomId") final Long roomId
) {
return BaseResponse.ok(roomShowPlayingDetailViewUseCase.getPlayingRoomDetailView(userId, roomId));
return BaseResponse.ok(roomShowPlayingOrExpiredDetailViewUseCase.getPlayingOrExpiredRoomDetailView(userId, roomId));
}

// 내 모임방 리스트 조회
Expand Down Expand Up @@ -148,16 +149,15 @@ public BaseResponse<RoomGetBookPageResponse> getBookPage(
}

@Operation(
summary = "마감 임박 및 인기 방 조회",
description = "카테고리별로 마감 임박 방과 인기 방을 조회합니다."
summary = "마감 임박/인기 방/최근 생성된 방 조회",
description = "카테고리별로 마감 임박 방, 인기 방, 최근 생성된 방을 조회합니다."
)
@ExceptionDescription(ROOM_GET_DEADLINE_POPULAR)
@ExceptionDescription(ROOM_GET_DEADLINE_POPULAR_RECENT)
@GetMapping("/rooms")
public BaseResponse<RoomGetDeadlinePopularResponse> getDeadlineAndPopularRoomList(
public BaseResponse<RoomGetDeadlinePopularRecentResponse> getDeadlineAndPopularAndRecentRoomList(
@Parameter(description = "카테고리 이름 (default : 문학)", example = "과학/IT")
@RequestParam(value = "category", defaultValue = "문학") final String category,
@Parameter(hidden = true) @UserId final Long userId
@RequestParam(value = "category", defaultValue = "문학") final String category
) {
return BaseResponse.ok(roomGetDeadlinePopularUsecase.getDeadlineAndPopularRoomList(category, userId));
return BaseResponse.ok(roomGetDeadlinePopularRecentUseCase.getDeadlineAndPopularAndRecentRoomList(category));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package konkuk.thip.room.adapter.in.web.response;

import java.util.List;

public record RoomGetDeadlinePopularRecentResponse(
List<RoomGetDeadlinePopularRecentDto> deadlineRoomList,
List<RoomGetDeadlinePopularRecentDto> popularRoomList,
List<RoomGetDeadlinePopularRecentDto> recentRoomList
) {
public record RoomGetDeadlinePopularRecentDto(
Long roomId,
String bookImageUrl,
String roomName,
int recruitCount, // 방 최대 인원 수
int memberCount,
String deadlineDate
) {
}

public static RoomGetDeadlinePopularRecentResponse of(List<RoomGetDeadlinePopularRecentDto> deadlineRoomList,
List<RoomGetDeadlinePopularRecentDto> popularRoomList,
List<RoomGetDeadlinePopularRecentDto> recentRoomList) {
return new RoomGetDeadlinePopularRecentResponse(deadlineRoomList, popularRoomList, recentRoomList);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package konkuk.thip.room.adapter.in.web.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

public record RoomGetHomeJoinedListResponse(
Expand All @@ -13,7 +15,12 @@ public record JoinedRoomInfo(
String bookImageUrl,
String roomTitle,
int memberCount,
int userPercentage
@Schema(description = "진행중인 방에서 유저의 방 진행도, 모집중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.",
example = "35")
int userPercentage,
@Schema(description = "모집중인 방에서 방 모집 마감일까지 남은 시간, 진행중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.",
example = "3일")
String deadlineDate // 방 모집 마감일 (~일/시 형식)
) {}
public static RoomGetHomeJoinedListResponse of(List<RoomGetHomeJoinedListResponse.JoinedRoomInfo> roomList,
String nickname, String nextCursor, boolean isLast){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.util.List;

@Builder
public record RoomPlayingDetailViewResponse(
public record RoomPlayingOrExpiredDetailViewResponse(
boolean isHost,
Long roomId,
String roomName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.springframework.stereotype.Repository;

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

@Repository
Expand Down Expand Up @@ -169,13 +170,18 @@ private CursorBasedList<RoomQueryDto> findRoomsByMemberCountCursor(Cursor cursor
}

@Override
public List<RoomQueryDto> findRoomsByCategoryOrderByDeadline(Category category, int limit, Long userId) {
return roomJpaRepository.findRoomsByCategoryOrderByStartDateAsc(category, limit, userId);
public List<RoomQueryDto> findRoomsByCategoryOrderByDeadline(Category category, int limit) {
return roomJpaRepository.findRoomsByCategoryOrderByStartDateAsc(category, limit);
}

@Override
public List<RoomQueryDto> findRoomsByCategoryOrderByPopular(Category category, int limit, Long userId) {
return roomJpaRepository.findRoomsByCategoryOrderByMemberCount(category, limit, userId);
public List<RoomQueryDto> findRoomsByCategoryOrderByPopular(Category category, int limit) {
return roomJpaRepository.findRoomsByCategoryOrderByMemberCount(category, limit);
}

@Override
public List<RoomQueryDto> findRoomsByCategoryOrderByRecent(Category category, LocalDateTime createdAfter, int limit) {
return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, createdAfter, limit);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import konkuk.thip.room.application.port.out.dto.RoomQueryDto;
import konkuk.thip.room.domain.value.Category;

import java.time.LocalDateTime;
import java.util.List;

import java.time.LocalDate;
Expand All @@ -31,9 +32,11 @@ public interface RoomQueryRepository {

List<RoomQueryDto> findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize);

List<RoomQueryDto> findRoomsByCategoryOrderByStartDateAsc(Category category, int limit, Long userId);
List<RoomQueryDto> findRoomsByCategoryOrderByStartDateAsc(Category category, int limit);

List<RoomQueryDto> findRoomsByCategoryOrderByMemberCount(Category category, int limit, Long userId);
List<RoomQueryDto> findRoomsByCategoryOrderByMemberCount(Category category, int limit);

List<RoomQueryDto> findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime createdAfter, int limit);

List<RoomQueryDto> findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize);
}
Loading