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
13 changes: 10 additions & 3 deletions src/main/java/konkuk/thip/common/util/DateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateUtil {

public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd");

//마지막 활동 시간 포맷팅 -> ex. 1분 전, 1시간 전, 1일 전
public static String formatBeforeTime(LocalDateTime createdAt) {
long minutes = Duration.between(createdAt, LocalDateTime.now()).toMinutes();
Expand All @@ -27,16 +30,20 @@ public static String formatAfterTime(LocalDate date) {

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

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

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

public static String formatDate(LocalDate date) {
return date.format(DATE_FORMATTER);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
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.in.RoomGetHomeJoinedListUseCase;
import konkuk.thip.room.application.port.in.RoomSearchUseCase;
import jakarta.validation.Valid;
import konkuk.thip.room.adapter.in.web.request.RoomVerifyPasswordRequest;
import konkuk.thip.room.application.port.in.RoomShowRecruitingDetailViewUseCase;
import konkuk.thip.room.application.port.in.RoomVerifyPasswordUseCase;
import konkuk.thip.room.application.port.in.dto.RoomGetHomeJoinedListQuery;
import lombok.RequiredArgsConstructor;
Expand All @@ -21,8 +23,9 @@
public class RoomQueryController {

private final RoomSearchUseCase roomSearchUseCase;
private final RoomGetHomeJoinedListUseCase roomGetHomeJoinedListUseCase;
private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase;
private final RoomShowRecruitingDetailViewUseCase roomShowRecruitingDetailViewUseCase;
private final RoomGetHomeJoinedListUseCase roomGetHomeJoinedListUseCase;

@GetMapping("/rooms/search")
public BaseResponse<RoomSearchResponse> searchRooms(
Expand All @@ -42,6 +45,13 @@ public BaseResponse<Void> verifyRoomPassword(@PathVariable("roomId") final Long
return BaseResponse.ok(roomVerifyPasswordUseCase.verifyRoomPassword(roomVerifyPasswordRequest.toQuery(roomId)));
}

@GetMapping("/rooms/{roomId}/recruiting")
public BaseResponse<RoomRecruitingDetailViewResponse> getRecruitingRoomDetailView(
@UserId final Long userId,
@PathVariable("roomId") final Long roomId) {
return BaseResponse.ok(roomShowRecruitingDetailViewUseCase.getRecruitingRoomDetailView(userId, roomId));
}

//[모임 홈] 참여중인 내 모임방 조회
@GetMapping("/rooms/home/joined")
public BaseResponse<RoomGetHomeJoinedListResponse> getHomeJoinedRooms(@UserId final Long userId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package konkuk.thip.room.adapter.in.web.response;

import lombok.Builder;

import java.util.List;

public record RoomRecruitingDetailViewResponse(
boolean isHost,
boolean isJoining,
Long roomId,
String roomName,
String roomImageUrl,
boolean isPublic,
String progressStartDate,
String progressEndDate,
String recruitEndDate,
String category,
String roomDescription,
int memberCount,
int recruitCount,
String isbn,
String bookImageUrl,
String bookTitle,
String authorName,
String bookDescription,
List<RecommendRoom> recommendRooms
) {
@Builder
public record RecommendRoom(
String roomImageUrl,
String roomName,
int memberCount,
int recruitCount,
String recruitEndDate
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class CategoryJpaEntity extends BaseJpaEntity {
@Column(name = "category_value",length = 50, nullable = false)
private String value;

@Column(name = "image_url", columnDefinition = "TEXT", nullable = false)
private String imageUrl;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_alias_id", nullable = false)
private AliasJpaEntity aliasForCategoryJpaEntity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package konkuk.thip.room.adapter.out.persistence;

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.application.port.out.RoomQueryPort;
import konkuk.thip.room.domain.Room;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

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

@Repository
@RequiredArgsConstructor
Expand All @@ -28,6 +31,11 @@ public Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, Stri
return roomJpaRepository.searchRoom(keyword, category, pageable);
}

@Override
public List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Room currentRoom, int count) {
return roomJpaRepository.findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(currentRoom.getId(), currentRoom.getCategory().getValue(), count);
}

@Override
public Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate date, Pageable pageable) {
return roomJpaRepository.searchHomeJoinedRooms(userId, date, pageable);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package konkuk.thip.room.adapter.out.persistence;

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 org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

import java.time.LocalDate;

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.querydsl.jpa.impl.JPAQueryFactory;
import konkuk.thip.book.adapter.out.jpa.QBookJpaEntity;
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.out.jpa.QRoomJpaEntity;
Expand Down Expand Up @@ -143,6 +144,34 @@ private OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, Numbe
}
}

@Override
public List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count) {
NumberExpression<Long> memberCountExpr = userRoom.userRoomId.count();
List<Tuple> tuples = queryFactory
.select(room.roomId, room.title, memberCountExpr, room.recruitCount, room.startDate)
.from(room)
.leftJoin(userRoom).on(userRoom.roomJpaEntity.eq(room))
.where(
room.categoryJpaEntity.value.eq(category)
.and(room.startDate.after(LocalDate.now())) // 모집 마감 시각 > 현재 시각
.and(room.roomId.ne(roomId)) // 현재 방 제외
)
.groupBy(room.roomId, room.title, room.recruitCount, room.startDate)
.orderBy(room.startDate.asc())
.limit(count)
.fetch();

return tuples.stream()
.map(t -> RoomRecruitingDetailViewResponse.RecommendRoom.builder()
.roomImageUrl(null) // roomImageUrl은 추후 구현
.roomName(t.get(room.title))
.memberCount(t.get(memberCountExpr).intValue())
.recruitCount(t.get(room.recruitCount))
.recruitEndDate(DateUtil.formatAfterTime(t.get(room.startDate)))
.build())
.toList();
}

@Override
public Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate date, Pageable pageable) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package konkuk.thip.room.application.port.in;

import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse;

public interface RoomShowRecruitingDetailViewUseCase {

RoomRecruitingDetailViewResponse getRecruitingRoomDetailView(Long userId, Long roomId);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package konkuk.thip.room.application.port.out;

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.domain.Room;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

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

public interface RoomQueryPort {
int countRecruitingRoomsByBookAndStartDateAfter(Long bookId, LocalDate currentDate);
Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable);

List<RoomRecruitingDetailViewResponse.RecommendRoom> findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Room currentRoom, int count);
Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package konkuk.thip.room.application.service;

import konkuk.thip.book.application.port.out.BookCommandPort;
import konkuk.thip.book.domain.Book;
import konkuk.thip.common.util.DateUtil;
import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse;
import konkuk.thip.room.application.port.in.RoomShowRecruitingDetailViewUseCase;
import konkuk.thip.room.application.port.out.RoomCommandPort;
import konkuk.thip.room.application.port.out.RoomQueryPort;
import konkuk.thip.room.domain.Room;
import konkuk.thip.user.application.port.out.UserRoomCommandPort;
import konkuk.thip.user.domain.UserRoom;
import konkuk.thip.user.domain.RoomParticipants;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class RoomShowRecruitingDetailViewService implements RoomShowRecruitingDetailViewUseCase {

private final static int RECOMMEND_ROOM_COUNT = 5;

private final RoomCommandPort roomCommandPort;
private final RoomQueryPort roomQueryPort;
private final BookCommandPort bookCommandPort;
private final UserRoomCommandPort userRoomCommandPort;

@Override
@Transactional(readOnly = true)
public RoomRecruitingDetailViewResponse getRecruitingRoomDetailView(Long userId, Long roomId) {
// 1. Room 조회, Book 조회
Room room = roomCommandPort.findById(roomId);
Book book = bookCommandPort.findById(room.getBookId());

// 2. Room과 연관된 UserRoom 조회, RoomParticipants 일급 컬렉션 생성
List<UserRoom> findByRoomId = userRoomCommandPort.findAllByRoomId(roomId);
RoomParticipants roomParticipants = RoomParticipants.from(findByRoomId);

// 3. 다른 모임방 추천
List<RoomRecruitingDetailViewResponse.RecommendRoom> recommendRooms = roomQueryPort.findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(room, RECOMMEND_ROOM_COUNT);

// 4. response 구성
return buildResponse(userId, room, book, roomParticipants, recommendRooms);
}

private RoomRecruitingDetailViewResponse buildResponse(
Long userId,
Room room,
Book book,
RoomParticipants participants,
List<RoomRecruitingDetailViewResponse.RecommendRoom> recommendRooms
) {
return new RoomRecruitingDetailViewResponse(
participants.isHostOfRoom(userId),
participants.isJoiningToRoom(userId),
Comment on lines +57 to +58

@buzz0331 buzz0331 Jul 12, 2025

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.

p3: 두 메서드의 조건이 중복되는 것 같은데 isJoiningToRoom(user)를 먼저 호출하여 true일 경우에만 isHostOfRoom(userId)을 호출하는거 어떨까요?

단순 의견이라 꼭 적용할 필요는 없습니다!

@seongjunnoh seongjunnoh Jul 13, 2025

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.

response를 구성할때 if 분기처리를 통해서 isJoining 이 true인 경우에만 isHost 메서드를 호출하는 흐름을 말하시는거 같은데, response를 구성할때 분기처리 로직을 추가하는거 보다, 단순히 메서드를 호출하는게 더 가독성이 있지않나 싶어서 이렇게 구현했긴합니다!

room.getId(),
room.getTitle(),
room.getCategory().getImageUrl(),
room.isPublic(),
DateUtil.formatDate(room.getStartDate()),
DateUtil.formatDate(room.getEndDate()),
DateUtil.formatAfterTime(room.getStartDate()),
room.getCategory().getValue(),
room.getDescription(),
participants.calculateMemberCount(),
room.getRecruitCount(),
book.getIsbn(),
book.getImageUrl(),
book.getTitle(),
book.getAuthorName(),
book.getDescription(),
recommendRooms
);
}
}
11 changes: 6 additions & 5 deletions src/main/java/konkuk/thip/room/domain/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ public enum Category {
* DB에 저장되어 있는 모든 카테고리들의 이름
* TODO : DB에서 value를 통해 카테고리를 조회하는것보다 id로 조회하는게 성능상 좋으니, id 값도 같이 보관 ??
*/
SCIENCE_IT("과학/IT"),
LITERATURE("문학"),
ART("예술"),
SOCIAL_SCIENCE("사회과학"),
HUMANITY("인문학");
SCIENCE_IT("과학/IT", "과학/IT_image"),
LITERATURE("문학", "문학_image"),
ART("예술", "예술_image"),
SOCIAL_SCIENCE("사회과학", "사회과학_image"),
HUMANITY("인문학", "인문학_image");

private final String value;
private final String imageUrl;

public static Category from(String value) {
return Arrays.stream(Category.values())
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/konkuk/thip/user/domain/RoomParticipants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package konkuk.thip.user.domain;

import konkuk.thip.user.adapter.out.jpa.UserRoomRole;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.List;

@Getter
@RequiredArgsConstructor
public class RoomParticipants {
/**
* 특정 Room 과 연관된 UserRoom 들을 모은 일급 컬렉션

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

*/

private final List<UserRoom> participants;

public static RoomParticipants from(List<UserRoom> participants) {
return new RoomParticipants(participants);
}

public int calculateMemberCount() {
return participants.size();
}

public boolean isJoiningToRoom(Long userId) {
return participants.stream()
.anyMatch(userRoom -> userRoom.getUserId().equals(userId));
}

public boolean isHostOfRoom(Long userId) {
return participants.stream()
.filter(userRoom -> userRoom.getUserId().equals(userId))
.anyMatch(userRoom -> userRoom.getUserRoomRole().equals(UserRoomRole.HOST.getType()));
}
}
Comment on lines +8 to +36

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.

p3: 특정 Room과 연관된 UserRoom들이니까 Room 도메인에 속하지 않을까 싶은데 User 패키지 아래 두신 이유가 따로있나욥

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.

딱히 이유 없습니다! 그냥 UserRoom 의 List를 포함하는 일급컬렉션이어서 UserRoom 과 같은 패키지에 위치시켰습니다!

2 changes: 2 additions & 0 deletions src/test/java/konkuk/thip/common/util/TestEntityFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static AliasJpaEntity createLiteratureAlias() {
public static CategoryJpaEntity createLiteratureCategory(AliasJpaEntity alias) {
return CategoryJpaEntity.builder() // 실제 존재하는 값으로
.value("문학")
.imageUrl("문학_image")
.aliasForCategoryJpaEntity(alias)
.build();
}
Expand All @@ -43,6 +44,7 @@ public static AliasJpaEntity createScienceAlias() {
public static CategoryJpaEntity createScienceCategory(AliasJpaEntity alias) {
return CategoryJpaEntity.builder() // 실제 존재하는 값으로
.value("과학/IT")
.imageUrl("과학/IT_image")
.aliasForCategoryJpaEntity(alias)
.build();
}
Expand Down
Loading