From 234176b068cbb01804d186218a7e867f21f79e5f Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sat, 12 Jul 2025 23:46:48 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[feat]=20=EB=AA=A8=EC=A7=91=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20controller=20=EA=B0=9C=EB=B0=9C=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomQueryController.java | 12 ++++++- .../RoomRecruitingDetailViewResponse.java | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java index 48e28536a..fcf67b868 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java @@ -1,10 +1,13 @@ 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.RoomRecruitingDetailViewResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; 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 lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; @@ -17,6 +20,8 @@ public class RoomQueryController { private final RoomSearchUseCase roomSearchUseCase; + private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase; + private final RoomShowRecruitingDetailViewUseCase roomShowRecruitingDetailViewUseCase; @GetMapping("/rooms/search") public BaseResponse searchRooms( @@ -27,7 +32,6 @@ public BaseResponse searchRooms( ) { return BaseResponse.ok(roomSearchUseCase.searchRoom(keyword, category, sort, page)); } - private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase; //비공개 방 비밀번호 입력 검증 @PostMapping("/rooms/{roomId}/password") @@ -37,4 +41,10 @@ public BaseResponse verifyRoomPassword(@PathVariable("roomId") final Long return BaseResponse.ok(roomVerifyPasswordUseCase.verifyRoomPassword(roomVerifyPasswordRequest.toQuery(roomId))); } + @GetMapping("/rooms/{roomId}/recruiting") + public BaseResponse getRecruitingRoomDetailView( + @UserId final Long userId, + @PathVariable("roomId") final Long roomId) { + return BaseResponse.ok(roomShowRecruitingDetailViewUseCase.getRecruitingRoomDetailView(userId, roomId)); + } } diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java new file mode 100644 index 000000000..e203287d2 --- /dev/null +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java @@ -0,0 +1,33 @@ +package konkuk.thip.room.adapter.in.web.response; + +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 recommendRooms +) { + public record RecommendRoom( + String roomImageUrl, + String roomName, + int memberCount, + int recruitCount, + String recruitEndDate + ) {} +} From 183faacf14924d4e6e95d25473e83228b3c332f4 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sat, 12 Jul 2025 23:48:02 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[feat]=20=EB=AA=A8=EC=A7=91=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20use=20case=20=EA=B0=9C=EB=B0=9C=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추후 Category 에 '대표 이미지 url' 을 추가해야함!! --- .../konkuk/thip/common/util/DateUtil.java | 13 +++- .../RoomShowRecruitingDetailViewUseCase.java | 8 ++ .../RoomShowRecruitingDetailViewService.java | 78 +++++++++++++++++++ .../thip/user/domain/RoomParticipants.java | 36 +++++++++ 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java create mode 100644 src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java create mode 100644 src/main/java/konkuk/thip/user/domain/RoomParticipants.java diff --git a/src/main/java/konkuk/thip/common/util/DateUtil.java b/src/main/java/konkuk/thip/common/util/DateUtil.java index 872c861e1..96979e894 100644 --- a/src/main/java/konkuk/thip/common/util/DateUtil.java +++ b/src/main/java/konkuk/thip/common/util/DateUtil.java @@ -5,10 +5,13 @@ import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; @Component public class DateUtil { + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + //마지막 활동 시간 포맷팅 -> ex. 1분 전, 1시간 전, 1일 전 public String formatLastActivityTime(LocalDateTime createdAt) { long minutes = Duration.between(createdAt, LocalDateTime.now()).toMinutes(); @@ -30,16 +33,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); } } diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java new file mode 100644 index 000000000..056429d99 --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/in/RoomShowRecruitingDetailViewUseCase.java @@ -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); +} diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java new file mode 100644 index 000000000..6c3d059dc --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java @@ -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 findByRoomId = userRoomCommandPort.findAllByRoomId(roomId); + RoomParticipants roomParticipants = RoomParticipants.from(findByRoomId); + + // 3. 다른 모임방 추천 + List 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 recommendRooms + ) { + return new RoomRecruitingDetailViewResponse( + participants.isHostOfRoom(userId), + participants.isJoiningToRoom(userId), + room.getId(), + room.getTitle(), + null, // roomImageUrl 미구현 + 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 + ); + } +} diff --git a/src/main/java/konkuk/thip/user/domain/RoomParticipants.java b/src/main/java/konkuk/thip/user/domain/RoomParticipants.java new file mode 100644 index 000000000..bceacbf17 --- /dev/null +++ b/src/main/java/konkuk/thip/user/domain/RoomParticipants.java @@ -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 들을 모은 일급 컬렉션 + */ + + private final List participants; + + public static RoomParticipants from(List 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())); + } +} From 9b0e6873a8fce8fa26403d7684dbcaf98d900eed Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sat, 12 Jul 2025 23:49:47 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[feat]=20=EB=AA=A8=EC=A7=91=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20=EC=98=81=EC=86=8D=EC=84=B1=20adapter=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추천하는 다른 모임방 정보 조회시 QueryDSL 활용 - 추후 Room 에 현재 참여중인 사람 수 정보 추가해야 함!! --- .../RoomQueryPersistenceAdapter.java | 9 ++++++ .../out/persistence/RoomQueryRepository.java | 5 ++++ .../persistence/RoomQueryRepositoryImpl.java | 30 +++++++++++++++++++ .../application/port/out/RoomQueryPort.java | 6 ++++ 4 files changed, 50 insertions(+) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index e2e50f8c4..0e583dab4 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -1,14 +1,17 @@ package konkuk.thip.room.adapter.out.persistence; +import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; 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 @@ -26,4 +29,10 @@ public int countRecruitingRoomsByBookAndStartDateAfter(Long bookId, LocalDate cu public Page searchRoom(String keyword, String category, Pageable pageable) { return roomJpaRepository.searchRoom(keyword, category, pageable); } + + @Override + public List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Room currentRoom, int count) { + return roomJpaRepository.findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(currentRoom.getId(), currentRoom.getCategory().getValue(), count); + } + } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java index 56c000969..e26fb7283 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepository.java @@ -1,10 +1,15 @@ package konkuk.thip.room.adapter.out.persistence; +import konkuk.thip.room.adapter.in.web.response.RoomRecruitingDetailViewResponse; 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; + public interface RoomQueryRepository { Page searchRoom(String keyword, String category, Pageable pageable); + + List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java index f4b89a0a0..e8d45bf83 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java @@ -4,11 +4,13 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.CaseBuilder; import com.querydsl.core.types.dsl.NumberExpression; 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.RoomSearchResponse; import konkuk.thip.room.adapter.out.jpa.QRoomJpaEntity; import konkuk.thip.user.adapter.out.jpa.QUserRoomJpaEntity; @@ -137,4 +139,32 @@ private OrderSpecifier toOrderSpecifier(Sort sort, QRoomJpaEntity room, Numbe return room.startDate.asc(); } } + + @Override + public List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Long roomId, String category, int count) { + NumberExpression memberCountExpr = userRoom.userRoomId.count(); + List 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 -> new RoomRecruitingDetailViewResponse.RecommendRoom( + null, // roomImageUrl은 추후 구현 + t.get(room.title), + t.get(memberCountExpr).intValue(), + t.get(room.recruitCount), + DateUtil.formatAfterTime(t.get(room.startDate)) + )) + .toList(); + } } diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index 1cd001ab1..b2f02b364 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -1,13 +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.RoomSearchResponse; +import konkuk.thip.room.domain.Category; +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 searchRoom(String keyword, String category, Pageable pageable); + + List findOtherRecruitingRoomsByCategoryOrderByStartDateAsc(Room currentRoom, int count); } From 0ae4a61fbf0f39453c3b5966d28005d9f330b0f4 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sat, 12 Jul 2025 23:50:09 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[test]=20=EB=AA=A8=EC=A7=91=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/RoomRecruitingDetailViewApiTest.java | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java new file mode 100644 index 000000000..7ea5b4254 --- /dev/null +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java @@ -0,0 +1,340 @@ +package konkuk.thip.room.adapter.in.web; + +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.BookJpaRepository; +import konkuk.thip.common.util.DateUtil; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.persistence.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.RoomJpaRepository; +import konkuk.thip.user.adapter.out.jpa.*; +import konkuk.thip.user.adapter.out.persistence.AliasJpaRepository; +import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.UserRoomJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.IntStream; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 모집 중인 방 상세조회 api 통합 테스트") +class RoomRecruitingDetailViewApiTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private CategoryJpaRepository categoryJpaRepository; + + @Autowired + private BookJpaRepository bookJpaRepository; + + @Autowired + private RoomJpaRepository roomJpaRepository; + + @Autowired + private UserRoomJpaRepository userRoomJpaRepository; + + @AfterEach + void tearDown() { + userRoomJpaRepository.deleteAll(); + roomJpaRepository.deleteAll(); + bookJpaRepository.deleteAll(); + userJpaRepository.deleteAll(); + categoryJpaRepository.deleteAll(); + aliasJpaRepository.deleteAll(); + } + + private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + + BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() + .title(bookTitle) + .isbn(isbn) + .authorName("한강") + .bestSeller(false) + .publisher("문학동네") + .imageUrl("https://image1.jpg") + .pageCount(300) + .description("한강의 소설") + .build()); + + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias)); + + return roomJpaRepository.save(RoomJpaEntity.builder() + .title(roomName) + .description("한강 작품 읽기 모임") + .isPublic(true) + .roomPercentage(0.0) + .startDate(startDate) + .endDate(LocalDate.now().plusDays(30)) + .recruitCount(recruitCount) + .bookJpaEntity(book) + .categoryJpaEntity(category) + .build()); + } + + private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + + BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() + .title(bookTitle) + .isbn(isbn) + .authorName("한강") + .bestSeller(false) + .publisher("문학동네") + .imageUrl("https://image1.jpg") + .pageCount(300) + .description("한강의 소설") + .build()); + + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); + + return roomJpaRepository.save(RoomJpaEntity.builder() + .title(roomName) + .description("한강 작품 읽기 모임") + .isPublic(true) + .roomPercentage(0.0) + .startDate(startDate) + .endDate(LocalDate.now().plusDays(30)) + .recruitCount(recruitCount) + .bookJpaEntity(book) + .categoryJpaEntity(category) + .build()); + } + + private void saveUsersToRoom(RoomJpaEntity roomJpaEntity, int count) { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + + // User 리스트 생성 및 저장 + List users = IntStream.rangeClosed(1, count) + .mapToObj(i -> UserJpaEntity.builder() + .nickname("user" + i) + .imageUrl("http://image") + .oauth2Id("oauth2Id") + .role(UserRole.USER) + .aliasForUserJpaEntity(alias) + .build()) + .toList(); + + List savedUsers = userJpaRepository.saveAll(users); + + // UserRoom 매핑 리스트 생성 및 저장 + List mappings = savedUsers.stream() + .map(user -> UserRoomJpaEntity.builder() + .userJpaEntity(user) + .roomJpaEntity(roomJpaEntity) + .userRoomRole(UserRoomRole.MEMBER) + .build()) + .toList(); + + userRoomJpaRepository.saveAll(mappings); + } + + @Test + @DisplayName("모집중인 모임방 상세조회할 경우, 해당 모임방의 정보, 책 정보, 추천할 모임방의 정보를 반환한다.") + void get_recruiting_room_detail() throws Exception { + //given + RoomJpaEntity targetRoom = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(targetRoom, 4); + UserJpaEntity joiningUser = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1).getUserJpaEntity(); + + RoomJpaEntity science_room_2 = saveScienceRoom("과학-책", "isbn2", "방이름입니다", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(science_room_2, 5); + + RoomJpaEntity science_room_3 = saveScienceRoom("과학-책", "isbn3", "무슨방일까요??", LocalDate.now().plusDays(5), 8); + saveUsersToRoom(science_room_3, 2); + + RoomJpaEntity science_room_4 = saveScienceRoom("과학-책", "isbn4", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), 8); + saveUsersToRoom(science_room_4, 1); + + RoomJpaEntity room_3 = saveLiteratureRoom("문학-책", "isbn5", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), 8); + saveUsersToRoom(room_3, 6); + + RoomJpaEntity recruit_expired_room_4 = saveScienceRoom("과학-책", "isbn6", "모집기한-지난-과학방", LocalDate.now().minusDays(1), 8); + saveUsersToRoom(recruit_expired_room_4, 6); + + //when + ResultActions result = mockMvc.perform(get("/rooms/{roomId}/recruiting", targetRoom.getRoomId()) + .requestAttr("userId", joiningUser.getUserId())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isHost", is(false))) + .andExpect(jsonPath("$.data.isJoining", is(true))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.memberCount", is(4))) + .andExpect(jsonPath("$.data.recruitCount", is(10))) + .andExpect(jsonPath("$.data.isbn", is("isbn1"))) + .andExpect(jsonPath("$.data.bookTitle", is("과학-책"))) + .andExpect(jsonPath("$.data.recommendRooms", hasSize(3))) + /** + * recommendRooms 검증 : 현재 조회하는 방과 동일한 카테고리의 다른 방을 추천 + * <정렬 순서> : 모집 마감 임박 순 + */ + .andExpect(jsonPath("$.data.recommendRooms[0].roomName", is("방이름입니다"))) + .andExpect(jsonPath("$.data.recommendRooms[1].roomName", is("무슨방일까요??"))) + .andExpect(jsonPath("$.data.recommendRooms[2].roomName", is("과학-방-8일뒤-활동시작"))); + } + + @Test + @DisplayName("모임방의 호스트가 조회할 경우, 유저가 해당 방의 호스트임을 응답값으로 보여준다. (나머지 응답값은 동일)") + void get_recruiting_room_detail_host() throws Exception { + //given + RoomJpaEntity targetRoom = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(targetRoom, 4); + UserRoomJpaEntity firstMember = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1); + userRoomJpaRepository.delete(firstMember); + UserRoomJpaEntity roomCreator = userRoomJpaRepository.save(UserRoomJpaEntity.builder() + .userJpaEntity(firstMember.getUserJpaEntity()) + .roomJpaEntity(firstMember.getRoomJpaEntity()) + .userRoomRole(UserRoomRole.HOST) + .build()); // firstMember 을 MEMBER -> HOST 로 수정 + + RoomJpaEntity science_room_2 = saveScienceRoom("과학-책", "isbn2", "방이름입니다", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(science_room_2, 5); + + RoomJpaEntity science_room_3 = saveScienceRoom("과학-책", "isbn3", "무슨방일까요??", LocalDate.now().plusDays(5), 8); + saveUsersToRoom(science_room_3, 2); + + RoomJpaEntity science_room_4 = saveScienceRoom("과학-책", "isbn4", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), 8); + saveUsersToRoom(science_room_4, 1); + + RoomJpaEntity room_3 = saveLiteratureRoom("문학-책", "isbn5", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), 8); + saveUsersToRoom(room_3, 6); + + RoomJpaEntity recruit_expired_room_4 = saveScienceRoom("과학-책", "isbn6", "모집기한-지난-과학방", LocalDate.now().minusDays(1), 8); + saveUsersToRoom(recruit_expired_room_4, 6); + + //when + ResultActions result = mockMvc.perform(get("/rooms/{roomId}/recruiting", targetRoom.getRoomId()) + .requestAttr("userId", roomCreator.getUserJpaEntity().getUserId())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isHost", is(true))) + .andExpect(jsonPath("$.data.isJoining", is(true))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.memberCount", is(4))) + .andExpect(jsonPath("$.data.recruitCount", is(10))) + .andExpect(jsonPath("$.data.isbn", is("isbn1"))) + .andExpect(jsonPath("$.data.bookTitle", is("과학-책"))) + .andExpect(jsonPath("$.data.recommendRooms", hasSize(3))) + /** + * recommendRooms 검증 : 현재 조회하는 방과 동일한 카테고리의 다른 방을 추천 + * <정렬 순서> : 모집 마감 임박 순 + */ + .andExpect(jsonPath("$.data.recommendRooms[0].roomName", is("방이름입니다"))) + .andExpect(jsonPath("$.data.recommendRooms[1].roomName", is("무슨방일까요??"))) + .andExpect(jsonPath("$.data.recommendRooms[2].roomName", is("과학-방-8일뒤-활동시작"))); + } + + @Test + @DisplayName("추천하는 다른 모집중인 모임방이 많을 경우, 모집 기한 마감임박 순으로 최대 5개만 반환한다.") + void get_recruiting_room_detail_too_many_recommend_rooms() throws Exception { + //given + RoomJpaEntity targetRoom = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(targetRoom, 4); + UserJpaEntity joiningUser = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1).getUserJpaEntity(); + + RoomJpaEntity science_room_2 = saveScienceRoom("과학-책", "isbn2", "방이름입니다", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(science_room_2, 5); + + RoomJpaEntity science_room_3 = saveScienceRoom("과학-책", "isbn3", "무슨방일까요??", LocalDate.now().plusDays(5), 8); + saveUsersToRoom(science_room_3, 2); + + RoomJpaEntity science_room_4 = saveScienceRoom("과학-책", "isbn4", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), 8); + saveUsersToRoom(science_room_4, 1); + + RoomJpaEntity science_room_5 = saveScienceRoom("과학-책", "isbn5", "과학-방-10일뒤-활동시작", LocalDate.now().plusDays(10), 8); + saveUsersToRoom(science_room_5, 1); + + RoomJpaEntity science_room_6 = saveScienceRoom("과학-책", "isbn6", "과학-방-15일뒤-활동시작", LocalDate.now().plusDays(15), 8); + saveUsersToRoom(science_room_6, 1); + + RoomJpaEntity science_room_7 = saveScienceRoom("과학-책", "isbn7", "과학-방-20일뒤-활동시작", LocalDate.now().plusDays(20), 8); + saveUsersToRoom(science_room_7, 1); + + //when + ResultActions result = mockMvc.perform(get("/rooms/{roomId}/recruiting", targetRoom.getRoomId()) + .requestAttr("userId", joiningUser.getUserId())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isHost", is(false))) + .andExpect(jsonPath("$.data.isJoining", is(true))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.memberCount", is(4))) + .andExpect(jsonPath("$.data.recruitCount", is(10))) + .andExpect(jsonPath("$.data.isbn", is("isbn1"))) + .andExpect(jsonPath("$.data.bookTitle", is("과학-책"))) + .andExpect(jsonPath("$.data.recommendRooms", hasSize(5))) + /** + * recommendRooms 검증 : 현재 조회하는 방과 동일한 카테고리의 다른 방을 추천 + * <정렬 순서> : 모집 마감 임박 순 + */ + .andExpect(jsonPath("$.data.recommendRooms[0].roomName", is("방이름입니다"))) + .andExpect(jsonPath("$.data.recommendRooms[1].roomName", is("무슨방일까요??"))) + .andExpect(jsonPath("$.data.recommendRooms[2].roomName", is("과학-방-8일뒤-활동시작"))) + .andExpect(jsonPath("$.data.recommendRooms[3].roomName", is("과학-방-10일뒤-활동시작"))) + .andExpect(jsonPath("$.data.recommendRooms[4].roomName", is("과학-방-15일뒤-활동시작"))); + } + + @Test + @DisplayName("추천하는 다른 모집중인 모임방이 없을 경우, 해당 데이터를 빈 배열로 반환한다.") + void get_recruiting_room_detail_no_recommend_rooms() throws Exception { + //given + RoomJpaEntity targetRoom = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + saveUsersToRoom(targetRoom, 4); + UserJpaEntity joiningUser = userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(targetRoom.getRoomId()).get(1).getUserJpaEntity(); + + RoomJpaEntity room_3 = saveLiteratureRoom("문학-책", "isbn5", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), 8); + saveUsersToRoom(room_3, 6); + + RoomJpaEntity recruit_expired_room_4 = saveScienceRoom("과학-책", "isbn6", "모집기한-지난-과학방", LocalDate.now().minusDays(1), 8); + saveUsersToRoom(recruit_expired_room_4, 6); + + //when + ResultActions result = mockMvc.perform(get("/rooms/{roomId}/recruiting", targetRoom.getRoomId()) + .requestAttr("userId", joiningUser.getUserId())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isHost", is(false))) + .andExpect(jsonPath("$.data.isJoining", is(true))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.memberCount", is(4))) + .andExpect(jsonPath("$.data.recruitCount", is(10))) + .andExpect(jsonPath("$.data.isbn", is("isbn1"))) + .andExpect(jsonPath("$.data.bookTitle", is("과학-책"))) + .andExpect(jsonPath("$.data.recommendRooms", hasSize(0))); + } +} From a52949ddc292a00a92fb861395170e49507752b7 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sat, 12 Jul 2025 23:50:25 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[test]=20RoomParticipants=20=EC=9D=BC?= =?UTF-8?q?=EA=B8=89=20=EC=BB=AC=EB=A0=89=EC=85=98=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/domain/RoomParticipantsTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java diff --git a/src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java b/src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java new file mode 100644 index 000000000..88fc566ed --- /dev/null +++ b/src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java @@ -0,0 +1,64 @@ +package konkuk.thip.user.domain; + +import konkuk.thip.user.adapter.out.jpa.UserRoomRole; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class RoomParticipantsTest { + + private UserRoom createUserRoom(Long id, Long userId, Long roomId, UserRoomRole role) { + return UserRoom.builder() + .id(id) + .currentPage(0) + .userPercentage(0.0) + .userRoomRole(role.getType()) + .userId(userId) + .roomId(roomId) + .build(); + } + + @Test + @DisplayName("해당 모임방에 속한 유저의 수를 반환한다.") + void calculate_member_count_test() { + //given + UserRoom ur1 = createUserRoom(1L, 100L, 10L, UserRoomRole.MEMBER); + UserRoom ur2 = createUserRoom(2L, 200L, 10L, UserRoomRole.HOST); + RoomParticipants participants = RoomParticipants.from(List.of(ur1, ur2)); + + //when + int count = participants.calculateMemberCount(); + + //then + assertEquals(2, count); + } + + @Test + @DisplayName("유저가 현재 모임방에 속하면 true, 속하지 않으면 false를 반환한다.") + void is_joining_to_room_test() { + //given + UserRoom ur = createUserRoom(1L, 123L, 10L, UserRoomRole.MEMBER); + RoomParticipants participants = RoomParticipants.from(List.of(ur)); + + //when //then + assertTrue(participants.isJoiningToRoom(123L)); + assertFalse(participants.isJoiningToRoom(999L)); + } + + @Test + @DisplayName("유저가 현재 모임방의 HOST이면 true, 아니면 false를 반환한다.") + void is_host_of_room_test() { + //given + UserRoom member = createUserRoom(1L, 1L, 5L, UserRoomRole.MEMBER); + UserRoom host = createUserRoom(2L, 2L, 5L, UserRoomRole.HOST); + RoomParticipants participants = RoomParticipants.from(List.of(member, host)); + + //when //then + assertTrue(participants.isHostOfRoom(2L)); + assertFalse(participants.isHostOfRoom(1L)); + assertFalse(participants.isHostOfRoom(3L)); + } +} From 68a1ad0eb7eb3cb9c0ac6e3f19396c46b59a5816 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 13 Jul 2025 17:30:19 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[refactor]=20QueryDsl=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20builder=EB=A1=9C=20=EB=A7=A4=ED=95=91=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/RoomRecruitingDetailViewResponse.java | 3 +++ .../out/persistence/RoomQueryRepositoryImpl.java | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java index e203287d2..f54901be7 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomRecruitingDetailViewResponse.java @@ -1,5 +1,7 @@ package konkuk.thip.room.adapter.in.web.response; +import lombok.Builder; + import java.util.List; public record RoomRecruitingDetailViewResponse( @@ -23,6 +25,7 @@ public record RoomRecruitingDetailViewResponse( String bookDescription, List recommendRooms ) { + @Builder public record RecommendRoom( String roomImageUrl, String roomName, diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java index e8d45bf83..15f152dff 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryRepositoryImpl.java @@ -158,13 +158,13 @@ public List findOtherRecruitingR .fetch(); return tuples.stream() - .map(t -> new RoomRecruitingDetailViewResponse.RecommendRoom( - null, // roomImageUrl은 추후 구현 - t.get(room.title), - t.get(memberCountExpr).intValue(), - t.get(room.recruitCount), - DateUtil.formatAfterTime(t.get(room.startDate)) - )) + .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(); } } From f8eed8f346208383d279e4470bf44fc4cdab7db8 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 13 Jul 2025 22:52:42 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[feat]=20Category=20=EC=97=90=20image=20u?= =?UTF-8?q?rl=20=EC=B6=94=EA=B0=80=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/room/adapter/out/jpa/CategoryJpaEntity.java | 3 +++ src/main/java/konkuk/thip/room/domain/Category.java | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/jpa/CategoryJpaEntity.java b/src/main/java/konkuk/thip/room/adapter/out/jpa/CategoryJpaEntity.java index f9a2fed80..f072818ce 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/jpa/CategoryJpaEntity.java +++ b/src/main/java/konkuk/thip/room/adapter/out/jpa/CategoryJpaEntity.java @@ -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; diff --git a/src/main/java/konkuk/thip/room/domain/Category.java b/src/main/java/konkuk/thip/room/domain/Category.java index 1ff96bba6..5be077742 100644 --- a/src/main/java/konkuk/thip/room/domain/Category.java +++ b/src/main/java/konkuk/thip/room/domain/Category.java @@ -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("문학", "과학/IT_image"), + ART("예술", "과학/IT_image"), + SOCIAL_SCIENCE("사회과학", "과학/IT_image"), + HUMANITY("인문학", "과학/IT_image"); private final String value; + private final String imageUrl; public static Category from(String value) { return Arrays.stream(Category.values()) From 7562ea355f5cc1e222a4d5242c781156bb8e7a2b Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 13 Jul 2025 22:53:11 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[refactor]=20=EB=AA=A8=EC=A7=91=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?api=20service=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RoomShowRecruitingDetailViewService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java index 6c3d059dc..c79c70d17 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java @@ -58,7 +58,7 @@ private RoomRecruitingDetailViewResponse buildResponse( participants.isJoiningToRoom(userId), room.getId(), room.getTitle(), - null, // roomImageUrl 미구현 + room.getCategory().getImageUrl(), room.isPublic(), DateUtil.formatDate(room.getStartDate()), DateUtil.formatDate(room.getEndDate()), From f39cdf5420f0f342baf22b258329200cd6c5dbd4 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 13 Jul 2025 22:53:21 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/konkuk/thip/common/util/TestEntityFactory.java | 2 ++ .../thip/vote/adapter/in/web/VoteCreateControllerTest.java | 1 + 2 files changed, 3 insertions(+) diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index ffd760b10..3ebebee79 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -28,6 +28,7 @@ public static AliasJpaEntity createLiteratureAlias() { public static CategoryJpaEntity createLiteratureCategory(AliasJpaEntity alias) { return CategoryJpaEntity.builder() // 실제 존재하는 값으로 .value("문학") + .imageUrl("문학_image") .aliasForCategoryJpaEntity(alias) .build(); } @@ -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(); } diff --git a/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java index b9b127415..7d6f14167 100644 --- a/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java +++ b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java @@ -114,6 +114,7 @@ private void saveUserAndRoom() { CategoryJpaEntity category = categoryJpaRepository.save(CategoryJpaEntity.builder() .value("과학/IT") + .imageUrl("과학/IT_image") .aliasForCategoryJpaEntity(alias) .build()); From 0baeb59da76a357d3a44d45eeb4a1560b6806498 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 13 Jul 2025 23:00:28 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[refactor]=20Category=20=EB=8C=80?= =?UTF-8?q?=ED=91=9C=20image=20url=20=EB=94=94=ED=8F=B4=ED=8A=B8=EA=B0=92?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/room/domain/Category.java | 8 ++++---- .../adapter/in/web/RoomRecruitingDetailViewApiTest.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/konkuk/thip/room/domain/Category.java b/src/main/java/konkuk/thip/room/domain/Category.java index 5be077742..7187e2735 100644 --- a/src/main/java/konkuk/thip/room/domain/Category.java +++ b/src/main/java/konkuk/thip/room/domain/Category.java @@ -17,10 +17,10 @@ public enum Category { * TODO : DB에서 value를 통해 카테고리를 조회하는것보다 id로 조회하는게 성능상 좋으니, id 값도 같이 보관 ?? */ SCIENCE_IT("과학/IT", "과학/IT_image"), - LITERATURE("문학", "과학/IT_image"), - ART("예술", "과학/IT_image"), - SOCIAL_SCIENCE("사회과학", "과학/IT_image"), - HUMANITY("인문학", "과학/IT_image"); + LITERATURE("문학", "문학_image"), + ART("예술", "예술_image"), + SOCIAL_SCIENCE("사회과학", "사회과학_image"), + HUMANITY("인문학", "인문학_image"); private final String value; private final String imageUrl; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java index 7ea5b4254..55b0d2a9d 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java @@ -187,6 +187,7 @@ void get_recruiting_room_detail() throws Exception { .andExpect(jsonPath("$.data.isHost", is(false))) .andExpect(jsonPath("$.data.isJoining", is(true))) .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomImageUrl", is("과학/IT_image"))) // 방 대표 이미지 추가 .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) .andExpect(jsonPath("$.data.memberCount", is(4))) .andExpect(jsonPath("$.data.recruitCount", is(10)))