Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
@@ -1,11 +1,15 @@
package konkuk.thip.room.adapter.in.web;

import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
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.RoomVerifyPasswordUseCase;
import konkuk.thip.room.application.port.in.dto.RoomGetHomeJoinedListQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -17,6 +21,8 @@
public class RoomQueryController {

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

@GetMapping("/rooms/search")
public BaseResponse<RoomSearchResponse> searchRooms(
Expand All @@ -27,7 +33,6 @@ public BaseResponse<RoomSearchResponse> searchRooms(
) {
return BaseResponse.ok(roomSearchUseCase.searchRoom(keyword, category, sort, page));
}
private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase;

//비공개 방 비밀번호 입력 검증
@PostMapping("/rooms/{roomId}/password")
Expand All @@ -37,4 +42,14 @@ public BaseResponse<Void> verifyRoomPassword(@PathVariable("roomId") final Long
return BaseResponse.ok(roomVerifyPasswordUseCase.verifyRoomPassword(roomVerifyPasswordRequest.toQuery(roomId)));
}

//[모임 홈] 참여중인 내 모임방 조회
@GetMapping("/rooms/home/joined")
public BaseResponse<RoomGetHomeJoinedListResponse> getHomeJoinedRooms(@UserId final Long userId,
@RequestParam("page") final int page) {
return BaseResponse.ok(roomGetHomeJoinedListUseCase.getHomeJoinedRoomList(
RoomGetHomeJoinedListQuery.builder()
.userId(userId)
.page(page).build()));
}

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

import lombok.Builder;

import java.util.List;

@Builder
public record RoomGetHomeJoinedListResponse(
List<RoomSearchResult> roomList,
String nickname,
int page, // 현재 페이지
int size, // 현재 페이지에 포함된 데이터 수
boolean last,
boolean first
) {


@Builder
public record RoomSearchResult(
Long roomId,
String bookImageUrl,
String bookTitle,
int memberCount,
int userPercentage
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import java.time.LocalDate;

//TODO 방에 이방에 참여중인 인원수 추가
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

@Entity
@Table(name = "rooms")
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package konkuk.thip.room.adapter.out.persistence;

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;
Expand All @@ -26,4 +27,9 @@ public int countRecruitingRoomsByBookAndStartDateAfter(Long bookId, LocalDate cu
public Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable) {
return roomJpaRepository.searchRoom(keyword, category, pageable);
}

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

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.time.LocalDate;

public interface RoomQueryRepository {

Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable);
Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
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.RoomGetHomeJoinedListResponse;
import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse;
import konkuk.thip.room.adapter.out.jpa.QRoomJpaEntity;
import konkuk.thip.user.adapter.out.jpa.QUserRoomJpaEntity;
Expand All @@ -21,6 +24,7 @@

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

@Repository
@RequiredArgsConstructor
Expand All @@ -31,6 +35,7 @@ public class RoomQueryRepositoryImpl implements RoomQueryRepository {
private final QBookJpaEntity book = QBookJpaEntity.bookJpaEntity;
private final QUserRoomJpaEntity userRoom = QUserRoomJpaEntity.userRoomJpaEntity;


@Override
public Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable) {
// 1. 검색 조건(where) 조립 : 방이름 or 첵제목에 keyword 포함, category 필터 적용, 멤버 모집중인(= 활동 시작전인) 방만 검색
Expand Down Expand Up @@ -137,4 +142,78 @@ private OrderSpecifier<?> toOrderSpecifier(Sort sort, QRoomJpaEntity room, Numbe
return room.startDate.asc();
}
}

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

QUserRoomJpaEntity userRoomSub = new QUserRoomJpaEntity("userRoomSub");

// 1. 검색 조건(where) 조립
// 유저가 참여한 방만: userId 조건
// 활동 기간 중인 방만: startDate ≤ today ≤ endDate
BooleanBuilder where = new BooleanBuilder();
where.and(userRoom.userJpaEntity.userId.eq(userId));
where.and(room.startDate.loe(date));
where.and(room.endDate.goe(date));
Comment on lines +151 to +157
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM


// TODO : Room 에 멤버 수 추가되면 로직 수정
// 멤버 수 서브쿼리
JPQLQuery<Long> memberCountSubQuery = JPAExpressions
.select(userRoomSub.count())
.from(userRoomSub)
.where(userRoomSub.roomJpaEntity.roomId.eq(room.roomId));

// 2. 페이징된 목록 조회
List<Tuple> tuples = queryFactory
.select(
room.roomId,
book.imageUrl,
room.title,
memberCountSubQuery,
room.recruitCount,
room.startDate,
book.title,
userRoom.userPercentage
)
.from(userRoom)
.join(userRoom.roomJpaEntity, room)
.join(room.bookJpaEntity, book)
.where(where)
.orderBy(
userRoom.userPercentage.desc(), // 진행률 높은 순(내림차순)
room.startDate.asc() // 진행률 같으면 활동 시작일 빠른 순 (오름차순)
)
Comment on lines +182 to +185
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// TODO : 추후에 오프셋 페이징이 아니라, 키셋 페이징 기법 도입 검토

// 3. Tuple → DTO 매핑
List<RoomGetHomeJoinedListResponse.RoomSearchResult> content = tuples.stream()
.map(t -> RoomGetHomeJoinedListResponse.RoomSearchResult.builder()
.roomId(t.get(room.roomId))
.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))
.map(val -> ((Number) val).doubleValue())
.map(Math::round)
.map(Long::intValue)
.orElse(0))
.build()
)
.toList();

// 4. 전체 개수 조회 (페이징 정보 계산용)
Long totalCount = queryFactory
.select(userRoom.count())
.from(userRoom)
.join(userRoom.roomJpaEntity, room)
.where(where)
.fetchOne();
long total = (totalCount != null) ? totalCount : 0L;

// 5. PageImpl 생성하여 반환
return new PageImpl<>(content, pageable, total);
}
}
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.RoomGetHomeJoinedListResponse;
import konkuk.thip.room.application.port.in.dto.RoomGetHomeJoinedListQuery;

public interface RoomGetHomeJoinedListUseCase {
RoomGetHomeJoinedListResponse getHomeJoinedRoomList(RoomGetHomeJoinedListQuery roomGetHomeJoinedListQuery);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package konkuk.thip.room.application.port.in.dto;

import lombok.Builder;

@Builder
public record RoomGetHomeJoinedListQuery(
Long userId,
int page
) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package konkuk.thip.room.application.port.out;

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;
Expand All @@ -8,6 +9,6 @@

public interface RoomQueryPort {
int countRecruitingRoomsByBookAndStartDateAfter(Long bookId, LocalDate currentDate);

Page<RoomSearchResponse.RoomSearchResult> searchRoom(String keyword, String category, Pageable pageable);
Page<RoomGetHomeJoinedListResponse.RoomSearchResult> searchHomeJoinedRooms(Long userId, LocalDate today, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package konkuk.thip.room.application.service;

import konkuk.thip.common.exception.InvalidStateException;
import konkuk.thip.common.exception.code.ErrorCode;
import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse;
import konkuk.thip.room.application.port.in.RoomGetHomeJoinedListUseCase;
import konkuk.thip.room.application.port.in.dto.RoomGetHomeJoinedListQuery;
import konkuk.thip.room.application.port.out.RoomQueryPort;
import konkuk.thip.user.application.port.out.UserCommandPort;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;

@Service
@RequiredArgsConstructor
public class RoomGetHomeJoinedListService implements RoomGetHomeJoinedListUseCase {

private static final int DEFAULT_PAGE_SIZE = 10;

private final RoomQueryPort roomQueryPort;
private final UserCommandPort userCommandPort;

@Override
@Transactional(readOnly = true)
public RoomGetHomeJoinedListResponse getHomeJoinedRoomList(RoomGetHomeJoinedListQuery query) {

// 1. page 값 검증
validatePage(query.page());

// 2. 유저 닉네임 조회
String nickname = userCommandPort.findById(query.userId()).getNickname();

// 3. Pageable 생성
int pageIndex = query.page() > 0 ? query.page() - 1 : 0;
Pageable pageable = PageRequest.of(pageIndex, DEFAULT_PAGE_SIZE);

// 4. 모임 홈에서 참여중인 모임 방 검색
Page<RoomGetHomeJoinedListResponse.RoomSearchResult> result = roomQueryPort.searchHomeJoinedRooms(query.userId(), LocalDate.now(), pageable);

// 5. response 구성
return RoomGetHomeJoinedListResponse.builder()
.roomList(result.getContent())
.nickname(nickname)
.page(query.page())
.size(result.getNumberOfElements())
.last(result.isLast())
.first(result.isFirst())
.build();
}

private void validatePage(int page) {
if(page< 1) {
throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new IllegalArgumentException("page은 1 이상의 값이어야 합니다."));
}
}

}
27 changes: 24 additions & 3 deletions src/test/java/konkuk/thip/common/util/TestEntityFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity;
import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity;
import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity;
import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity;
import konkuk.thip.user.adapter.out.jpa.UserJpaEntity;
import konkuk.thip.user.adapter.out.jpa.UserRole;
import konkuk.thip.user.adapter.out.jpa.*;
import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity;
import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity;
import konkuk.thip.post.adapter.out.jpa.PostJpaEntity;
Expand Down Expand Up @@ -85,6 +83,29 @@ public static RoomJpaEntity createRoom(BookJpaEntity book, CategoryJpaEntity cat
.build();
}

public static RoomJpaEntity createCustomRoom(BookJpaEntity book, CategoryJpaEntity category,LocalDate startDate,LocalDate endDate) {
return RoomJpaEntity.builder()
.title("방이름")
.description("설명")
.isPublic(true)
.startDate(startDate)
.endDate(endDate)
.recruitCount(3)
.bookJpaEntity(book)
.categoryJpaEntity(category)
.build();
}

public static UserRoomJpaEntity createUserRoom(RoomJpaEntity room, UserJpaEntity user, UserRoomRole userRoomRole, double userPercentage) {
return UserRoomJpaEntity.builder()
.userJpaEntity(user)
.roomJpaEntity(room)
.userRoomRole(userRoomRole)
.currentPage(0)
.userPercentage(userPercentage)
.build();
}

public static RecordJpaEntity createRecord(UserJpaEntity user, RoomJpaEntity room) {
return RecordJpaEntity.builder()
.content("기록 내용")
Expand Down
Loading