Skip to content

Commit

Permalink
Merge pull request #96 from Shimpyo-House/feature/get-rooms
Browse files Browse the repository at this point in the history
refactor: 주문/결제페이지 객실 정보 조회 기능 개발
  • Loading branch information
jo0oy authored Dec 12, 2023
2 parents 8fe6e32 + b1aa8c8 commit 8695a3c
Show file tree
Hide file tree
Showing 14 changed files with 737 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public record ReservationProductRequestDto(
String endDate,
String visitorName,
String visitorPhone,
@Min(value = 0, message = "객실 이용 금액은 음수일 수 없습니다.")
@Min(value = 0, message = "객실 이용 금액은 0원 이상부터 가능합니다.")
Integer price
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.fc.shimpyo_be.domain.room.controller;

import com.fc.shimpyo_be.domain.room.dto.request.GetRoomListWithProductInfoRequestDto;
import com.fc.shimpyo_be.domain.room.dto.response.RoomListWithProductInfoResponseDto;
import com.fc.shimpyo_be.domain.room.service.RoomService;
import com.fc.shimpyo_be.global.common.ResponseDto;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/rooms")
@RestController
public class RoomRestController {

private final RoomService roomService;

@GetMapping
public ResponseEntity<ResponseDto<RoomListWithProductInfoResponseDto>> getRoomsWithProductInfo(
@Valid @RequestBody GetRoomListWithProductInfoRequestDto request
) {
log.debug("GET /api/rooms, roomIds : {}", request.roomIds());
return ResponseEntity
.status(HttpStatus.OK)
.body(
ResponseDto.res(
HttpStatus.OK,
new RoomListWithProductInfoResponseDto(
roomService.getRoomsWithProductInfo(request.roomIds())
),
"숙소 정보를 포함한 객실 정보 리스트가 정상적으로 조회되었습니다."
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.fc.shimpyo_be.domain.room.dto.request;

import jakarta.validation.constraints.Size;
import lombok.Builder;

import java.util.List;

@Builder
public record GetRoomListWithProductInfoRequestDto(
@Size(min = 1, max = 3, message = "최소 1개, 최대 3개의 객실 식별자 정보가 필요합니다.")
List<Long> roomIds
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fc.shimpyo_be.domain.room.dto.response;

import lombok.Builder;

import java.util.List;

@Builder
public record RoomListWithProductInfoResponseDto(
List<RoomWithProductResponseDto> rooms
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.fc.shimpyo_be.domain.room.dto.response;

import lombok.Builder;

@Builder
public record RoomWithProductResponseDto(
Long productId,
String productName,
String productThumbnail,
String productAddress,
String productDetailAddress,
Long roomId,
String roomName,
Integer standard,
Integer capacity,
String checkIn,
String checkOut,
Long price
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fc.shimpyo_be.domain.room.entity.Room;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoomRepository extends JpaRepository<Room, Long> {
public interface RoomRepository
extends JpaRepository<Room, Long>, RoomRepositoryCustom {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.fc.shimpyo_be.domain.room.repository;

import com.fc.shimpyo_be.domain.room.entity.Room;

import java.util.List;

public interface RoomRepositoryCustom {

List<Room> findAllInIdsWithProductAndPrice(List<Long> roomIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.fc.shimpyo_be.domain.room.repository;

import com.fc.shimpyo_be.domain.room.entity.Room;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;

import java.util.List;

import static com.fc.shimpyo_be.domain.product.entity.QProduct.product;
import static com.fc.shimpyo_be.domain.room.entity.QRoom.room;
import static com.fc.shimpyo_be.domain.room.entity.QRoomPrice.roomPrice;

@RequiredArgsConstructor
public class RoomRepositoryImpl implements RoomRepositoryCustom {

private final JPAQueryFactory jpaQueryFactory;

@Override
public List<Room> findAllInIdsWithProductAndPrice(List<Long> roomIds) {
return jpaQueryFactory.selectFrom(room)
.join(room.product, product).fetchJoin()
.join(room.price, roomPrice).fetchJoin()
.where(room.id.in(roomIds))
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.fc.shimpyo_be.domain.room.service;

import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import com.fc.shimpyo_be.domain.room.util.RoomMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Service
public class RoomService {

private final RoomRepository roomRepository;

@Transactional(readOnly = true)
public List<RoomWithProductResponseDto> getRoomsWithProductInfo(List<Long> roomIds) {
log.debug("{} ::: {}", getClass().getSimpleName(), "getRoomsWithProductInfo");

return roomRepository.findAllInIdsWithProductAndPrice(roomIds)
.stream()
.map(RoomMapper::toRoomWithProductResponse)
.toList();
}

}
24 changes: 24 additions & 0 deletions src/main/java/com/fc/shimpyo_be/domain/room/util/RoomMapper.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.fc.shimpyo_be.domain.room.util;

import com.fc.shimpyo_be.domain.product.entity.Address;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.room.dto.response.RoomOptionResponse;
import com.fc.shimpyo_be.domain.room.dto.response.RoomResponse;
import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.entity.RoomOption;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
import com.fc.shimpyo_be.global.util.PricePickerByDateUtil;

public class RoomMapper {
Expand Down Expand Up @@ -46,4 +50,24 @@ public static RoomOptionResponse toRoomOptionResponse(RoomOption roomOption) {
.refrigerator(roomOption.isRefrigerator())
.build();
}

public static RoomWithProductResponseDto toRoomWithProductResponse(Room room) {
Product product = room.getProduct();
Address productAddress = product.getAddress();

return RoomWithProductResponseDto.builder()
.productId(product.getId())
.productName(product.getName())
.productThumbnail(product.getThumbnail())
.productAddress(productAddress.getAddress())
.productDetailAddress(productAddress.getDetailAddress())
.roomId(room.getId())
.roomName(room.getName())
.standard(room.getStandard())
.capacity(room.getCapacity())
.checkIn(DateTimeUtil.toString(room.getCheckIn()))
.checkOut(DateTimeUtil.toString(room.getCheckOut()))
.price(PricePickerByDateUtil.getPrice(room))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.fc.shimpyo_be.domain.room.docs;

import com.fc.shimpyo_be.config.RestDocsSupport;
import com.fc.shimpyo_be.domain.room.dto.request.GetRoomListWithProductInfoRequestDto;
import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
import com.fc.shimpyo_be.domain.room.service.RoomService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.constraints.ConstraintDescriptions;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.security.test.context.support.WithMockUser;

import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class RoomRestControllerDocsTest extends RestDocsSupport {

@MockBean
private RoomService roomService;

private final ConstraintDescriptions getRoomListWithProductInfoDescriptions
= new ConstraintDescriptions(GetRoomListWithProductInfoRequestDto.class);

@WithMockUser(roles = "USER")
@DisplayName("getRoomsWithProductInfo()는 숙소 정보를 포함한 객실 정보 리스트를 조회할 수 있다.")
@Test
void getRoomsWithProductInfo() throws Exception {
//given
String requestUrl = "/api/rooms";
List<Long> roomIds = List.of(1L, 3L, 4L);
GetRoomListWithProductInfoRequestDto requestDto = new GetRoomListWithProductInfoRequestDto(roomIds);

List<RoomWithProductResponseDto> rooms = List.of(
RoomWithProductResponseDto.builder()
.productId(1L)
.productName("호텔1")
.productThumbnail("호텔1 썸네일")
.productAddress("호텔1 주소")
.productDetailAddress("호텔1 상세 주소")
.roomId(1L)
.roomName("객실1")
.standard(2)
.capacity(4)
.checkIn("14:00")
.checkOut("12:00")
.price(80000L)
.build(),
RoomWithProductResponseDto.builder()
.productId(2L)
.productName("호텔2")
.productThumbnail("호텔2 썸네일")
.productAddress("호텔2 주소")
.productDetailAddress("호텔2 상세 주소")
.roomId(3L)
.roomName("객실3")
.standard(2)
.capacity(4)
.checkIn("14:00")
.checkOut("11:30")
.price(95000L)
.build(),
RoomWithProductResponseDto.builder()
.productId(3L)
.productName("호텔3")
.productThumbnail("호텔3 썸네일")
.productAddress("호텔3 주소")
.productDetailAddress("호텔3 상세 주소")
.roomId(4L)
.roomName("객실4")
.standard(2)
.capacity(4)
.checkIn("13:00")
.checkOut("11:00")
.price(80000L)
.build()
);

given(roomService.getRoomsWithProductInfo(roomIds))
.willReturn(rooms);

//when & then
mockMvc.perform(
get(requestUrl)
.content(objectMapper.writeValueAsString(requestDto))
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding(StandardCharsets.UTF_8)
)
.andExpect(status().isOk())
.andDo(restDoc.document(
requestFields(
fieldWithPath("roomIds").type(JsonFieldType.ARRAY).description("조회할 객실 식별자 리스트")
.attributes(key("constraints").value(
getRoomListWithProductInfoDescriptions.descriptionsForProperty("roomIds")))
),
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.rooms").type(JsonFieldType.ARRAY).description("조회한 객실 정보 리스트"),
fieldWithPath("data.rooms[].productId").type(JsonFieldType.NUMBER).description("숙소 식별자"),
fieldWithPath("data.rooms[].productName").type(JsonFieldType.STRING).description("숙소명"),
fieldWithPath("data.rooms[].productThumbnail").type(JsonFieldType.STRING).description("숙소 썸네일 이미지 URL"),
fieldWithPath("data.rooms[].productAddress").type(JsonFieldType.STRING).description("숙소 주소"),
fieldWithPath("data.rooms[].productDetailAddress").type(JsonFieldType.STRING).description("숙소 상세 주소"),
fieldWithPath("data.rooms[].roomId").type(JsonFieldType.NUMBER).description("객실 식별자"),
fieldWithPath("data.rooms[].roomName").type(JsonFieldType.STRING).description("객실명"),
fieldWithPath("data.rooms[].standard").type(JsonFieldType.NUMBER).description("기준 인원"),
fieldWithPath("data.rooms[].capacity").type(JsonFieldType.NUMBER).description("최대 인원"),
fieldWithPath("data.rooms[].checkIn").type(JsonFieldType.STRING).description("체크인 시간"),
fieldWithPath("data.rooms[].checkOut").type(JsonFieldType.STRING).description("체크아웃 시간"),
fieldWithPath("data.rooms[].price").type(JsonFieldType.NUMBER).description("객실 가격")
)
)
);

verify(roomService, times(1)).getRoomsWithProductInfo(roomIds);
}
}
Loading

0 comments on commit 8695a3c

Please sign in to comment.