From 64335cd85b2ce2c807e75c3758064005cea5b031 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 14:30:07 +0900 Subject: [PATCH 01/75] =?UTF-8?q?feat:=20=EC=9D=B4=EC=A0=84=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=BD=94=EB=93=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 147 +++++++++++++++++ .../config/TimeFormatterConfig.java | 17 ++ .../controller/AdminController.java | 24 +++ .../controller/ReservationController.java | 38 +++++ .../controller/ReservationTimeController.java | 38 +++++ .../java/roomescape/domain/Reservation.java | 97 ++++++++++++ .../roomescape/domain/ReservationTime.java | 58 +++++++ .../roomescape/dto/ReservationRequest.java | 6 + .../roomescape/dto/ReservationResponse.java | 6 + .../dto/ReservationTimeRequest.java | 6 + .../dto/ReservationTimeResponse.java | 6 + .../JdbcTemplateReservationRepository.java | 82 ++++++++++ ...JdbcTemplateReservationTimeRepository.java | 51 ++++++ .../repository/ReservationRepository.java | 13 ++ .../repository/ReservationTimeRepository.java | 13 ++ .../service/ReservationService.java | 42 +++++ .../service/ReservationTimeService.java | 36 +++++ src/main/resources/schema.sql | 16 ++ .../controller/AdminControllerTest.java | 34 ++++ .../controller/ReservationControllerTest.java | 97 ++++++++++++ .../ReservationTimeControllerTest.java | 78 +++++++++ .../roomescape/domain/ReservationTest.java | 21 +++ .../roomescape/infra/DBConnectionTest.java | 36 +++++ .../integration/AdminIntegrationTest.java | 148 ++++++++++++++++++ .../CollectionReservationRepository.java | 70 +++++++++ .../CollectionReservationTimeRepository.java | 46 ++++++ ...JdbcTemplateReservationRepositoryTest.java | 68 ++++++++ ...TemplateReservationTimeRepositoryTest.java | 68 ++++++++ src/test/resources/application.properties | 2 + src/test/resources/schema.sql | 16 ++ 30 files changed, 1380 insertions(+) create mode 100644 README.md create mode 100644 src/main/java/roomescape/config/TimeFormatterConfig.java create mode 100644 src/main/java/roomescape/controller/AdminController.java create mode 100644 src/main/java/roomescape/controller/ReservationController.java create mode 100644 src/main/java/roomescape/controller/ReservationTimeController.java create mode 100644 src/main/java/roomescape/domain/Reservation.java create mode 100644 src/main/java/roomescape/domain/ReservationTime.java create mode 100644 src/main/java/roomescape/dto/ReservationRequest.java create mode 100644 src/main/java/roomescape/dto/ReservationResponse.java create mode 100644 src/main/java/roomescape/dto/ReservationTimeRequest.java create mode 100644 src/main/java/roomescape/dto/ReservationTimeResponse.java create mode 100644 src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java create mode 100644 src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java create mode 100644 src/main/java/roomescape/repository/ReservationRepository.java create mode 100644 src/main/java/roomescape/repository/ReservationTimeRepository.java create mode 100644 src/main/java/roomescape/service/ReservationService.java create mode 100644 src/main/java/roomescape/service/ReservationTimeService.java create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/roomescape/controller/AdminControllerTest.java create mode 100644 src/test/java/roomescape/controller/ReservationControllerTest.java create mode 100644 src/test/java/roomescape/controller/ReservationTimeControllerTest.java create mode 100644 src/test/java/roomescape/domain/ReservationTest.java create mode 100644 src/test/java/roomescape/infra/DBConnectionTest.java create mode 100644 src/test/java/roomescape/integration/AdminIntegrationTest.java create mode 100644 src/test/java/roomescape/repository/CollectionReservationRepository.java create mode 100644 src/test/java/roomescape/repository/CollectionReservationTimeRepository.java create mode 100644 src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java create mode 100644 src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java create mode 100644 src/test/resources/application.properties create mode 100644 src/test/resources/schema.sql diff --git a/README.md b/README.md new file mode 100644 index 0000000000..08500f40f5 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# 요구사항 문서 + +- [x] localhost:8080/admin 요청 시 어드민 메인 페이지가 응답할 수 있도록 구현한다. +- [x] 어드민 메인 페이지는 templates/admin/index.html 파일을 이용한다. +- [x] localhost:8080/admin/reservation 요청 시 아래 화면과 같이 예약 관리 페이지가 응답할 수 있도록 구현한다. +- [x] 페이지는 templates/admin/reservation-legacy.html 파일을 이용한다. +- [x] 예약 조회 API 명세를 따라 예약 관리 페이지 로드 시 호출되는 예약 목록 조회 API를 구현한다. +- [x] API 명세를 따라 예약 추가 API 와 삭제 API를 구현한다. +- [x] 예약 추가와 취소가 잘 동작한다. +- [x] 이상의 요구 사항을 데이터베이스와 연동하도록 한다. + +- [x] 방탈출 시간표에 따라 방탈출 예약 시 시간을 선택하는 방식으로 수정한다. +- [x] API 명세를 따라 시간 관리 API를 구현한다. +- [x] 페이지는 templates/admin/time.html 파일을 이용한다. + +- [x] 기존에 구현한 예약 기능에서 시간을 시간 테이블에 저장된 값만 선택할 수 있도록 수정한다. +- [x] templates/admin/reservation.html 을 사용한다. + +- [x] 레이어드 아키텍처를 적용하여 레이어별 책임과 역할에 따라 클래스를 분리한다. +- [x] 분리한 클래스는 매번 새로 생성하지 않고 스프링 빈으로 등록한다. + +# API 명세 + +## 예약 조회 API + +### Request + +> GET /reservations HTTP/1.1 + +### Response + +> HTTP/1.1 200 +> +> Content-Type: application/json + +``` JSON +[ + { + "id": 1, + "name": "브라운", + "date": "2023-08-05", + "time": { + "id": 1, + "startAt": "10:00" + } + } +] +``` + +## 예약 추가 API + +### Request + +> POST /reservations HTTP/1.1 +> +> content-type: application/json + +```JSON +{ + "date": "2023-08-05", + "name": "브라운", + "timeId": 1 +} +``` + +### Response + +> HTTP/1.1 200 +> +> Content-Type: application/json + +```JSON +{ + "id": 1, + "name": "브라운", + "date": "2023-08-05", + "time": { + "id": 1, + "startAt": "10:00" + } +} +``` + +## 예약 취소 API + +### Request + +> DELETE /reservations/1 HTTP/1.1 + +### Response + +> HTTP/1.1 200 + +## 시간 추가 API + +### request + +> POST /times HTTP/1.1 +> content-type: application/json + +```JSON +{ + "startAt": "10:00" +} +``` + +### response + +> HTTP/1.1 200 +> Content-Type: application/json + +```JSON +{ + "id": 1, + "startAt": "10:00" +} +``` + +## 시간 조회 API + +### request + +> GET /times HTTP/1.1 + +### response + +> HTTP/1.1 200 +> Content-Type: application/json + +```JSON +[ + { + "id": 1, + "startAt": "10:00" + } +] +``` + +## 시간 삭제 API + +### request + +> DELETE /times/1 HTTP/1.1 + +### response + +> HTTP/1.1 200 diff --git a/src/main/java/roomescape/config/TimeFormatterConfig.java b/src/main/java/roomescape/config/TimeFormatterConfig.java new file mode 100644 index 0000000000..35c20b11b1 --- /dev/null +++ b/src/main/java/roomescape/config/TimeFormatterConfig.java @@ -0,0 +1,17 @@ +package roomescape.config; + +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import java.time.format.DateTimeFormatter; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TimeFormatterConfig { + private static final String TIME_FORMAT = "HH:mm"; + + @Bean + public Jackson2ObjectMapperBuilderCustomizer localTimeSerializerCustomizer() { + return builder -> builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT))); + } +} diff --git a/src/main/java/roomescape/controller/AdminController.java b/src/main/java/roomescape/controller/AdminController.java new file mode 100644 index 0000000000..cbdee6650a --- /dev/null +++ b/src/main/java/roomescape/controller/AdminController.java @@ -0,0 +1,24 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/admin") +public class AdminController { + @GetMapping + public String mainPage() { + return "admin/index"; + } + + @GetMapping("/reservation") + public String reservationPage() { + return "admin/reservation"; + } + + @GetMapping("/time") + public String reservationTimePage() { + return "admin/time"; + } +} diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java new file mode 100644 index 0000000000..26be3989e9 --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -0,0 +1,38 @@ +package roomescape.controller; + +import java.util.List; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import roomescape.dto.ReservationRequest; +import roomescape.dto.ReservationResponse; +import roomescape.service.ReservationService; + +@RestController +@RequestMapping("/reservations") +public class ReservationController { + private final ReservationService reservationService; + + public ReservationController(ReservationService reservationService) { + this.reservationService = reservationService; + } + + @PostMapping + public ReservationResponse saveReservation(@RequestBody ReservationRequest reservationRequest) { + return reservationService.save(reservationRequest); + } + + @GetMapping + public List<ReservationResponse> findAllReservations() { + return reservationService.findAll(); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable long id) { + reservationService.delete(id); + } +} diff --git a/src/main/java/roomescape/controller/ReservationTimeController.java b/src/main/java/roomescape/controller/ReservationTimeController.java new file mode 100644 index 0000000000..21d0e51b7b --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationTimeController.java @@ -0,0 +1,38 @@ +package roomescape.controller; + +import java.util.List; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import roomescape.dto.ReservationTimeRequest; +import roomescape.dto.ReservationTimeResponse; +import roomescape.service.ReservationTimeService; + +@RestController +@RequestMapping("/times") +public class ReservationTimeController { + private final ReservationTimeService reservationTimeService; + + public ReservationTimeController(ReservationTimeService reservationTimeService) { + this.reservationTimeService = reservationTimeService; + } + + @PostMapping + public ReservationTimeResponse save(@RequestBody ReservationTimeRequest reservationTimeRequest) { + return reservationTimeService.save(reservationTimeRequest); + } + + @GetMapping + public List<ReservationTimeResponse> findAll() { + return reservationTimeService.findAll(); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable long id) { + reservationTimeService.delete(id); + } +} diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java new file mode 100644 index 0000000000..004dfd31ac --- /dev/null +++ b/src/main/java/roomescape/domain/Reservation.java @@ -0,0 +1,97 @@ +package roomescape.domain; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Objects; + +public class Reservation implements Comparable<Reservation> { + private final Long id; + private final String name; + private final LocalDate date; + private final ReservationTime time; + + public Reservation(long id, Reservation reservationBeforeSave) { + this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time); + } + + public Reservation(Long id, String name, LocalDate date, ReservationTime time) { + this.id = id; + this.name = name; + this.date = date; + this.time = time; + } + + @Override + public int compareTo(Reservation other) { + LocalDateTime dateTime = LocalDateTime.of(date, time.getStartAt()); + LocalDateTime otherDateTime = LocalDateTime.of(other.date, other.time.getStartAt()); + return dateTime.compareTo(otherDateTime); + } + + public boolean hasSameId(long id) { + return this.id == id; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public LocalDate getDate() { + return date; + } + + public LocalTime getTime() { + return time.getStartAt(); + } + + public ReservationTime getReservationTime() { + return time; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (date != null ? date.hashCode() : 0); + result = 31 * result + (time != null ? time.hashCode() : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Reservation that = (Reservation) o; + + if (!Objects.equals(id, that.id)) { + return false; + } + if (!Objects.equals(name, that.name)) { + return false; + } + if (!Objects.equals(date, that.date)) { + return false; + } + return Objects.equals(time, that.time); + } + + @Override + public String toString() { + return "Reservation{" + + "id=" + id + + ", name='" + name + '\'' + + ", date=" + date + + ", time=" + time + + '}'; + } +} diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java new file mode 100644 index 0000000000..8b54f99f81 --- /dev/null +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -0,0 +1,58 @@ +package roomescape.domain; + +import java.time.LocalTime; +import java.util.Objects; + +public class ReservationTime { + private final Long id; + private final LocalTime startAt; + + public ReservationTime(LocalTime startAt) { + this(null, startAt); + } + + public ReservationTime(Long id, LocalTime startAt) { + this.id = id; + this.startAt = startAt; + } + + public Long getId() { + return id; + } + + public LocalTime getStartAt() { + return startAt; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (startAt != null ? startAt.hashCode() : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ReservationTime that = (ReservationTime) o; + + if (!Objects.equals(id, that.id)) { + return false; + } + return Objects.equals(startAt, that.startAt); + } + + @Override + public String toString() { + return "ReservationTime{" + + "id=" + id + + ", startAt=" + startAt + + '}'; + } +} diff --git a/src/main/java/roomescape/dto/ReservationRequest.java b/src/main/java/roomescape/dto/ReservationRequest.java new file mode 100644 index 0000000000..ad5cb41a22 --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationRequest.java @@ -0,0 +1,6 @@ +package roomescape.dto; + +import java.time.LocalDate; + +public record ReservationRequest(LocalDate date, String name, long timeId) { +} diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java new file mode 100644 index 0000000000..d9980c55fd --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -0,0 +1,6 @@ +package roomescape.dto; + +import java.time.LocalDate; + +public record ReservationResponse(long id, String name, LocalDate date, ReservationTimeResponse time) { +} diff --git a/src/main/java/roomescape/dto/ReservationTimeRequest.java b/src/main/java/roomescape/dto/ReservationTimeRequest.java new file mode 100644 index 0000000000..5cb43c58e1 --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationTimeRequest.java @@ -0,0 +1,6 @@ +package roomescape.dto; + +import java.time.LocalTime; + +public record ReservationTimeRequest(LocalTime startAt) { +} diff --git a/src/main/java/roomescape/dto/ReservationTimeResponse.java b/src/main/java/roomescape/dto/ReservationTimeResponse.java new file mode 100644 index 0000000000..2334e40c23 --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationTimeResponse.java @@ -0,0 +1,6 @@ +package roomescape.dto; + +import java.time.LocalTime; + +public record ReservationTimeResponse(long id, LocalTime startAt) { +} diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java new file mode 100644 index 0000000000..7884d19fdc --- /dev/null +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -0,0 +1,82 @@ +package roomescape.repository; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationRequest; + +@Repository +public class JdbcTemplateReservationRepository implements ReservationRepository { + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Reservation save(ReservationRequest reservationRequest) { + ReservationTime reservationTime = findReservationTime(reservationRequest); + Reservation reservation = new Reservation(null, reservationRequest.name(), reservationRequest.date(), + reservationTime); + KeyHolder keyHolder = new GeneratedKeyHolder(); + save(reservation, keyHolder); + long id = keyHolder.getKey().longValue(); + return new Reservation(id, reservation); + } + + private ReservationTime findReservationTime(ReservationRequest reservationRequest) { + String reservationTimeSelectSql = "select * from reservation_time where id = ?"; + return jdbcTemplate.queryForObject(reservationTimeSelectSql, (rs, rowNum) -> { + long id = rs.getLong(1); + LocalTime startAt = rs.getTime(2).toLocalTime(); + return new ReservationTime(id, startAt); + }, reservationRequest.timeId()); + } + + private void save(Reservation reservation, KeyHolder keyHolder) { + jdbcTemplate.update(con -> { + String sql = "insert into reservation(name,date,time_id) values ( ?,?,? )"; + PreparedStatement preparedStatement = con.prepareStatement(sql, new String[]{"id"}); + preparedStatement.setString(1, reservation.getName()); + preparedStatement.setDate(2, Date.valueOf(reservation.getDate())); + preparedStatement.setLong(3, reservation.getReservationTime().getId()); + return preparedStatement; + }, keyHolder); + } + + @Override + public List<Reservation> findAll() { + String query = "SELECT " + + " r.id as reservation_id," + + " r.name," + + " r.date," + + " t.id as time_id," + + " t.start_at as time_value" + + " FROM reservation as r" + + " inner join reservation_time as t" + + " on r.time_id = t.id"; + return jdbcTemplate.query(query, + (rs, rowNum) -> { + long id = rs.getLong(1); + String name = rs.getString(2); + LocalDate date = rs.getDate(3).toLocalDate(); + long timeId = rs.getLong(4); + LocalTime startAt = rs.getTime(5).toLocalTime(); + ReservationTime reservationTime = new ReservationTime(timeId, startAt); + return new Reservation(id, name, date, reservationTime); + }); + } + + @Override + public void delete(long id) { + jdbcTemplate.update("delete from reservation where id = ?", id); + } +} diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java new file mode 100644 index 0000000000..639db36eaa --- /dev/null +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -0,0 +1,51 @@ +package roomescape.repository; + +import java.sql.PreparedStatement; +import java.sql.Time; +import java.time.LocalTime; +import java.util.List; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; + +@Repository +public class JdbcTemplateReservationTimeRepository implements ReservationTimeRepository { + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateReservationTimeRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public ReservationTime save(ReservationTimeRequest reservationTimeRequest) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + save(reservationTimeRequest, keyHolder); + return new ReservationTime(keyHolder.getKey().longValue(), reservationTimeRequest.startAt()); + } + + private void save(ReservationTimeRequest reservationTimeRequest, KeyHolder keyHolder) { + jdbcTemplate.update(con -> { + PreparedStatement pstmt = con.prepareStatement("insert into reservation_time(start_at) values ( ? )", + new String[]{"id"}); + pstmt.setTime(1, Time.valueOf(reservationTimeRequest.startAt())); + return pstmt; + }, keyHolder); + } + + @Override + public List<ReservationTime> findAll() { + return jdbcTemplate.query("select * from reservation_time", (rs, rowNum) -> { + long id = rs.getLong(1); + LocalTime time = rs.getTime(2).toLocalTime(); + return new ReservationTime(id, time); + }); + } + + @Override + public void delete(long id) { + jdbcTemplate.update("delete from reservation_time where id = ?", id); + } +} diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java new file mode 100644 index 0000000000..79c62ca32f --- /dev/null +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -0,0 +1,13 @@ +package roomescape.repository; + +import java.util.List; +import roomescape.domain.Reservation; +import roomescape.dto.ReservationRequest; + +public interface ReservationRepository { + Reservation save(ReservationRequest reservationRequest); + + List<Reservation> findAll(); + + void delete(long id); +} diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java new file mode 100644 index 0000000000..5f6e144eea --- /dev/null +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -0,0 +1,13 @@ +package roomescape.repository; + +import java.util.List; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; + +public interface ReservationTimeRepository { + ReservationTime save(ReservationTimeRequest reservationTimeRequest); + + List<ReservationTime> findAll(); + + void delete(long id); +} diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java new file mode 100644 index 0000000000..de6d8d5288 --- /dev/null +++ b/src/main/java/roomescape/service/ReservationService.java @@ -0,0 +1,42 @@ +package roomescape.service; + +import java.util.List; +import org.springframework.stereotype.Service; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationRequest; +import roomescape.dto.ReservationResponse; +import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.ReservationRepository; + +@Service +public class ReservationService { + private final ReservationRepository reservationRepository; + + public ReservationService(ReservationRepository reservationRepository) { + this.reservationRepository = reservationRepository; + } + + public ReservationResponse save(ReservationRequest reservationRequest) { + Reservation saved = reservationRepository.save(reservationRequest); + return toResponse(saved); + } + + private ReservationResponse toResponse(Reservation reservation) { + ReservationTime reservationTime = reservation.getReservationTime(); + ReservationTimeResponse reservationTimeResponse = new ReservationTimeResponse(reservationTime.getId(), + reservation.getTime()); + return new ReservationResponse(reservation.getId(), + reservation.getName(), reservation.getDate(), reservationTimeResponse); + } + + public List<ReservationResponse> findAll() { + return reservationRepository.findAll().stream() + .map(this::toResponse) + .toList(); + } + + public void delete(long id) { + reservationRepository.delete(id); + } +} diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java new file mode 100644 index 0000000000..908a747d0b --- /dev/null +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -0,0 +1,36 @@ +package roomescape.service; + +import java.util.List; +import org.springframework.stereotype.Service; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; +import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.ReservationTimeRepository; + +@Service +public class ReservationTimeService { + private final ReservationTimeRepository reservationTimeRepository; + + public ReservationTimeService(ReservationTimeRepository reservationTimeRepository) { + this.reservationTimeRepository = reservationTimeRepository; + } + + public ReservationTimeResponse save(ReservationTimeRequest reservationTimeRequest) { + ReservationTime saved = reservationTimeRepository.save(reservationTimeRequest); + return toResponse(saved); + } + + private ReservationTimeResponse toResponse(ReservationTime saved) { + return new ReservationTimeResponse(saved.getId(), saved.getStartAt()); + } + + public List<ReservationTimeResponse> findAll() { + return reservationTimeRepository.findAll().stream() + .map(this::toResponse) + .toList(); + } + + public void delete(long id) { + reservationTimeRepository.delete(id); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..44a68814dd --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS reservation_time +( + id BIGINT NOT NULL AUTO_INCREMENT, + start_at VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS reservation +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + date VARCHAR(255) NOT NULL, + time_id BIGINT NOT NULL, -- 컬럼 수정 + PRIMARY KEY (id), + FOREIGN KEY (time_id) REFERENCES reservation_time (id) -- 외래키 추가 +); diff --git a/src/test/java/roomescape/controller/AdminControllerTest.java b/src/test/java/roomescape/controller/AdminControllerTest.java new file mode 100644 index 0000000000..832a31fc0e --- /dev/null +++ b/src/test/java/roomescape/controller/AdminControllerTest.java @@ -0,0 +1,34 @@ +package roomescape.controller; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AdminControllerTest { + @Test + @DisplayName("관리자 메인 페이지 경로를 정해진 경로로 매핑한다.") + void mainPage() { + AdminController adminController = new AdminController(); + String mainPage = adminController.mainPage(); + Assertions.assertThat(mainPage) + .isEqualTo("admin/index"); + } + + @Test + @DisplayName("관리자 예약 정보 페이지 경로를 정해진 경로로 매핑한다.") + void reservationPage() { + AdminController adminController = new AdminController(); + String reservationPage = adminController.reservationPage(); + Assertions.assertThat(reservationPage) + .isEqualTo("admin/reservation"); + } + + @Test + @DisplayName("시간 관리 페이지 경로를 정해진 경로로 매핑한다.") + void reservationTimePage() { + AdminController adminController = new AdminController(); + String reservationTimePage = adminController.reservationTimePage(); + Assertions.assertThat(reservationTimePage) + .isEqualTo("admin/time"); + } +} diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java new file mode 100644 index 0000000000..5f301b1993 --- /dev/null +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -0,0 +1,97 @@ +package roomescape.controller; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationRequest; +import roomescape.dto.ReservationResponse; +import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.CollectionReservationRepository; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.service.ReservationService; + +class ReservationControllerTest { + static final long timeId = 1L; + static final LocalTime time = LocalTime.now(); + private final CollectionReservationTimeRepository timeRepository = new CollectionReservationTimeRepository( + new ArrayList<>(List.of(new ReservationTime(timeId, time))) + ); + + @Test + @DisplayName("예약 정보를 잘 저장하는지 확인한다.") + void saveReservation() { + CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( + timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationController reservationController = new ReservationController(reservationService); + LocalDate date = LocalDate.now(); + + ReservationResponse saveResponse = reservationController.saveReservation( + new ReservationRequest(date, "폴라", timeId)); + + long id = Objects.requireNonNull(saveResponse).id(); + ReservationResponse expected = new ReservationResponse(id, "폴라", date, + new ReservationTimeResponse(timeId, time)); + + Assertions.assertThat(saveResponse) + .isEqualTo(expected); + } + + @Test + @DisplayName("예약 정보를 잘 불러오는지 확인한다.") + void findAllReservations() { + CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( + timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationController reservationController = new ReservationController(reservationService); + List<ReservationResponse> allReservations = reservationController.findAllReservations(); + + Assertions.assertThat(allReservations) + .isEmpty(); + } + + @Test + @DisplayName("예약 정보를 잘 지우는지 확인한다.") + void delete() { + List<Reservation> reservations = List.of( + new Reservation(1L, "폴라", LocalDate.now(), new ReservationTime(LocalTime.now()))); + CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( + new ArrayList<>(reservations), timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationController reservationController = new ReservationController(reservationService); + + reservationController.delete(1L); + List<ReservationResponse> reservationResponses = reservationController.findAllReservations(); + + Assertions.assertThat(reservationResponses) + .isEmpty(); + } + + @Test + @DisplayName("내부에 Repository를 의존하고 있지 않은지 확인한다.") + void checkRepositoryDependency() { + CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( + timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationController reservationController = new ReservationController(reservationService); + + boolean isRepositoryInjected = false; + + for (Field field : reservationController.getClass().getDeclaredFields()) { + if (field.getType().getName().contains("Repository")) { + isRepositoryInjected = true; + break; + } + } + + Assertions.assertThat(isRepositoryInjected).isFalse(); + } +} diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java new file mode 100644 index 0000000000..84ad5b27ac --- /dev/null +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -0,0 +1,78 @@ +package roomescape.controller; + +import java.lang.reflect.Field; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; +import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.service.ReservationTimeService; + +class ReservationTimeControllerTest { + @Test + @DisplayName("시간을 잘 저장하는지 확인한다.") + void save() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); + LocalTime time = LocalTime.now(); + + ReservationTimeResponse save = reservationTimeController.save(new ReservationTimeRequest(time)); + + ReservationTimeResponse expected = new ReservationTimeResponse(save.id(), time); + Assertions.assertThat(save) + .isEqualTo(expected); + } + + @Test + @DisplayName("시간을 잘 불러오는지 확인한다.") + void findAll() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); + List<ReservationTimeResponse> reservationTimeResponses = reservationTimeController.findAll(); + + Assertions.assertThat(reservationTimeResponses) + .isEmpty(); + } + + @Test + @DisplayName("시간을 잘 지우는지 확인한다.") + void delete() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository( + new ArrayList<>(List.of(new ReservationTime(1L, LocalTime.now()))) + ); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); + + reservationTimeController.delete(1); + + List<ReservationTimeResponse> reservationTimeResponses = reservationTimeController.findAll(); + Assertions.assertThat(reservationTimeResponses) + .isEmpty(); + } + + @Test + @DisplayName("내부에 Repository를 의존하고 있지 않은지 확인한다.") + void checkRepositoryDependency() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); + + boolean isRepositoryInjected = false; + + for (Field field : reservationTimeController.getClass().getDeclaredFields()) { + if (field.getType().getName().contains("Repository")) { + isRepositoryInjected = true; + break; + } + } + + Assertions.assertThat(isRepositoryInjected).isFalse(); + } +} diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java new file mode 100644 index 0000000000..63bc689091 --- /dev/null +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -0,0 +1,21 @@ +package roomescape.domain; + +import java.time.LocalDate; +import java.time.LocalTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ReservationTest { + @Test + @DisplayName("날짜를 기준으로 비교를 잘 하는지 확인.") + void compareTo() { + Reservation first = new Reservation(1L, "폴라", LocalDate.of(1999, 12, 1), new ReservationTime( + LocalTime.of(16, 30))); + Reservation second = new Reservation(2L, "로빈", LocalDate.of(1998, 1, 8), new ReservationTime( + LocalTime.of(16, 30))); + int compareTo = first.compareTo(second); + Assertions.assertThat(compareTo) + .isGreaterThan(0); + } +} diff --git a/src/test/java/roomescape/infra/DBConnectionTest.java b/src/test/java/roomescape/infra/DBConnectionTest.java new file mode 100644 index 0000000000..8f856916a1 --- /dev/null +++ b/src/test/java/roomescape/infra/DBConnectionTest.java @@ -0,0 +1,36 @@ +package roomescape.infra; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; + +@SpringBootTest +public class DBConnectionTest { + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + @DisplayName("데이터베이스 접속이 잘 되는지 확인.") + void connectToDB() { + try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { + assertConnection(connection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void assertConnection(Connection connection) { + assertAll( + () -> assertThat(connection).isNotNull(), + () -> assertThat(connection.getCatalog()).isEqualTo("TEST"), + () -> assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue() + ); + } +} diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java new file mode 100644 index 0000000000..068b0dbe25 --- /dev/null +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -0,0 +1,148 @@ +package roomescape.integration; + +import static org.hamcrest.Matchers.is; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import java.util.HashMap; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.jdbc.core.JdbcTemplate; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class AdminIntegrationTest { + @LocalServerPort + private int port; + @Autowired + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void init() { + jdbcTemplate.update("delete from reservation"); + jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); + jdbcTemplate.update("delete from reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); + jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); + RestAssured.port = port; + } + + @Test + @DisplayName("관리자 메인 페이지가 잘 접속된다.") + void adminMainPageLoad() { + RestAssured.given().log().all() + .when().get("/admin") + .then().log().all() + .statusCode(200); + } + + @Test + @DisplayName("관리자 예약 페이지가 잘 접속된다.") + void adminReservationPageLoad() { + RestAssured.given().log().all() + .when().get("/admin/reservation") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); + } + + @Test + @DisplayName("관리자 예약 페이지가 잘 동작한다.") + void adminReservationPageWork() { + Integer integer = jdbcTemplate.queryForObject("select id from reservation_time", + Integer.class); + System.out.println(integer); + Map<String, Object> params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("timeId", 1); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(200) + .body("id", is(1)); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(1)); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); + } + + @Test + @DisplayName("관리자 예약 페이지가 DB와 함께 잘 동작한다.") + void adminReservationPageWorkWithDB() { + Map<String, Object> params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("timeId", 1); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(200); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + Assertions.assertThat(count).isEqualTo(1); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(200); + + Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + Assertions.assertThat(countAfterDelete).isEqualTo(0); + } + + @Test + @DisplayName("시간 관리 페이지가 잘 동작한다.") + void reservationTimePageWork() { + Map<String, String> params = new HashMap<>(); + params.put("startAt", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/times") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/times") + .then().log().all() + .statusCode(200) + .body("size()", is(2)); + + RestAssured.given().log().all() + .when().delete("/times/2") + .then().log().all() + .statusCode(200); + } +} diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java new file mode 100644 index 0000000000..087cc9de04 --- /dev/null +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -0,0 +1,70 @@ +package roomescape.repository; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationRequest; + +public class CollectionReservationRepository implements ReservationRepository { + private final List<Reservation> reservations; + private final AtomicLong atomicLong; + private final CollectionReservationTimeRepository timeRepository; + + public CollectionReservationRepository(CollectionReservationTimeRepository timeRepository) { + this(new ArrayList<>(), new AtomicLong(0), timeRepository); + } + + public CollectionReservationRepository(List<Reservation> reservations, AtomicLong atomicLong, + CollectionReservationTimeRepository timeRepository) { + this.reservations = reservations; + this.atomicLong = atomicLong; + this.timeRepository = timeRepository; + } + + public CollectionReservationRepository(List<Reservation> reservations, + CollectionReservationTimeRepository timeRepository) { + this(reservations, new AtomicLong(0), timeRepository); + } + + @Override + public Reservation save(ReservationRequest reservationRequest) { + Reservation reservation = fromRequest(reservationRequest); + reservations.add(reservation); + return reservation; + } + + private Reservation fromRequest(ReservationRequest reservationRequest) { + long id = atomicLong.incrementAndGet(); + + String name = reservationRequest.name(); + LocalDate date = reservationRequest.date(); + ReservationTime reservationTime = timeRepository.findAll().stream() + .filter(sameId(reservationRequest)) + .findAny() + .orElseThrow(); + return new Reservation(id, name, date, reservationTime); + } + + private static Predicate<ReservationTime> sameId(ReservationRequest reservationRequest) { + return reservationTime -> reservationTime.getId() == reservationRequest.timeId(); + } + + @Override + public List<Reservation> findAll() { + return reservations.stream() + .sorted() + .toList(); + } + + @Override + public void delete(long id) { + reservations.stream() + .filter(reservation -> reservation.hasSameId(id)) + .findAny() + .ifPresent(reservations::remove); + } +} diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java new file mode 100644 index 0000000000..b6f22fe988 --- /dev/null +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -0,0 +1,46 @@ +package roomescape.repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; + +public class CollectionReservationTimeRepository implements ReservationTimeRepository { + private final List<ReservationTime> reservationTimes; + private final AtomicLong atomicLong; + + public CollectionReservationTimeRepository() { + this(new ArrayList<>()); + } + + public CollectionReservationTimeRepository(List<ReservationTime> reservationTimes) { + this(reservationTimes, new AtomicLong(0)); + } + + public CollectionReservationTimeRepository(List<ReservationTime> reservationTimes, AtomicLong atomicLong) { + this.reservationTimes = reservationTimes; + this.atomicLong = atomicLong; + } + + @Override + public ReservationTime save(ReservationTimeRequest reservationTimeRequest) { + ReservationTime reservationTime = new ReservationTime(atomicLong.incrementAndGet(), + reservationTimeRequest.startAt()); + reservationTimes.add(reservationTime); + return reservationTime; + } + + @Override + public List<ReservationTime> findAll() { + return List.copyOf(reservationTimes); + } + + @Override + public void delete(long id) { + reservationTimes.stream() + .filter(reservationTime -> reservationTime.getId().equals(id)) + .findAny() + .ifPresent(reservationTimes::remove); + } +} diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java new file mode 100644 index 0000000000..c2acc8994f --- /dev/null +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -0,0 +1,68 @@ +package roomescape.repository; + +import java.time.LocalDate; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import roomescape.domain.Reservation; +import roomescape.dto.ReservationRequest; + +@SpringBootTest +class JdbcTemplateReservationRepositoryTest { + @Autowired + private ReservationRepository reservationRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void init() { + jdbcTemplate.update("delete from reservation"); + jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); + jdbcTemplate.update("delete from reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); + jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); + } + + @Test + @DisplayName("Reservation 을 잘 저장하는지 확인한다.") + void save() { + var beforeSave = reservationRepository.findAll(); + Reservation saved = reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); + var afterSave = reservationRepository.findAll(); + + Assertions.assertThat(afterSave) + .containsAll(beforeSave) + .contains(saved); + } + + @Test + @DisplayName("Reservation 을 잘 조회하는지 확인한다.") + void findAll() { + List<Reservation> beforeSave = reservationRepository.findAll(); + reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); + reservationRepository.save(new ReservationRequest(LocalDate.now(), "test2", 1)); + + List<Reservation> afterSave = reservationRepository.findAll(); + Assertions.assertThat(afterSave.size()) + .isEqualTo(beforeSave.size() + 2); + } + + @Test + @DisplayName("Reservation 을 잘 지우는지 확인한다.") + void delete() { + List<Reservation> beforeSaveAndDelete = reservationRepository.findAll(); + reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); + + reservationRepository.delete(1L); + + List<Reservation> afterSaveAndDelete = reservationRepository.findAll(); + + Assertions.assertThat(beforeSaveAndDelete) + .containsExactlyElementsOf(afterSaveAndDelete); + } +} diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java new file mode 100644 index 0000000000..5bc5cd67f2 --- /dev/null +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java @@ -0,0 +1,68 @@ +package roomescape.repository; + +import java.time.LocalTime; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import roomescape.domain.ReservationTime; +import roomescape.dto.ReservationTimeRequest; + +@SpringBootTest +class JdbcTemplateReservationTimeRepositoryTest { + @Autowired + private ReservationTimeRepository reservationTimeRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void init() { + jdbcTemplate.update("delete from reservation"); + jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); + jdbcTemplate.update("delete from reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); + } + + @Test + @DisplayName("ReservationTime 을 잘 저장하는지 확인한다.") + void save() { + var beforeSave = reservationTimeRepository.findAll().stream().map(ReservationTime::getId).toList(); + ReservationTime saved = reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + var afterSave = reservationTimeRepository.findAll().stream().map(ReservationTime::getId).toList(); + + Assertions.assertThat(afterSave) + .containsAll(beforeSave) + .contains(saved.getId()); + } + + @Test + @DisplayName("ReservationTime 을 잘 조회하는지 확인한다.") + void findAll() { + List<ReservationTime> beforeSave = reservationTimeRepository.findAll(); + reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + + List<ReservationTime> afterSave = reservationTimeRepository.findAll(); + + Assertions.assertThat(afterSave.size()) + .isEqualTo(beforeSave.size() + 2); + } + + @Test + @DisplayName("ReservationTime 을 잘 지우하는지 확인한다.") + void delete() { + List<ReservationTime> beforeSaveAndDelete = reservationTimeRepository.findAll(); + reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + + reservationTimeRepository.delete(1L); + + List<ReservationTime> afterSaveAndDelete = reservationTimeRepository.findAll(); + + Assertions.assertThat(beforeSaveAndDelete) + .containsExactlyInAnyOrderElementsOf(afterSaveAndDelete); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000000..b53e73970d --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,2 @@ +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:test diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000000..0945a159b6 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS reservation_time +( + id BIGINT NOT NULL AUTO_INCREMENT, + start_at VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS reservation +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + date VARCHAR(255) NOT NULL, + time_id BIGINT, -- 컬럼 수정 + PRIMARY KEY (id), + FOREIGN KEY (time_id) REFERENCES reservation_time (id) -- 외래키 추가 +); From 9d575feb34bea987b14acdca5584211da84869e6 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 14:53:41 +0900 Subject: [PATCH 02/75] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?README.md=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 08500f40f5..2cd133e7aa 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,17 @@ # 요구사항 문서 -- [x] localhost:8080/admin 요청 시 어드민 메인 페이지가 응답할 수 있도록 구현한다. -- [x] 어드민 메인 페이지는 templates/admin/index.html 파일을 이용한다. -- [x] localhost:8080/admin/reservation 요청 시 아래 화면과 같이 예약 관리 페이지가 응답할 수 있도록 구현한다. -- [x] 페이지는 templates/admin/reservation-legacy.html 파일을 이용한다. -- [x] 예약 조회 API 명세를 따라 예약 관리 페이지 로드 시 호출되는 예약 목록 조회 API를 구현한다. -- [x] API 명세를 따라 예약 추가 API 와 삭제 API를 구현한다. -- [x] 예약 추가와 취소가 잘 동작한다. -- [x] 이상의 요구 사항을 데이터베이스와 연동하도록 한다. - -- [x] 방탈출 시간표에 따라 방탈출 예약 시 시간을 선택하는 방식으로 수정한다. -- [x] API 명세를 따라 시간 관리 API를 구현한다. -- [x] 페이지는 templates/admin/time.html 파일을 이용한다. - -- [x] 기존에 구현한 예약 기능에서 시간을 시간 테이블에 저장된 값만 선택할 수 있도록 수정한다. -- [x] templates/admin/reservation.html 을 사용한다. - -- [x] 레이어드 아키텍처를 적용하여 레이어별 책임과 역할에 따라 클래스를 분리한다. -- [x] 분리한 클래스는 매번 새로 생성하지 않고 스프링 빈으로 등록한다. +- [ ] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 +- [ ] 예약 시간에 대한 제약 조건 추가 + - [ ] 중복된 예약 시간 생성 요청 시 에러 + - [ ] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 + - [ ] 예약이 있는 예약 시간을 삭제 요청 시 에러 + - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 +- [ ] 예약에 대한 제약 조건 추가 + - [ ] 동일한 날짜와 시간에 예약 생성 요청 시 에러 + - [ ] 존재하지 않는 시간에 예약 생성 요청 시 에러 + - [ ] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 + - [ ] 존재하지 않는 예약을 삭제 요청 시 에러 + - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 # API 명세 From 258c9b6dedb7c64604f62dbfcaf5e3cd82a9086c Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 15:12:00 +0900 Subject: [PATCH 03/75] =?UTF-8?q?fix:=20=EC=8B=9C=EA=B0=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EC=9D=91=EB=8B=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상태코드를 200 -> 201 변경 - Location 헤더 추가 Co-authored-by: zangsu <zangsu_@naver.com> --- .../roomescape/controller/ReservationTimeController.java | 8 ++++++-- .../controller/ReservationTimeControllerTest.java | 2 +- .../java/roomescape/integration/AdminIntegrationTest.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/roomescape/controller/ReservationTimeController.java b/src/main/java/roomescape/controller/ReservationTimeController.java index 21d0e51b7b..0dd8f6c839 100644 --- a/src/main/java/roomescape/controller/ReservationTimeController.java +++ b/src/main/java/roomescape/controller/ReservationTimeController.java @@ -1,6 +1,8 @@ package roomescape.controller; +import java.net.URI; import java.util.List; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -22,8 +24,10 @@ public ReservationTimeController(ReservationTimeService reservationTimeService) } @PostMapping - public ReservationTimeResponse save(@RequestBody ReservationTimeRequest reservationTimeRequest) { - return reservationTimeService.save(reservationTimeRequest); + public ResponseEntity<ReservationTimeResponse> save(@RequestBody ReservationTimeRequest reservationTimeRequest) { + ReservationTimeResponse saved = reservationTimeService.save(reservationTimeRequest); + return ResponseEntity.created(URI.create("/times/" + saved.id())) + .body(saved); } @GetMapping diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java index 84ad5b27ac..d9b50035ca 100644 --- a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -22,7 +22,7 @@ void save() { ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); LocalTime time = LocalTime.now(); - ReservationTimeResponse save = reservationTimeController.save(new ReservationTimeRequest(time)); + ReservationTimeResponse save = reservationTimeController.save(new ReservationTimeRequest(time)).getBody(); ReservationTimeResponse expected = new ReservationTimeResponse(save.id(), time); Assertions.assertThat(save) diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index 068b0dbe25..e27469203d 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -132,7 +132,7 @@ void reservationTimePageWork() { .body(params) .when().post("/times") .then().log().all() - .statusCode(200); + .statusCode(201); RestAssured.given().log().all() .when().get("/times") From dbb1b8b482a9f3f19e9c97516c58753e610747fe Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 15:18:55 +0900 Subject: [PATCH 04/75] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EC=9D=91=EB=8B=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상태코드를 200 -> 201 변경 - Location 헤더 추가 Co-authored-by: zangsu <zangsu_@naver.com> --- .../java/roomescape/controller/ReservationController.java | 8 ++++++-- .../roomescape/controller/ReservationControllerTest.java | 3 ++- .../java/roomescape/integration/AdminIntegrationTest.java | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 26be3989e9..a4db562d27 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,6 +1,8 @@ package roomescape.controller; +import java.net.URI; import java.util.List; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -22,8 +24,10 @@ public ReservationController(ReservationService reservationService) { } @PostMapping - public ReservationResponse saveReservation(@RequestBody ReservationRequest reservationRequest) { - return reservationService.save(reservationRequest); + public ResponseEntity<ReservationResponse> saveReservation(@RequestBody ReservationRequest reservationRequest) { + ReservationResponse saved = reservationService.save(reservationRequest); + return ResponseEntity.created(URI.create("/reservations/" + saved.id())) + .body(saved); } @GetMapping diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index 5f301b1993..37ddb85fbb 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -35,7 +35,8 @@ void saveReservation() { LocalDate date = LocalDate.now(); ReservationResponse saveResponse = reservationController.saveReservation( - new ReservationRequest(date, "폴라", timeId)); + new ReservationRequest(date, "폴라", timeId)) + .getBody(); long id = Objects.requireNonNull(saveResponse).id(); ReservationResponse expected = new ReservationResponse(id, "폴라", date, diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index e27469203d..f94062788f 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -73,7 +73,7 @@ void adminReservationPageWork() { .body(params) .when().post("/reservations") .then().log().all() - .statusCode(200) + .statusCode(201) .body("id", is(1)); RestAssured.given().log().all() @@ -107,7 +107,7 @@ void adminReservationPageWorkWithDB() { .body(params) .when().post("/reservations") .then().log().all() - .statusCode(200); + .statusCode(201); Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); Assertions.assertThat(count).isEqualTo(1); From 202216df6df9f5f87135d17024848961d3642ebe Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 15:22:28 +0900 Subject: [PATCH 05/75] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD,=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=8B=9C=EA=B0=84=20=EC=82=AD=EC=A0=9C=20API=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상태코드를 200 -> 204 변경 Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../java/roomescape/controller/ReservationController.java | 3 ++- .../roomescape/controller/ReservationTimeController.java | 3 ++- .../java/roomescape/integration/AdminIntegrationTest.java | 6 +++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2cd133e7aa..1c39527256 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 요구사항 문서 -- [ ] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 +- [x] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 - [ ] 예약 시간에 대한 제약 조건 추가 - [ ] 중복된 예약 시간 생성 요청 시 에러 - [ ] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index a4db562d27..349124b6d0 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -36,7 +36,8 @@ public List<ReservationResponse> findAllReservations() { } @DeleteMapping("/{id}") - public void delete(@PathVariable long id) { + public ResponseEntity<Void> delete(@PathVariable long id) { reservationService.delete(id); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/controller/ReservationTimeController.java b/src/main/java/roomescape/controller/ReservationTimeController.java index 0dd8f6c839..868e43f4b1 100644 --- a/src/main/java/roomescape/controller/ReservationTimeController.java +++ b/src/main/java/roomescape/controller/ReservationTimeController.java @@ -36,7 +36,8 @@ public List<ReservationTimeResponse> findAll() { } @DeleteMapping("/{id}") - public void delete(@PathVariable long id) { + public ResponseEntity<Void> delete(@PathVariable long id) { reservationTimeService.delete(id); + return ResponseEntity.noContent().build(); } } diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index f94062788f..1ebcabf417 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -85,7 +85,7 @@ void adminReservationPageWork() { RestAssured.given().log().all() .when().delete("/reservations/1") .then().log().all() - .statusCode(200); + .statusCode(204); RestAssured.given().log().all() .when().get("/reservations") @@ -115,7 +115,7 @@ void adminReservationPageWorkWithDB() { RestAssured.given().log().all() .when().delete("/reservations/1") .then().log().all() - .statusCode(200); + .statusCode(204); Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); Assertions.assertThat(countAfterDelete).isEqualTo(0); @@ -143,6 +143,6 @@ void reservationTimePageWork() { RestAssured.given().log().all() .when().delete("/times/2") .then().log().all() - .statusCode(200); + .statusCode(204); } } From bae62c990daec11c1704c4d67c88a77c914d45b3 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 15:41:16 +0900 Subject: [PATCH 06/75] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- src/main/resources/application.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb2..63c01678ab 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:product From fc59282994985bc1251e2eb4cd2c755d88a4fe87 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 15:46:52 +0900 Subject: [PATCH 07/75] =?UTF-8?q?fix:=20hh:MM=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EC=8B=9C=EA=B0=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../roomescape/config/TimeFormatterConfig.java | 6 ++++-- .../controller/RoomescapeExceptionHandler.java | 18 ++++++++++++++++++ .../java/roomescape/dto/ErrorResponse.java | 4 ++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/main/java/roomescape/controller/RoomescapeExceptionHandler.java create mode 100644 src/main/java/roomescape/dto/ErrorResponse.java diff --git a/README.md b/README.md index 1c39527256..725e1478e2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - [x] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 - [ ] 예약 시간에 대한 제약 조건 추가 - [ ] 중복된 예약 시간 생성 요청 시 에러 - - [ ] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 + - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [ ] 예약이 있는 예약 시간을 삭제 요청 시 에러 - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 - [ ] 예약에 대한 제약 조건 추가 diff --git a/src/main/java/roomescape/config/TimeFormatterConfig.java b/src/main/java/roomescape/config/TimeFormatterConfig.java index 35c20b11b1..3cf3aa6c9c 100644 --- a/src/main/java/roomescape/config/TimeFormatterConfig.java +++ b/src/main/java/roomescape/config/TimeFormatterConfig.java @@ -1,5 +1,6 @@ package roomescape.config; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.time.format.DateTimeFormatter; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; @@ -8,10 +9,11 @@ @Configuration public class TimeFormatterConfig { - private static final String TIME_FORMAT = "HH:mm"; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); @Bean public Jackson2ObjectMapperBuilderCustomizer localTimeSerializerCustomizer() { - return builder -> builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT))); + return builder -> builder.serializers(new LocalTimeSerializer(FORMATTER)) + .deserializers(new LocalTimeDeserializer(FORMATTER)); } } diff --git a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java new file mode 100644 index 0000000000..da293fb62f --- /dev/null +++ b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java @@ -0,0 +1,18 @@ +package roomescape.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import roomescape.dto.ErrorResponse; + +@ControllerAdvice +public class RoomescapeExceptionHandler { + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException e) { + e.printStackTrace(); + return ResponseEntity.badRequest() + .body(new ErrorResponse("입력값이 잘못되었습니다.")); + } +} diff --git a/src/main/java/roomescape/dto/ErrorResponse.java b/src/main/java/roomescape/dto/ErrorResponse.java new file mode 100644 index 0000000000..a218a73952 --- /dev/null +++ b/src/main/java/roomescape/dto/ErrorResponse.java @@ -0,0 +1,4 @@ +package roomescape.dto; + +public record ErrorResponse(String message) { +} From 65733d0b65cc6a53b8b295bae8821d0fca43487f Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 16:15:37 +0900 Subject: [PATCH 08/75] =?UTF-8?q?refactor=20:=20Repository=20=EA=B0=80=20D?= =?UTF-8?q?TO=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- .../java/roomescape/domain/Reservation.java | 8 +++-- .../JdbcTemplateReservationRepository.java | 13 ++++---- ...JdbcTemplateReservationTimeRepository.java | 11 +++---- .../repository/ReservationRepository.java | 3 +- .../repository/ReservationTimeRepository.java | 3 +- .../service/ReservationService.java | 6 +++- .../service/ReservationTimeService.java | 3 +- .../CollectionReservationRepository.java | 32 +++++++------------ .../CollectionReservationTimeRepository.java | 10 +++--- ...JdbcTemplateReservationRepositoryTest.java | 12 ++++--- ...TemplateReservationTimeRepositoryTest.java | 9 +++--- 11 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 004dfd31ac..a64065c4cd 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -11,8 +11,8 @@ public class Reservation implements Comparable<Reservation> { private final LocalDate date; private final ReservationTime time; - public Reservation(long id, Reservation reservationBeforeSave) { - this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time); + public Reservation(String name, LocalDate date, ReservationTime time) { + this(null, name, date, time); } public Reservation(Long id, String name, LocalDate date, ReservationTime time) { @@ -22,6 +22,10 @@ public Reservation(Long id, String name, LocalDate date, ReservationTime time) { this.time = time; } + public Reservation(long id, Reservation reservationBeforeSave) { + this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time); + } + @Override public int compareTo(Reservation other) { LocalDateTime dateTime = LocalDateTime.of(date, time.getStartAt()); diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index 7884d19fdc..31eef64900 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -11,7 +11,6 @@ import org.springframework.stereotype.Repository; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationRequest; @Repository public class JdbcTemplateReservationRepository implements ReservationRepository { @@ -22,23 +21,23 @@ public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate) { } @Override - public Reservation save(ReservationRequest reservationRequest) { - ReservationTime reservationTime = findReservationTime(reservationRequest); - Reservation reservation = new Reservation(null, reservationRequest.name(), reservationRequest.date(), + public Reservation save(Reservation reservation) { + ReservationTime reservationTime = findReservationTime(reservation.getReservationTime().getId()); + Reservation beforeSaved = new Reservation(null, reservation.getName(), reservation.getDate(), reservationTime); KeyHolder keyHolder = new GeneratedKeyHolder(); - save(reservation, keyHolder); + save(beforeSaved, keyHolder); long id = keyHolder.getKey().longValue(); return new Reservation(id, reservation); } - private ReservationTime findReservationTime(ReservationRequest reservationRequest) { + private ReservationTime findReservationTime(long timeId) { String reservationTimeSelectSql = "select * from reservation_time where id = ?"; return jdbcTemplate.queryForObject(reservationTimeSelectSql, (rs, rowNum) -> { long id = rs.getLong(1); LocalTime startAt = rs.getTime(2).toLocalTime(); return new ReservationTime(id, startAt); - }, reservationRequest.timeId()); + }, timeId); } private void save(Reservation reservation, KeyHolder keyHolder) { diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index 639db36eaa..2e7950a460 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -9,7 +9,6 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationTimeRequest; @Repository public class JdbcTemplateReservationTimeRepository implements ReservationTimeRepository { @@ -20,17 +19,17 @@ public JdbcTemplateReservationTimeRepository(JdbcTemplate jdbcTemplate) { } @Override - public ReservationTime save(ReservationTimeRequest reservationTimeRequest) { + public ReservationTime save(ReservationTime reservationTime) { KeyHolder keyHolder = new GeneratedKeyHolder(); - save(reservationTimeRequest, keyHolder); - return new ReservationTime(keyHolder.getKey().longValue(), reservationTimeRequest.startAt()); + save(reservationTime, keyHolder); + return new ReservationTime(keyHolder.getKey().longValue(), reservationTime.getStartAt()); } - private void save(ReservationTimeRequest reservationTimeRequest, KeyHolder keyHolder) { + private void save(ReservationTime reservationTime, KeyHolder keyHolder) { jdbcTemplate.update(con -> { PreparedStatement pstmt = con.prepareStatement("insert into reservation_time(start_at) values ( ? )", new String[]{"id"}); - pstmt.setTime(1, Time.valueOf(reservationTimeRequest.startAt())); + pstmt.setTime(1, Time.valueOf(reservationTime.getStartAt())); return pstmt; }, keyHolder); } diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java index 79c62ca32f..1ac0fbd917 100644 --- a/src/main/java/roomescape/repository/ReservationRepository.java +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -2,10 +2,9 @@ import java.util.List; import roomescape.domain.Reservation; -import roomescape.dto.ReservationRequest; public interface ReservationRepository { - Reservation save(ReservationRequest reservationRequest); + Reservation save(Reservation reservation); List<Reservation> findAll(); diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java index 5f6e144eea..ac26358500 100644 --- a/src/main/java/roomescape/repository/ReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -2,10 +2,9 @@ import java.util.List; import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationTimeRequest; public interface ReservationTimeRepository { - ReservationTime save(ReservationTimeRequest reservationTimeRequest); + ReservationTime save(ReservationTime reservationTime); List<ReservationTime> findAll(); diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index de6d8d5288..1c0e54c4b5 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -18,7 +18,11 @@ public ReservationService(ReservationRepository reservationRepository) { } public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation saved = reservationRepository.save(reservationRequest); + Reservation saved = reservationRepository.save(new Reservation( + reservationRequest.name(), + reservationRequest.date(), + new ReservationTime(reservationRequest.timeId(), null) + )); return toResponse(saved); } diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 908a747d0b..e6548bb1d7 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -16,7 +16,8 @@ public ReservationTimeService(ReservationTimeRepository reservationTimeRepositor } public ReservationTimeResponse save(ReservationTimeRequest reservationTimeRequest) { - ReservationTime saved = reservationTimeRepository.save(reservationTimeRequest); + ReservationTime reservationTime = new ReservationTime(reservationTimeRequest.startAt()); + ReservationTime saved = reservationTimeRepository.save(reservationTime); return toResponse(saved); } diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java index 087cc9de04..ef0eac28b5 100644 --- a/src/test/java/roomescape/repository/CollectionReservationRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -1,6 +1,5 @@ package roomescape.repository; -import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -30,29 +29,22 @@ public CollectionReservationRepository(List<Reservation> reservations, this(reservations, new AtomicLong(0), timeRepository); } - @Override - public Reservation save(ReservationRequest reservationRequest) { - Reservation reservation = fromRequest(reservationRequest); - reservations.add(reservation); - return reservation; - } - - private Reservation fromRequest(ReservationRequest reservationRequest) { - long id = atomicLong.incrementAndGet(); - - String name = reservationRequest.name(); - LocalDate date = reservationRequest.date(); - ReservationTime reservationTime = timeRepository.findAll().stream() - .filter(sameId(reservationRequest)) - .findAny() - .orElseThrow(); - return new Reservation(id, name, date, reservationTime); - } - private static Predicate<ReservationTime> sameId(ReservationRequest reservationRequest) { return reservationTime -> reservationTime.getId() == reservationRequest.timeId(); } + @Override + public Reservation save(Reservation reservation) { + ReservationTime findTime = timeRepository.findAll().stream() + .filter(reservationTime -> reservationTime.getId() == reservation.getReservationTime().getId()) + .findFirst() + .get(); + Reservation saved = new Reservation(atomicLong.incrementAndGet(), reservation.getName(), reservation.getDate(), + findTime); + reservations.add(saved); + return saved; + } + @Override public List<Reservation> findAll() { return reservations.stream() diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index b6f22fe988..6b90de607c 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationTimeRequest; public class CollectionReservationTimeRepository implements ReservationTimeRepository { private final List<ReservationTime> reservationTimes; @@ -24,11 +23,10 @@ public CollectionReservationTimeRepository(List<ReservationTime> reservationTime } @Override - public ReservationTime save(ReservationTimeRequest reservationTimeRequest) { - ReservationTime reservationTime = new ReservationTime(atomicLong.incrementAndGet(), - reservationTimeRequest.startAt()); - reservationTimes.add(reservationTime); - return reservationTime; + public ReservationTime save(ReservationTime reservationTime) { + ReservationTime saved = new ReservationTime(atomicLong.incrementAndGet(), reservationTime.getStartAt()); + reservationTimes.add(saved); + return saved; } @Override diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java index c2acc8994f..ddb8faa35b 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -1,6 +1,7 @@ package roomescape.repository; import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -10,10 +11,11 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import roomescape.domain.Reservation; -import roomescape.dto.ReservationRequest; +import roomescape.domain.ReservationTime; @SpringBootTest class JdbcTemplateReservationRepositoryTest { + private static final ReservationTime DEFAULT_TIME = new ReservationTime(1L, LocalTime.of(11, 56)); @Autowired private ReservationRepository reservationRepository; @Autowired @@ -32,7 +34,7 @@ void init() { @DisplayName("Reservation 을 잘 저장하는지 확인한다.") void save() { var beforeSave = reservationRepository.findAll(); - Reservation saved = reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); + Reservation saved = reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); var afterSave = reservationRepository.findAll(); Assertions.assertThat(afterSave) @@ -44,8 +46,8 @@ void save() { @DisplayName("Reservation 을 잘 조회하는지 확인한다.") void findAll() { List<Reservation> beforeSave = reservationRepository.findAll(); - reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); - reservationRepository.save(new ReservationRequest(LocalDate.now(), "test2", 1)); + reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); + reservationRepository.save(new Reservation("test2", LocalDate.now(), DEFAULT_TIME)); List<Reservation> afterSave = reservationRepository.findAll(); Assertions.assertThat(afterSave.size()) @@ -56,7 +58,7 @@ void findAll() { @DisplayName("Reservation 을 잘 지우는지 확인한다.") void delete() { List<Reservation> beforeSaveAndDelete = reservationRepository.findAll(); - reservationRepository.save(new ReservationRequest(LocalDate.now(), "test", 1)); + reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); reservationRepository.delete(1L); diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java index 5bc5cd67f2..71d364044d 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java @@ -10,7 +10,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationTimeRequest; @SpringBootTest class JdbcTemplateReservationTimeRepositoryTest { @@ -31,7 +30,7 @@ void init() { @DisplayName("ReservationTime 을 잘 저장하는지 확인한다.") void save() { var beforeSave = reservationTimeRepository.findAll().stream().map(ReservationTime::getId).toList(); - ReservationTime saved = reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + ReservationTime saved = reservationTimeRepository.save(new ReservationTime(LocalTime.now())); var afterSave = reservationTimeRepository.findAll().stream().map(ReservationTime::getId).toList(); Assertions.assertThat(afterSave) @@ -43,8 +42,8 @@ void save() { @DisplayName("ReservationTime 을 잘 조회하는지 확인한다.") void findAll() { List<ReservationTime> beforeSave = reservationTimeRepository.findAll(); - reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); - reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + reservationTimeRepository.save(new ReservationTime(LocalTime.now())); + reservationTimeRepository.save(new ReservationTime(LocalTime.now())); List<ReservationTime> afterSave = reservationTimeRepository.findAll(); @@ -56,7 +55,7 @@ void findAll() { @DisplayName("ReservationTime 을 잘 지우하는지 확인한다.") void delete() { List<ReservationTime> beforeSaveAndDelete = reservationTimeRepository.findAll(); - reservationTimeRepository.save(new ReservationTimeRequest(LocalTime.now())); + reservationTimeRepository.save(new ReservationTime(LocalTime.now())); reservationTimeRepository.delete(1L); From 1bbadab2c025afcae469f8d7b4eac5a4d3c8a443 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 17:04:24 +0900 Subject: [PATCH 09/75] =?UTF-8?q?feat=20:=20ReservationTime=20=EC=9D=98=20?= =?UTF-8?q?startAt=20=EC=97=90=20=EB=8C=80=ED=95=9C=20null=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=97=90=20=EC=98=88=EC=95=BD=20=EC=83=9D=EC=84=B1=20=EC=8B=9C?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../RoomescapeExceptionHandler.java | 8 ++++++ .../roomescape/domain/ReservationTime.java | 4 +++ ...JdbcTemplateReservationTimeRepository.java | 25 +++++++++++++------ .../repository/ReservationTimeRepository.java | 3 +++ .../service/ReservationService.java | 13 ++++++++-- .../CollectionReservationTimeRepository.java | 8 ++++++ 7 files changed, 53 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 725e1478e2..75a938003c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 - [ ] 예약에 대한 제약 조건 추가 - [ ] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - - [ ] 존재하지 않는 시간에 예약 생성 요청 시 에러 + - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [ ] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [ ] 존재하지 않는 예약을 삭제 요청 시 에러 - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 diff --git a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java index da293fb62f..e9a2f23984 100644 --- a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java +++ b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java @@ -15,4 +15,12 @@ public ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException e) { return ResponseEntity.badRequest() .body(new ErrorResponse("입력값이 잘못되었습니다.")); } + + //TODO : 커스템 에러로 처리하도록 변경 + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity<ErrorResponse> handle(IllegalArgumentException e) { + e.printStackTrace(); + return ResponseEntity.badRequest(). + body(new ErrorResponse(e.getMessage())); + } } diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 8b54f99f81..63f0c19ef3 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -11,7 +11,11 @@ public ReservationTime(LocalTime startAt) { this(null, startAt); } + //TODO 테스트 추가 public ReservationTime(Long id, LocalTime startAt) { + if (startAt == null) { + throw new IllegalArgumentException("시간은 없을 수 없습니다."); + } this.id = id; this.startAt = startAt; } diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index 2e7950a460..2f2a3251b3 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -4,6 +4,7 @@ import java.sql.Time; import java.time.LocalTime; import java.util.List; +import java.util.Optional; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; @@ -25,13 +26,14 @@ public ReservationTime save(ReservationTime reservationTime) { return new ReservationTime(keyHolder.getKey().longValue(), reservationTime.getStartAt()); } - private void save(ReservationTime reservationTime, KeyHolder keyHolder) { - jdbcTemplate.update(con -> { - PreparedStatement pstmt = con.prepareStatement("insert into reservation_time(start_at) values ( ? )", - new String[]{"id"}); - pstmt.setTime(1, Time.valueOf(reservationTime.getStartAt())); - return pstmt; - }, keyHolder); + @Override + public Optional<ReservationTime> findById(long id) { + List<ReservationTime> times = jdbcTemplate.query("select start_at from reservation_time where id = ?", + (rs, rowNum) -> { + LocalTime time = rs.getTime(1).toLocalTime(); + return new ReservationTime(id, time); + }, id); + return times.stream().findFirst(); } @Override @@ -47,4 +49,13 @@ public List<ReservationTime> findAll() { public void delete(long id) { jdbcTemplate.update("delete from reservation_time where id = ?", id); } + + private void save(ReservationTime reservationTime, KeyHolder keyHolder) { + jdbcTemplate.update(con -> { + PreparedStatement pstmt = con.prepareStatement("insert into reservation_time(start_at) values ( ? )", + new String[]{"id"}); + pstmt.setTime(1, Time.valueOf(reservationTime.getStartAt())); + return pstmt; + }, keyHolder); + } } diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java index ac26358500..5c8b92d09c 100644 --- a/src/main/java/roomescape/repository/ReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -1,11 +1,14 @@ package roomescape.repository; import java.util.List; +import java.util.Optional; import roomescape.domain.ReservationTime; public interface ReservationTimeRepository { ReservationTime save(ReservationTime reservationTime); + Optional<ReservationTime> findById(long id); + List<ReservationTime> findAll(); void delete(long id); diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 1c0e54c4b5..764a88b110 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,6 +1,7 @@ package roomescape.service; import java.util.List; +import java.util.Optional; import org.springframework.stereotype.Service; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; @@ -8,20 +9,28 @@ import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationTimeResponse; import roomescape.repository.ReservationRepository; +import roomescape.repository.ReservationTimeRepository; @Service public class ReservationService { private final ReservationRepository reservationRepository; + private final ReservationTimeRepository reservationTimeRepository; - public ReservationService(ReservationRepository reservationRepository) { + public ReservationService(ReservationRepository reservationRepository, + ReservationTimeRepository reservationTimeRepository) { this.reservationRepository = reservationRepository; + this.reservationTimeRepository = reservationTimeRepository; } public ReservationResponse save(ReservationRequest reservationRequest) { + //TODO 변수명 + Optional<ReservationTime> reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()); + Reservation saved = reservationRepository.save(new Reservation( reservationRequest.name(), reservationRequest.date(), - new ReservationTime(reservationRequest.timeId(), null) + //TODO : 커스텀 예외 사용할지 고민해보기 + reservationTime.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")) )); return toResponse(saved); } diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index 6b90de607c..77f1d2d834 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import roomescape.domain.ReservationTime; @@ -29,6 +30,13 @@ public ReservationTime save(ReservationTime reservationTime) { return saved; } + @Override + public Optional<ReservationTime> findById(long id) { + return reservationTimes.stream() + .filter(reservationTime -> reservationTime.getId() == id) //Todo 메서드로 바꾸기 + .findFirst(); + } + @Override public List<ReservationTime> findAll() { return List.copyOf(reservationTimes); From a14d3903b18783158f36254de8fd6d708a594ad9 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 17:23:58 +0900 Subject: [PATCH 10/75] =?UTF-8?q?feat=20:=20=EC=A4=91=EB=B3=B5=EB=90=9C=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EC=8B=9C=EA=B0=84=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- ...JdbcTemplateReservationTimeRepository.java | 7 +++++ .../repository/ReservationTimeRepository.java | 4 +++ .../service/ReservationTimeService.java | 3 +++ .../controller/ReservationControllerTest.java | 8 +++--- .../CollectionReservationTimeRepository.java | 7 +++++ .../service/ReservationTimeServiceTest.java | 26 +++++++++++++++++++ 7 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 src/test/java/roomescape/service/ReservationTimeServiceTest.java diff --git a/README.md b/README.md index 75a938003c..2735894936 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ - [x] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 - [ ] 예약 시간에 대한 제약 조건 추가 - - [ ] 중복된 예약 시간 생성 요청 시 에러 + - [x] 중복된 예약 시간 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [ ] 예약이 있는 예약 시간을 삭제 요청 시 에러 - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index 2f2a3251b3..9b6685bb52 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -26,6 +26,13 @@ public ReservationTime save(ReservationTime reservationTime) { return new ReservationTime(keyHolder.getKey().longValue(), reservationTime.getStartAt()); } + @Override + public boolean existsByStartAt(LocalTime startAt) { + return jdbcTemplate.queryForObject( + "select exists(select 1 from RESERVATION_TIME where START_AT = ?)", + Boolean.class, startAt); + } + @Override public Optional<ReservationTime> findById(long id) { List<ReservationTime> times = jdbcTemplate.query("select start_at from reservation_time where id = ?", diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java index 5c8b92d09c..f30eeb9c60 100644 --- a/src/main/java/roomescape/repository/ReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -1,5 +1,6 @@ package roomescape.repository; +import java.time.LocalTime; import java.util.List; import java.util.Optional; import roomescape.domain.ReservationTime; @@ -7,6 +8,9 @@ public interface ReservationTimeRepository { ReservationTime save(ReservationTime reservationTime); + //Todo 메서드명 고민 + boolean existsByStartAt(LocalTime startAt); + Optional<ReservationTime> findById(long id); List<ReservationTime> findAll(); diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index e6548bb1d7..1d20629e9c 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -16,6 +16,9 @@ public ReservationTimeService(ReservationTimeRepository reservationTimeRepositor } public ReservationTimeResponse save(ReservationTimeRequest reservationTimeRequest) { + if (reservationTimeRepository.existsByStartAt(reservationTimeRequest.startAt())) { + throw new IllegalArgumentException("중복된 시간은 생성할 수 없습니다."); + } ReservationTime reservationTime = new ReservationTime(reservationTimeRequest.startAt()); ReservationTime saved = reservationTimeRepository.save(reservationTime); return toResponse(saved); diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index 37ddb85fbb..b358e198cc 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -30,7 +30,7 @@ class ReservationControllerTest { void saveReservation() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); ReservationController reservationController = new ReservationController(reservationService); LocalDate date = LocalDate.now(); @@ -51,7 +51,7 @@ void saveReservation() { void findAllReservations() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); ReservationController reservationController = new ReservationController(reservationService); List<ReservationResponse> allReservations = reservationController.findAllReservations(); @@ -66,7 +66,7 @@ void delete() { new Reservation(1L, "폴라", LocalDate.now(), new ReservationTime(LocalTime.now()))); CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( new ArrayList<>(reservations), timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); ReservationController reservationController = new ReservationController(reservationService); reservationController.delete(1L); @@ -81,7 +81,7 @@ void delete() { void checkRepositoryDependency() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); ReservationController reservationController = new ReservationController(reservationService); boolean isRepositoryInjected = false; diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index 77f1d2d834..fc8901ecf4 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -1,5 +1,6 @@ package roomescape.repository; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -30,6 +31,12 @@ public ReservationTime save(ReservationTime reservationTime) { return saved; } + @Override + public boolean existsByStartAt(LocalTime startAt) { + return reservationTimes.stream() + .anyMatch(reservationTime -> startAt.equals(reservationTime.getStartAt())); + } + @Override public Optional<ReservationTime> findById(long id) { return reservationTimes.stream() diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java new file mode 100644 index 0000000000..7e3ab8de9f --- /dev/null +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -0,0 +1,26 @@ +package roomescape.service; + +import java.time.LocalTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.dto.ReservationTimeRequest; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.ReservationTimeRepository; + +class ReservationTimeServiceTest { + + @Test + @DisplayName("중복된 시간은 생성할 수 없는지 검증") + void saveFailCauseDuplicate() { + ReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + ReservationTimeRequest reservationTimeRequest = new ReservationTimeRequest(LocalTime.of(10, 0)); + reservationTimeService.save(reservationTimeRequest); + + Assertions.assertThatThrownBy(() -> reservationTimeService.save(reservationTimeRequest)) + .isInstanceOf(IllegalArgumentException.class) + //TODO 커스텀 에러 + .hasMessage("중복된 시간은 생성할 수 없습니다."); + } +} From 2da2b7ac36e52a982bf4971317f53580804a2376 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 17:33:33 +0900 Subject: [PATCH 11/75] =?UTF-8?q?feat=20:=20=EC=98=88=EC=95=BD=EC=9D=B4=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EC=98=88=EC=95=BD=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=84=20=EC=82=AD=EC=A0=9C=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../RoomescapeExceptionHandler.java | 4 ++-- .../service/ReservationTimeService.java | 16 +++++++++++++- .../ReservationTimeControllerTest.java | 21 +++++++++++++++---- .../service/ReservationTimeServiceTest.java | 9 +++++--- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2735894936..c63c9ee357 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - [ ] 예약 시간에 대한 제약 조건 추가 - [x] 중복된 예약 시간 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - - [ ] 예약이 있는 예약 시간을 삭제 요청 시 에러 + - [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 - [ ] 예약에 대한 제약 조건 추가 - [ ] 동일한 날짜와 시간에 예약 생성 요청 시 에러 diff --git a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java index e9a2f23984..e7cc8497d2 100644 --- a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java +++ b/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java @@ -17,8 +17,8 @@ public ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException e) { } //TODO : 커스템 에러로 처리하도록 변경 - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity<ErrorResponse> handle(IllegalArgumentException e) { + @ExceptionHandler(RuntimeException.class) + public ResponseEntity<ErrorResponse> handle(RuntimeException e) { e.printStackTrace(); return ResponseEntity.badRequest(). body(new ErrorResponse(e.getMessage())); diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 1d20629e9c..09d12908a3 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -2,16 +2,21 @@ import java.util.List; import org.springframework.stereotype.Service; +import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; import roomescape.dto.ReservationTimeRequest; import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; @Service public class ReservationTimeService { + private final ReservationRepository reservationRepository; private final ReservationTimeRepository reservationTimeRepository; - public ReservationTimeService(ReservationTimeRepository reservationTimeRepository) { + public ReservationTimeService(ReservationRepository reservationRepository, + ReservationTimeRepository reservationTimeRepository) { + this.reservationRepository = reservationRepository; this.reservationTimeRepository = reservationTimeRepository; } @@ -34,7 +39,16 @@ public List<ReservationTimeResponse> findAll() { .toList(); } + //TODO : 테스트 생성 public void delete(long id) { + //todo SQL로 구현 + List<Reservation> reservations = reservationRepository.findAll(); + //TODO : 지역변수 네이밍 고민 + boolean invalidDelete = reservations.stream() + .anyMatch(reservation -> reservation.getReservationTime().getId() == id); + if (invalidDelete) { + throw new IllegalStateException("예약이 존재하는 시간을 지울 수 없습니다."); + } reservationTimeRepository.delete(id); } } diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java index d9b50035ca..7726d9e2d5 100644 --- a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -10,6 +10,7 @@ import roomescape.domain.ReservationTime; import roomescape.dto.ReservationTimeRequest; import roomescape.dto.ReservationTimeResponse; +import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; import roomescape.service.ReservationTimeService; @@ -18,7 +19,10 @@ class ReservationTimeControllerTest { @DisplayName("시간을 잘 저장하는지 확인한다.") void save() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository( + reservationTimeRepository); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); LocalTime time = LocalTime.now(); @@ -33,7 +37,10 @@ void save() { @DisplayName("시간을 잘 불러오는지 확인한다.") void findAll() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository( + reservationTimeRepository); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); List<ReservationTimeResponse> reservationTimeResponses = reservationTimeController.findAll(); @@ -47,7 +54,10 @@ void delete() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository( new ArrayList<>(List.of(new ReservationTime(1L, LocalTime.now()))) ); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository( + reservationTimeRepository); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); reservationTimeController.delete(1); @@ -61,7 +71,10 @@ void delete() { @DisplayName("내부에 Repository를 의존하고 있지 않은지 확인한다.") void checkRepositoryDependency() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository( + reservationTimeRepository); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); boolean isRepositoryInjected = false; diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 7e3ab8de9f..05bfff4ced 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -5,16 +5,19 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.dto.ReservationTimeRequest; +import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; -import roomescape.repository.ReservationTimeRepository; class ReservationTimeServiceTest { @Test @DisplayName("중복된 시간은 생성할 수 없는지 검증") void saveFailCauseDuplicate() { - ReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationTimeRepository); + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository( + reservationTimeRepository); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); ReservationTimeRequest reservationTimeRequest = new ReservationTimeRequest(LocalTime.of(10, 0)); reservationTimeService.save(reservationTimeRequest); From 4f222f4acc92cdf836401c5eef5efe9945bef66d Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 17:41:27 +0900 Subject: [PATCH 12/75] =?UTF-8?q?docs=20:=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=9E=90=EC=9B=90=EC=9D=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 어차피 자원이 존재하지 않는 것은 동일하므로 문제가 없다고 결정 Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c63c9ee357..6c7e7c2133 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ # 요구사항 문서 - [x] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 -- [ ] 예약 시간에 대한 제약 조건 추가 +- [x] 예약 시간에 대한 제약 조건 추가 - [x] 중복된 예약 시간 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 - - [ ] 존재하지 않는 시간을 삭제 요청 시 에러 - [ ] 예약에 대한 제약 조건 추가 - [ ] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [ ] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - - [ ] 존재하지 않는 예약을 삭제 요청 시 에러 - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 # API 명세 From 6a4ebb7c19e0afe91ab42966346e410cffd635f3 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 17:49:48 +0900 Subject: [PATCH 13/75] =?UTF-8?q?feat:=20=EB=8F=99=EC=9D=BC=ED=95=9C=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=EC=99=80=20=EC=8B=9C=EA=B0=84=EC=97=90=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EC=83=9D=EC=84=B1=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../service/ReservationService.java | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c7e7c2133..dab9f638df 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 - [ ] 예약에 대한 제약 조건 추가 - - [ ] 동일한 날짜와 시간에 예약 생성 요청 시 에러 + - [x] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [ ] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 764a88b110..5757bd910f 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,5 +1,8 @@ package roomescape.service; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.Optional; import org.springframework.stereotype.Service; @@ -22,19 +25,33 @@ public ReservationService(ReservationRepository reservationRepository, this.reservationTimeRepository = reservationTimeRepository; } + //ToDo 테스트 작성 public ReservationResponse save(ReservationRequest reservationRequest) { //TODO 변수명 Optional<ReservationTime> reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()); - Reservation saved = reservationRepository.save(new Reservation( + Reservation beforeSave = new Reservation( reservationRequest.name(), reservationRequest.date(), //TODO : 커스텀 예외 사용할지 고민해보기 reservationTime.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")) - )); + ); + boolean isDuplicate = reservationRepository.findAll() + .stream() + .anyMatch(reservation -> mapToLocalDateTime(reservation).equals(mapToLocalDateTime(beforeSave))); + if (isDuplicate) { + throw new IllegalArgumentException("동일한 날짜와 시간에 이미 예약이 존재합니다."); + } + Reservation saved = reservationRepository.save(beforeSave); return toResponse(saved); } + private LocalDateTime mapToLocalDateTime(Reservation reservation) { + LocalDate date = reservation.getDate(); + LocalTime time = reservation.getReservationTime().getStartAt(); + return LocalDateTime.of(date, time); + } + private ReservationResponse toResponse(Reservation reservation) { ReservationTime reservationTime = reservation.getReservationTime(); ReservationTimeResponse reservationTimeResponse = new ReservationTimeResponse(reservationTime.getId(), From 60e2f8896a2c04d220b47678d4ffab2a1298de90 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 19:13:17 +0900 Subject: [PATCH 14/75] =?UTF-8?q?feat:=20ISO=208601=20=ED=91=9C=EC=A4=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20YYYY-MM-dd=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=82=A0=EC=A7=9C=EA=B0=80=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=EB=90=9C=20=EC=98=88=EC=95=BD=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../java/roomescape/config/TimeFormatterConfig.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dab9f638df..33ae7832ff 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - [ ] 예약에 대한 제약 조건 추가 - [x] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - - [ ] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 + - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 # API 명세 diff --git a/src/main/java/roomescape/config/TimeFormatterConfig.java b/src/main/java/roomescape/config/TimeFormatterConfig.java index 3cf3aa6c9c..7b301a6547 100644 --- a/src/main/java/roomescape/config/TimeFormatterConfig.java +++ b/src/main/java/roomescape/config/TimeFormatterConfig.java @@ -1,6 +1,8 @@ package roomescape.config; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.time.format.DateTimeFormatter; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; @@ -9,11 +11,13 @@ @Configuration public class TimeFormatterConfig { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); @Bean public Jackson2ObjectMapperBuilderCustomizer localTimeSerializerCustomizer() { - return builder -> builder.serializers(new LocalTimeSerializer(FORMATTER)) - .deserializers(new LocalTimeDeserializer(FORMATTER)); + return builder -> builder.serializers(new LocalTimeSerializer(TIME_FORMATTER), + new LocalDateSerializer(DATE_FORMATTER)) + .deserializers(new LocalTimeDeserializer(TIME_FORMATTER), new LocalDateDeserializer(DATE_FORMATTER)); } } From 80b2371fabb048470aa7f34ae74939a71a66eff9 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 19:25:24 +0900 Subject: [PATCH 15/75] =?UTF-8?q?feat:=20=EC=A7=80=EB=82=98=EA=B0=84=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=EC=99=80=20=EC=8B=9C=EA=B0=84=EC=9D=98=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 3 ++- .../roomescape/service/ReservationService.java | 17 ++++++++++++++--- .../controller/ReservationControllerTest.java | 2 +- .../integration/AdminIntegrationTest.java | 7 +++++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 33ae7832ff..ef8f07fe10 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ - [x] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - - [ ] 지나간 날짜와 시간의 예약 요청 시 에러 + - [x] 지나간 날짜와 시간의 예약 요청 시 에러 + - [ ] 이름이 비어있는 예약 요청 시 에러 # API 명세 diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 5757bd910f..9327997a0d 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; -import java.util.Optional; import org.springframework.stereotype.Service; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; @@ -28,13 +27,14 @@ public ReservationService(ReservationRepository reservationRepository, //ToDo 테스트 작성 public ReservationResponse save(ReservationRequest reservationRequest) { //TODO 변수명 - Optional<ReservationTime> reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()); + ReservationTime reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")); Reservation beforeSave = new Reservation( reservationRequest.name(), reservationRequest.date(), //TODO : 커스텀 예외 사용할지 고민해보기 - reservationTime.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")) + reservationTime ); boolean isDuplicate = reservationRepository.findAll() .stream() @@ -42,10 +42,21 @@ public ReservationResponse save(ReservationRequest reservationRequest) { if (isDuplicate) { throw new IllegalArgumentException("동일한 날짜와 시간에 이미 예약이 존재합니다."); } + + //todo 테스트 + if (isBefore(beforeSave)) { + throw new IllegalArgumentException("이미 지난 시간에 예약할 수 없습니다."); + } + Reservation saved = reservationRepository.save(beforeSave); return toResponse(saved); } + private static boolean isBefore(Reservation beforeSave) { + return LocalDateTime.of(beforeSave.getDate(), beforeSave.getTime()) + .isBefore(LocalDateTime.now()); + } + private LocalDateTime mapToLocalDateTime(Reservation reservation) { LocalDate date = reservation.getDate(); LocalTime time = reservation.getReservationTime().getStartAt(); diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index b358e198cc..fa257e5e74 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -32,7 +32,7 @@ void saveReservation() { timeRepository); ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); ReservationController reservationController = new ReservationController(reservationService); - LocalDate date = LocalDate.now(); + LocalDate date = LocalDate.now().plusDays(1); ReservationResponse saveResponse = reservationController.saveReservation( new ReservationRequest(date, "폴라", timeId)) diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index 1ebcabf417..9a525e8289 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -4,6 +4,8 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import org.assertj.core.api.Assertions; @@ -18,6 +20,7 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class AdminIntegrationTest { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @LocalServerPort private int port; @Autowired @@ -65,7 +68,7 @@ void adminReservationPageWork() { System.out.println(integer); Map<String, Object> params = new HashMap<>(); params.put("name", "브라운"); - params.put("date", "2023-08-05"); + params.put("date", LocalDate.now().plusDays(1).format(DATE_FORMATTER)); params.put("timeId", 1); RestAssured.given().log().all() @@ -99,7 +102,7 @@ void adminReservationPageWork() { void adminReservationPageWorkWithDB() { Map<String, Object> params = new HashMap<>(); params.put("name", "브라운"); - params.put("date", "2023-08-05"); + params.put("date", LocalDate.now().plusDays(1).format(DATE_FORMATTER)); params.put("timeId", 1); RestAssured.given().log().all() From a5ca6d4533afef91af8d76c1a10a0158d345aca7 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 19:29:58 +0900 Subject: [PATCH 16/75] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A6=84=EC=9D=B4=20?= =?UTF-8?q?=EB=B9=84=EC=96=B4=EC=9E=88=EB=8A=94=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 4 ++-- src/main/java/roomescape/domain/Reservation.java | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef8f07fe10..f7c1ea8c75 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ - [x] 중복된 예약 시간 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 -- [ ] 예약에 대한 제약 조건 추가 +- [x] 예약에 대한 제약 조건 추가 - [x] 동일한 날짜와 시간에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [x] 지나간 날짜와 시간의 예약 요청 시 에러 - - [ ] 이름이 비어있는 예약 요청 시 에러 + - [x] 이름이 비어있는 예약 요청 시 에러 # API 명세 diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index a64065c4cd..c3f7b867e7 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -16,6 +16,9 @@ public Reservation(String name, LocalDate date, ReservationTime time) { } public Reservation(Long id, String name, LocalDate date, ReservationTime time) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("이름은 필수 값입니다."); + } this.id = id; this.name = name; this.date = date; From 36fe37e745cd67874f1a050cd9141d7d685c22e1 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 20:09:48 +0900 Subject: [PATCH 17/75] =?UTF-8?q?refactor:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- .../java/roomescape/domain/Reservation.java | 5 ++- .../roomescape/domain/ReservationTime.java | 5 ++- .../roomescape/exception/ExceptionType.java | 35 +++++++++++++++++++ .../exception/RoomescapeException.java | 20 +++++++++++ .../RoomescapeExceptionHandler.java | 18 +++++----- .../service/ReservationService.java | 11 ++++-- .../service/ReservationTimeService.java | 8 +++-- .../service/ReservationTimeServiceTest.java | 8 +++-- 8 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 src/main/java/roomescape/exception/ExceptionType.java create mode 100644 src/main/java/roomescape/exception/RoomescapeException.java rename src/main/java/roomescape/{controller => exception}/RoomescapeExceptionHandler.java (52%) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index c3f7b867e7..85c23ba10c 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,9 +1,12 @@ package roomescape.domain; +import static roomescape.exception.ExceptionType.NAME_EMPTY; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Objects; +import roomescape.exception.RoomescapeException; public class Reservation implements Comparable<Reservation> { private final Long id; @@ -17,7 +20,7 @@ public Reservation(String name, LocalDate date, ReservationTime time) { public Reservation(Long id, String name, LocalDate date, ReservationTime time) { if (name == null || name.isBlank()) { - throw new IllegalArgumentException("이름은 필수 값입니다."); + throw new RoomescapeException(NAME_EMPTY); } this.id = id; this.name = name; diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 63f0c19ef3..847a933d2c 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -1,7 +1,10 @@ package roomescape.domain; +import static roomescape.exception.ExceptionType.TIME_EMPTY; + import java.time.LocalTime; import java.util.Objects; +import roomescape.exception.RoomescapeException; public class ReservationTime { private final Long id; @@ -14,7 +17,7 @@ public ReservationTime(LocalTime startAt) { //TODO 테스트 추가 public ReservationTime(Long id, LocalTime startAt) { if (startAt == null) { - throw new IllegalArgumentException("시간은 없을 수 없습니다."); + throw new RoomescapeException(TIME_EMPTY); } this.id = id; this.startAt = startAt; diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java new file mode 100644 index 0000000000..ac9fb75a94 --- /dev/null +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -0,0 +1,35 @@ +package roomescape.exception; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +import org.springframework.http.HttpStatus; + +public enum ExceptionType { + + NAME_EMPTY(BAD_REQUEST, "이름은 필수 값입니다."), + TIME_EMPTY(BAD_REQUEST, "시작 시간은 필수 값입니다."), + DUPLICATE_RESERVATION(BAD_REQUEST, "같은 시간에 이미 예약이 존재합니다."), + DUPLICATE_RESERVATION_TIME(BAD_REQUEST, "이미 예약시간이 존재합니다."), + INVALID_DATE_TIME_FORMAT(BAD_REQUEST, "해석할 수 없는 날짜, 시간 포맷입니다."), + //Todo 이름 변경 + INVALID_DELETE_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), + RESERVATION_TIME_NOT_FOUND(BAD_REQUEST, "존재하지 않는 시간입니다."), + //todo 이름 변경 + PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."); + + private final HttpStatus status; + private final String message; + + ExceptionType(HttpStatus status, String message) { + this.status = status; + this.message = message; + } + + public String getMessage() { + return message; + } + + public HttpStatus getStatus() { + return status; + } +} diff --git a/src/main/java/roomescape/exception/RoomescapeException.java b/src/main/java/roomescape/exception/RoomescapeException.java new file mode 100644 index 0000000000..5a647bbafd --- /dev/null +++ b/src/main/java/roomescape/exception/RoomescapeException.java @@ -0,0 +1,20 @@ +package roomescape.exception; + +import org.springframework.http.HttpStatus; + +public class RoomescapeException extends RuntimeException { + private final ExceptionType exceptionType; + + public RoomescapeException(ExceptionType exceptionType) { + this.exceptionType = exceptionType; + } + + @Override + public String getMessage() { + return exceptionType.getMessage(); + } + + public HttpStatus getHttpStatus() { + return exceptionType.getStatus(); + } +} diff --git a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java b/src/main/java/roomescape/exception/RoomescapeExceptionHandler.java similarity index 52% rename from src/main/java/roomescape/controller/RoomescapeExceptionHandler.java rename to src/main/java/roomescape/exception/RoomescapeExceptionHandler.java index e7cc8497d2..c2a49f1fc9 100644 --- a/src/main/java/roomescape/controller/RoomescapeExceptionHandler.java +++ b/src/main/java/roomescape/exception/RoomescapeExceptionHandler.java @@ -1,4 +1,6 @@ -package roomescape.controller; +package roomescape.exception; + +import static roomescape.exception.ExceptionType.INVALID_DATE_TIME_FORMAT; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -12,15 +14,15 @@ public class RoomescapeExceptionHandler { @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException e) { e.printStackTrace(); - return ResponseEntity.badRequest() - .body(new ErrorResponse("입력값이 잘못되었습니다.")); + return ResponseEntity.status(INVALID_DATE_TIME_FORMAT.getStatus()) + .body(new ErrorResponse(INVALID_DATE_TIME_FORMAT.getMessage())); } - //TODO : 커스템 에러로 처리하도록 변경 - @ExceptionHandler(RuntimeException.class) - public ResponseEntity<ErrorResponse> handle(RuntimeException e) { + @ExceptionHandler(RoomescapeException.class) + public ResponseEntity<ErrorResponse> handle(RoomescapeException e) { e.printStackTrace(); - return ResponseEntity.badRequest(). - body(new ErrorResponse(e.getMessage())); + return ResponseEntity + .status(e.getHttpStatus()) + .body(new ErrorResponse(e.getMessage())); } } diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 9327997a0d..a054867776 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,5 +1,9 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; +import static roomescape.exception.ExceptionType.PAST_TIME; +import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,6 +14,7 @@ import roomescape.dto.ReservationRequest; import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationTimeResponse; +import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; @@ -28,7 +33,7 @@ public ReservationService(ReservationRepository reservationRepository, public ReservationResponse save(ReservationRequest reservationRequest) { //TODO 변수명 ReservationTime reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")); + .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); Reservation beforeSave = new Reservation( reservationRequest.name(), @@ -40,12 +45,12 @@ public ReservationResponse save(ReservationRequest reservationRequest) { .stream() .anyMatch(reservation -> mapToLocalDateTime(reservation).equals(mapToLocalDateTime(beforeSave))); if (isDuplicate) { - throw new IllegalArgumentException("동일한 날짜와 시간에 이미 예약이 존재합니다."); + throw new RoomescapeException(DUPLICATE_RESERVATION); } //todo 테스트 if (isBefore(beforeSave)) { - throw new IllegalArgumentException("이미 지난 시간에 예약할 수 없습니다."); + throw new RoomescapeException(PAST_TIME); } Reservation saved = reservationRepository.save(beforeSave); diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 09d12908a3..0b1b97b5c8 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -1,11 +1,15 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; +import static roomescape.exception.ExceptionType.INVALID_DELETE_TIME; + import java.util.List; import org.springframework.stereotype.Service; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; import roomescape.dto.ReservationTimeRequest; import roomescape.dto.ReservationTimeResponse; +import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; @@ -22,7 +26,7 @@ public ReservationTimeService(ReservationRepository reservationRepository, public ReservationTimeResponse save(ReservationTimeRequest reservationTimeRequest) { if (reservationTimeRepository.existsByStartAt(reservationTimeRequest.startAt())) { - throw new IllegalArgumentException("중복된 시간은 생성할 수 없습니다."); + throw new RoomescapeException(DUPLICATE_RESERVATION_TIME); } ReservationTime reservationTime = new ReservationTime(reservationTimeRequest.startAt()); ReservationTime saved = reservationTimeRepository.save(reservationTime); @@ -47,7 +51,7 @@ public void delete(long id) { boolean invalidDelete = reservations.stream() .anyMatch(reservation -> reservation.getReservationTime().getId() == id); if (invalidDelete) { - throw new IllegalStateException("예약이 존재하는 시간을 지울 수 없습니다."); + throw new RoomescapeException(INVALID_DELETE_TIME); } reservationTimeRepository.delete(id); } diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 05bfff4ced..cc622871d0 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -1,10 +1,13 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; + import java.time.LocalTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.dto.ReservationTimeRequest; +import roomescape.exception.RoomescapeException; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; @@ -22,8 +25,7 @@ void saveFailCauseDuplicate() { reservationTimeService.save(reservationTimeRequest); Assertions.assertThatThrownBy(() -> reservationTimeService.save(reservationTimeRequest)) - .isInstanceOf(IllegalArgumentException.class) - //TODO 커스텀 에러 - .hasMessage("중복된 시간은 생성할 수 없습니다."); + .isInstanceOf(RoomescapeException.class) + .hasMessage(DUPLICATE_RESERVATION_TIME.getMessage()); } } From 3e37f25132e9ebca5d9c105f5acdee3e5a43ed7d Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 20:12:29 +0900 Subject: [PATCH 18/75] =?UTF-8?q?docs:=20api=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f7c1ea8c75..314cd1e4dc 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,10 @@ ### Response -> HTTP/1.1 200 +> HTTP/1.1 201 > > Content-Type: application/json +> Location: /reservations/{id} ```JSON { @@ -82,7 +83,7 @@ ### Response -> HTTP/1.1 200 +> HTTP/1.1 204 ## 시간 추가 API @@ -99,8 +100,9 @@ ### response -> HTTP/1.1 200 +> HTTP/1.1 201 > Content-Type: application/json +> Location: /times/{id} ```JSON { @@ -137,4 +139,4 @@ ### response -> HTTP/1.1 200 +> HTTP/1.1 204 From 9d83ffeaf5c5f637d7a790d41948b2fcec88cd6f Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 20:22:56 +0900 Subject: [PATCH 19/75] =?UTF-8?q?docs:=20api=20=EB=AA=85=EC=84=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/README.md b/README.md index 314cd1e4dc..7e8e69f083 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,65 @@ ### response > HTTP/1.1 204 + +## 테마 조회 API + +### request + +> GET /themes HTTP/1.1 + +### response + +> HTTP/1.1 200 +> Content-Type: application/json + +```json +[ + { + "id": 1, + "name": "레벨2 탈출", + "description": "우테코 레벨2를 탈출하는 내용입니다.", + "thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" + } +] +``` + +## 테마 추가 API + +### request + +> POST /themes HTTP/1.1 +> content-type: application/json + +```json +{ + "name": "레벨2 탈출", + "description": "우테코 레벨2를 탈출하는 내용입니다.", + "thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" +} +``` + +### response + +> HTTP/1.1 201 +> Location: /themes/1 +> Content-Type: application/json + +```json +{ + "id": 1, + "name": "레벨2 탈출", + "description": "우테코 레벨2를 탈출하는 내용입니다.", + "thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" +} +``` + +## 테마 삭제 API + +### request + +> DELETE /themes/1 HTTP/1.1 + +### response + +> HTTP/1.1 204 From 6f1be1a8648d2aca4f2281f9fd355c4ad9b59d9c Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 20:26:51 +0900 Subject: [PATCH 20/75] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7e8e69f083..2307ca89c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,13 @@ - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [x] 지나간 날짜와 시간의 예약 요청 시 에러 - [x] 이름이 비어있는 예약 요청 시 에러 + - [ ] 존재하지 않는 테마 예약 생성 요청시 에러 + - [ ] 테마 값이 비어있는 예약 요청 시 에러 + +- [ ] 테마에 대한 제약 조건 추가 + - [ ] 테마 이름이 비어 있을 경우 에러 + - [ ] 중복된 이름의 테마 생성 요청시 에러 + - [ ] 예약이 있는 테마를 삭제 요청시 에러 # API 명세 From c75c4852542df80fb71e5ac484e60d49a666297f Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 20:27:53 +0900 Subject: [PATCH 21/75] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- src/main/java/roomescape/controller/AdminController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/roomescape/controller/AdminController.java b/src/main/java/roomescape/controller/AdminController.java index cbdee6650a..cf9bfaee2e 100644 --- a/src/main/java/roomescape/controller/AdminController.java +++ b/src/main/java/roomescape/controller/AdminController.java @@ -14,7 +14,7 @@ public String mainPage() { @GetMapping("/reservation") public String reservationPage() { - return "admin/reservation"; + return "admin/reservation-new"; } @GetMapping("/time") From 4d4c98526302dd3ca3b3666d9f355c77e291c3cc Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 21:16:38 +0900 Subject: [PATCH 22/75] =?UTF-8?q?feat:=20=ED=85=8C=EB=A7=88=20CRD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테마 이름, 설명, 썸네일 이미자가 비어 있을 경우 에러 - 중복된 이름의 테마 생성 요청시 에러 Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 4 +- .../controller/AdminController.java | 5 ++ .../controller/ThemeController.java | 44 +++++++++++++ src/main/java/roomescape/domain/Theme.java | 64 +++++++++++++++++++ .../java/roomescape/dto/ThemeRequest.java | 4 ++ .../java/roomescape/dto/ThemeResponse.java | 4 ++ .../roomescape/exception/ExceptionType.java | 7 +- .../JdbcTemplateThemeRepository.java | 54 ++++++++++++++++ .../repository/ThemeRepository.java | 12 ++++ .../java/roomescape/service/ThemeService.java | 46 +++++++++++++ src/main/resources/schema.sql | 15 ++++- src/test/resources/schema.sql | 15 ++++- 12 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 src/main/java/roomescape/controller/ThemeController.java create mode 100644 src/main/java/roomescape/domain/Theme.java create mode 100644 src/main/java/roomescape/dto/ThemeRequest.java create mode 100644 src/main/java/roomescape/dto/ThemeResponse.java create mode 100644 src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java create mode 100644 src/main/java/roomescape/repository/ThemeRepository.java create mode 100644 src/main/java/roomescape/service/ThemeService.java diff --git a/README.md b/README.md index 2307ca89c8..5ac7fb2187 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ - [ ] 테마 값이 비어있는 예약 요청 시 에러 - [ ] 테마에 대한 제약 조건 추가 - - [ ] 테마 이름이 비어 있을 경우 에러 - - [ ] 중복된 이름의 테마 생성 요청시 에러 + - [x] 테마 이름, 설명, 썸네일 이미자가 비어 있을 경우 에러 + - [x] 중복된 이름의 테마 생성 요청시 에러 - [ ] 예약이 있는 테마를 삭제 요청시 에러 # API 명세 diff --git a/src/main/java/roomescape/controller/AdminController.java b/src/main/java/roomescape/controller/AdminController.java index cf9bfaee2e..4e9a8510fa 100644 --- a/src/main/java/roomescape/controller/AdminController.java +++ b/src/main/java/roomescape/controller/AdminController.java @@ -21,4 +21,9 @@ public String reservationPage() { public String reservationTimePage() { return "admin/time"; } + + @GetMapping("/theme") + public String themePage() { + return "admin/theme"; + } } diff --git a/src/main/java/roomescape/controller/ThemeController.java b/src/main/java/roomescape/controller/ThemeController.java new file mode 100644 index 0000000000..bc38a24576 --- /dev/null +++ b/src/main/java/roomescape/controller/ThemeController.java @@ -0,0 +1,44 @@ +package roomescape.controller; + +import java.net.URI; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import roomescape.dto.ThemeRequest; +import roomescape.dto.ThemeResponse; +import roomescape.service.ThemeService; + +//Todo 테스트코드 작성 +@RestController +@RequestMapping("/themes") +public class ThemeController { + private final ThemeService themeService; + + public ThemeController(ThemeService themeService) { + this.themeService = themeService; + } + + @GetMapping + public List<ThemeResponse> findAll() { + return themeService.findAll(); + } + + @PostMapping + public ResponseEntity<ThemeResponse> save(@RequestBody ThemeRequest themeRequest) { + ThemeResponse saved = themeService.save(themeRequest); + return ResponseEntity.created(URI.create("/themes/" + saved.id())) + .body(saved); + } + + @DeleteMapping("/{id}") + public ResponseEntity<Void> delete(@PathVariable long id) { + themeService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java new file mode 100644 index 0000000000..407b60c7f6 --- /dev/null +++ b/src/main/java/roomescape/domain/Theme.java @@ -0,0 +1,64 @@ +package roomescape.domain; + +import roomescape.exception.ExceptionType; +import roomescape.exception.RoomescapeException; + +public class Theme { + private final Long id; + private final String name; + private final String description; + private final String thumbnail; + + public Theme(long id, Theme theme) { + this(id, theme.name, theme.description, theme.thumbnail); + } + + public Theme(Long id, String name, String description, String thumbnail) { + validateName(name); + validateDescription(description); + validateThumbnail(thumbnail); + this.id = id; + this.name = name; + this.description = description; + this.thumbnail = thumbnail; + } + + private void validateName(String name) { + if (name == null || name.isBlank()) { + //TODO : NAME_EMPYT 재사용 고민 + throw new RoomescapeException(ExceptionType.NAME_EMPTY); + } + } + + private void validateDescription(String description) { + if (description == null || description.isBlank()) { + throw new RoomescapeException(ExceptionType.DESCRIPTION_EMPTY); + } + } + + private void validateThumbnail(String thumbnail) { + if (thumbnail == null || thumbnail.isBlank()) { + throw new RoomescapeException(ExceptionType.THUMBNAIL_EMPTY); + } + } + + public Theme(String name, String description, String thumbnail) { + this(null, name, description, thumbnail); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getThumbnail() { + return thumbnail; + } +} diff --git a/src/main/java/roomescape/dto/ThemeRequest.java b/src/main/java/roomescape/dto/ThemeRequest.java new file mode 100644 index 0000000000..bbc725828e --- /dev/null +++ b/src/main/java/roomescape/dto/ThemeRequest.java @@ -0,0 +1,4 @@ +package roomescape.dto; + +public record ThemeRequest(String name, String description, String thumbnail) { +} diff --git a/src/main/java/roomescape/dto/ThemeResponse.java b/src/main/java/roomescape/dto/ThemeResponse.java new file mode 100644 index 0000000000..6cd1eab9be --- /dev/null +++ b/src/main/java/roomescape/dto/ThemeResponse.java @@ -0,0 +1,4 @@ +package roomescape.dto; + +public record ThemeResponse(long id, String name, String description, String thumbnail) { +} diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index ac9fb75a94..f6fd3c2e1b 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -10,12 +10,15 @@ public enum ExceptionType { TIME_EMPTY(BAD_REQUEST, "시작 시간은 필수 값입니다."), DUPLICATE_RESERVATION(BAD_REQUEST, "같은 시간에 이미 예약이 존재합니다."), DUPLICATE_RESERVATION_TIME(BAD_REQUEST, "이미 예약시간이 존재합니다."), + DUPLICATE_THEME(BAD_REQUEST, "이미 동일한 테마가 존재합니다."), INVALID_DATE_TIME_FORMAT(BAD_REQUEST, "해석할 수 없는 날짜, 시간 포맷입니다."), //Todo 이름 변경 INVALID_DELETE_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), RESERVATION_TIME_NOT_FOUND(BAD_REQUEST, "존재하지 않는 시간입니다."), - //todo 이름 변경 - PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."); + //todo 이름 변경, + PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), + DESCRIPTION_EMPTY(BAD_REQUEST, "테마 설명은 필수값 입니다."), + THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java new file mode 100644 index 0000000000..d1521f76a0 --- /dev/null +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -0,0 +1,54 @@ +package roomescape.repository; + +import java.sql.PreparedStatement; +import java.util.List; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.Theme; + +@Repository +public class JdbcTemplateThemeRepository implements ThemeRepository { + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public List<Theme> findAll() { + return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", (rs, rowNum) -> { + long id = rs.getLong(1); + String name = rs.getString(2); + String description = rs.getString(3); + String thumbnail = rs.getString(4); + return new Theme(id, name, description, thumbnail); + }); + } + + @Override + public Theme save(Theme theme) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + save(theme, keyHolder); + long id = keyHolder.getKey().longValue(); + return new Theme(id, theme); + } + + private void save(Theme theme, KeyHolder keyHolder) { + jdbcTemplate.update(con -> { + PreparedStatement pstm = con.prepareStatement( + "insert into THEME (NAME, DESCRIPTION, THUMBNAIL) values (?, ?, ?) ", new String[]{"id"}); + pstm.setString(1, theme.getName()); + pstm.setString(2, theme.getDescription()); + pstm.setString(3, theme.getThumbnail()); + return pstm; + }, + keyHolder); + } + + @Override + public void delete(long id) { + jdbcTemplate.update("delete from THEME where id = ?", id); + } +} diff --git a/src/main/java/roomescape/repository/ThemeRepository.java b/src/main/java/roomescape/repository/ThemeRepository.java new file mode 100644 index 0000000000..c1fd6c88a7 --- /dev/null +++ b/src/main/java/roomescape/repository/ThemeRepository.java @@ -0,0 +1,12 @@ +package roomescape.repository; + +import java.util.List; +import roomescape.domain.Theme; + +public interface ThemeRepository { + List<Theme> findAll(); + + Theme save(Theme theme); + + void delete(long id); +} diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java new file mode 100644 index 0000000000..f62507dfd3 --- /dev/null +++ b/src/main/java/roomescape/service/ThemeService.java @@ -0,0 +1,46 @@ +package roomescape.service; + +import java.util.List; +import org.springframework.stereotype.Service; +import roomescape.domain.Theme; +import roomescape.dto.ThemeRequest; +import roomescape.dto.ThemeResponse; +import roomescape.exception.ExceptionType; +import roomescape.exception.RoomescapeException; +import roomescape.repository.ThemeRepository; + +//todo 테스트코드 작성 +@Service +public class ThemeService { + + private final ThemeRepository themeRepository; + + public ThemeService(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; + } + + public ThemeResponse save(ThemeRequest themeRequest) { + boolean hasDuplicateTheme = themeRepository.findAll().stream() + .anyMatch(theme -> theme.getName().equals(themeRequest.name())); + if (hasDuplicateTheme) { + throw new RoomescapeException(ExceptionType.DUPLICATE_THEME); + } + Theme saved = themeRepository.save( + new Theme(themeRequest.name(), themeRequest.description(), themeRequest.thumbnail())); + return toResponse(saved); + } + + private ThemeResponse toResponse(Theme theme) { + return new ThemeResponse(theme.getId(), theme.getName(), theme.getDescription(), theme.getThumbnail()); + } + + public List<ThemeResponse> findAll() { + return themeRepository.findAll().stream() + .map(this::toResponse) + .toList(); + } + + public void delete(long id) { + themeRepository.delete(id); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 44a68814dd..d246ddc4a6 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -5,12 +5,23 @@ CREATE TABLE IF NOT EXISTS reservation_time PRIMARY KEY (id) ); +CREATE TABLE IF NOT EXISTS theme +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + description VARCHAR(255) NOT NULL, + thumbnail VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + CREATE TABLE IF NOT EXISTS reservation ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, date VARCHAR(255) NOT NULL, - time_id BIGINT NOT NULL, -- 컬럼 수정 + time_id BIGINT NOT NULL, + -- theme_id BIGINT NOT NULL, PRIMARY KEY (id), - FOREIGN KEY (time_id) REFERENCES reservation_time (id) -- 외래키 추가 + FOREIGN KEY (time_id) REFERENCES reservation_time (id) + -- FOREIGN KEY (theme_id) REFERENCES theme (id) ); diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index 0945a159b6..991d50c4a7 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -5,12 +5,23 @@ CREATE TABLE IF NOT EXISTS reservation_time PRIMARY KEY (id) ); +CREATE TABLE IF NOT EXISTS theme +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + description VARCHAR(255) NOT NULL, + thumbnail VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + CREATE TABLE IF NOT EXISTS reservation ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, date VARCHAR(255) NOT NULL, - time_id BIGINT, -- 컬럼 수정 + time_id BIGINT NOT NULL, + theme_id BIGINT NOT NULL, PRIMARY KEY (id), - FOREIGN KEY (time_id) REFERENCES reservation_time (id) -- 외래키 추가 + FOREIGN KEY (time_id) REFERENCES reservation_time (id), + FOREIGN KEY (theme_id) REFERENCES theme (id) ); From 44c19743cf1cc8f7d3bb5027d6a7f2f28dfa58b2 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 30 Apr 2024 22:26:07 +0900 Subject: [PATCH 23/75] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20=EC=97=90?= =?UTF-8?q?=EC=95=BD=20API=20=EC=97=90=20=ED=85=8C=EB=A7=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 15 ++++++- .../java/roomescape/domain/Reservation.java | 15 +++++-- .../roomescape/dto/ReservationRequest.java | 2 +- .../roomescape/dto/ReservationResponse.java | 3 +- .../roomescape/exception/ExceptionType.java | 4 +- .../JdbcTemplateReservationRepository.java | 39 +++++++++++++------ .../JdbcTemplateThemeRepository.java | 13 +++++++ .../repository/ThemeRepository.java | 3 ++ .../service/ReservationService.java | 19 +++++++-- src/main/resources/schema.sql | 6 +-- .../resources/static/js/reservation-new.js | 1 + .../controller/AdminControllerTest.java | 2 +- .../controller/ReservationControllerTest.java | 24 ++++++++---- .../roomescape/domain/ReservationTest.java | 7 +++- .../integration/AdminIntegrationTest.java | 5 +++ .../CollectionReservationRepository.java | 3 +- .../repository/CollectionThemeRepository.java | 27 +++++++++++++ ...JdbcTemplateReservationRepositoryTest.java | 12 ++++-- 18 files changed, 159 insertions(+), 41 deletions(-) create mode 100644 src/test/java/roomescape/repository/CollectionThemeRepository.java diff --git a/README.md b/README.md index 5ac7fb2187..1d935687fd 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ "id": 1, "startAt": "10:00" } + "theme" : { + "id": 1, + "name": "이름", + "description": "설명", + "thumbnail": "썸네일" + } } ] ``` @@ -59,7 +65,8 @@ { "date": "2023-08-05", "name": "브라운", - "timeId": 1 + "timeId": 1, + "themeId": 1 } ``` @@ -78,6 +85,12 @@ "time": { "id": 1, "startAt": "10:00" + }, + "theme": { + "id": 1, + "name": "이름", + "description": "설명", + "thumbnail": "썸네일" } } ``` diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 85c23ba10c..a5d2c3e45d 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -13,12 +13,13 @@ public class Reservation implements Comparable<Reservation> { private final String name; private final LocalDate date; private final ReservationTime time; + private final Theme theme; - public Reservation(String name, LocalDate date, ReservationTime time) { - this(null, name, date, time); + public Reservation(String name, LocalDate date, ReservationTime time, Theme theme) { + this(null, name, date, time, theme); } - public Reservation(Long id, String name, LocalDate date, ReservationTime time) { + public Reservation(Long id, String name, LocalDate date, ReservationTime time, Theme theme) { if (name == null || name.isBlank()) { throw new RoomescapeException(NAME_EMPTY); } @@ -26,10 +27,12 @@ public Reservation(Long id, String name, LocalDate date, ReservationTime time) { this.name = name; this.date = date; this.time = time; + this.theme = theme; } public Reservation(long id, Reservation reservationBeforeSave) { - this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time); + this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time, + reservationBeforeSave.theme); } @Override @@ -63,6 +66,10 @@ public ReservationTime getReservationTime() { return time; } + public Theme getTheme() { + return theme; + } + @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; diff --git a/src/main/java/roomescape/dto/ReservationRequest.java b/src/main/java/roomescape/dto/ReservationRequest.java index ad5cb41a22..fe3e4ec125 100644 --- a/src/main/java/roomescape/dto/ReservationRequest.java +++ b/src/main/java/roomescape/dto/ReservationRequest.java @@ -2,5 +2,5 @@ import java.time.LocalDate; -public record ReservationRequest(LocalDate date, String name, long timeId) { +public record ReservationRequest(LocalDate date, String name, long timeId, long themeId) { } diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java index d9980c55fd..20bd66cb9a 100644 --- a/src/main/java/roomescape/dto/ReservationResponse.java +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -2,5 +2,6 @@ import java.time.LocalDate; -public record ReservationResponse(long id, String name, LocalDate date, ReservationTimeResponse time) { +public record ReservationResponse(long id, String name, LocalDate date, ReservationTimeResponse time, + ThemeResponse theme) { } diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index f6fd3c2e1b..038a88ce56 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -18,7 +18,9 @@ public enum ExceptionType { //todo 이름 변경, PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), DESCRIPTION_EMPTY(BAD_REQUEST, "테마 설명은 필수값 입니다."), - THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."); + THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), + THEME_NOT_FOUND(BAD_REQUEST, "없는 테마입니다."), + ; private final HttpStatus status; private final String message; diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index 31eef64900..213d7ef6f4 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Repository; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; @Repository public class JdbcTemplateReservationRepository implements ReservationRepository { @@ -24,7 +25,7 @@ public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate) { public Reservation save(Reservation reservation) { ReservationTime reservationTime = findReservationTime(reservation.getReservationTime().getId()); Reservation beforeSaved = new Reservation(null, reservation.getName(), reservation.getDate(), - reservationTime); + reservationTime, reservation.getTheme()); KeyHolder keyHolder = new GeneratedKeyHolder(); save(beforeSaved, keyHolder); long id = keyHolder.getKey().longValue(); @@ -42,26 +43,35 @@ private ReservationTime findReservationTime(long timeId) { private void save(Reservation reservation, KeyHolder keyHolder) { jdbcTemplate.update(con -> { - String sql = "insert into reservation(name,date,time_id) values ( ?,?,? )"; + String sql = "insert into reservation(name,date,time_id,THEME_ID) values ( ?,?,?,? )"; PreparedStatement preparedStatement = con.prepareStatement(sql, new String[]{"id"}); preparedStatement.setString(1, reservation.getName()); preparedStatement.setDate(2, Date.valueOf(reservation.getDate())); preparedStatement.setLong(3, reservation.getReservationTime().getId()); + preparedStatement.setLong(4, reservation.getTheme().getId()); return preparedStatement; }, keyHolder); } + //Todo 개선 고민 @Override public List<Reservation> findAll() { - String query = "SELECT " - + " r.id as reservation_id," - + " r.name," - + " r.date," - + " t.id as time_id," - + " t.start_at as time_value" - + " FROM reservation as r" - + " inner join reservation_time as t" - + " on r.time_id = t.id"; + String query = """ + SELECT + r.id as reservation_id, + r.name, + r.date, + t.id as time_id, + t.start_at as time_value, + t2.id as theme_id, + t2.NAME as theme_name, + t2.DESCRIPTION as description, + t2.THUMBNAIL as thumbnail + FROM reservation as r + inner join reservation_time t + on r.time_id = t.id + inner join theme t2 + on t2.id = r.theme_id"""; return jdbcTemplate.query(query, (rs, rowNum) -> { long id = rs.getLong(1); @@ -70,7 +80,12 @@ public List<Reservation> findAll() { long timeId = rs.getLong(4); LocalTime startAt = rs.getTime(5).toLocalTime(); ReservationTime reservationTime = new ReservationTime(timeId, startAt); - return new Reservation(id, name, date, reservationTime); + long themeId = rs.getLong("theme_id"); + String themeName = rs.getString("theme_name"); + String description = rs.getString("description"); + String thumbnail = rs.getString("thumbnail"); + Theme theme = new Theme(themeId, themeName, description, thumbnail); + return new Reservation(id, name, date, reservationTime, theme); }); } diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index d1521f76a0..7d44bd86be 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -2,6 +2,7 @@ import java.sql.PreparedStatement; import java.util.List; +import java.util.Optional; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; @@ -27,6 +28,18 @@ public List<Theme> findAll() { }); } + @Override + public Optional<Theme> findById(long id) { + List<Theme> themes = jdbcTemplate.query("select id, name, description, thumbnail from theme where id = ?", + (rs, rowNum) -> { + String name = rs.getString("name"); + String description = rs.getString("description"); + String thumbnail = rs.getString("thumbnail"); + return new Theme(id, name, description, thumbnail); + }, id); + return themes.stream().findFirst(); + } + @Override public Theme save(Theme theme) { KeyHolder keyHolder = new GeneratedKeyHolder(); diff --git a/src/main/java/roomescape/repository/ThemeRepository.java b/src/main/java/roomescape/repository/ThemeRepository.java index c1fd6c88a7..a43cb24851 100644 --- a/src/main/java/roomescape/repository/ThemeRepository.java +++ b/src/main/java/roomescape/repository/ThemeRepository.java @@ -1,11 +1,14 @@ package roomescape.repository; import java.util.List; +import java.util.Optional; import roomescape.domain.Theme; public interface ThemeRepository { List<Theme> findAll(); + Optional<Theme> findById(long id); + Theme save(Theme theme); void delete(long id); diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index a054867776..2980fdae95 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -11,22 +11,28 @@ import org.springframework.stereotype.Service; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.dto.ReservationRequest; import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationTimeResponse; +import roomescape.dto.ThemeResponse; +import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; +import roomescape.repository.ThemeRepository; @Service public class ReservationService { private final ReservationRepository reservationRepository; private final ReservationTimeRepository reservationTimeRepository; + private final ThemeRepository themeRepository; public ReservationService(ReservationRepository reservationRepository, - ReservationTimeRepository reservationTimeRepository) { + ReservationTimeRepository reservationTimeRepository, ThemeRepository themeRepository) { this.reservationRepository = reservationRepository; this.reservationTimeRepository = reservationTimeRepository; + this.themeRepository = themeRepository; } //ToDo 테스트 작성 @@ -35,11 +41,15 @@ public ReservationResponse save(ReservationRequest reservationRequest) { ReservationTime reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()) .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); + Theme theme = themeRepository.findById(reservationRequest.themeId()) + .orElseThrow(() -> new RoomescapeException(ExceptionType.THEME_NOT_FOUND)); + Reservation beforeSave = new Reservation( reservationRequest.name(), reservationRequest.date(), //TODO : 커스텀 예외 사용할지 고민해보기 - reservationTime + reservationTime, + theme ); boolean isDuplicate = reservationRepository.findAll() .stream() @@ -72,8 +82,11 @@ private ReservationResponse toResponse(Reservation reservation) { ReservationTime reservationTime = reservation.getReservationTime(); ReservationTimeResponse reservationTimeResponse = new ReservationTimeResponse(reservationTime.getId(), reservation.getTime()); + Theme theme = reservation.getTheme(); + ThemeResponse themeResponse = new ThemeResponse(theme.getId(), theme.getName(), theme.getDescription(), + theme.getThumbnail()); return new ReservationResponse(reservation.getId(), - reservation.getName(), reservation.getDate(), reservationTimeResponse); + reservation.getName(), reservation.getDate(), reservationTimeResponse, themeResponse); } public List<ReservationResponse> findAll() { diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index d246ddc4a6..991d50c4a7 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -20,8 +20,8 @@ CREATE TABLE IF NOT EXISTS reservation name VARCHAR(255) NOT NULL, date VARCHAR(255) NOT NULL, time_id BIGINT NOT NULL, - -- theme_id BIGINT NOT NULL, + theme_id BIGINT NOT NULL, PRIMARY KEY (id), - FOREIGN KEY (time_id) REFERENCES reservation_time (id) - -- FOREIGN KEY (theme_id) REFERENCES theme (id) + FOREIGN KEY (time_id) REFERENCES reservation_time (id), + FOREIGN KEY (theme_id) REFERENCES theme (id) ); diff --git a/src/main/resources/static/js/reservation-new.js b/src/main/resources/static/js/reservation-new.js index 098b8b70d8..2d8e132cce 100644 --- a/src/main/resources/static/js/reservation-new.js +++ b/src/main/resources/static/js/reservation-new.js @@ -27,6 +27,7 @@ function render(data) { TODO: [2단계] 관리자 기능 - 예약 목록 조회 API 호출 후 렌더링 response 명세에 맞춰 값 설정 */ + console.log(item); row.insertCell(0).textContent = item.id; // 예약 id row.insertCell(1).textContent = item.name; // 예약자명 row.insertCell(2).textContent = item.theme.name; // 테마명 diff --git a/src/test/java/roomescape/controller/AdminControllerTest.java b/src/test/java/roomescape/controller/AdminControllerTest.java index 832a31fc0e..adeb4f8b2c 100644 --- a/src/test/java/roomescape/controller/AdminControllerTest.java +++ b/src/test/java/roomescape/controller/AdminControllerTest.java @@ -20,7 +20,7 @@ void reservationPage() { AdminController adminController = new AdminController(); String reservationPage = adminController.reservationPage(); Assertions.assertThat(reservationPage) - .isEqualTo("admin/reservation"); + .isEqualTo("admin/reservation-new"); } @Test diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index fa257e5e74..cbba143c3e 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -11,36 +11,43 @@ import org.junit.jupiter.api.Test; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.dto.ReservationRequest; import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationTimeResponse; +import roomescape.dto.ThemeResponse; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; import roomescape.service.ReservationService; +//TODO : 전체적으로 테스트 수정 class ReservationControllerTest { static final long timeId = 1L; static final LocalTime time = LocalTime.now(); + private static final Theme DEFUALT_THEME = new Theme(1L, "이름", "설명", "썸네일"); private final CollectionReservationTimeRepository timeRepository = new CollectionReservationTimeRepository( new ArrayList<>(List.of(new ReservationTime(timeId, time))) ); + private final CollectionThemeRepository themeRepository = new CollectionThemeRepository(); @Test @DisplayName("예약 정보를 잘 저장하는지 확인한다.") void saveReservation() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, + themeRepository); ReservationController reservationController = new ReservationController(reservationService); LocalDate date = LocalDate.now().plusDays(1); ReservationResponse saveResponse = reservationController.saveReservation( - new ReservationRequest(date, "폴라", timeId)) + new ReservationRequest(date, "폴라", timeId, 1)) .getBody(); long id = Objects.requireNonNull(saveResponse).id(); ReservationResponse expected = new ReservationResponse(id, "폴라", date, - new ReservationTimeResponse(timeId, time)); + new ReservationTimeResponse(timeId, time), new ThemeResponse(1, "이름", "설명", "썸네일")); Assertions.assertThat(saveResponse) .isEqualTo(expected); @@ -51,7 +58,8 @@ void saveReservation() { void findAllReservations() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, + null); ReservationController reservationController = new ReservationController(reservationService); List<ReservationResponse> allReservations = reservationController.findAllReservations(); @@ -63,10 +71,11 @@ void findAllReservations() { @DisplayName("예약 정보를 잘 지우는지 확인한다.") void delete() { List<Reservation> reservations = List.of( - new Reservation(1L, "폴라", LocalDate.now(), new ReservationTime(LocalTime.now()))); + new Reservation(1L, "폴라", LocalDate.now(), new ReservationTime(LocalTime.now()), DEFUALT_THEME)); CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( new ArrayList<>(reservations), timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, + null); ReservationController reservationController = new ReservationController(reservationService); reservationController.delete(1L); @@ -81,7 +90,8 @@ void delete() { void checkRepositoryDependency() { CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, + null); ReservationController reservationController = new ReservationController(reservationService); boolean isRepositoryInjected = false; diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index 63bc689091..571020049a 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -7,13 +7,16 @@ import org.junit.jupiter.api.Test; class ReservationTest { + + private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "썸네일"); + @Test @DisplayName("날짜를 기준으로 비교를 잘 하는지 확인.") void compareTo() { Reservation first = new Reservation(1L, "폴라", LocalDate.of(1999, 12, 1), new ReservationTime( - LocalTime.of(16, 30))); + LocalTime.of(16, 30)), DEFAULT_THEME); Reservation second = new Reservation(2L, "로빈", LocalDate.of(1998, 1, 8), new ReservationTime( - LocalTime.of(16, 30))); + LocalTime.of(16, 30)), DEFAULT_THEME); int compareTo = first.compareTo(second); Assertions.assertThat(compareTo) .isGreaterThan(0); diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index 9a525e8289..2db65f8542 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -33,6 +33,9 @@ void init() { jdbcTemplate.update("delete from reservation_time"); jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); + jdbcTemplate.update("delete from THEME"); + jdbcTemplate.update("ALTER TABLE THEME alter column id restart with 1"); + jdbcTemplate.update("insert into THEME values ( 1,'a','a','a')"); RestAssured.port = port; } @@ -70,6 +73,7 @@ void adminReservationPageWork() { params.put("name", "브라운"); params.put("date", LocalDate.now().plusDays(1).format(DATE_FORMATTER)); params.put("timeId", 1); + params.put("themeId", 1); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -104,6 +108,7 @@ void adminReservationPageWorkWithDB() { params.put("name", "브라운"); params.put("date", LocalDate.now().plusDays(1).format(DATE_FORMATTER)); params.put("timeId", 1); + params.put("themeId", 1); RestAssured.given().log().all() .contentType(ContentType.JSON) diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java index ef0eac28b5..213022259a 100644 --- a/src/test/java/roomescape/repository/CollectionReservationRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -35,12 +35,13 @@ private static Predicate<ReservationTime> sameId(ReservationRequest reservationR @Override public Reservation save(Reservation reservation) { + //TODO 안해도 되는지 확인하기 ReservationTime findTime = timeRepository.findAll().stream() .filter(reservationTime -> reservationTime.getId() == reservation.getReservationTime().getId()) .findFirst() .get(); Reservation saved = new Reservation(atomicLong.incrementAndGet(), reservation.getName(), reservation.getDate(), - findTime); + findTime, reservation.getTheme()); reservations.add(saved); return saved; } diff --git a/src/test/java/roomescape/repository/CollectionThemeRepository.java b/src/test/java/roomescape/repository/CollectionThemeRepository.java new file mode 100644 index 0000000000..a851648ec3 --- /dev/null +++ b/src/test/java/roomescape/repository/CollectionThemeRepository.java @@ -0,0 +1,27 @@ +package roomescape.repository; + +import java.util.List; +import java.util.Optional; +import roomescape.domain.Theme; + +public class CollectionThemeRepository implements ThemeRepository { + @Override + public List<Theme> findAll() { + return null; + } + + @Override + public Optional<Theme> findById(long id) { + return Optional.of(new Theme(id, "이름", "설명", "썸네일")); + } + + @Override + public Theme save(Theme theme) { + return null; + } + + @Override + public void delete(long id) { + + } +} diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java index ddb8faa35b..400186c4b1 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -12,10 +12,13 @@ import org.springframework.jdbc.core.JdbcTemplate; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; @SpringBootTest class JdbcTemplateReservationRepositoryTest { private static final ReservationTime DEFAULT_TIME = new ReservationTime(1L, LocalTime.of(11, 56)); + private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "썸네일"); + @Autowired private ReservationRepository reservationRepository; @Autowired @@ -34,7 +37,8 @@ void init() { @DisplayName("Reservation 을 잘 저장하는지 확인한다.") void save() { var beforeSave = reservationRepository.findAll(); - Reservation saved = reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); + Reservation saved = reservationRepository.save( + new Reservation("test", LocalDate.now(), DEFAULT_TIME, DEFAULT_THEME)); var afterSave = reservationRepository.findAll(); Assertions.assertThat(afterSave) @@ -46,8 +50,8 @@ void save() { @DisplayName("Reservation 을 잘 조회하는지 확인한다.") void findAll() { List<Reservation> beforeSave = reservationRepository.findAll(); - reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); - reservationRepository.save(new Reservation("test2", LocalDate.now(), DEFAULT_TIME)); + reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME, DEFAULT_THEME)); + reservationRepository.save(new Reservation("test2", LocalDate.now(), DEFAULT_TIME, DEFAULT_THEME)); List<Reservation> afterSave = reservationRepository.findAll(); Assertions.assertThat(afterSave.size()) @@ -58,7 +62,7 @@ void findAll() { @DisplayName("Reservation 을 잘 지우는지 확인한다.") void delete() { List<Reservation> beforeSaveAndDelete = reservationRepository.findAll(); - reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME)); + reservationRepository.save(new Reservation("test", LocalDate.now(), DEFAULT_TIME, DEFAULT_THEME)); reservationRepository.delete(1L); From af7f7e670b9675fbb73817e77f2a123b017cf72f Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 01:10:22 +0900 Subject: [PATCH 24/75] =?UTF-8?q?docs:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 구현된 기능의 체크 리스트 누락된 것 작성 Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d935687fd..34a3f53a0e 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [x] 지나간 날짜와 시간의 예약 요청 시 에러 - [x] 이름이 비어있는 예약 요청 시 에러 - - [ ] 존재하지 않는 테마 예약 생성 요청시 에러 - - [ ] 테마 값이 비어있는 예약 요청 시 에러 + - [x] 존재하지 않는 테마 예약 생성 요청시 에러 + - [x] 테마 값이 비어있는 예약 요청 시 에러 - [ ] 테마에 대한 제약 조건 추가 - [x] 테마 이름, 설명, 썸네일 이미자가 비어 있을 경우 에러 From 31df8bd3a67a04785411106e4d8b6ecab0d4c885 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 01:10:59 +0900 Subject: [PATCH 25/75] =?UTF-8?q?docs:=20=EC=9D=B4=EB=AF=B8=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=20Todo=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- src/main/java/roomescape/service/ReservationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 2980fdae95..8ee1c730e1 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -47,7 +47,6 @@ public ReservationResponse save(ReservationRequest reservationRequest) { Reservation beforeSave = new Reservation( reservationRequest.name(), reservationRequest.date(), - //TODO : 커스텀 예외 사용할지 고민해보기 reservationTime, theme ); From 564ecaf17cc89e378317d8bd1a5454a29ee9d82e Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 01:17:40 +0900 Subject: [PATCH 26/75] =?UTF-8?q?docs:=20=EC=9D=B4=EB=AF=B8=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=20Todo=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 4 ++-- src/main/java/roomescape/domain/Theme.java | 1 + .../java/roomescape/exception/ExceptionType.java | 2 ++ src/main/java/roomescape/service/ThemeService.java | 12 +++++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34a3f53a0e..dd95dddb2e 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ - [x] 존재하지 않는 테마 예약 생성 요청시 에러 - [x] 테마 값이 비어있는 예약 요청 시 에러 -- [ ] 테마에 대한 제약 조건 추가 +- [x] 테마에 대한 제약 조건 추가 - [x] 테마 이름, 설명, 썸네일 이미자가 비어 있을 경우 에러 - [x] 중복된 이름의 테마 생성 요청시 에러 - - [ ] 예약이 있는 테마를 삭제 요청시 에러 + - [x] 예약이 있는 테마를 삭제 요청시 에러 # API 명세 diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 407b60c7f6..38f6701881 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -46,6 +46,7 @@ public Theme(String name, String description, String thumbnail) { this(null, name, description, thumbnail); } + //todo : long 으로 변경 public Long getId() { return id; } diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index 038a88ce56..51319d5b25 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; +//Todo 상태코드 고민해보기 public enum ExceptionType { NAME_EMPTY(BAD_REQUEST, "이름은 필수 값입니다."), @@ -14,6 +15,7 @@ public enum ExceptionType { INVALID_DATE_TIME_FORMAT(BAD_REQUEST, "해석할 수 없는 날짜, 시간 포맷입니다."), //Todo 이름 변경 INVALID_DELETE_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), + INVALID_DELETE_THEME(BAD_REQUEST, "예약이 존재하는 테마는 삭제할 수 없습니다."), RESERVATION_TIME_NOT_FOUND(BAD_REQUEST, "존재하지 않는 시간입니다."), //todo 이름 변경, PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index f62507dfd3..3fd8f6bab1 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -1,12 +1,14 @@ package roomescape.service; import java.util.List; +import java.util.Objects; import org.springframework.stereotype.Service; import roomescape.domain.Theme; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; +import roomescape.repository.ReservationRepository; import roomescape.repository.ThemeRepository; //todo 테스트코드 작성 @@ -14,9 +16,11 @@ public class ThemeService { private final ThemeRepository themeRepository; + private final ReservationRepository reservationRepository; - public ThemeService(ThemeRepository themeRepository) { + public ThemeService(ThemeRepository themeRepository, ReservationRepository reservationRepository) { this.themeRepository = themeRepository; + this.reservationRepository = reservationRepository; } public ThemeResponse save(ThemeRequest themeRequest) { @@ -41,6 +45,12 @@ public List<ThemeResponse> findAll() { } public void delete(long id) { + //todo : 변수명 고민 + boolean invalidDelete = reservationRepository.findAll().stream() + .anyMatch(reservation -> Objects.equals(reservation.getTheme().getId(), id)); + if (invalidDelete) { + throw new RoomescapeException(ExceptionType.INVALID_DELETE_THEME); + } themeRepository.delete(id); } } From 550f4cd78b7bc9a63ba262ca636387794c384961 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 01:26:40 +0900 Subject: [PATCH 27/75] =?UTF-8?q?fix:=20equals=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EA=B0=80=20id=EB=A7=8C=EC=9C=BC=EB=A1=9C=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- .../java/roomescape/domain/Reservation.java | 11 +--------- .../roomescape/domain/ReservationTime.java | 5 +---- src/main/java/roomescape/domain/Theme.java | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index a5d2c3e45d..a157423a48 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -90,16 +90,7 @@ public boolean equals(Object o) { Reservation that = (Reservation) o; - if (!Objects.equals(id, that.id)) { - return false; - } - if (!Objects.equals(name, that.name)) { - return false; - } - if (!Objects.equals(date, that.date)) { - return false; - } - return Objects.equals(time, that.time); + return Objects.equals(id, that.id); } @Override diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 847a933d2c..da26cf0c89 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -49,10 +49,7 @@ public boolean equals(Object o) { ReservationTime that = (ReservationTime) o; - if (!Objects.equals(id, that.id)) { - return false; - } - return Objects.equals(startAt, that.startAt); + return Objects.equals(id, that.id); } @Override diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 38f6701881..0134804dbb 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -1,5 +1,6 @@ package roomescape.domain; +import java.util.Objects; import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; @@ -62,4 +63,23 @@ public String getDescription() { public String getThumbnail() { return thumbnail; } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Theme theme = (Theme) o; + + return Objects.equals(id, theme.id); + } } From d1c9bee0ca00fce245a120f07c7f9f05e4968266 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 01:27:09 +0900 Subject: [PATCH 28/75] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=9D=B4=20?= =?UTF-8?q?=ED=85=8C=EB=A7=88=EB=8F=84=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 2 +- .../java/roomescape/service/ReservationService.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd95dddb2e..eb88573794 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 - [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 - [x] 예약에 대한 제약 조건 추가 - - [x] 동일한 날짜와 시간에 예약 생성 요청 시 에러 + - [x] 동일한 날짜와 시간, 테마에 예약 생성 요청 시 에러 - [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 - [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 - [x] 지나간 날짜와 시간의 예약 요청 시 에러 diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 8ee1c730e1..4d76b6254b 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -52,7 +52,7 @@ public ReservationResponse save(ReservationRequest reservationRequest) { ); boolean isDuplicate = reservationRepository.findAll() .stream() - .anyMatch(reservation -> mapToLocalDateTime(reservation).equals(mapToLocalDateTime(beforeSave))); + .anyMatch(reservation -> validateDuplicateReservation(beforeSave, reservation)); if (isDuplicate) { throw new RoomescapeException(DUPLICATE_RESERVATION); } @@ -66,9 +66,9 @@ public ReservationResponse save(ReservationRequest reservationRequest) { return toResponse(saved); } - private static boolean isBefore(Reservation beforeSave) { - return LocalDateTime.of(beforeSave.getDate(), beforeSave.getTime()) - .isBefore(LocalDateTime.now()); + private boolean validateDuplicateReservation(Reservation beforeSave, Reservation reservation) { + return mapToLocalDateTime(reservation).equals(mapToLocalDateTime(beforeSave)) + && beforeSave.getTheme().equals(reservation.getTheme()); } private LocalDateTime mapToLocalDateTime(Reservation reservation) { @@ -77,6 +77,11 @@ private LocalDateTime mapToLocalDateTime(Reservation reservation) { return LocalDateTime.of(date, time); } + private static boolean isBefore(Reservation beforeSave) { + return LocalDateTime.of(beforeSave.getDate(), beforeSave.getTime()) + .isBefore(LocalDateTime.now()); + } + private ReservationResponse toResponse(Reservation reservation) { ReservationTime reservationTime = reservation.getReservationTime(); ReservationTimeResponse reservationTimeResponse = new ReservationTimeResponse(reservationTime.getId(), From 1cfec9a404d2f0e42a40fae26ff4e5f9ea6ff94a Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 02:58:35 +0900 Subject: [PATCH 29/75] =?UTF-8?q?docs:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20API=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index eb88573794..7c7b3324bd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ - [x] 중복된 이름의 테마 생성 요청시 에러 - [x] 예약이 있는 테마를 삭제 요청시 에러 +- [ ] 사용자 예약 기능 추가 + # API 명세 ## 예약 조회 API @@ -222,3 +224,24 @@ ### response > HTTP/1.1 204 + +## 예약 가능 시간 조회 API + +### Request + +> GET /availableTimes?date=${date}&themeId=${themeId} + +### response + +> HTTP/1.1 200 +> Content-Type: application/json + +```json +[ + { + "id": 0, + "startAt": "02:53", + "isBooked": false + } +] +``` From 37c3bc124d174a52893af5ebe81fccfc36c9b789 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Wed, 1 May 2024 02:58:57 +0900 Subject: [PATCH 30/75] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu <zangsu_@naver.com> --- .../controller/AvailableTimeController.java | 26 +++++++++++ .../roomescape/controller/UserController.java | 12 ++++++ .../roomescape/dto/AvailableTimeResponse.java | 6 +++ .../service/AvailableTimeService.java | 43 +++++++++++++++++++ .../resources/static/js/user-reservation.js | 14 +++--- 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/main/java/roomescape/controller/AvailableTimeController.java create mode 100644 src/main/java/roomescape/controller/UserController.java create mode 100644 src/main/java/roomescape/dto/AvailableTimeResponse.java create mode 100644 src/main/java/roomescape/service/AvailableTimeService.java diff --git a/src/main/java/roomescape/controller/AvailableTimeController.java b/src/main/java/roomescape/controller/AvailableTimeController.java new file mode 100644 index 0000000000..ffc8f65829 --- /dev/null +++ b/src/main/java/roomescape/controller/AvailableTimeController.java @@ -0,0 +1,26 @@ +package roomescape.controller; + +import java.time.LocalDate; +import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import roomescape.dto.AvailableTimeResponse; +import roomescape.service.AvailableTimeService; + +@RestController +@RequestMapping("/availableTimes") +public class AvailableTimeController { + + private final AvailableTimeService availableTimeService; + + public AvailableTimeController(AvailableTimeService availableTimeService) { + this.availableTimeService = availableTimeService; + } + + @GetMapping() + public List<AvailableTimeResponse> findByThemeAndDate(@RequestParam LocalDate date, @RequestParam long themeId) { + return availableTimeService.findByThemeAndDate(date, themeId); + } +} diff --git a/src/main/java/roomescape/controller/UserController.java b/src/main/java/roomescape/controller/UserController.java new file mode 100644 index 0000000000..39d95139d3 --- /dev/null +++ b/src/main/java/roomescape/controller/UserController.java @@ -0,0 +1,12 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class UserController { + @GetMapping("/reservation") + public String reservationPage() { + return "reservation"; + } +} diff --git a/src/main/java/roomescape/dto/AvailableTimeResponse.java b/src/main/java/roomescape/dto/AvailableTimeResponse.java new file mode 100644 index 0000000000..8297b1dc4f --- /dev/null +++ b/src/main/java/roomescape/dto/AvailableTimeResponse.java @@ -0,0 +1,6 @@ +package roomescape.dto; + +import java.time.LocalTime; + +public record AvailableTimeResponse(long id, LocalTime startAt, boolean isBooked) { +} diff --git a/src/main/java/roomescape/service/AvailableTimeService.java b/src/main/java/roomescape/service/AvailableTimeService.java new file mode 100644 index 0000000000..07b90674cc --- /dev/null +++ b/src/main/java/roomescape/service/AvailableTimeService.java @@ -0,0 +1,43 @@ +package roomescape.service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.dto.AvailableTimeResponse; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ReservationTimeRepository; + +@Service +public class AvailableTimeService { + private final ReservationRepository reservationRepository; + private final ReservationTimeRepository reservationTimeRepository; + + public AvailableTimeService(ReservationRepository reservationRepository, + ReservationTimeRepository reservationTimeRepository) { + this.reservationRepository = reservationRepository; + this.reservationTimeRepository = reservationTimeRepository; + } + + //todo : 메서드 개선 + public List<AvailableTimeResponse> findByThemeAndDate(LocalDate date, long themeId) { + Set<Long> alreadyReservedTimeIds = reservationRepository.findAll().stream() + .filter(reservation -> reservation.getDate().equals(date)) + //todo Reservation에 메서드 구현하기 + .filter(reservation -> reservation.getTheme().getId().equals(themeId)) + .map(Reservation::getReservationTime) + .map(ReservationTime::getId) + .collect(Collectors.toSet()); + + return reservationTimeRepository.findAll().stream() + .map(reservationTime -> { + long id = reservationTime.getId(); + boolean isBooked = alreadyReservedTimeIds.contains(id); + return new AvailableTimeResponse(id, reservationTime.getStartAt(), isBooked); + }) + .toList(); + } +} diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js index 89ff141af8..a0bcaed178 100644 --- a/src/main/resources/static/js/user-reservation.js +++ b/src/main/resources/static/js/user-reservation.js @@ -36,8 +36,8 @@ function renderTheme(themes) { const themeSlots = document.getElementById('theme-slots'); themeSlots.innerHTML = ''; themes.forEach(theme => { - const name = ''; - const themeId = ''; + const name = theme.name; + const themeId = theme.id; /* TODO: [3단계] 사용자 예약 - 테마 목록 조회 API 호출 후 렌더링 response 명세에 맞춰 createSlot 함수 호출 시 값 설정 @@ -91,7 +91,7 @@ function fetchAvailableTimes(date, themeId) { TODO: [3단계] 사용자 예약 - 예약 가능 시간 조회 API 호출 요청 포맷에 맞게 설정 */ - fetch('/', { // 예약 가능 시간 조회 API endpoint + fetch(`/availableTimes?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint method: 'GET', headers: { 'Content-Type': 'application/json', @@ -100,7 +100,7 @@ function fetchAvailableTimes(date, themeId) { if (response.status === 200) return response.json(); throw new Error('Read failed'); }).then(renderAvailableTimes) - .catch(error => console.error("Error fetching available times:", error)); + .catch(error => console.error("Error fetching available times:", error)); } function renderAvailableTimes(times) { @@ -120,9 +120,9 @@ function renderAvailableTimes(times) { TODO: [3단계] 사용자 예약 - 예약 가능 시간 조회 API 호출 후 렌더링 response 명세에 맞춰 createSlot 함수 호출 시 값 설정 */ - const startAt = ''; - const timeId = ''; - const alreadyBooked = false; + const startAt = time.startAt; + const timeId = time.id; + const alreadyBooked = time.isBooked; const div = createSlot('time', startAt, timeId, alreadyBooked); // createSlot('time', 시작 시간, time id, 예약 여부) timeSlots.appendChild(div); From cb113fdbede005484ebf6334e35d1757ca371506 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 16:58:34 +0900 Subject: [PATCH 31/75] =?UTF-8?q?test=20:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/domain/ReservationTime.java | 1 - .../roomescape/domain/ReservationTest.java | 14 +++++ .../domain/ReservationTimeTest.java | 37 ++++++++++++ .../java/roomescape/domain/ThemeTest.java | 58 +++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/test/java/roomescape/domain/ReservationTimeTest.java create mode 100644 src/test/java/roomescape/domain/ThemeTest.java diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index da26cf0c89..09c20cdd93 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -14,7 +14,6 @@ public ReservationTime(LocalTime startAt) { this(null, startAt); } - //TODO 테스트 추가 public ReservationTime(Long id, LocalTime startAt) { if (startAt == null) { throw new RoomescapeException(TIME_EMPTY); diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index 571020049a..cdd5840fb7 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -1,15 +1,29 @@ package roomescape.domain; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import java.time.LocalDate; import java.time.LocalTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import roomescape.exception.ExceptionType; +import roomescape.exception.RoomescapeException; class ReservationTest { + private static final LocalDate DEFAULT_DATE = LocalDate.now(); + private static final ReservationTime DEFAULT_TIME = new ReservationTime(1L, LocalTime.now()); private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "썸네일"); + @DisplayName("생성 테스트") + @Test + void constructTest() { + assertThatThrownBy(() -> new Reservation(null, DEFAULT_DATE, DEFAULT_TIME, DEFAULT_THEME)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(ExceptionType.NAME_EMPTY.getMessage()); + } + @Test @DisplayName("날짜를 기준으로 비교를 잘 하는지 확인.") void compareTo() { diff --git a/src/test/java/roomescape/domain/ReservationTimeTest.java b/src/test/java/roomescape/domain/ReservationTimeTest.java new file mode 100644 index 0000000000..e822f5646b --- /dev/null +++ b/src/test/java/roomescape/domain/ReservationTimeTest.java @@ -0,0 +1,37 @@ +package roomescape.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.LocalTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.exception.ExceptionType; +import roomescape.exception.RoomescapeException; + +class ReservationTimeTest { + + @DisplayName("시간이 null 인 ReservationTime 을 생성할 수 없다.") + @Test + void startAtMustBeNotNull() { + assertThatThrownBy(() -> new ReservationTime(null)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(ExceptionType.TIME_EMPTY.getMessage()); + } + + @DisplayName("ReservationTime 은 id 값으로만 동등성을 비교한다.") + @Test + void equalsTest() { + assertAll( + () -> assertThat(new ReservationTime(1L, LocalTime.of(11, 11))) + .isEqualTo(new ReservationTime(1L, LocalTime.of(11, 20))), + + () -> assertThat(new ReservationTime(1L, LocalTime.of(11, 11))) + .isNotEqualTo(new ReservationTime(2L, LocalTime.of(11, 11))), + + () -> assertThat(new ReservationTime(null, LocalTime.of(11, 11))) + .isNotEqualTo(new ReservationTime(1L, LocalTime.of(11, 11))) + ); + } +} diff --git a/src/test/java/roomescape/domain/ThemeTest.java b/src/test/java/roomescape/domain/ThemeTest.java new file mode 100644 index 0000000000..9d63424f52 --- /dev/null +++ b/src/test/java/roomescape/domain/ThemeTest.java @@ -0,0 +1,58 @@ +package roomescape.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static roomescape.exception.ExceptionType.DESCRIPTION_EMPTY; +import static roomescape.exception.ExceptionType.NAME_EMPTY; +import static roomescape.exception.ExceptionType.THUMBNAIL_EMPTY; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.exception.RoomescapeException; + +class ThemeTest { + + @DisplayName("객체 생성 테스트") + @Test + void constructTest() { + assertAll( + () -> assertThatThrownBy(() -> new Theme(null, "description", "thumbnail")) + .isInstanceOf(RoomescapeException.class) + .hasMessage(NAME_EMPTY.getMessage()), + + () -> assertThatThrownBy(() -> new Theme("name", null, "thumbnail")) + .isInstanceOf(RoomescapeException.class) + .hasMessage(DESCRIPTION_EMPTY.getMessage()), + + () -> assertThatThrownBy(() -> new Theme("name", "description", null)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(THUMBNAIL_EMPTY.getMessage()), + + () -> assertThatCode(() -> new Theme("name", "description", "thumbnail")) + .doesNotThrowAnyException(), + + () -> assertThatCode(() -> new Theme(null, "name", "description", "thumbnail")) + .doesNotThrowAnyException(), + + () -> assertThatCode(() -> new Theme(1L, "name", "description", "thumbnail")) + .doesNotThrowAnyException() + ); + } + + @DisplayName("동등성 테스트") + @Test + void equalsTest() { + assertAll( + () -> assertThat(new Theme(1L, "name", "description", "thumbnail")) + .isEqualTo(new Theme(1L, "otherName", "otherDescription", "otherThumbnail")), + + () -> assertThat(new Theme(1L, "sameName", "sameDescription", "sameThumbnail")) + .isNotEqualTo(new Theme(2L, "sameName", "sameDescription", "sameThumbnail")), + + () -> assertThat(new Theme(1L, "sameName", "sameDescription", "sameThumbnail")) + .isNotEqualTo(new Theme(null, "sameName", "sameDescription", "sameThumbnail")) + ); + } +} From 42c24154ae72d1c5b95708a3671eb1210babd9de Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 17:24:57 +0900 Subject: [PATCH 32/75] =?UTF-8?q?refactor=20:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=97=90=EA=B2=8C=20=EC=9C=84=EC=9E=84=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=B1=85=EC=9E=84=EC=9D=84=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 21 +++++++++++++++++++ .../roomescape/domain/ReservationTime.java | 4 ++++ src/main/java/roomescape/domain/Theme.java | 8 +++++++ .../service/AvailableTimeService.java | 5 ++--- .../service/ReservationService.java | 11 +++------- .../service/ReservationTimeService.java | 2 +- .../java/roomescape/service/ThemeService.java | 4 ++-- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index a157423a48..aaa3a1744d 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -46,6 +46,27 @@ public boolean hasSameId(long id) { return this.id == id; } + public boolean isReservationTimeOf(long id) { + return this.time.isIdOf(id); + } + + public boolean isDateOf(LocalDate date) { + return this.date.equals(date); + } + + public boolean isThemeOf(long id) { + return this.theme.isIdOf(id); + } + + public boolean isSameDateTime(Reservation beforeSave) { + return LocalDateTime.of(this.date, this.getTime()) + .equals(LocalDateTime.of(beforeSave.date, beforeSave.getTime())); + } + + public boolean isSameTheme(Reservation reservation) { + return this.theme.equals(reservation.theme); + } + public long getId() { return id; } diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 09c20cdd93..bfd1bd8763 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -22,6 +22,10 @@ public ReservationTime(Long id, LocalTime startAt) { this.startAt = startAt; } + public boolean isIdOf(long id) { + return this.id == id; + } + public Long getId() { return id; } diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 0134804dbb..cc7ada5a3a 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -47,6 +47,14 @@ public Theme(String name, String description, String thumbnail) { this(null, name, description, thumbnail); } + public boolean isIdOf(long id) { + return this.id == id; + } + + public boolean isNameOf(String name) { + return this.name.equals(name); + } + //todo : long 으로 변경 public Long getId() { return id; diff --git a/src/main/java/roomescape/service/AvailableTimeService.java b/src/main/java/roomescape/service/AvailableTimeService.java index 07b90674cc..909c6ad493 100644 --- a/src/main/java/roomescape/service/AvailableTimeService.java +++ b/src/main/java/roomescape/service/AvailableTimeService.java @@ -25,9 +25,8 @@ public AvailableTimeService(ReservationRepository reservationRepository, //todo : 메서드 개선 public List<AvailableTimeResponse> findByThemeAndDate(LocalDate date, long themeId) { Set<Long> alreadyReservedTimeIds = reservationRepository.findAll().stream() - .filter(reservation -> reservation.getDate().equals(date)) - //todo Reservation에 메서드 구현하기 - .filter(reservation -> reservation.getTheme().getId().equals(themeId)) + .filter(reservation -> reservation.isDateOf(date)) + .filter(reservation -> reservation.isThemeOf(themeId)) .map(Reservation::getReservationTime) .map(ReservationTime::getId) .collect(Collectors.toSet()); diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 4d76b6254b..f8415a526f 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -67,16 +67,11 @@ public ReservationResponse save(ReservationRequest reservationRequest) { } private boolean validateDuplicateReservation(Reservation beforeSave, Reservation reservation) { - return mapToLocalDateTime(reservation).equals(mapToLocalDateTime(beforeSave)) - && beforeSave.getTheme().equals(reservation.getTheme()); - } - - private LocalDateTime mapToLocalDateTime(Reservation reservation) { - LocalDate date = reservation.getDate(); - LocalTime time = reservation.getReservationTime().getStartAt(); - return LocalDateTime.of(date, time); + return reservation.isSameDateTime(beforeSave) + && beforeSave.isSameTheme(reservation); } + //TODO : 도메인에게 넘길 수 있을 것 같은데 private static boolean isBefore(Reservation beforeSave) { return LocalDateTime.of(beforeSave.getDate(), beforeSave.getTime()) .isBefore(LocalDateTime.now()); diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 0b1b97b5c8..74db95e3a9 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -49,7 +49,7 @@ public void delete(long id) { List<Reservation> reservations = reservationRepository.findAll(); //TODO : 지역변수 네이밍 고민 boolean invalidDelete = reservations.stream() - .anyMatch(reservation -> reservation.getReservationTime().getId() == id); + .anyMatch(reservation -> reservation.isReservationTimeOf(id)); if (invalidDelete) { throw new RoomescapeException(INVALID_DELETE_TIME); } diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index 3fd8f6bab1..8d1cea7d18 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -25,7 +25,7 @@ public ThemeService(ThemeRepository themeRepository, ReservationRepository reser public ThemeResponse save(ThemeRequest themeRequest) { boolean hasDuplicateTheme = themeRepository.findAll().stream() - .anyMatch(theme -> theme.getName().equals(themeRequest.name())); + .anyMatch(theme -> theme.isNameOf(themeRequest.name())); if (hasDuplicateTheme) { throw new RoomescapeException(ExceptionType.DUPLICATE_THEME); } @@ -47,7 +47,7 @@ public List<ThemeResponse> findAll() { public void delete(long id) { //todo : 변수명 고민 boolean invalidDelete = reservationRepository.findAll().stream() - .anyMatch(reservation -> Objects.equals(reservation.getTheme().getId(), id)); + .anyMatch(reservation -> reservation.isThemeOf(id)); if (invalidDelete) { throw new RoomescapeException(ExceptionType.INVALID_DELETE_THEME); } From 8a42260ce8b000596508465dbc8d643ac7ed3e9a Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 20:21:24 +0900 Subject: [PATCH 33/75] =?UTF-8?q?test=20:=20ThemeService=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/service/ThemeService.java | 2 - .../CollectionReservationRepository.java | 5 +- .../repository/CollectionThemeRepository.java | 23 +++- .../roomescape/service/ThemeServiceTest.java | 117 ++++++++++++++++++ 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 src/test/java/roomescape/service/ThemeServiceTest.java diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index 8d1cea7d18..4daac439e3 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -1,7 +1,6 @@ package roomescape.service; import java.util.List; -import java.util.Objects; import org.springframework.stereotype.Service; import roomescape.domain.Theme; import roomescape.dto.ThemeRequest; @@ -11,7 +10,6 @@ import roomescape.repository.ReservationRepository; import roomescape.repository.ThemeRepository; -//todo 테스트코드 작성 @Service public class ThemeService { diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java index 213022259a..ca6daa0aa2 100644 --- a/src/test/java/roomescape/repository/CollectionReservationRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -1,5 +1,7 @@ package roomescape.repository; +import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -7,6 +9,7 @@ import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; import roomescape.dto.ReservationRequest; +import roomescape.exception.RoomescapeException; public class CollectionReservationRepository implements ReservationRepository { private final List<Reservation> reservations; @@ -39,7 +42,7 @@ public Reservation save(Reservation reservation) { ReservationTime findTime = timeRepository.findAll().stream() .filter(reservationTime -> reservationTime.getId() == reservation.getReservationTime().getId()) .findFirst() - .get(); + .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); Reservation saved = new Reservation(atomicLong.incrementAndGet(), reservation.getName(), reservation.getDate(), findTime, reservation.getTheme()); reservations.add(saved); diff --git a/src/test/java/roomescape/repository/CollectionThemeRepository.java b/src/test/java/roomescape/repository/CollectionThemeRepository.java index a851648ec3..d234bb8f5d 100644 --- a/src/test/java/roomescape/repository/CollectionThemeRepository.java +++ b/src/test/java/roomescape/repository/CollectionThemeRepository.java @@ -1,27 +1,42 @@ package roomescape.repository; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import roomescape.domain.Theme; public class CollectionThemeRepository implements ThemeRepository { + + private final List<Theme> themes; + private final AtomicLong index; + + public CollectionThemeRepository() { + themes = new ArrayList<>(); + index = new AtomicLong(0); + } + @Override public List<Theme> findAll() { - return null; + return new ArrayList<>(themes); } @Override public Optional<Theme> findById(long id) { - return Optional.of(new Theme(id, "이름", "설명", "썸네일")); + return themes.stream() + .filter(theme -> theme.isIdOf(id)) + .findFirst(); } @Override public Theme save(Theme theme) { - return null; + Theme saved = new Theme(index.incrementAndGet(), theme); + themes.add(saved); + return saved; } @Override public void delete(long id) { - + themes.removeIf(theme -> theme.isIdOf(id)); } } diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java new file mode 100644 index 0000000000..45d32c1856 --- /dev/null +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -0,0 +1,117 @@ +package roomescape.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static roomescape.exception.ExceptionType.DUPLICATE_THEME; +import static roomescape.exception.ExceptionType.INVALID_DELETE_THEME; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; +import roomescape.dto.ThemeRequest; +import roomescape.dto.ThemeResponse; +import roomescape.exception.RoomescapeException; +import roomescape.repository.CollectionReservationRepository; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ThemeRepository; + +class ThemeServiceTest { + + private ThemeRepository themeRepository; + private CollectionReservationTimeRepository reservationTimeRepository; + private ReservationRepository reservationRepository; + private ThemeService themeService; + + @BeforeEach + void initService() { + themeRepository = new CollectionThemeRepository(); + reservationTimeRepository = new CollectionReservationTimeRepository(); + reservationRepository = new CollectionReservationRepository(reservationTimeRepository); + themeService = new ThemeService(themeRepository, reservationRepository); + } + + @DisplayName("테마, 시간이 하나 존재할 때") + @Nested + class OneThemeTest { + private ReservationTime defaultTime = new ReservationTime(LocalTime.now().plusMinutes(5)); + private Theme defaultTheme = new Theme("name", "description", "thumbnail"); + + @BeforeEach + void addDefaultData() { + defaultTime = reservationTimeRepository.save(defaultTime); + defaultTheme = themeRepository.save(defaultTheme); + } + + @DisplayName("동일한 이름의 테마를 예약할 수 없다.") + @Test + void duplicatedThemeSaveFailTest() { + assertThatThrownBy(() -> themeService.save(new ThemeRequest( + defaultTheme.getName(), "description", "thumbnail" + ))).isInstanceOf(RoomescapeException.class) + .hasMessage(DUPLICATE_THEME.getMessage()); + } + + @DisplayName("다른 이름의 테마를 예약할 수 있다.") + @Test + void notDuplicatedThemeNameSaveTest() { + themeService.save(new ThemeRequest("otherName", "description", "thumbnail")); + + assertThat(themeRepository.findAll()) + .hasSize(2); + } + + @DisplayName("테마에 예약이 없다면 테마를 삭제할 수 있다.") + @Test + void removeSuccessTest() { + + themeService.delete(1L); + assertThat(themeRepository.findById(1L)).isEmpty(); + } + + @DisplayName("테마에 예약이 있다면 테마를 삭제할 수 없다.") + @Test + void removeFailTest() { + //given + reservationRepository.save(new Reservation( + "name", LocalDate.now().plusDays(1), defaultTime, defaultTheme)); + + //when & then + assertThatThrownBy(() -> themeService.delete(1L)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(INVALID_DELETE_THEME.getMessage()); + } + + @DisplayName("존재하지 않는 테마 id로 삭제하더라도 오류로 간주하지 않는다.") + @Test + void notExistThemeDeleteTest() { + assertThatCode(() -> themeService.delete(2L)) + .doesNotThrowAnyException(); + } + } + + @DisplayName("테마가 여러개 있으면 테마를 모두 조회할 수 있다.") + @Test + void findAllTest() { + //given + themeRepository.save(new Theme("name1", "description1", "thumbnail1")); + themeRepository.save(new Theme("name2", "description2", "thumbnail2")); + themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + themeRepository.save(new Theme("name4", "description4", "thumbnail4")); + + //when + List<ThemeResponse> themeResponses = themeService.findAll(); + + //then + assertThat(themeResponses).hasSize(4); + } +} From 584df82d6649e19537c729421a96e451c8efa524 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 20:21:45 +0900 Subject: [PATCH 34/75] =?UTF-8?q?style=20:=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/service/ReservationService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index f8415a526f..0b99e7e072 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -4,9 +4,7 @@ import static roomescape.exception.ExceptionType.PAST_TIME; import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; -import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.List; import org.springframework.stereotype.Service; import roomescape.domain.Reservation; From f6966e194536ce045196012cd7117717bb2d2549 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 20:45:30 +0900 Subject: [PATCH 35/75] =?UTF-8?q?refactor=20:=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=EC=9D=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationControllerTest.java | 80 ++++++++++--------- .../service/ReservationTimeServiceTest.java | 16 ++-- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index cbba143c3e..ee1b61be86 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -3,10 +3,10 @@ import java.lang.reflect.Field; import java.time.LocalDate; import java.time.LocalTime; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.domain.Reservation; @@ -23,64 +23,72 @@ //TODO : 전체적으로 테스트 수정 class ReservationControllerTest { - static final long timeId = 1L; - static final LocalTime time = LocalTime.now(); - private static final Theme DEFUALT_THEME = new Theme(1L, "이름", "설명", "썸네일"); - private final CollectionReservationTimeRepository timeRepository = new CollectionReservationTimeRepository( - new ArrayList<>(List.of(new ReservationTime(timeId, time))) - ); - private final CollectionThemeRepository themeRepository = new CollectionThemeRepository(); + private static final long TIME_ID = 1L; + private static final LocalTime TIME = LocalTime.now(); + private ReservationTime defaultTime = new ReservationTime(TIME_ID, TIME); + private Theme defualtTheme = new Theme("name", "description", "thumbnail"); + + private CollectionReservationRepository collectionReservationRepository; + private ReservationController reservationController; + + @BeforeEach + void initController() { + CollectionReservationTimeRepository timeRepository = new CollectionReservationTimeRepository(); + CollectionThemeRepository themeRepository = new CollectionThemeRepository(); + collectionReservationRepository = new CollectionReservationRepository(timeRepository); + ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, + themeRepository); + reservationController = new ReservationController(reservationService); + + defaultTime = timeRepository.save(defaultTime); + defualtTheme = themeRepository.save(defualtTheme); + } @Test @DisplayName("예약 정보를 잘 저장하는지 확인한다.") void saveReservation() { - CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( - timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, - themeRepository); - ReservationController reservationController = new ReservationController(reservationService); + //given LocalDate date = LocalDate.now().plusDays(1); + //when ReservationResponse saveResponse = reservationController.saveReservation( - new ReservationRequest(date, "폴라", timeId, 1)) + new ReservationRequest(date, "폴라", TIME_ID, defualtTheme.getId())) .getBody(); long id = Objects.requireNonNull(saveResponse).id(); + + //then ReservationResponse expected = new ReservationResponse(id, "폴라", date, - new ReservationTimeResponse(timeId, time), new ThemeResponse(1, "이름", "설명", "썸네일")); + new ReservationTimeResponse(TIME_ID, TIME), + //Todo : [로빈] Response 가 변환 로직을 가지고 있으면 아래 코드도 간단해 질 것 같음 + new ThemeResponse(defualtTheme.getId(), defualtTheme.getName(), defualtTheme.getDescription(), + defualtTheme.getThumbnail())); - Assertions.assertThat(saveResponse) - .isEqualTo(expected); + Assertions.assertThat(saveResponse).isEqualTo(expected); } @Test @DisplayName("예약 정보를 잘 불러오는지 확인한다.") void findAllReservations() { - CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( - timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, - null); - ReservationController reservationController = new ReservationController(reservationService); + //when List<ReservationResponse> allReservations = reservationController.findAllReservations(); - Assertions.assertThat(allReservations) - .isEmpty(); + //then + Assertions.assertThat(allReservations).isEmpty(); } @Test @DisplayName("예약 정보를 잘 지우는지 확인한다.") void delete() { - List<Reservation> reservations = List.of( - new Reservation(1L, "폴라", LocalDate.now(), new ReservationTime(LocalTime.now()), DEFUALT_THEME)); - CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( - new ArrayList<>(reservations), timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, - null); - ReservationController reservationController = new ReservationController(reservationService); + //given + Reservation saved = collectionReservationRepository.save( + new Reservation("폴라", LocalDate.now(), defaultTime, defualtTheme)); - reservationController.delete(1L); - List<ReservationResponse> reservationResponses = reservationController.findAllReservations(); + //when + reservationController.delete(saved.getId()); + //then + List<ReservationResponse> reservationResponses = reservationController.findAllReservations(); Assertions.assertThat(reservationResponses) .isEmpty(); } @@ -88,12 +96,6 @@ void delete() { @Test @DisplayName("내부에 Repository를 의존하고 있지 않은지 확인한다.") void checkRepositoryDependency() { - CollectionReservationRepository collectionReservationRepository = new CollectionReservationRepository( - timeRepository); - ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, - null); - ReservationController reservationController = new ReservationController(reservationService); - boolean isRepositoryInjected = false; for (Field field : reservationController.getClass().getDeclaredFields()) { diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index cc622871d0..3865031b59 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -4,6 +4,7 @@ import java.time.LocalTime; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.dto.ReservationTimeRequest; @@ -13,14 +14,19 @@ class ReservationTimeServiceTest { + private ReservationTimeService reservationTimeService; + + @BeforeEach + void initService() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + CollectionReservationRepository reservationRepository = + new CollectionReservationRepository(reservationTimeRepository); + reservationTimeService = new ReservationTimeService(reservationRepository, reservationTimeRepository); + } + @Test @DisplayName("중복된 시간은 생성할 수 없는지 검증") void saveFailCauseDuplicate() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository( - reservationTimeRepository); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, - reservationTimeRepository); ReservationTimeRequest reservationTimeRequest = new ReservationTimeRequest(LocalTime.of(10, 0)); reservationTimeService.save(reservationTimeRequest); From 9adfa8d8c0ce7292c56375c9d4a2a268667c151e Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 21:32:17 +0900 Subject: [PATCH 36/75] =?UTF-8?q?refactor=20:=20Reservation=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=97=90=EC=84=9C=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 34 +++++++++++++++++-- .../roomescape/exception/ExceptionType.java | 10 +++--- .../roomescape/domain/ReservationTest.java | 27 ++++++++++++--- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index aaa3a1744d..6ac260905d 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,6 +1,9 @@ package roomescape.domain; +import static roomescape.exception.ExceptionType.DATE_EMPTY; import static roomescape.exception.ExceptionType.NAME_EMPTY; +import static roomescape.exception.ExceptionType.THEME_EMPTY; +import static roomescape.exception.ExceptionType.TIME_EMPTY; import java.time.LocalDate; import java.time.LocalDateTime; @@ -20,9 +23,10 @@ public Reservation(String name, LocalDate date, ReservationTime time, Theme them } public Reservation(Long id, String name, LocalDate date, ReservationTime time, Theme theme) { - if (name == null || name.isBlank()) { - throw new RoomescapeException(NAME_EMPTY); - } + validateName(name); + validateDate(date); + validateTime(time); + validateTheme(theme); this.id = id; this.name = name; this.date = date; @@ -30,6 +34,30 @@ public Reservation(Long id, String name, LocalDate date, ReservationTime time, T this.theme = theme; } + private void validateTheme(Theme theme) { + if (theme == null) { + throw new RoomescapeException(THEME_EMPTY); + } + } + + private void validateTime(ReservationTime time) { + if (time == null) { + throw new RoomescapeException(TIME_EMPTY); + } + } + + private void validateDate(LocalDate date) { + if (date == null) { + throw new RoomescapeException(DATE_EMPTY); + } + } + + private void validateName(String name) { + if (name == null || name.isBlank()) { + throw new RoomescapeException(NAME_EMPTY); + } + } + public Reservation(long id, Reservation reservationBeforeSave) { this(id, reservationBeforeSave.name, reservationBeforeSave.date, reservationBeforeSave.time, reservationBeforeSave.theme); diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index 51319d5b25..7725238da8 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -9,18 +9,20 @@ public enum ExceptionType { NAME_EMPTY(BAD_REQUEST, "이름은 필수 값입니다."), TIME_EMPTY(BAD_REQUEST, "시작 시간은 필수 값입니다."), + DATE_EMPTY(BAD_REQUEST, "날짜는 필수값 입니다."), + THEME_EMPTY(BAD_REQUEST, "테마는 필수값 입니다."), + DESCRIPTION_EMPTY(BAD_REQUEST, "테마 설명은 필수값 입니다."), + THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), + PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), DUPLICATE_RESERVATION(BAD_REQUEST, "같은 시간에 이미 예약이 존재합니다."), DUPLICATE_RESERVATION_TIME(BAD_REQUEST, "이미 예약시간이 존재합니다."), DUPLICATE_THEME(BAD_REQUEST, "이미 동일한 테마가 존재합니다."), INVALID_DATE_TIME_FORMAT(BAD_REQUEST, "해석할 수 없는 날짜, 시간 포맷입니다."), - //Todo 이름 변경 + //Todo 이름 변경, INVALID_DELETE_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), INVALID_DELETE_THEME(BAD_REQUEST, "예약이 존재하는 테마는 삭제할 수 없습니다."), RESERVATION_TIME_NOT_FOUND(BAD_REQUEST, "존재하지 않는 시간입니다."), //todo 이름 변경, - PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), - DESCRIPTION_EMPTY(BAD_REQUEST, "테마 설명은 필수값 입니다."), - THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), THEME_NOT_FOUND(BAD_REQUEST, "없는 테마입니다."), ; diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index cdd5840fb7..17b021a41d 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -1,13 +1,17 @@ package roomescape.domain; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static roomescape.exception.ExceptionType.DATE_EMPTY; +import static roomescape.exception.ExceptionType.NAME_EMPTY; +import static roomescape.exception.ExceptionType.THEME_EMPTY; +import static roomescape.exception.ExceptionType.TIME_EMPTY; import java.time.LocalDate; import java.time.LocalTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; class ReservationTest { @@ -19,9 +23,24 @@ class ReservationTest { @DisplayName("생성 테스트") @Test void constructTest() { - assertThatThrownBy(() -> new Reservation(null, DEFAULT_DATE, DEFAULT_TIME, DEFAULT_THEME)) - .isInstanceOf(RoomescapeException.class) - .hasMessage(ExceptionType.NAME_EMPTY.getMessage()); + assertAll( + () -> assertThatThrownBy(() -> new Reservation(null, DEFAULT_DATE, DEFAULT_TIME, DEFAULT_THEME)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(NAME_EMPTY.getMessage()), + + () -> assertThatThrownBy(() -> new Reservation("name", null, DEFAULT_TIME, DEFAULT_THEME)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(DATE_EMPTY.getMessage()), + + () -> assertThatThrownBy(() -> new Reservation("name", DEFAULT_DATE, null, DEFAULT_THEME)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(TIME_EMPTY.getMessage()), + + () -> assertThatThrownBy(() -> new Reservation("name", DEFAULT_DATE, DEFAULT_TIME, null)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(THEME_EMPTY.getMessage()) + ); + } @Test From 24c83c6801f763cd4374939308bc61fd02f79546 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 21:41:28 +0900 Subject: [PATCH 37/75] =?UTF-8?q?test=20:=20ReservationService=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationService.java | 13 +- .../service/ReservationServiceTest.java | 179 ++++++++++++++++++ 2 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 src/test/java/roomescape/service/ReservationServiceTest.java diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 0b99e7e072..de04fb3a12 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -33,20 +33,18 @@ public ReservationService(ReservationRepository reservationRepository, this.themeRepository = themeRepository; } - //ToDo 테스트 작성 public ReservationResponse save(ReservationRequest reservationRequest) { - //TODO 변수명 - ReservationTime reservationTime = reservationTimeRepository.findById(reservationRequest.timeId()) - .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); - Theme theme = themeRepository.findById(reservationRequest.themeId()) + ReservationTime requestedTime = reservationTimeRepository.findById(reservationRequest.timeId()) + .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); + Theme requestedTheme = themeRepository.findById(reservationRequest.themeId()) .orElseThrow(() -> new RoomescapeException(ExceptionType.THEME_NOT_FOUND)); Reservation beforeSave = new Reservation( reservationRequest.name(), reservationRequest.date(), - reservationTime, - theme + requestedTime, + requestedTheme ); boolean isDuplicate = reservationRepository.findAll() .stream() @@ -55,7 +53,6 @@ public ReservationResponse save(ReservationRequest reservationRequest) { throw new RoomescapeException(DUPLICATE_RESERVATION); } - //todo 테스트 if (isBefore(beforeSave)) { throw new RoomescapeException(PAST_TIME); } diff --git a/src/test/java/roomescape/service/ReservationServiceTest.java b/src/test/java/roomescape/service/ReservationServiceTest.java new file mode 100644 index 0000000000..f49356cff9 --- /dev/null +++ b/src/test/java/roomescape/service/ReservationServiceTest.java @@ -0,0 +1,179 @@ +package roomescape.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; +import static roomescape.exception.ExceptionType.NAME_EMPTY; +import static roomescape.exception.ExceptionType.PAST_TIME; +import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; +import static roomescape.exception.ExceptionType.THEME_NOT_FOUND; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; +import roomescape.dto.ReservationRequest; +import roomescape.dto.ReservationResponse; +import roomescape.exception.RoomescapeException; +import roomescape.repository.CollectionReservationRepository; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ThemeRepository; + +class ReservationServiceTest { + + private ReservationRepository reservationRepository; + private ReservationService reservationService; + + private ReservationTime defaultTime = new ReservationTime(LocalTime.now()); + private Theme defaultTheme = new Theme("name", "description", "thumbnail"); + + @BeforeEach + void initService() { + CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); + ThemeRepository themeRepository = new CollectionThemeRepository(); + reservationRepository = new CollectionReservationRepository(reservationTimeRepository); + reservationService = new ReservationService(reservationRepository, reservationTimeRepository, themeRepository); + + defaultTime = reservationTimeRepository.save(defaultTime); + defaultTheme = themeRepository.save(defaultTheme); + } + + @DisplayName("지나지 않은 시간에 대한 예약을 생성할 수 있다.") + @Test + void createFutureReservationTest() { + //when + ReservationResponse saved = reservationService.save(new ReservationRequest( + LocalDate.now().plusDays(1), + "name", + defaultTime.getId(), + defaultTheme.getId() + )); + + //then + assertAll( + () -> assertThat(reservationRepository.findAll()) + .hasSize(1), + () -> assertThat(saved.id()).isEqualTo(1L) + ); + } + + @DisplayName("지난 시간에 대해 예약을 시도할 경우 예외가 발생한다.") + @Test + void createPastReservationFailTest() { + assertThatThrownBy(() -> reservationService.save(new ReservationRequest( + LocalDate.now().minusDays(1), + "name", + defaultTime.getId(), + defaultTheme.getId() + ))) + .isInstanceOf(RoomescapeException.class) + .hasMessage(PAST_TIME.getMessage()); + } + + @DisplayName("존재하지 않는 시간에 대해 예약을 생성하면 예외가 발생한다.") + @Test + void createReservationWithTimeNotExistsTest() { + assertThatThrownBy(() -> reservationService.save(new ReservationRequest( + LocalDate.now().minusDays(1), + "name", + 2L, + defaultTheme.getId() + ))) + .isInstanceOf(RoomescapeException.class) + .hasMessage(RESERVATION_TIME_NOT_FOUND.getMessage()); + } + + @DisplayName("존재하지 않는 테마에 대해 예약을 생성하면 예외가 발생한다.") + @Test + void createReservationWithThemeNotExistsTest() { + assertThatThrownBy(() -> reservationService.save(new ReservationRequest( + LocalDate.now().minusDays(1), + "name", + defaultTime.getId(), + 2L + ))) + .isInstanceOf(RoomescapeException.class) + .hasMessage(THEME_NOT_FOUND.getMessage()); + } + + @DisplayName("필수값이 입력되지 않은 예약 생성 요청에 대해 예외가 발생한다.") + @Test + void emptyRequiredValueTest() { + //todo [로빈] 이 테스트를 작성하다 보니 Response dto 에도 NotNull 검증은 필요할 것 같다는 생각이 듭니다. + assertAll( + () -> assertThatThrownBy(() -> reservationService.save(new ReservationRequest( + LocalDate.now().minusDays(1), + null, + defaultTime.getId(), + defaultTheme.getId() + ))).isInstanceOf(RoomescapeException.class) + .hasMessage(NAME_EMPTY.getMessage()) + ); + } + + @DisplayName("예약이 하나 존재하는 경우") + @Nested + class OneReservationExistsTest { + + LocalDate defaultDate = LocalDate.now().plusDays(1); + Reservation defaultReservation; + + @BeforeEach + void addDefaultReservation() { + defaultReservation = new Reservation("name", defaultDate, defaultTime, defaultTheme); + defaultReservation = reservationRepository.save(defaultReservation); + } + + @DisplayName("이미 예약된 시간, 테마의 예약을 또 생성할 수 없다.") + @Test + void duplicatedReservationFailTest() { + assertThatThrownBy(() -> reservationService.save( + new ReservationRequest(defaultDate, "otherName", defaultTime.getId(), defaultTheme.getId()))) + .isInstanceOf(RoomescapeException.class) + .hasMessage(DUPLICATE_RESERVATION.getMessage()); + } + + @DisplayName("예약을 삭제할 수 있다.") + @Test + void deleteReservationTest() { + //when + reservationService.delete(1L); + + //then + assertThat(reservationRepository.findAll()).isEmpty(); + } + + @DisplayName("존재하지 않는 예약에 대한 삭제 요청은 정상 요청으로 간주한다.") + @Test + void deleteNotExistReservationNotThrowsException() { + assertThatCode(() -> reservationService.delete(2L)) + .doesNotThrowAnyException(); + } + } + + @DisplayName("예약이 여러 개 존재하는 경우 모든 예약을 조회할 수 있다.") + @Test + void findAllTest() { + //given + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(1), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(2), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(3), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(4), defaultTime, defaultTheme)); + + //when + List<ReservationResponse> reservationResponses = reservationService.findAll(); + + //then + assertThat(reservationResponses).hasSize(4); + } +} From 5f25eaf54a817017f56884915941da9a17aa4e57 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 22:07:54 +0900 Subject: [PATCH 38/75] =?UTF-8?q?refactor=20:=20CollectionReservationRepos?= =?UTF-8?q?itory=20=EA=B0=80=20=EB=8B=A4=EB=A5=B8=20Repository=20=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationControllerTest.java | 3 +- .../ReservationTimeControllerTest.java | 46 ++++++++----------- .../CollectionReservationRepository.java | 35 ++------------ .../service/ReservationServiceTest.java | 2 +- .../service/ReservationTimeServiceTest.java | 3 +- .../roomescape/service/ThemeServiceTest.java | 2 +- 6 files changed, 26 insertions(+), 65 deletions(-) diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index ee1b61be86..5522db8150 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -21,7 +21,6 @@ import roomescape.repository.CollectionThemeRepository; import roomescape.service.ReservationService; -//TODO : 전체적으로 테스트 수정 class ReservationControllerTest { private static final long TIME_ID = 1L; private static final LocalTime TIME = LocalTime.now(); @@ -35,7 +34,7 @@ class ReservationControllerTest { void initController() { CollectionReservationTimeRepository timeRepository = new CollectionReservationTimeRepository(); CollectionThemeRepository themeRepository = new CollectionThemeRepository(); - collectionReservationRepository = new CollectionReservationRepository(timeRepository); + collectionReservationRepository = new CollectionReservationRepository(); ReservationService reservationService = new ReservationService(collectionReservationRepository, timeRepository, themeRepository); reservationController = new ReservationController(reservationService); diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java index 7726d9e2d5..46c4f4e5bb 100644 --- a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.domain.ReservationTime; @@ -15,17 +16,23 @@ import roomescape.service.ReservationTimeService; class ReservationTimeControllerTest { + + private CollectionReservationTimeRepository reservationTimeRepository; + private ReservationTimeController reservationTimeController; + + @BeforeEach + void init() { + reservationTimeRepository = new CollectionReservationTimeRepository(); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository(); + ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, + reservationTimeRepository); + reservationTimeController = new ReservationTimeController(reservationTimeService); + } + @Test @DisplayName("시간을 잘 저장하는지 확인한다.") void save() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository( - reservationTimeRepository); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, - reservationTimeRepository); - ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); LocalTime time = LocalTime.now(); - ReservationTimeResponse save = reservationTimeController.save(new ReservationTimeRequest(time)).getBody(); ReservationTimeResponse expected = new ReservationTimeResponse(save.id(), time); @@ -36,12 +43,6 @@ void save() { @Test @DisplayName("시간을 잘 불러오는지 확인한다.") void findAll() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository( - reservationTimeRepository); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, - reservationTimeRepository); - ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); List<ReservationTimeResponse> reservationTimeResponses = reservationTimeController.findAll(); Assertions.assertThat(reservationTimeResponses) @@ -51,17 +52,13 @@ void findAll() { @Test @DisplayName("시간을 잘 지우는지 확인한다.") void delete() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository( - new ArrayList<>(List.of(new ReservationTime(1L, LocalTime.now()))) - ); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository( - reservationTimeRepository); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, - reservationTimeRepository); - ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); + //given + reservationTimeRepository.save(new ReservationTime(1L, LocalTime.now())); + //when reservationTimeController.delete(1); + //then List<ReservationTimeResponse> reservationTimeResponses = reservationTimeController.findAll(); Assertions.assertThat(reservationTimeResponses) .isEmpty(); @@ -70,13 +67,6 @@ void delete() { @Test @DisplayName("내부에 Repository를 의존하고 있지 않은지 확인한다.") void checkRepositoryDependency() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository( - reservationTimeRepository); - ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, - reservationTimeRepository); - ReservationTimeController reservationTimeController = new ReservationTimeController(reservationTimeService); - boolean isRepositoryInjected = false; for (Field field : reservationTimeController.getClass().getDeclaredFields()) { diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java index ca6daa0aa2..6673a8f600 100644 --- a/src/test/java/roomescape/repository/CollectionReservationRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -1,50 +1,23 @@ package roomescape.repository; -import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Predicate; import roomescape.domain.Reservation; -import roomescape.domain.ReservationTime; -import roomescape.dto.ReservationRequest; -import roomescape.exception.RoomescapeException; public class CollectionReservationRepository implements ReservationRepository { private final List<Reservation> reservations; private final AtomicLong atomicLong; - private final CollectionReservationTimeRepository timeRepository; - - public CollectionReservationRepository(CollectionReservationTimeRepository timeRepository) { - this(new ArrayList<>(), new AtomicLong(0), timeRepository); - } - public CollectionReservationRepository(List<Reservation> reservations, AtomicLong atomicLong, - CollectionReservationTimeRepository timeRepository) { - this.reservations = reservations; - this.atomicLong = atomicLong; - this.timeRepository = timeRepository; - } - - public CollectionReservationRepository(List<Reservation> reservations, - CollectionReservationTimeRepository timeRepository) { - this(reservations, new AtomicLong(0), timeRepository); - } - private static Predicate<ReservationTime> sameId(ReservationRequest reservationRequest) { - return reservationTime -> reservationTime.getId() == reservationRequest.timeId(); + public CollectionReservationRepository() { + this.reservations = new ArrayList<>(); + this.atomicLong = new AtomicLong(0); } @Override public Reservation save(Reservation reservation) { - //TODO 안해도 되는지 확인하기 - ReservationTime findTime = timeRepository.findAll().stream() - .filter(reservationTime -> reservationTime.getId() == reservation.getReservationTime().getId()) - .findFirst() - .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); - Reservation saved = new Reservation(atomicLong.incrementAndGet(), reservation.getName(), reservation.getDate(), - findTime, reservation.getTheme()); + Reservation saved = new Reservation(atomicLong.incrementAndGet(), reservation); reservations.add(saved); return saved; } diff --git a/src/test/java/roomescape/service/ReservationServiceTest.java b/src/test/java/roomescape/service/ReservationServiceTest.java index f49356cff9..1bf6505187 100644 --- a/src/test/java/roomescape/service/ReservationServiceTest.java +++ b/src/test/java/roomescape/service/ReservationServiceTest.java @@ -41,7 +41,7 @@ class ReservationServiceTest { void initService() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); ThemeRepository themeRepository = new CollectionThemeRepository(); - reservationRepository = new CollectionReservationRepository(reservationTimeRepository); + reservationRepository = new CollectionReservationRepository(); reservationService = new ReservationService(reservationRepository, reservationTimeRepository, themeRepository); defaultTime = reservationTimeRepository.save(defaultTime); diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 3865031b59..2d4c478b70 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -19,8 +19,7 @@ class ReservationTimeServiceTest { @BeforeEach void initService() { CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = - new CollectionReservationRepository(reservationTimeRepository); + CollectionReservationRepository reservationRepository = new CollectionReservationRepository(); reservationTimeService = new ReservationTimeService(reservationRepository, reservationTimeRepository); } diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java index 45d32c1856..04220a1b6e 100644 --- a/src/test/java/roomescape/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -36,7 +36,7 @@ class ThemeServiceTest { void initService() { themeRepository = new CollectionThemeRepository(); reservationTimeRepository = new CollectionReservationTimeRepository(); - reservationRepository = new CollectionReservationRepository(reservationTimeRepository); + reservationRepository = new CollectionReservationRepository(); themeService = new ThemeService(themeRepository, reservationRepository); } From b4b52b4e75b909173c010700533177aad43affb4 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 22:10:26 +0900 Subject: [PATCH 39/75] =?UTF-8?q?refactor=20:=20getter=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20=EA=B5=AC=ED=98=84=EB=90=9C=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CollectionReservationTimeRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index fc8901ecf4..cac0956a7b 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -40,7 +40,7 @@ public boolean existsByStartAt(LocalTime startAt) { @Override public Optional<ReservationTime> findById(long id) { return reservationTimes.stream() - .filter(reservationTime -> reservationTime.getId() == id) //Todo 메서드로 바꾸기 + .filter(reservationTime -> reservationTime.isIdOf(id)) //Todo 메서드로 바꾸기 .findFirst(); } From cc62f4b565f06565c0a496dd5d212c4e03daf851 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Wed, 1 May 2024 22:10:35 +0900 Subject: [PATCH 40/75] =?UTF-8?q?refactor=20:=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=EB=90=9C=20TODO=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/repository/ReservationTimeRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java index f30eeb9c60..ca04e207b9 100644 --- a/src/main/java/roomescape/repository/ReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -8,7 +8,6 @@ public interface ReservationTimeRepository { ReservationTime save(ReservationTime reservationTime); - //Todo 메서드명 고민 boolean existsByStartAt(LocalTime startAt); Optional<ReservationTime> findById(long id); From f69070806acacb42fb32fcff068fe3804c222837 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 09:46:37 +0900 Subject: [PATCH 41/75] =?UTF-8?q?style:=20=EB=88=84=EB=9D=BD=EB=90=9C=20st?= =?UTF-8?q?atic=20import=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/service/ReservationService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index de04fb3a12..ba706b53cd 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,5 +1,6 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.*; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; import static roomescape.exception.ExceptionType.PAST_TIME; import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; @@ -38,7 +39,7 @@ public ReservationResponse save(ReservationRequest reservationRequest) { ReservationTime requestedTime = reservationTimeRepository.findById(reservationRequest.timeId()) .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); Theme requestedTheme = themeRepository.findById(reservationRequest.themeId()) - .orElseThrow(() -> new RoomescapeException(ExceptionType.THEME_NOT_FOUND)); + .orElseThrow(() -> new RoomescapeException(THEME_NOT_FOUND)); Reservation beforeSave = new Reservation( reservationRequest.name(), From 1e96e33f00836371f67cb9d516621d451bb5aa74 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 09:48:14 +0900 Subject: [PATCH 42/75] =?UTF-8?q?style:=20=EC=B2=98=EB=A6=AC=EB=90=9C=20to?= =?UTF-8?q?do=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CollectionReservationTimeRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index cac0956a7b..cc074089a3 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -40,7 +40,7 @@ public boolean existsByStartAt(LocalTime startAt) { @Override public Optional<ReservationTime> findById(long id) { return reservationTimes.stream() - .filter(reservationTime -> reservationTime.isIdOf(id)) //Todo 메서드로 바꾸기 + .filter(reservationTime -> reservationTime.isIdOf(id)) .findFirst(); } From 06bb27c1f465046b99161869cd6d4f43497946b5 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 10:32:36 +0900 Subject: [PATCH 43/75] =?UTF-8?q?test:=20ReservationTimeService=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ReservationTimeServiceTest.java | 95 +++++++++++++++++-- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 2d4c478b70..bc70c5d82f 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -1,36 +1,111 @@ package roomescape.service; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThatCode; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; +import static roomescape.exception.ExceptionType.INVALID_DELETE_TIME; +import java.time.LocalDate; import java.time.LocalTime; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.dto.ReservationTimeRequest; +import roomescape.dto.ReservationTimeResponse; import roomescape.exception.RoomescapeException; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; class ReservationTimeServiceTest { + private CollectionReservationRepository reservationRepository; + private CollectionReservationTimeRepository reservationTimeRepository; private ReservationTimeService reservationTimeService; @BeforeEach void initService() { - CollectionReservationTimeRepository reservationTimeRepository = new CollectionReservationTimeRepository(); - CollectionReservationRepository reservationRepository = new CollectionReservationRepository(); + reservationRepository = new CollectionReservationRepository(); + reservationTimeRepository = new CollectionReservationTimeRepository(); reservationTimeService = new ReservationTimeService(reservationRepository, reservationTimeRepository); } + @DisplayName("예약 시간이 하나 존재할 때") + @Nested + class OneReservationTimeExists { + private static final LocalTime SAVED_TIME = LocalTime.of(10, 0); + + @BeforeEach + void addDefaultTime() { + ReservationTimeRequest reservationTimeRequest = new ReservationTimeRequest(SAVED_TIME); + reservationTimeService.save(reservationTimeRequest); + } + + @DisplayName("정상적으로 시간을 생성할 수 있다.") + @Test + void saveReservationTimeTest() { + assertThatCode(() -> + reservationTimeService.save(new ReservationTimeRequest(SAVED_TIME.plusHours(1)))) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("중복된 시간은 생성할 수 없는지 검증") + void saveFailCauseDuplicate() { + assertThatThrownBy(() -> reservationTimeService.save(new ReservationTimeRequest(SAVED_TIME))) + .isInstanceOf(RoomescapeException.class) + .hasMessage(DUPLICATE_RESERVATION_TIME.getMessage()); + } + + @DisplayName("저장된 시간을 삭제할 수 있다.") + @Test + void deleteByIdTest() { + //when + reservationTimeService.delete(1L); + + //then + assertThat(reservationTimeRepository.findAll()) + .isEmpty(); + } + + @DisplayName("예약 시간을 사용하는 예약이 있으면 예약을 삭제할 수 없다.") + @Test + void usedReservationTimeDeleteTest() { + //given + reservationRepository.save(new Reservation( + "name", + LocalDate.now(), + new ReservationTime(1L, SAVED_TIME), + new Theme(1L, "name", "description", "thumbnail") + )); + + //when & then + assertThatCode(() -> reservationTimeService.delete(1L)) + .isInstanceOf(RoomescapeException.class) + .hasMessage(INVALID_DELETE_TIME.getMessage()); + } + } + + @DisplayName("저장된 시간을 모두 조회할 수 있다.") @Test - @DisplayName("중복된 시간은 생성할 수 없는지 검증") - void saveFailCauseDuplicate() { - ReservationTimeRequest reservationTimeRequest = new ReservationTimeRequest(LocalTime.of(10, 0)); - reservationTimeService.save(reservationTimeRequest); - - Assertions.assertThatThrownBy(() -> reservationTimeService.save(reservationTimeRequest)) - .isInstanceOf(RoomescapeException.class) - .hasMessage(DUPLICATE_RESERVATION_TIME.getMessage()); + void findAllTest() { + //given + reservationTimeRepository.save(new ReservationTime(LocalTime.of(10, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(13, 0))); + + //when + List<ReservationTimeResponse> reservationTimeResponses = reservationTimeService.findAll(); + + //then + assertThat(reservationTimeResponses) + .hasSize(4); } } From 225539175a7c0dd3b43f6f2a5157f92eeea88e8e Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 10:43:38 +0900 Subject: [PATCH 44/75] =?UTF-8?q?test:=20AvailableTimeService=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AvailableTimeServiceTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/test/java/roomescape/service/AvailableTimeServiceTest.java diff --git a/src/test/java/roomescape/service/AvailableTimeServiceTest.java b/src/test/java/roomescape/service/AvailableTimeServiceTest.java new file mode 100644 index 0000000000..87b5427efd --- /dev/null +++ b/src/test/java/roomescape/service/AvailableTimeServiceTest.java @@ -0,0 +1,59 @@ +package roomescape.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; +import roomescape.dto.AvailableTimeResponse; +import roomescape.repository.CollectionReservationRepository; +import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ReservationTimeRepository; + +class AvailableTimeServiceTest { + + private AvailableTimeService availableTimeService; + private ReservationRepository reservationRepository; + private ReservationTimeRepository reservationTimeRepository; + + @BeforeEach + void init() { + reservationRepository = new CollectionReservationRepository(); + reservationTimeRepository = new CollectionReservationTimeRepository(); + availableTimeService = new AvailableTimeService(reservationRepository, reservationTimeRepository); + } + + @DisplayName("날짜와 테마, 시간에 대한 예약 내역을 확인할 수 있다.") + @Test + void findAvailableTimeTest() { + //given + Theme DEFUALT_THEME = new Theme(1L, "name", "description", "thumbnail"); + ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); + ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); + ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(13, 0))); + ReservationTime reservationTime4 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(14, 0))); + + LocalDate selectedDate = LocalDate.of(2024, 1, 1); + reservationRepository.save(new Reservation("name", selectedDate, reservationTime1, DEFUALT_THEME)); + reservationRepository.save(new Reservation("name", selectedDate, reservationTime3, DEFUALT_THEME)); + + //when + List<AvailableTimeResponse> availableTimeResponses = availableTimeService.findByThemeAndDate(selectedDate, + DEFUALT_THEME.getId()); + + //then + assertThat(availableTimeResponses).containsExactlyInAnyOrder( + new AvailableTimeResponse(1L, reservationTime1.getStartAt(), true), + new AvailableTimeResponse(2L, reservationTime2.getStartAt(), false), + new AvailableTimeResponse(3L, reservationTime3.getStartAt(), true), + new AvailableTimeResponse(4L, reservationTime4.getStartAt(), false) + ); + } +} From ee5b1150c753efb78fe0dd033abc22aad49a7547 Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 10:45:05 +0900 Subject: [PATCH 45/75] =?UTF-8?q?style:=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/service/ReservationService.java | 3 +-- src/test/java/roomescape/RoomescapeApplicationTest.java | 6 +++--- .../controller/ReservationTimeControllerTest.java | 1 - .../java/roomescape/service/ReservationTimeServiceTest.java | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index ba706b53cd..a396df0645 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,9 +1,9 @@ package roomescape.service; -import static roomescape.exception.ExceptionType.*; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; import static roomescape.exception.ExceptionType.PAST_TIME; import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; +import static roomescape.exception.ExceptionType.THEME_NOT_FOUND; import java.time.LocalDateTime; import java.util.List; @@ -15,7 +15,6 @@ import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationTimeResponse; import roomescape.dto.ThemeResponse; -import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; diff --git a/src/test/java/roomescape/RoomescapeApplicationTest.java b/src/test/java/roomescape/RoomescapeApplicationTest.java index 326a3ff677..6c9ad1d0c7 100644 --- a/src/test/java/roomescape/RoomescapeApplicationTest.java +++ b/src/test/java/roomescape/RoomescapeApplicationTest.java @@ -6,8 +6,8 @@ @SpringBootTest class RoomescapeApplicationTest { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java index 46c4f4e5bb..c6082b30a5 100644 --- a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -2,7 +2,6 @@ import java.lang.reflect.Field; import java.time.LocalTime; -import java.util.ArrayList; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index bc70c5d82f..cac7188ab2 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -1,14 +1,14 @@ package roomescape.service; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; import static roomescape.exception.ExceptionType.INVALID_DELETE_TIME; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -21,7 +21,6 @@ import roomescape.exception.RoomescapeException; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; -import roomescape.repository.CollectionThemeRepository; class ReservationTimeServiceTest { From 77a3e2432825e1ab5246ea5bb119af38bb493d1f Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 11:41:23 +0900 Subject: [PATCH 46/75] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=84=EC=97=90=20theme=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EB=8F=84=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/JdbcTemplateReservationRepositoryTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java index 400186c4b1..79dfd24631 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -28,9 +28,16 @@ class JdbcTemplateReservationRepositoryTest { void init() { jdbcTemplate.update("delete from reservation"); jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); + jdbcTemplate.update("delete from reservation_time"); jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); + + jdbcTemplate.update("delete from theme"); + jdbcTemplate.update("ALTER TABLE theme alter column id restart with 1"); + jdbcTemplate.update( + "insert into theme (name, description, thumbnail) values('name', 'description', 'thumbnail')"); + } @Test From 3689dc743073aa6c913afd3a29ae65b7144c108d Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 11:47:30 +0900 Subject: [PATCH 47/75] =?UTF-8?q?docs:=20=EC=9D=B8=EA=B8=B0=20=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c7b3324bd..41fc748304 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [x] 예약이 있는 테마를 삭제 요청시 에러 - [ ] 사용자 예약 기능 추가 +- [ ] 인기 테마 기능 추가 # API 명세 From 0516901892ec253e933d2ae0763291991e7f86ee Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 11:47:47 +0900 Subject: [PATCH 48/75] =?UTF-8?q?feat:=20=EC=9D=B8=EA=B8=B0=20=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../controller/ThemeController.java | 9 +++ .../roomescape/controller/UserController.java | 5 ++ .../JdbcTemplateThemeRepository.java | 32 +++++---- .../repository/ThemeRepository.java | 3 + .../java/roomescape/service/ThemeService.java | 7 ++ src/main/resources/static/js/ranking.js | 72 +++++++++++-------- .../repository/CollectionThemeRepository.java | 6 ++ 8 files changed, 94 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 41fc748304..fd820a31e5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - [x] 예약이 있는 테마를 삭제 요청시 에러 - [ ] 사용자 예약 기능 추가 -- [ ] 인기 테마 기능 추가 +- [x] 인기 테마 기능 추가 # API 명세 diff --git a/src/main/java/roomescape/controller/ThemeController.java b/src/main/java/roomescape/controller/ThemeController.java index bc38a24576..dbb0cb2520 100644 --- a/src/main/java/roomescape/controller/ThemeController.java +++ b/src/main/java/roomescape/controller/ThemeController.java @@ -1,6 +1,7 @@ package roomescape.controller; import java.net.URI; +import java.time.LocalDate; import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; @@ -29,6 +31,13 @@ public List<ThemeResponse> findAll() { return themeService.findAll(); } + @GetMapping("/ranking") + public List<ThemeResponse> findAndOrderByPopularity(@RequestParam LocalDate start, + @RequestParam LocalDate end, + @RequestParam int count) { + return themeService.findAndOrderByPopularity(start, end, count); + } + @PostMapping public ResponseEntity<ThemeResponse> save(@RequestBody ThemeRequest themeRequest) { ThemeResponse saved = themeService.save(themeRequest); diff --git a/src/main/java/roomescape/controller/UserController.java b/src/main/java/roomescape/controller/UserController.java index 39d95139d3..86ecc0f8ce 100644 --- a/src/main/java/roomescape/controller/UserController.java +++ b/src/main/java/roomescape/controller/UserController.java @@ -9,4 +9,9 @@ public class UserController { public String reservationPage() { return "reservation"; } + + @GetMapping("/") + public String bestThemePage() { + return "index"; + } } diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index 7d44bd86be..497b82c526 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -1,9 +1,11 @@ package roomescape.repository; import java.sql.PreparedStatement; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; @@ -12,6 +14,14 @@ @Repository public class JdbcTemplateThemeRepository implements ThemeRepository { private final JdbcTemplate jdbcTemplate; + private RowMapper<Theme> themeRowMapper = (rs, rowNum) -> { + long id = rs.getLong("id"); + String name = rs.getString("name"); + String description = rs.getString("description"); + String thumbnail = rs.getString("thumbnail"); + return new Theme(id, name, description, thumbnail); + }; + ; public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -19,24 +29,20 @@ public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { @Override public List<Theme> findAll() { - return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", (rs, rowNum) -> { - long id = rs.getLong(1); - String name = rs.getString(2); - String description = rs.getString(3); - String thumbnail = rs.getString(4); - return new Theme(id, name, description, thumbnail); - }); + return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", themeRowMapper); + } + + @Override + public List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return jdbcTemplate.query( + "select th.*, count(*) as count from theme th join reservation r on r.theme_id = th.id where PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') and PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') group by th.id order by count desc limit ?", + themeRowMapper, start, end, count); } @Override public Optional<Theme> findById(long id) { List<Theme> themes = jdbcTemplate.query("select id, name, description, thumbnail from theme where id = ?", - (rs, rowNum) -> { - String name = rs.getString("name"); - String description = rs.getString("description"); - String thumbnail = rs.getString("thumbnail"); - return new Theme(id, name, description, thumbnail); - }, id); + themeRowMapper, id); return themes.stream().findFirst(); } diff --git a/src/main/java/roomescape/repository/ThemeRepository.java b/src/main/java/roomescape/repository/ThemeRepository.java index a43cb24851..f9cdad4015 100644 --- a/src/main/java/roomescape/repository/ThemeRepository.java +++ b/src/main/java/roomescape/repository/ThemeRepository.java @@ -1,5 +1,6 @@ package roomescape.repository; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import roomescape.domain.Theme; @@ -7,6 +8,8 @@ public interface ThemeRepository { List<Theme> findAll(); + List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count); + Optional<Theme> findById(long id); Theme save(Theme theme); diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index 3fd8f6bab1..f417befdc9 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -1,5 +1,6 @@ package roomescape.service; +import java.time.LocalDate; import java.util.List; import java.util.Objects; import org.springframework.stereotype.Service; @@ -44,6 +45,12 @@ public List<ThemeResponse> findAll() { .toList(); } + public List<ThemeResponse> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return themeRepository.findAndOrderByPopularity(start, end, count).stream() + .map(this::toResponse) + .toList(); + } + public void delete(long id) { //todo : 변수명 고민 boolean invalidDelete = reservationRepository.findAll().stream() diff --git a/src/main/resources/static/js/ranking.js b/src/main/resources/static/js/ranking.js index dee05edf0b..c4b20948b1 100644 --- a/src/main/resources/static/js/ranking.js +++ b/src/main/resources/static/js/ranking.js @@ -1,25 +1,41 @@ document.addEventListener('DOMContentLoaded', () => { - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 - */ - requestRead('/') // 인기 테마 목록 조회 API endpoint - .then(render) - .catch(error => console.error('Error fetching times:', error)); + /* + TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 + */ + const today = new Date(); + let startDate = formatDate(minusDay(today, 7)); + let endDate = formatDate(minusDay(today, 1)); + const count = 10; + const endpoint = `/themes/ranking?start=${startDate}&end=${endDate}&count=${count}`; + requestRead(endpoint) // 인기 테마 목록 조회 API endpoint + .then(render) + .catch(error => console.error('Error fetching times:', error)); }); +function minusDay(date, minusValue) { + return new Date(new Date(date).setDate(date.getDate() - minusValue)); +} + +function formatDate(date) { + const year = date.getFullYear(); + const month = ('0' + (date.getMonth() + 1)).slice(-2); + const day = ('0' + date.getDate()).slice(-2); + return year + '-' + month + '-' + day; +} + function render(data) { - const container = document.getElementById('theme-ranking'); - - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 후 렌더링 - response 명세에 맞춰 name, thumbnail, description 값 설정 - */ - data.forEach(theme => { - const name = ''; - const thumbnail = ''; - const description = ''; - - const htmlContent = ` + const container = document.getElementById('theme-ranking'); + + /* + TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 후 렌더링 + response 명세에 맞춰 name, thumbnail, description 값 설정 + */ + data.forEach(theme => { + const name = theme.name; + const thumbnail = theme.thumbnail; + const description = theme.description; + + const htmlContent = ` <img class="mr-3 img-thumbnail" src="${thumbnail}" alt="${name}"> <div class="media-body"> <h5 class="mt-0 mb-1">${name}</h5> @@ -27,18 +43,18 @@ function render(data) { </div> `; - const div = document.createElement('li'); - div.className = 'media my-4'; - div.innerHTML = htmlContent; + const div = document.createElement('li'); + div.className = 'media my-4'; + div.innerHTML = htmlContent; - container.appendChild(div); - }) + container.appendChild(div); + }) } function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); + return fetch(endpoint) + .then(response => { + if (response.status === 200) return response.json(); + throw new Error('Read failed'); + }); } diff --git a/src/test/java/roomescape/repository/CollectionThemeRepository.java b/src/test/java/roomescape/repository/CollectionThemeRepository.java index a851648ec3..5df15fb0c1 100644 --- a/src/test/java/roomescape/repository/CollectionThemeRepository.java +++ b/src/test/java/roomescape/repository/CollectionThemeRepository.java @@ -1,5 +1,6 @@ package roomescape.repository; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import roomescape.domain.Theme; @@ -10,6 +11,11 @@ public List<Theme> findAll() { return null; } + @Override + public List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return null; + } + @Override public Optional<Theme> findById(long id) { return Optional.of(new Theme(id, "이름", "설명", "썸네일")); From 2789daefd09acbdcf85c0aa568850f3de0279b9f Mon Sep 17 00:00:00 2001 From: zangsu <zangsu_@naver.com> Date: Thu, 2 May 2024 15:27:43 +0900 Subject: [PATCH 49/75] =?UTF-8?q?refactor:=20ExceptionType=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 16 +++++------ .../roomescape/domain/ReservationTime.java | 4 +-- src/main/java/roomescape/domain/Theme.java | 11 +++++--- .../roomescape/exception/ExceptionType.java | 27 +++++++++---------- .../service/ReservationService.java | 12 ++++----- .../service/ReservationTimeService.java | 4 +-- .../java/roomescape/service/ThemeService.java | 8 +++--- .../roomescape/domain/ReservationTest.java | 16 +++++------ .../domain/ReservationTimeTest.java | 4 +-- .../java/roomescape/domain/ThemeTest.java | 12 ++++----- .../service/ReservationServiceTest.java | 16 +++++------ .../service/ReservationTimeServiceTest.java | 4 +-- .../roomescape/service/ThemeServiceTest.java | 4 +-- 13 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 6ac260905d..9e0abaaf20 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,9 +1,9 @@ package roomescape.domain; -import static roomescape.exception.ExceptionType.DATE_EMPTY; -import static roomescape.exception.ExceptionType.NAME_EMPTY; -import static roomescape.exception.ExceptionType.THEME_EMPTY; -import static roomescape.exception.ExceptionType.TIME_EMPTY; +import static roomescape.exception.ExceptionType.EMPTY_DATE; +import static roomescape.exception.ExceptionType.EMPTY_NAME; +import static roomescape.exception.ExceptionType.EMPTY_THEME; +import static roomescape.exception.ExceptionType.EMPTY_TIME; import java.time.LocalDate; import java.time.LocalDateTime; @@ -36,25 +36,25 @@ public Reservation(Long id, String name, LocalDate date, ReservationTime time, T private void validateTheme(Theme theme) { if (theme == null) { - throw new RoomescapeException(THEME_EMPTY); + throw new RoomescapeException(EMPTY_THEME); } } private void validateTime(ReservationTime time) { if (time == null) { - throw new RoomescapeException(TIME_EMPTY); + throw new RoomescapeException(EMPTY_TIME); } } private void validateDate(LocalDate date) { if (date == null) { - throw new RoomescapeException(DATE_EMPTY); + throw new RoomescapeException(EMPTY_DATE); } } private void validateName(String name) { if (name == null || name.isBlank()) { - throw new RoomescapeException(NAME_EMPTY); + throw new RoomescapeException(EMPTY_NAME); } } diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index bfd1bd8763..50f22a934e 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -1,6 +1,6 @@ package roomescape.domain; -import static roomescape.exception.ExceptionType.TIME_EMPTY; +import static roomescape.exception.ExceptionType.EMPTY_TIME; import java.time.LocalTime; import java.util.Objects; @@ -16,7 +16,7 @@ public ReservationTime(LocalTime startAt) { public ReservationTime(Long id, LocalTime startAt) { if (startAt == null) { - throw new RoomescapeException(TIME_EMPTY); + throw new RoomescapeException(EMPTY_TIME); } this.id = id; this.startAt = startAt; diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index cc7ada5a3a..82483a5a3f 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -1,7 +1,10 @@ package roomescape.domain; +import static roomescape.exception.ExceptionType.EMPTY_DESCRIPTION; +import static roomescape.exception.ExceptionType.EMPTY_NAME; +import static roomescape.exception.ExceptionType.EMPTY_THUMBNAIL; + import java.util.Objects; -import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; public class Theme { @@ -27,19 +30,19 @@ public Theme(Long id, String name, String description, String thumbnail) { private void validateName(String name) { if (name == null || name.isBlank()) { //TODO : NAME_EMPYT 재사용 고민 - throw new RoomescapeException(ExceptionType.NAME_EMPTY); + throw new RoomescapeException(EMPTY_NAME); } } private void validateDescription(String description) { if (description == null || description.isBlank()) { - throw new RoomescapeException(ExceptionType.DESCRIPTION_EMPTY); + throw new RoomescapeException(EMPTY_DESCRIPTION); } } private void validateThumbnail(String thumbnail) { if (thumbnail == null || thumbnail.isBlank()) { - throw new RoomescapeException(ExceptionType.THUMBNAIL_EMPTY); + throw new RoomescapeException(EMPTY_THUMBNAIL); } } diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index 7725238da8..2ef6842495 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -4,26 +4,23 @@ import org.springframework.http.HttpStatus; -//Todo 상태코드 고민해보기 +//Todo 상태코드 고민해보기 -> 4~6 단계 인증 인가가 들어갈 때를 대비 public enum ExceptionType { - - NAME_EMPTY(BAD_REQUEST, "이름은 필수 값입니다."), - TIME_EMPTY(BAD_REQUEST, "시작 시간은 필수 값입니다."), - DATE_EMPTY(BAD_REQUEST, "날짜는 필수값 입니다."), - THEME_EMPTY(BAD_REQUEST, "테마는 필수값 입니다."), - DESCRIPTION_EMPTY(BAD_REQUEST, "테마 설명은 필수값 입니다."), - THUMBNAIL_EMPTY(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), - PAST_TIME(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), + EMPTY_NAME(BAD_REQUEST, "이름은 필수 값입니다."), + EMPTY_TIME(BAD_REQUEST, "시작 시간은 필수 값입니다."), + EMPTY_DATE(BAD_REQUEST, "날짜는 필수값 입니다."), + EMPTY_THEME(BAD_REQUEST, "테마는 필수값 입니다."), + EMPTY_DESCRIPTION(BAD_REQUEST, "테마 설명은 필수값 입니다."), + EMPTY_THUMBNAIL(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), + PAST_TIME_RESERVATION(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), DUPLICATE_RESERVATION(BAD_REQUEST, "같은 시간에 이미 예약이 존재합니다."), DUPLICATE_RESERVATION_TIME(BAD_REQUEST, "이미 예약시간이 존재합니다."), DUPLICATE_THEME(BAD_REQUEST, "이미 동일한 테마가 존재합니다."), INVALID_DATE_TIME_FORMAT(BAD_REQUEST, "해석할 수 없는 날짜, 시간 포맷입니다."), - //Todo 이름 변경, - INVALID_DELETE_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), - INVALID_DELETE_THEME(BAD_REQUEST, "예약이 존재하는 테마는 삭제할 수 없습니다."), - RESERVATION_TIME_NOT_FOUND(BAD_REQUEST, "존재하지 않는 시간입니다."), - //todo 이름 변경, - THEME_NOT_FOUND(BAD_REQUEST, "없는 테마입니다."), + DELETE_USED_TIME(BAD_REQUEST, "예약이 존재하는 시간은 삭제할 수 없습니다."), + DELETE_USED_THEME(BAD_REQUEST, "예약이 존재하는 테마는 삭제할 수 없습니다."), + NOT_FOUND_RESERVATION_TIME(BAD_REQUEST, "존재하지 않는 시간입니다."), + NOT_FOUND_THEME(BAD_REQUEST, "없는 테마입니다."), ; private final HttpStatus status; diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index a396df0645..12c392135b 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -1,9 +1,9 @@ package roomescape.service; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; -import static roomescape.exception.ExceptionType.PAST_TIME; -import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; -import static roomescape.exception.ExceptionType.THEME_NOT_FOUND; +import static roomescape.exception.ExceptionType.NOT_FOUND_RESERVATION_TIME; +import static roomescape.exception.ExceptionType.NOT_FOUND_THEME; +import static roomescape.exception.ExceptionType.PAST_TIME_RESERVATION; import java.time.LocalDateTime; import java.util.List; @@ -36,9 +36,9 @@ public ReservationService(ReservationRepository reservationRepository, public ReservationResponse save(ReservationRequest reservationRequest) { ReservationTime requestedTime = reservationTimeRepository.findById(reservationRequest.timeId()) - .orElseThrow(() -> new RoomescapeException(RESERVATION_TIME_NOT_FOUND)); + .orElseThrow(() -> new RoomescapeException(NOT_FOUND_RESERVATION_TIME)); Theme requestedTheme = themeRepository.findById(reservationRequest.themeId()) - .orElseThrow(() -> new RoomescapeException(THEME_NOT_FOUND)); + .orElseThrow(() -> new RoomescapeException(NOT_FOUND_THEME)); Reservation beforeSave = new Reservation( reservationRequest.name(), @@ -54,7 +54,7 @@ public ReservationResponse save(ReservationRequest reservationRequest) { } if (isBefore(beforeSave)) { - throw new RoomescapeException(PAST_TIME); + throw new RoomescapeException(PAST_TIME_RESERVATION); } Reservation saved = reservationRepository.save(beforeSave); diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 74db95e3a9..31584562a7 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -1,7 +1,7 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.DELETE_USED_TIME; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; -import static roomescape.exception.ExceptionType.INVALID_DELETE_TIME; import java.util.List; import org.springframework.stereotype.Service; @@ -51,7 +51,7 @@ public void delete(long id) { boolean invalidDelete = reservations.stream() .anyMatch(reservation -> reservation.isReservationTimeOf(id)); if (invalidDelete) { - throw new RoomescapeException(INVALID_DELETE_TIME); + throw new RoomescapeException(DELETE_USED_TIME); } reservationTimeRepository.delete(id); } diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index d06634420a..9f355f8b71 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -1,12 +1,14 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.DELETE_USED_THEME; +import static roomescape.exception.ExceptionType.DUPLICATE_THEME; + import java.time.LocalDate; import java.util.List; import org.springframework.stereotype.Service; import roomescape.domain.Theme; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; -import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationRepository; import roomescape.repository.ThemeRepository; @@ -26,7 +28,7 @@ public ThemeResponse save(ThemeRequest themeRequest) { boolean hasDuplicateTheme = themeRepository.findAll().stream() .anyMatch(theme -> theme.isNameOf(themeRequest.name())); if (hasDuplicateTheme) { - throw new RoomescapeException(ExceptionType.DUPLICATE_THEME); + throw new RoomescapeException(DUPLICATE_THEME); } Theme saved = themeRepository.save( new Theme(themeRequest.name(), themeRequest.description(), themeRequest.thumbnail())); @@ -54,7 +56,7 @@ public void delete(long id) { boolean invalidDelete = reservationRepository.findAll().stream() .anyMatch(reservation -> reservation.isThemeOf(id)); if (invalidDelete) { - throw new RoomescapeException(ExceptionType.INVALID_DELETE_THEME); + throw new RoomescapeException(DELETE_USED_THEME); } themeRepository.delete(id); } diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index 17b021a41d..6c20ebe0a0 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -2,10 +2,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import static roomescape.exception.ExceptionType.DATE_EMPTY; -import static roomescape.exception.ExceptionType.NAME_EMPTY; -import static roomescape.exception.ExceptionType.THEME_EMPTY; -import static roomescape.exception.ExceptionType.TIME_EMPTY; +import static roomescape.exception.ExceptionType.EMPTY_DATE; +import static roomescape.exception.ExceptionType.EMPTY_NAME; +import static roomescape.exception.ExceptionType.EMPTY_THEME; +import static roomescape.exception.ExceptionType.EMPTY_TIME; import java.time.LocalDate; import java.time.LocalTime; @@ -26,19 +26,19 @@ void constructTest() { assertAll( () -> assertThatThrownBy(() -> new Reservation(null, DEFAULT_DATE, DEFAULT_TIME, DEFAULT_THEME)) .isInstanceOf(RoomescapeException.class) - .hasMessage(NAME_EMPTY.getMessage()), + .hasMessage(EMPTY_NAME.getMessage()), () -> assertThatThrownBy(() -> new Reservation("name", null, DEFAULT_TIME, DEFAULT_THEME)) .isInstanceOf(RoomescapeException.class) - .hasMessage(DATE_EMPTY.getMessage()), + .hasMessage(EMPTY_DATE.getMessage()), () -> assertThatThrownBy(() -> new Reservation("name", DEFAULT_DATE, null, DEFAULT_THEME)) .isInstanceOf(RoomescapeException.class) - .hasMessage(TIME_EMPTY.getMessage()), + .hasMessage(EMPTY_TIME.getMessage()), () -> assertThatThrownBy(() -> new Reservation("name", DEFAULT_DATE, DEFAULT_TIME, null)) .isInstanceOf(RoomescapeException.class) - .hasMessage(THEME_EMPTY.getMessage()) + .hasMessage(EMPTY_THEME.getMessage()) ); } diff --git a/src/test/java/roomescape/domain/ReservationTimeTest.java b/src/test/java/roomescape/domain/ReservationTimeTest.java index e822f5646b..afc563831c 100644 --- a/src/test/java/roomescape/domain/ReservationTimeTest.java +++ b/src/test/java/roomescape/domain/ReservationTimeTest.java @@ -3,11 +3,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import static roomescape.exception.ExceptionType.EMPTY_TIME; import java.time.LocalTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import roomescape.exception.ExceptionType; import roomescape.exception.RoomescapeException; class ReservationTimeTest { @@ -17,7 +17,7 @@ class ReservationTimeTest { void startAtMustBeNotNull() { assertThatThrownBy(() -> new ReservationTime(null)) .isInstanceOf(RoomescapeException.class) - .hasMessage(ExceptionType.TIME_EMPTY.getMessage()); + .hasMessage(EMPTY_TIME.getMessage()); } @DisplayName("ReservationTime 은 id 값으로만 동등성을 비교한다.") diff --git a/src/test/java/roomescape/domain/ThemeTest.java b/src/test/java/roomescape/domain/ThemeTest.java index 9d63424f52..a5b3e088d4 100644 --- a/src/test/java/roomescape/domain/ThemeTest.java +++ b/src/test/java/roomescape/domain/ThemeTest.java @@ -4,9 +4,9 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import static roomescape.exception.ExceptionType.DESCRIPTION_EMPTY; -import static roomescape.exception.ExceptionType.NAME_EMPTY; -import static roomescape.exception.ExceptionType.THUMBNAIL_EMPTY; +import static roomescape.exception.ExceptionType.EMPTY_DESCRIPTION; +import static roomescape.exception.ExceptionType.EMPTY_NAME; +import static roomescape.exception.ExceptionType.EMPTY_THUMBNAIL; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,15 +20,15 @@ void constructTest() { assertAll( () -> assertThatThrownBy(() -> new Theme(null, "description", "thumbnail")) .isInstanceOf(RoomescapeException.class) - .hasMessage(NAME_EMPTY.getMessage()), + .hasMessage(EMPTY_NAME.getMessage()), () -> assertThatThrownBy(() -> new Theme("name", null, "thumbnail")) .isInstanceOf(RoomescapeException.class) - .hasMessage(DESCRIPTION_EMPTY.getMessage()), + .hasMessage(EMPTY_DESCRIPTION.getMessage()), () -> assertThatThrownBy(() -> new Theme("name", "description", null)) .isInstanceOf(RoomescapeException.class) - .hasMessage(THUMBNAIL_EMPTY.getMessage()), + .hasMessage(EMPTY_THUMBNAIL.getMessage()), () -> assertThatCode(() -> new Theme("name", "description", "thumbnail")) .doesNotThrowAnyException(), diff --git a/src/test/java/roomescape/service/ReservationServiceTest.java b/src/test/java/roomescape/service/ReservationServiceTest.java index 1bf6505187..095bf220bf 100644 --- a/src/test/java/roomescape/service/ReservationServiceTest.java +++ b/src/test/java/roomescape/service/ReservationServiceTest.java @@ -5,10 +5,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION; -import static roomescape.exception.ExceptionType.NAME_EMPTY; -import static roomescape.exception.ExceptionType.PAST_TIME; -import static roomescape.exception.ExceptionType.RESERVATION_TIME_NOT_FOUND; -import static roomescape.exception.ExceptionType.THEME_NOT_FOUND; +import static roomescape.exception.ExceptionType.EMPTY_NAME; +import static roomescape.exception.ExceptionType.NOT_FOUND_RESERVATION_TIME; +import static roomescape.exception.ExceptionType.NOT_FOUND_THEME; +import static roomescape.exception.ExceptionType.PAST_TIME_RESERVATION; import java.time.LocalDate; import java.time.LocalTime; @@ -77,7 +77,7 @@ void createPastReservationFailTest() { defaultTheme.getId() ))) .isInstanceOf(RoomescapeException.class) - .hasMessage(PAST_TIME.getMessage()); + .hasMessage(PAST_TIME_RESERVATION.getMessage()); } @DisplayName("존재하지 않는 시간에 대해 예약을 생성하면 예외가 발생한다.") @@ -90,7 +90,7 @@ void createReservationWithTimeNotExistsTest() { defaultTheme.getId() ))) .isInstanceOf(RoomescapeException.class) - .hasMessage(RESERVATION_TIME_NOT_FOUND.getMessage()); + .hasMessage(NOT_FOUND_RESERVATION_TIME.getMessage()); } @DisplayName("존재하지 않는 테마에 대해 예약을 생성하면 예외가 발생한다.") @@ -103,7 +103,7 @@ void createReservationWithThemeNotExistsTest() { 2L ))) .isInstanceOf(RoomescapeException.class) - .hasMessage(THEME_NOT_FOUND.getMessage()); + .hasMessage(NOT_FOUND_THEME.getMessage()); } @DisplayName("필수값이 입력되지 않은 예약 생성 요청에 대해 예외가 발생한다.") @@ -117,7 +117,7 @@ void emptyRequiredValueTest() { defaultTime.getId(), defaultTheme.getId() ))).isInstanceOf(RoomescapeException.class) - .hasMessage(NAME_EMPTY.getMessage()) + .hasMessage(EMPTY_NAME.getMessage()) ); } diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index cac7188ab2..53ac9e7fa5 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static roomescape.exception.ExceptionType.DELETE_USED_TIME; import static roomescape.exception.ExceptionType.DUPLICATE_RESERVATION_TIME; -import static roomescape.exception.ExceptionType.INVALID_DELETE_TIME; import java.time.LocalDate; import java.time.LocalTime; @@ -87,7 +87,7 @@ void usedReservationTimeDeleteTest() { //when & then assertThatCode(() -> reservationTimeService.delete(1L)) .isInstanceOf(RoomescapeException.class) - .hasMessage(INVALID_DELETE_TIME.getMessage()); + .hasMessage(DELETE_USED_TIME.getMessage()); } } diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java index 04220a1b6e..241d341168 100644 --- a/src/test/java/roomescape/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static roomescape.exception.ExceptionType.DELETE_USED_THEME; import static roomescape.exception.ExceptionType.DUPLICATE_THEME; -import static roomescape.exception.ExceptionType.INVALID_DELETE_THEME; import java.time.LocalDate; import java.time.LocalTime; @@ -88,7 +88,7 @@ void removeFailTest() { //when & then assertThatThrownBy(() -> themeService.delete(1L)) .isInstanceOf(RoomescapeException.class) - .hasMessage(INVALID_DELETE_THEME.getMessage()); + .hasMessage(DELETE_USED_THEME.getMessage()); } @DisplayName("존재하지 않는 테마 id로 삭제하더라도 오류로 간주하지 않는다.") From 6ec052d45f0d9553c8a2e99f1262d5e7e4f818ca Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 15:41:15 +0900 Subject: [PATCH 50/75] =?UTF-8?q?refactor:=20ResultSet=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcTemplateReservationRepository.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index 213d7ef6f4..33250ec189 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -2,10 +2,10 @@ import java.sql.Date; import java.sql.PreparedStatement; -import java.time.LocalDate; import java.time.LocalTime; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; @@ -59,8 +59,8 @@ public List<Reservation> findAll() { String query = """ SELECT r.id as reservation_id, - r.name, - r.date, + r.name as reservation_name, + r.date as reservation_date, t.id as time_id, t.start_at as time_value, t2.id as theme_id, @@ -72,21 +72,22 @@ public List<Reservation> findAll() { on r.time_id = t.id inner join theme t2 on t2.id = r.theme_id"""; - return jdbcTemplate.query(query, - (rs, rowNum) -> { - long id = rs.getLong(1); - String name = rs.getString(2); - LocalDate date = rs.getDate(3).toLocalDate(); - long timeId = rs.getLong(4); - LocalTime startAt = rs.getTime(5).toLocalTime(); - ReservationTime reservationTime = new ReservationTime(timeId, startAt); - long themeId = rs.getLong("theme_id"); - String themeName = rs.getString("theme_name"); - String description = rs.getString("description"); - String thumbnail = rs.getString("thumbnail"); - Theme theme = new Theme(themeId, themeName, description, thumbnail); - return new Reservation(id, name, date, reservationTime, theme); - }); + RowMapper<Reservation> reservationRowMapper = (rs, rowNum) -> new Reservation( + rs.getLong("reservation_id"), + rs.getString("reservation_name"), + rs.getDate("reservation_date").toLocalDate(), + new ReservationTime( + rs.getLong("time_id"), + rs.getTime("time_value").toLocalTime() + ), + new Theme( + rs.getLong("theme_id"), + rs.getString("theme_name"), + rs.getString("description"), + rs.getString("thumbnail") + ) + ); + return jdbcTemplate.query(query, reservationRowMapper); } @Override From ad587b545771c51973c6168831e117e3330632f4 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:03:11 +0900 Subject: [PATCH 51/75] =?UTF-8?q?docs:=20=ED=95=B4=EA=B2=B0=ED=95=9C=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=20=EA=B0=80=EC=9D=B4=EB=93=9C=20Todo=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/js/ranking.js | 7 ------- src/main/resources/static/js/reservation-new.js | 4 ---- src/main/resources/static/js/user-reservation.js | 16 +--------------- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/main/resources/static/js/ranking.js b/src/main/resources/static/js/ranking.js index c4b20948b1..ba5dda7a9a 100644 --- a/src/main/resources/static/js/ranking.js +++ b/src/main/resources/static/js/ranking.js @@ -1,7 +1,4 @@ document.addEventListener('DOMContentLoaded', () => { - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 - */ const today = new Date(); let startDate = formatDate(minusDay(today, 7)); let endDate = formatDate(minusDay(today, 1)); @@ -26,10 +23,6 @@ function formatDate(date) { function render(data) { const container = document.getElementById('theme-ranking'); - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 후 렌더링 - response 명세에 맞춰 name, thumbnail, description 값 설정 - */ data.forEach(theme => { const name = theme.name; const thumbnail = theme.thumbnail; diff --git a/src/main/resources/static/js/reservation-new.js b/src/main/resources/static/js/reservation-new.js index 2d8e132cce..fd87452bf6 100644 --- a/src/main/resources/static/js/reservation-new.js +++ b/src/main/resources/static/js/reservation-new.js @@ -23,10 +23,6 @@ function render(data) { data.forEach(item => { const row = tableBody.insertRow(); - /* - TODO: [2단계] 관리자 기능 - 예약 목록 조회 API 호출 후 렌더링 - response 명세에 맞춰 값 설정 - */ console.log(item); row.insertCell(0).textContent = item.id; // 예약 id row.insertCell(1).textContent = item.name; // 예약자명 diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js index a0bcaed178..12a26c866a 100644 --- a/src/main/resources/static/js/user-reservation.js +++ b/src/main/resources/static/js/user-reservation.js @@ -38,11 +38,6 @@ function renderTheme(themes) { themes.forEach(theme => { const name = theme.name; const themeId = theme.id; - /* - TODO: [3단계] 사용자 예약 - 테마 목록 조회 API 호출 후 렌더링 - response 명세에 맞춰 createSlot 함수 호출 시 값 설정 - createSlot('theme', theme name, theme id) 형태로 호출 - */ themeSlots.appendChild(createSlot('theme', name, themeId)); }); } @@ -87,10 +82,6 @@ function checkDateAndTheme() { } function fetchAvailableTimes(date, themeId) { - /* - TODO: [3단계] 사용자 예약 - 예약 가능 시간 조회 API 호출 - 요청 포맷에 맞게 설정 - */ fetch(`/availableTimes?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint method: 'GET', headers: { @@ -116,10 +107,6 @@ function renderAvailableTimes(times) { return; } times.forEach(time => { - /* - TODO: [3단계] 사용자 예약 - 예약 가능 시간 조회 API 호출 후 렌더링 - response 명세에 맞춰 createSlot 함수 호출 시 값 설정 - */ const startAt = time.startAt; const timeId = time.id; const alreadyBooked = time.isBooked; @@ -158,8 +145,7 @@ function onReservationButtonClick() { if (selectedDate && selectedThemeId && selectedTimeId) { /* - TODO: [3단계] 사용자 예약 - 예약 요청 API 호출 - [5단계] 예약 생성 기능 변경 - 사용자 + TODO: [5단계] 예약 생성 기능 변경 - 사용자 request 명세에 맞게 설정 */ const reservationData = { From ddf88b1a7bad4fb1031e206a1488c84c36d430e6 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:05:01 +0900 Subject: [PATCH 52/75] =?UTF-8?q?docs:=20"TODO=20:=20NAME=5FEMPYT=20?= =?UTF-8?q?=EC=9E=AC=EC=82=AC=EC=9A=A9=20=EA=B3=A0=EB=AF=BC"=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이름이 비어있지 않아야 한다는 조건은 모든 도메인이 공통적으로 가질 가능성이 그렇지 않을 가능성보다 훨씬 높다고 판단함. --- src/main/java/roomescape/domain/Theme.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 82483a5a3f..7694a2a092 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -29,7 +29,6 @@ public Theme(Long id, String name, String description, String thumbnail) { private void validateName(String name) { if (name == null || name.isBlank()) { - //TODO : NAME_EMPYT 재사용 고민 throw new RoomescapeException(EMPTY_NAME); } } From e36c4974347c35728581d5350e822e39eec612eb Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:08:18 +0900 Subject: [PATCH 53/75] =?UTF-8?q?docs:=20"//todo=20:=20long=20=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD"=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 식별자가 null 인 경우 이 getId 메서드를 호출할 때 예외가 발생하는 것이 타당하다고 판단. --- src/main/java/roomescape/domain/ReservationTime.java | 8 ++++---- src/main/java/roomescape/domain/Theme.java | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 50f22a934e..7ca9dd0b5f 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -26,7 +26,7 @@ public boolean isIdOf(long id) { return this.id == id; } - public Long getId() { + public long getId() { return id; } @@ -58,8 +58,8 @@ public boolean equals(Object o) { @Override public String toString() { return "ReservationTime{" + - "id=" + id + - ", startAt=" + startAt + - '}'; + "id=" + id + + ", startAt=" + startAt + + '}'; } } diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 7694a2a092..83f72ce50b 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -57,8 +57,7 @@ public boolean isNameOf(String name) { return this.name.equals(name); } - //todo : long 으로 변경 - public Long getId() { + public long getId() { return id; } From 3961bb6e3209b8a39e2a8b0dfd33001845a692aa Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:09:47 +0900 Subject: [PATCH 54/75] =?UTF-8?q?docs:=20"//todo=20:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EA=B3=A0=EB=AF=BC"=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 변수명을 변경하는 것 대신 메서드를 분리하는 것이 더 가독성이 향상된다고 판단 --- .../roomescape/service/ReservationTimeService.java | 10 ++++++---- src/main/java/roomescape/service/ThemeService.java | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 31584562a7..697bd24a99 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -47,12 +47,14 @@ public List<ReservationTimeResponse> findAll() { public void delete(long id) { //todo SQL로 구현 List<Reservation> reservations = reservationRepository.findAll(); - //TODO : 지역변수 네이밍 고민 - boolean invalidDelete = reservations.stream() - .anyMatch(reservation -> reservation.isReservationTimeOf(id)); - if (invalidDelete) { + if (isUsedTime(id, reservations)) { throw new RoomescapeException(DELETE_USED_TIME); } reservationTimeRepository.delete(id); } + + private static boolean isUsedTime(long id, List<Reservation> reservations) { + return reservations.stream() + .anyMatch(reservation -> reservation.isReservationTimeOf(id)); + } } diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index 9f355f8b71..d15bfff70a 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -52,12 +52,14 @@ public List<ThemeResponse> findAndOrderByPopularity(LocalDate start, LocalDate e } public void delete(long id) { - //todo : 변수명 고민 - boolean invalidDelete = reservationRepository.findAll().stream() - .anyMatch(reservation -> reservation.isThemeOf(id)); - if (invalidDelete) { + if (isUsedTheme(id)) { throw new RoomescapeException(DELETE_USED_THEME); } themeRepository.delete(id); } + + private boolean isUsedTheme(long id) { + return reservationRepository.findAll().stream() + .anyMatch(reservation -> reservation.isThemeOf(id)); + } } From 1378a922285ffef5b20ca8a55b2c12a1c40e7255 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:10:10 +0900 Subject: [PATCH 55/75] =?UTF-8?q?docs:=20"//TODO=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1"=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 작성함. --- src/main/java/roomescape/service/ReservationTimeService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 697bd24a99..893ea7eac6 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -43,7 +43,6 @@ public List<ReservationTimeResponse> findAll() { .toList(); } - //TODO : 테스트 생성 public void delete(long id) { //todo SQL로 구현 List<Reservation> reservations = reservationRepository.findAll(); From 093f68b69640d5fb3910dad79dfcc08296cad4eb Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:13:07 +0900 Subject: [PATCH 56/75] =?UTF-8?q?docs:=20"//Todo=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1"=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Controller 는 서비스의 코드를 그대로 호출하므로 테스트 작성이 필요하지 않다고 판단 - 단, 기존의 잘 동작하는 테스트 코드를 삭제하지는 않음 --- src/main/java/roomescape/controller/ThemeController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/controller/ThemeController.java b/src/main/java/roomescape/controller/ThemeController.java index dbb0cb2520..73c765d680 100644 --- a/src/main/java/roomescape/controller/ThemeController.java +++ b/src/main/java/roomescape/controller/ThemeController.java @@ -16,7 +16,6 @@ import roomescape.dto.ThemeResponse; import roomescape.service.ThemeService; -//Todo 테스트코드 작성 @RestController @RequestMapping("/themes") public class ThemeController { From 1024c7e57e43ddaaf952ffac521002beafc6ce23 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:15:34 +0900 Subject: [PATCH 57/75] =?UTF-8?q?docs:=20"todo=20[=EB=A1=9C=EB=B9=88]=20?= =?UTF-8?q?=EC=9D=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8B=A4=20=EB=B3=B4=EB=8B=88=20Response=20d?= =?UTF-8?q?to=20=EC=97=90=EB=8F=84=20NotNull=20=EA=B2=80=EC=A6=9D=EC=9D=80?= =?UTF-8?q?=20=ED=95=84=EC=9A=94=ED=95=A0=20=EA=B2=83=20=EA=B0=99=EB=8B=A4?= =?UTF-8?q?=EB=8A=94=20=EC=83=9D=EA=B0=81=EC=9D=B4=20=EB=93=AD=EB=8B=88?= =?UTF-8?q?=EB=8B=A4."=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 각자 추구하는 방향이 달라 페어 프로그래밍이 끝난 후 각자 개선하는 것으로 합의 --- .../service/ReservationServiceTest.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/test/java/roomescape/service/ReservationServiceTest.java b/src/test/java/roomescape/service/ReservationServiceTest.java index 095bf220bf..b28d9bced8 100644 --- a/src/test/java/roomescape/service/ReservationServiceTest.java +++ b/src/test/java/roomescape/service/ReservationServiceTest.java @@ -109,7 +109,6 @@ void createReservationWithThemeNotExistsTest() { @DisplayName("필수값이 입력되지 않은 예약 생성 요청에 대해 예외가 발생한다.") @Test void emptyRequiredValueTest() { - //todo [로빈] 이 테스트를 작성하다 보니 Response dto 에도 NotNull 검증은 필요할 것 같다는 생각이 듭니다. assertAll( () -> assertThatThrownBy(() -> reservationService.save(new ReservationRequest( LocalDate.now().minusDays(1), @@ -121,6 +120,22 @@ void emptyRequiredValueTest() { ); } + @DisplayName("예약이 여러 개 존재하는 경우 모든 예약을 조회할 수 있다.") + @Test + void findAllTest() { + //given + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(1), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(2), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(3), defaultTime, defaultTheme)); + reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(4), defaultTime, defaultTheme)); + + //when + List<ReservationResponse> reservationResponses = reservationService.findAll(); + + //then + assertThat(reservationResponses).hasSize(4); + } + @DisplayName("예약이 하나 존재하는 경우") @Nested class OneReservationExistsTest { @@ -160,20 +175,4 @@ void deleteNotExistReservationNotThrowsException() { .doesNotThrowAnyException(); } } - - @DisplayName("예약이 여러 개 존재하는 경우 모든 예약을 조회할 수 있다.") - @Test - void findAllTest() { - //given - reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(1), defaultTime, defaultTheme)); - reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(2), defaultTime, defaultTheme)); - reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(3), defaultTime, defaultTheme)); - reservationRepository.save(new Reservation("name1", LocalDate.now().plusDays(4), defaultTime, defaultTheme)); - - //when - List<ReservationResponse> reservationResponses = reservationService.findAll(); - - //then - assertThat(reservationResponses).hasSize(4); - } } From 258466cd2c6bb13d6be2e38f7a558689ff6bdadb Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:17:41 +0900 Subject: [PATCH 58/75] =?UTF-8?q?docs:=20"TODO=20:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=97=90=EA=B2=8C=20=EB=84=98=EA=B8=B8=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EC=9D=84=20=EA=B2=83=20=EA=B0=99=EC=9D=80=EB=8D=B0"?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 도메인으로 이동할 수 있는 책임을 이동 --- src/main/java/roomescape/domain/Reservation.java | 8 ++++++-- src/main/java/roomescape/service/ReservationService.java | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 9e0abaaf20..97f602d14f 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -70,8 +70,12 @@ public int compareTo(Reservation other) { return dateTime.compareTo(otherDateTime); } - public boolean hasSameId(long id) { - return this.id == id; + public boolean isBefore(LocalDateTime base) { + return LocalDateTime.of(date, getTime()).isBefore(base); + } + + public LocalTime getTime() { + return time.getStartAt(); } public boolean isReservationTimeOf(long id) { diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 12c392135b..7b030e71ff 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -53,7 +53,7 @@ public ReservationResponse save(ReservationRequest reservationRequest) { throw new RoomescapeException(DUPLICATE_RESERVATION); } - if (isBefore(beforeSave)) { + if (beforeSave.isBefore(LocalDateTime.now())) { throw new RoomescapeException(PAST_TIME_RESERVATION); } @@ -66,12 +66,6 @@ private boolean validateDuplicateReservation(Reservation beforeSave, Reservation && beforeSave.isSameTheme(reservation); } - //TODO : 도메인에게 넘길 수 있을 것 같은데 - private static boolean isBefore(Reservation beforeSave) { - return LocalDateTime.of(beforeSave.getDate(), beforeSave.getTime()) - .isBefore(LocalDateTime.now()); - } - private ReservationResponse toResponse(Reservation reservation) { ReservationTime reservationTime = reservation.getReservationTime(); ReservationTimeResponse reservationTimeResponse = new ReservationTimeResponse(reservationTime.getId(), From 8c4efbc7f217dc1efeaecba8452415a8213dfc36 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:18:43 +0900 Subject: [PATCH 59/75] =?UTF-8?q?docs:=20"Todo=20:=20[=EB=A1=9C=EB=B9=88]?= =?UTF-8?q?=20Response=20=EA=B0=80=20=EB=B3=80=ED=99=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EA=B0=80=EC=A7=80=EA=B3=A0=20=EC=9E=88?= =?UTF-8?q?=EC=9C=BC=EB=A9=B4=20=EC=95=84=EB=9E=98=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=8F=84=20=EA=B0=84=EB=8B=A8=ED=95=B4=20=EC=A7=88=20=EA=B2=83?= =?UTF-8?q?=20=EA=B0=99=EC=9D=8C"=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 각자 추구하는 방향이 달라 페어 프로그래밍이 끝난 후 각자 개선하는 것으로 합의 --- .../java/roomescape/controller/ReservationControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index 5522db8150..277eedaba8 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -59,7 +59,6 @@ void saveReservation() { //then ReservationResponse expected = new ReservationResponse(id, "폴라", date, new ReservationTimeResponse(TIME_ID, TIME), - //Todo : [로빈] Response 가 변환 로직을 가지고 있으면 아래 코드도 간단해 질 것 같음 new ThemeResponse(defualtTheme.getId(), defualtTheme.getName(), defualtTheme.getDescription(), defualtTheme.getThumbnail())); From c7c503332b1e1e6f9382e492e7e4dc9460ffbb72 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:22:48 +0900 Subject: [PATCH 60/75] =?UTF-8?q?refactor=20:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=B1=85=EC=9E=84=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 97f602d14f..0c8a38037f 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -78,6 +78,10 @@ public LocalTime getTime() { return time.getStartAt(); } + public boolean hasSameId(long id) { + return this.id == id; + } + public boolean isReservationTimeOf(long id) { return this.time.isIdOf(id); } @@ -111,10 +115,6 @@ public LocalDate getDate() { return date; } - public LocalTime getTime() { - return time.getStartAt(); - } - public ReservationTime getReservationTime() { return time; } @@ -149,10 +149,10 @@ public boolean equals(Object o) { @Override public String toString() { return "Reservation{" + - "id=" + id + - ", name='" + name + '\'' + - ", date=" + date + - ", time=" + time + - '}'; + "id=" + id + + ", name='" + name + '\'' + + ", date=" + date + + ", time=" + time + + '}'; } } From 03918034014fd76503b1007a601ce0c86e44977d Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:23:56 +0900 Subject: [PATCH 61/75] =?UTF-8?q?docs:=20"Todo=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B3=A0=EB=AF=BC=ED=95=B4=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 4~6 단계 인증 인가가 들어갈 때 고민이 해소 될 것이라 판단 --- src/main/java/roomescape/exception/ExceptionType.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index 2ef6842495..e0d16e572c 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -4,7 +4,6 @@ import org.springframework.http.HttpStatus; -//Todo 상태코드 고민해보기 -> 4~6 단계 인증 인가가 들어갈 때를 대비 public enum ExceptionType { EMPTY_NAME(BAD_REQUEST, "이름은 필수 값입니다."), EMPTY_TIME(BAD_REQUEST, "시작 시간은 필수 값입니다."), From c7f2dad7ccbef7757328db8ca547c18f78920947 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:28:30 +0900 Subject: [PATCH 62/75] =?UTF-8?q?refactor:=20getter=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CollectionReservationTimeRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index cc074089a3..a22896c61f 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -52,7 +52,7 @@ public List<ReservationTime> findAll() { @Override public void delete(long id) { reservationTimes.stream() - .filter(reservationTime -> reservationTime.getId().equals(id)) + .filter(reservationTime -> reservationTime.isIdOf(id)) .findAny() .ifPresent(reservationTimes::remove); } From 241689654a96536eb151e471632a6072b90aeab1 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:29:28 +0900 Subject: [PATCH 63/75] =?UTF-8?q?docs:=20=ED=95=B4=EA=B2=B0=EB=90=9C=20tod?= =?UTF-8?q?o=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/repository/JdbcTemplateReservationRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index 33250ec189..b6619dee52 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -53,7 +53,6 @@ private void save(Reservation reservation, KeyHolder keyHolder) { }, keyHolder); } - //Todo 개선 고민 @Override public List<Reservation> findAll() { String query = """ From e8e7a6f7678710f3ea07ad48abdb8dd2edfb3007 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Thu, 2 May 2024 16:31:32 +0900 Subject: [PATCH 64/75] =?UTF-8?q?style:=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcTemplateThemeRepository.java | 3 +- .../service/ReservationService.java | 2 +- .../service/ReservationTimeServiceTest.java | 34 +++++++++---------- .../roomescape/service/ThemeServiceTest.java | 32 ++++++++--------- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index 497b82c526..310633913d 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -14,14 +14,13 @@ @Repository public class JdbcTemplateThemeRepository implements ThemeRepository { private final JdbcTemplate jdbcTemplate; - private RowMapper<Theme> themeRowMapper = (rs, rowNum) -> { + private final RowMapper<Theme> themeRowMapper = (rs, rowNum) -> { long id = rs.getLong("id"); String name = rs.getString("name"); String description = rs.getString("description"); String thumbnail = rs.getString("thumbnail"); return new Theme(id, name, description, thumbnail); }; - ; public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 7b030e71ff..4d99050d83 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -63,7 +63,7 @@ public ReservationResponse save(ReservationRequest reservationRequest) { private boolean validateDuplicateReservation(Reservation beforeSave, Reservation reservation) { return reservation.isSameDateTime(beforeSave) - && beforeSave.isSameTheme(reservation); + && beforeSave.isSameTheme(reservation); } private ReservationResponse toResponse(Reservation reservation) { diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 53ac9e7fa5..0b0cd90642 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -35,6 +35,23 @@ void initService() { reservationTimeService = new ReservationTimeService(reservationRepository, reservationTimeRepository); } + @DisplayName("저장된 시간을 모두 조회할 수 있다.") + @Test + void findAllTest() { + //given + reservationTimeRepository.save(new ReservationTime(LocalTime.of(10, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); + reservationTimeRepository.save(new ReservationTime(LocalTime.of(13, 0))); + + //when + List<ReservationTimeResponse> reservationTimeResponses = reservationTimeService.findAll(); + + //then + assertThat(reservationTimeResponses) + .hasSize(4); + } + @DisplayName("예약 시간이 하나 존재할 때") @Nested class OneReservationTimeExists { @@ -90,21 +107,4 @@ void usedReservationTimeDeleteTest() { .hasMessage(DELETE_USED_TIME.getMessage()); } } - - @DisplayName("저장된 시간을 모두 조회할 수 있다.") - @Test - void findAllTest() { - //given - reservationTimeRepository.save(new ReservationTime(LocalTime.of(10, 0))); - reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); - reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); - reservationTimeRepository.save(new ReservationTime(LocalTime.of(13, 0))); - - //when - List<ReservationTimeResponse> reservationTimeResponses = reservationTimeService.findAll(); - - //then - assertThat(reservationTimeResponses) - .hasSize(4); - } } diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java index 241d341168..8e1505d457 100644 --- a/src/test/java/roomescape/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -40,6 +40,22 @@ void initService() { themeService = new ThemeService(themeRepository, reservationRepository); } + @DisplayName("테마가 여러개 있으면 테마를 모두 조회할 수 있다.") + @Test + void findAllTest() { + //given + themeRepository.save(new Theme("name1", "description1", "thumbnail1")); + themeRepository.save(new Theme("name2", "description2", "thumbnail2")); + themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + themeRepository.save(new Theme("name4", "description4", "thumbnail4")); + + //when + List<ThemeResponse> themeResponses = themeService.findAll(); + + //then + assertThat(themeResponses).hasSize(4); + } + @DisplayName("테마, 시간이 하나 존재할 때") @Nested class OneThemeTest { @@ -98,20 +114,4 @@ void notExistThemeDeleteTest() { .doesNotThrowAnyException(); } } - - @DisplayName("테마가 여러개 있으면 테마를 모두 조회할 수 있다.") - @Test - void findAllTest() { - //given - themeRepository.save(new Theme("name1", "description1", "thumbnail1")); - themeRepository.save(new Theme("name2", "description2", "thumbnail2")); - themeRepository.save(new Theme("name3", "description3", "thumbnail3")); - themeRepository.save(new Theme("name4", "description4", "thumbnail4")); - - //when - List<ThemeResponse> themeResponses = themeService.findAll(); - - //then - assertThat(themeResponses).hasSize(4); - } } From 62cef24c731d38fafba18ec5b6e144a94ef77814 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Sat, 4 May 2024 16:00:05 +0900 Subject: [PATCH 65/75] =?UTF-8?q?docs:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=ED=91=9C=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd820a31e5..f3265c68e4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ - [x] 중복된 이름의 테마 생성 요청시 에러 - [x] 예약이 있는 테마를 삭제 요청시 에러 -- [ ] 사용자 예약 기능 추가 +- [x] 사용자 예약 기능 추가 - [x] 인기 테마 기능 추가 # API 명세 From 1561f7ed7df81ad31d1580392fc9a0b631c314a0 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Mon, 6 May 2024 01:10:25 +0900 Subject: [PATCH 66/75] =?UTF-8?q?docs:=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EB=8C=80=EC=83=81=20=EB=AA=A9=EB=A1=9D=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- REFACTORING_LIST.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 REFACTORING_LIST.md diff --git a/REFACTORING_LIST.md b/REFACTORING_LIST.md new file mode 100644 index 0000000000..62a7673edf --- /dev/null +++ b/REFACTORING_LIST.md @@ -0,0 +1,12 @@ +## 리팩토링 대상 목록 + +### 우선순위 높음 + +1. Repository.findAll() + Stream 을 사용한 코드 +2. SQL 표준 작성 방식에 따라 대소문자 통일 +3. Repository 의 인메모리 구현체 대신 다른 종류의 테스트 더블 사용하기 + +### 우선순위 낮음 + +1. Service의 일부 검증 로직을 도메인으로 이동 + - 조회 도메인과 생성 도메인을 분리 From c9b2f37a3376e6597ac39494a7d9d381e0cf1700 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Mon, 6 May 2024 02:12:05 +0900 Subject: [PATCH 67/75] =?UTF-8?q?refactor:=20RowMapper=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20sql=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcTemplateReservationRepository.java | 70 ++++++------------- ...JdbcTemplateReservationTimeRepository.java | 52 +++++++------- .../JdbcTemplateThemeRepository.java | 46 ++++++------ .../rowmapper/ReservationRowMapper.java | 31 ++++++++ .../rowmapper/ReservationTimeRowMapper.java | 18 +++++ .../repository/rowmapper/ThemeRowMapper.java | 19 +++++ 6 files changed, 136 insertions(+), 100 deletions(-) create mode 100644 src/main/java/roomescape/repository/rowmapper/ReservationRowMapper.java create mode 100644 src/main/java/roomescape/repository/rowmapper/ReservationTimeRowMapper.java create mode 100644 src/main/java/roomescape/repository/rowmapper/ThemeRowMapper.java diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index b6619dee52..e19fd98616 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -2,48 +2,35 @@ import java.sql.Date; import java.sql.PreparedStatement; -import java.time.LocalTime; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.Reservation; -import roomescape.domain.ReservationTime; -import roomescape.domain.Theme; +import roomescape.repository.rowmapper.ReservationRowMapper; @Repository public class JdbcTemplateReservationRepository implements ReservationRepository { private final JdbcTemplate jdbcTemplate; + private final ReservationRowMapper reservationRowMapper; - public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate) { + public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate, ReservationRowMapper reservationRowMapper) { this.jdbcTemplate = jdbcTemplate; + this.reservationRowMapper = reservationRowMapper; } @Override public Reservation save(Reservation reservation) { - ReservationTime reservationTime = findReservationTime(reservation.getReservationTime().getId()); - Reservation beforeSaved = new Reservation(null, reservation.getName(), reservation.getDate(), - reservationTime, reservation.getTheme()); KeyHolder keyHolder = new GeneratedKeyHolder(); - save(beforeSaved, keyHolder); + save(reservation, keyHolder); long id = keyHolder.getKey().longValue(); return new Reservation(id, reservation); } - private ReservationTime findReservationTime(long timeId) { - String reservationTimeSelectSql = "select * from reservation_time where id = ?"; - return jdbcTemplate.queryForObject(reservationTimeSelectSql, (rs, rowNum) -> { - long id = rs.getLong(1); - LocalTime startAt = rs.getTime(2).toLocalTime(); - return new ReservationTime(id, startAt); - }, timeId); - } - private void save(Reservation reservation, KeyHolder keyHolder) { jdbcTemplate.update(con -> { - String sql = "insert into reservation(name,date,time_id,THEME_ID) values ( ?,?,?,? )"; + String sql = "INSERT INTO reservation(name, date, time_id, theme_id) VALUES ( ?,?,?,? )"; PreparedStatement preparedStatement = con.prepareStatement(sql, new String[]{"id"}); preparedStatement.setString(1, reservation.getName()); preparedStatement.setDate(2, Date.valueOf(reservation.getDate())); @@ -57,40 +44,25 @@ private void save(Reservation reservation, KeyHolder keyHolder) { public List<Reservation> findAll() { String query = """ SELECT - r.id as reservation_id, - r.name as reservation_name, - r.date as reservation_date, - t.id as time_id, - t.start_at as time_value, - t2.id as theme_id, - t2.NAME as theme_name, - t2.DESCRIPTION as description, - t2.THUMBNAIL as thumbnail - FROM reservation as r - inner join reservation_time t - on r.time_id = t.id - inner join theme t2 - on t2.id = r.theme_id"""; - RowMapper<Reservation> reservationRowMapper = (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getDate("reservation_date").toLocalDate(), - new ReservationTime( - rs.getLong("time_id"), - rs.getTime("time_value").toLocalTime() - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("description"), - rs.getString("thumbnail") - ) - ); + r.id AS reservation_id, + r.name AS reservation_name, + r.date AS reservation_date, + t.id AS time_id, + t.start_at AS time_value, + t2.id AS theme_id, + t2.name AS theme_name, + t2.description AS description, + t2.thumbnail AS thumbnail + FROM reservation AS r + INNER JOIN reservation_time t + ON r.time_id = t.id + INNER JOIN theme t2 + ON t2.id = r.theme_id"""; return jdbcTemplate.query(query, reservationRowMapper); } @Override public void delete(long id) { - jdbcTemplate.update("delete from reservation where id = ?", id); + jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); } } diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index 9b6685bb52..814c9c06a9 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -10,59 +10,59 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.ReservationTime; +import roomescape.repository.rowmapper.ReservationTimeRowMapper; @Repository public class JdbcTemplateReservationTimeRepository implements ReservationTimeRepository { private final JdbcTemplate jdbcTemplate; + private final ReservationTimeRowMapper reservationTimeRowMapper; - public JdbcTemplateReservationTimeRepository(JdbcTemplate jdbcTemplate) { + public JdbcTemplateReservationTimeRepository(JdbcTemplate jdbcTemplate, + ReservationTimeRowMapper reservationTimeRowMapper) { this.jdbcTemplate = jdbcTemplate; + this.reservationTimeRowMapper = reservationTimeRowMapper; } @Override public ReservationTime save(ReservationTime reservationTime) { KeyHolder keyHolder = new GeneratedKeyHolder(); save(reservationTime, keyHolder); - return new ReservationTime(keyHolder.getKey().longValue(), reservationTime.getStartAt()); + long id = keyHolder.getKey().longValue(); + return new ReservationTime(id, reservationTime.getStartAt()); + } + + private void save(ReservationTime reservationTime, KeyHolder keyHolder) { + jdbcTemplate.update(con -> { + String sql = "INSERT INTO reservation_time(start_at) VALUES ( ? )"; + PreparedStatement pstmt = con.prepareStatement(sql, new String[]{"id"}); + pstmt.setTime(1, Time.valueOf(reservationTime.getStartAt())); + return pstmt; + }, keyHolder); } @Override public boolean existsByStartAt(LocalTime startAt) { - return jdbcTemplate.queryForObject( - "select exists(select 1 from RESERVATION_TIME where START_AT = ?)", - Boolean.class, startAt); + String sql = "SELECT EXISTS(SELECT 1 FROM reservation_time WHERE start_at = ?)"; + return Boolean.TRUE.equals(jdbcTemplate.queryForObject(sql, Boolean.class, startAt)); } @Override public Optional<ReservationTime> findById(long id) { - List<ReservationTime> times = jdbcTemplate.query("select start_at from reservation_time where id = ?", - (rs, rowNum) -> { - LocalTime time = rs.getTime(1).toLocalTime(); - return new ReservationTime(id, time); - }, id); - return times.stream().findFirst(); + String sql = "SELECT id, start_at FROM reservation_time WHERE id = ?"; + return jdbcTemplate.query(sql, reservationTimeRowMapper, id) + .stream() + .findAny(); } @Override public List<ReservationTime> findAll() { - return jdbcTemplate.query("select * from reservation_time", (rs, rowNum) -> { - long id = rs.getLong(1); - LocalTime time = rs.getTime(2).toLocalTime(); - return new ReservationTime(id, time); - }); + String sql = "SELECT id, start_at FROM reservation_time"; + return jdbcTemplate.query(sql, reservationTimeRowMapper); } @Override public void delete(long id) { - jdbcTemplate.update("delete from reservation_time where id = ?", id); - } - - private void save(ReservationTime reservationTime, KeyHolder keyHolder) { - jdbcTemplate.update(con -> { - PreparedStatement pstmt = con.prepareStatement("insert into reservation_time(start_at) values ( ? )", - new String[]{"id"}); - pstmt.setTime(1, Time.valueOf(reservationTime.getStartAt())); - return pstmt; - }, keyHolder); + String sql = "DELETE FROM reservation_time WHERE id = ?"; + jdbcTemplate.update(sql, id); } } diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index 310633913d..dd95c751cb 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -5,44 +5,40 @@ import java.util.List; import java.util.Optional; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.Theme; +import roomescape.repository.rowmapper.ThemeRowMapper; @Repository public class JdbcTemplateThemeRepository implements ThemeRepository { private final JdbcTemplate jdbcTemplate; - private final RowMapper<Theme> themeRowMapper = (rs, rowNum) -> { - long id = rs.getLong("id"); - String name = rs.getString("name"); - String description = rs.getString("description"); - String thumbnail = rs.getString("thumbnail"); - return new Theme(id, name, description, thumbnail); - }; + private final ThemeRowMapper themeRowMapper; - public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { + public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate, ThemeRowMapper themeRowMapper) { this.jdbcTemplate = jdbcTemplate; + this.themeRowMapper = themeRowMapper; } @Override public List<Theme> findAll() { - return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", themeRowMapper); + String sql = "SELECT id, name, description, thumbnail FROM theme"; + return jdbcTemplate.query(sql, themeRowMapper); } @Override public List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { - return jdbcTemplate.query( - "select th.*, count(*) as count from theme th join reservation r on r.theme_id = th.id where PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') and PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') group by th.id order by count desc limit ?", - themeRowMapper, start, end, count); + String sql = "SELECT th.id AS id, th.name AS name, description, thumbnail, COUNT(*) AS count FROM theme th JOIN reservation r ON r.theme_id = th.id WHERE PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') AND PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') GROUP BY th.id ORDER BY count DESC LIMIT ?"; + return jdbcTemplate.query(sql, themeRowMapper, start, end, count); } @Override public Optional<Theme> findById(long id) { - List<Theme> themes = jdbcTemplate.query("select id, name, description, thumbnail from theme where id = ?", - themeRowMapper, id); - return themes.stream().findFirst(); + String sql = "SELECT id, name, description, thumbnail FROM theme WHERE id = ?"; + return jdbcTemplate.query(sql, themeRowMapper, id) + .stream() + .findAny(); } @Override @@ -55,18 +51,18 @@ public Theme save(Theme theme) { private void save(Theme theme, KeyHolder keyHolder) { jdbcTemplate.update(con -> { - PreparedStatement pstm = con.prepareStatement( - "insert into THEME (NAME, DESCRIPTION, THUMBNAIL) values (?, ?, ?) ", new String[]{"id"}); - pstm.setString(1, theme.getName()); - pstm.setString(2, theme.getDescription()); - pstm.setString(3, theme.getThumbnail()); - return pstm; - }, - keyHolder); + String sql = "INSERT INTO theme (name, description, thumbnail) VALUES (?, ?, ?)"; + PreparedStatement pstmt = con.prepareStatement(sql, new String[]{"id"}); + pstmt.setString(1, theme.getName()); + pstmt.setString(2, theme.getDescription()); + pstmt.setString(3, theme.getThumbnail()); + return pstmt; + }, keyHolder); } @Override public void delete(long id) { - jdbcTemplate.update("delete from THEME where id = ?", id); + String sql = "DELETE FROM theme WHERE id = ?"; + jdbcTemplate.update(sql, id); } } diff --git a/src/main/java/roomescape/repository/rowmapper/ReservationRowMapper.java b/src/main/java/roomescape/repository/rowmapper/ReservationRowMapper.java new file mode 100644 index 0000000000..ac08ea7ad5 --- /dev/null +++ b/src/main/java/roomescape/repository/rowmapper/ReservationRowMapper.java @@ -0,0 +1,31 @@ +package roomescape.repository.rowmapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; + +@Component +public class ReservationRowMapper implements RowMapper<Reservation> { + @Override + public Reservation mapRow(ResultSet rs, int rowNum) throws SQLException { + return new Reservation( + rs.getLong("reservation_id"), + rs.getString("reservation_name"), + rs.getDate("reservation_date").toLocalDate(), + new ReservationTime( + rs.getLong("time_id"), + rs.getTime("time_value").toLocalTime() + ), + new Theme( + rs.getLong("theme_id"), + rs.getString("theme_name"), + rs.getString("description"), + rs.getString("thumbnail") + ) + ); + } +} diff --git a/src/main/java/roomescape/repository/rowmapper/ReservationTimeRowMapper.java b/src/main/java/roomescape/repository/rowmapper/ReservationTimeRowMapper.java new file mode 100644 index 0000000000..11ef7468f1 --- /dev/null +++ b/src/main/java/roomescape/repository/rowmapper/ReservationTimeRowMapper.java @@ -0,0 +1,18 @@ +package roomescape.repository.rowmapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalTime; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import roomescape.domain.ReservationTime; + +@Component +public class ReservationTimeRowMapper implements RowMapper<ReservationTime> { + @Override + public ReservationTime mapRow(ResultSet rs, int rowNum) throws SQLException { + Long id = rs.getLong("id"); + LocalTime time = rs.getTime("start_at").toLocalTime(); + return new ReservationTime(id, time); + } +} diff --git a/src/main/java/roomescape/repository/rowmapper/ThemeRowMapper.java b/src/main/java/roomescape/repository/rowmapper/ThemeRowMapper.java new file mode 100644 index 0000000000..30a1705d93 --- /dev/null +++ b/src/main/java/roomescape/repository/rowmapper/ThemeRowMapper.java @@ -0,0 +1,19 @@ +package roomescape.repository.rowmapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import roomescape.domain.Theme; + +@Component +public class ThemeRowMapper implements RowMapper<Theme> { + @Override + public Theme mapRow(ResultSet rs, int rowNum) throws SQLException { + long id = rs.getLong("id"); + String name = rs.getString("name"); + String description = rs.getString("description"); + String thumbnail = rs.getString("thumbnail"); + return new Theme(id, name, description, thumbnail); + } +} From 92e851928a7775a2290e4ecf7cb588415579c675 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Mon, 6 May 2024 03:34:03 +0900 Subject: [PATCH 68/75] =?UTF-8?q?refactor:=20Stream=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20SQL=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcTemplateReservationRepository.java | 25 ++++++++++++ ...JdbcTemplateReservationTimeRepository.java | 15 +++++++ .../repository/ReservationRepository.java | 9 +++++ .../repository/ReservationTimeRepository.java | 4 ++ .../service/AvailableTimeService.java | 39 ++++++++++--------- .../service/ReservationService.java | 27 +++++++------ .../service/ReservationTimeService.java | 21 +++++----- .../java/roomescape/service/ThemeService.java | 18 ++++++--- .../CollectionReservationRepository.java | 23 +++++++++++ .../CollectionReservationTimeRepository.java | 27 +++++++++++++ .../service/AvailableTimeServiceTest.java | 9 ++++- 11 files changed, 169 insertions(+), 48 deletions(-) diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index e19fd98616..d6dffca4f7 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -2,12 +2,15 @@ import java.sql.Date; import java.sql.PreparedStatement; +import java.time.LocalDate; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.repository.rowmapper.ReservationRowMapper; @Repository @@ -61,6 +64,28 @@ public List<Reservation> findAll() { return jdbcTemplate.query(query, reservationRowMapper); } + @Override + public boolean existsByThemeAndDateAndTime(Theme theme, LocalDate date, ReservationTime reservationTime) { + String sql = "SELECT EXISTS(SELECT 1 FROM reservation WHERE theme_id = ? AND date = ? AND time_id = ?)"; + long themeId = theme.getId(); + long timeId = reservationTime.getId(); + return Boolean.TRUE.equals(jdbcTemplate.queryForObject(sql, Boolean.class, themeId, date, timeId)); + } + + @Override + public boolean existsByTime(ReservationTime reservationTime) { + String sql = "SELECT EXISTS(SELECT 1 FROM reservation WHERE time_id = ?)"; + long timeId = reservationTime.getId(); + return Boolean.TRUE.equals(jdbcTemplate.queryForObject(sql, Boolean.class, timeId)); + } + + @Override + public boolean existsByTheme(Theme theme) { + String sql = "SELECT EXISTS(SELECT 1 FROM reservation WHERE theme_id = ?)"; + long themeId = theme.getId(); + return Boolean.TRUE.equals(jdbcTemplate.queryForObject(sql, Boolean.class, themeId)); + } + @Override public void delete(long id) { jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index 814c9c06a9..da84250a1b 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -2,6 +2,7 @@ import java.sql.PreparedStatement; import java.sql.Time; +import java.time.LocalDate; import java.time.LocalTime; import java.util.List; import java.util.Optional; @@ -10,6 +11,7 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.repository.rowmapper.ReservationTimeRowMapper; @Repository @@ -60,6 +62,19 @@ public List<ReservationTime> findAll() { return jdbcTemplate.query(sql, reservationTimeRowMapper); } + @Override + public List<ReservationTime> findUsedTimeByDateAndTheme(LocalDate date, Theme theme) { + String sql = """ + SELECT rt.id, start_at + FROM reservation_time rt + JOIN reservation r + ON rt.id = r.time_id + WHERE r.date = ? + AND r.theme_id = ? + """; + return jdbcTemplate.query(sql, reservationTimeRowMapper, date, theme.getId()); + } + @Override public void delete(long id) { String sql = "DELETE FROM reservation_time WHERE id = ?"; diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java index 1ac0fbd917..c224f4975a 100644 --- a/src/main/java/roomescape/repository/ReservationRepository.java +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -1,12 +1,21 @@ package roomescape.repository; +import java.time.LocalDate; import java.util.List; import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; public interface ReservationRepository { Reservation save(Reservation reservation); List<Reservation> findAll(); + boolean existsByThemeAndDateAndTime(Theme theme, LocalDate date, ReservationTime reservationTime); + + boolean existsByTime(ReservationTime reservationTime); + + boolean existsByTheme(Theme theme); + void delete(long id); } diff --git a/src/main/java/roomescape/repository/ReservationTimeRepository.java b/src/main/java/roomescape/repository/ReservationTimeRepository.java index ca04e207b9..e033a38451 100644 --- a/src/main/java/roomescape/repository/ReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/ReservationTimeRepository.java @@ -1,9 +1,11 @@ package roomescape.repository; +import java.time.LocalDate; import java.time.LocalTime; import java.util.List; import java.util.Optional; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; public interface ReservationTimeRepository { ReservationTime save(ReservationTime reservationTime); @@ -14,5 +16,7 @@ public interface ReservationTimeRepository { List<ReservationTime> findAll(); + List<ReservationTime> findUsedTimeByDateAndTheme(LocalDate date, Theme theme); + void delete(long id); } diff --git a/src/main/java/roomescape/service/AvailableTimeService.java b/src/main/java/roomescape/service/AvailableTimeService.java index 909c6ad493..cd7d328b43 100644 --- a/src/main/java/roomescape/service/AvailableTimeService.java +++ b/src/main/java/roomescape/service/AvailableTimeService.java @@ -1,42 +1,43 @@ package roomescape.service; +import static roomescape.exception.ExceptionType.NOT_FOUND_THEME; + import java.time.LocalDate; +import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.stereotype.Service; -import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; import roomescape.dto.AvailableTimeResponse; -import roomescape.repository.ReservationRepository; +import roomescape.exception.RoomescapeException; import roomescape.repository.ReservationTimeRepository; +import roomescape.repository.ThemeRepository; @Service public class AvailableTimeService { - private final ReservationRepository reservationRepository; private final ReservationTimeRepository reservationTimeRepository; + private final ThemeRepository themeRepository; - public AvailableTimeService(ReservationRepository reservationRepository, - ReservationTimeRepository reservationTimeRepository) { - this.reservationRepository = reservationRepository; + public AvailableTimeService(ReservationTimeRepository reservationTimeRepository, ThemeRepository themeRepository) { this.reservationTimeRepository = reservationTimeRepository; + this.themeRepository = themeRepository; } - //todo : 메서드 개선 public List<AvailableTimeResponse> findByThemeAndDate(LocalDate date, long themeId) { - Set<Long> alreadyReservedTimeIds = reservationRepository.findAll().stream() - .filter(reservation -> reservation.isDateOf(date)) - .filter(reservation -> reservation.isThemeOf(themeId)) - .map(Reservation::getReservationTime) - .map(ReservationTime::getId) - .collect(Collectors.toSet()); + Theme theme = themeRepository.findById(themeId) + .orElseThrow(() -> new RoomescapeException(NOT_FOUND_THEME)); + + var alreadyUsedTimes = new HashSet<>(reservationTimeRepository.findUsedTimeByDateAndTheme(date, theme)); return reservationTimeRepository.findAll().stream() - .map(reservationTime -> { - long id = reservationTime.getId(); - boolean isBooked = alreadyReservedTimeIds.contains(id); - return new AvailableTimeResponse(id, reservationTime.getStartAt(), isBooked); - }) + .map(reservationTime -> toResponse(alreadyUsedTimes, reservationTime)) .toList(); } + + private AvailableTimeResponse toResponse(Set<ReservationTime> alreadyUsedTimes, ReservationTime reservationTime) { + boolean isBooked = alreadyUsedTimes.contains(reservationTime); + long id = reservationTime.getId(); + return new AvailableTimeResponse(id, reservationTime.getStartAt(), isBooked); + } } diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java index 4d99050d83..a4160cebe8 100644 --- a/src/main/java/roomescape/service/ReservationService.java +++ b/src/main/java/roomescape/service/ReservationService.java @@ -5,9 +5,11 @@ import static roomescape.exception.ExceptionType.NOT_FOUND_THEME; import static roomescape.exception.ExceptionType.PAST_TIME_RESERVATION; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; @@ -21,6 +23,7 @@ import roomescape.repository.ThemeRepository; @Service +@Transactional public class ReservationService { private final ReservationRepository reservationRepository; private final ReservationTimeRepository reservationTimeRepository; @@ -34,7 +37,6 @@ public ReservationService(ReservationRepository reservationRepository, } public ReservationResponse save(ReservationRequest reservationRequest) { - ReservationTime requestedTime = reservationTimeRepository.findById(reservationRequest.timeId()) .orElseThrow(() -> new RoomescapeException(NOT_FOUND_RESERVATION_TIME)); Theme requestedTheme = themeRepository.findById(reservationRequest.themeId()) @@ -46,24 +48,25 @@ public ReservationResponse save(ReservationRequest reservationRequest) { requestedTime, requestedTheme ); - boolean isDuplicate = reservationRepository.findAll() - .stream() - .anyMatch(reservation -> validateDuplicateReservation(beforeSave, reservation)); + + validateDuplicateReservation(requestedTime, requestedTheme, beforeSave.getDate()); + validatePastTimeReservation(beforeSave); + + Reservation saved = reservationRepository.save(beforeSave); + return toResponse(saved); + } + + private void validateDuplicateReservation(ReservationTime requestedTime, Theme requestedTheme, LocalDate date) { + boolean isDuplicate = reservationRepository.existsByThemeAndDateAndTime(requestedTheme, date, requestedTime); if (isDuplicate) { throw new RoomescapeException(DUPLICATE_RESERVATION); } + } + private void validatePastTimeReservation(Reservation beforeSave) { if (beforeSave.isBefore(LocalDateTime.now())) { throw new RoomescapeException(PAST_TIME_RESERVATION); } - - Reservation saved = reservationRepository.save(beforeSave); - return toResponse(saved); - } - - private boolean validateDuplicateReservation(Reservation beforeSave, Reservation reservation) { - return reservation.isSameDateTime(beforeSave) - && beforeSave.isSameTheme(reservation); } private ReservationResponse toResponse(Reservation reservation) { diff --git a/src/main/java/roomescape/service/ReservationTimeService.java b/src/main/java/roomescape/service/ReservationTimeService.java index 893ea7eac6..b4e6b98289 100644 --- a/src/main/java/roomescape/service/ReservationTimeService.java +++ b/src/main/java/roomescape/service/ReservationTimeService.java @@ -5,7 +5,7 @@ import java.util.List; import org.springframework.stereotype.Service; -import roomescape.domain.Reservation; +import org.springframework.transaction.annotation.Transactional; import roomescape.domain.ReservationTime; import roomescape.dto.ReservationTimeRequest; import roomescape.dto.ReservationTimeResponse; @@ -14,6 +14,7 @@ import roomescape.repository.ReservationTimeRepository; @Service +@Transactional public class ReservationTimeService { private final ReservationRepository reservationRepository; private final ReservationTimeRepository reservationTimeRepository; @@ -44,16 +45,18 @@ public List<ReservationTimeResponse> findAll() { } public void delete(long id) { - //todo SQL로 구현 - List<Reservation> reservations = reservationRepository.findAll(); - if (isUsedTime(id, reservations)) { - throw new RoomescapeException(DELETE_USED_TIME); - } + validateUsedTime(id); reservationTimeRepository.delete(id); } - private static boolean isUsedTime(long id, List<Reservation> reservations) { - return reservations.stream() - .anyMatch(reservation -> reservation.isReservationTimeOf(id)); + private void validateUsedTime(long id) { + reservationTimeRepository.findById(id).ifPresent(this::validateUsedTime); + } + + private void validateUsedTime(ReservationTime reservationTime) { + boolean existsByTime = reservationRepository.existsByTime(reservationTime); + if (existsByTime) { + throw new RoomescapeException(DELETE_USED_TIME); + } } } diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index d15bfff70a..7556b42577 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -6,6 +6,7 @@ import java.time.LocalDate; import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import roomescape.domain.Theme; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; @@ -14,6 +15,7 @@ import roomescape.repository.ThemeRepository; @Service +@Transactional public class ThemeService { private final ThemeRepository themeRepository; @@ -52,14 +54,18 @@ public List<ThemeResponse> findAndOrderByPopularity(LocalDate start, LocalDate e } public void delete(long id) { - if (isUsedTheme(id)) { - throw new RoomescapeException(DELETE_USED_THEME); - } + validateUsedTheme(id); themeRepository.delete(id); } - private boolean isUsedTheme(long id) { - return reservationRepository.findAll().stream() - .anyMatch(reservation -> reservation.isThemeOf(id)); + private void validateUsedTheme(long id) { + themeRepository.findById(id).ifPresent(this::validateUsedTheme); + } + + private void validateUsedTheme(Theme theme) { + boolean existsByTime = reservationRepository.existsByTheme(theme); + if (existsByTime) { + throw new RoomescapeException(DELETE_USED_THEME); + } } } diff --git a/src/test/java/roomescape/repository/CollectionReservationRepository.java b/src/test/java/roomescape/repository/CollectionReservationRepository.java index 6673a8f600..391f8b157a 100644 --- a/src/test/java/roomescape/repository/CollectionReservationRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationRepository.java @@ -1,9 +1,12 @@ package roomescape.repository; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; public class CollectionReservationRepository implements ReservationRepository { private final List<Reservation> reservations; @@ -29,6 +32,26 @@ public List<Reservation> findAll() { .toList(); } + @Override + public boolean existsByThemeAndDateAndTime(Theme theme, LocalDate date, ReservationTime reservationTime) { + return reservations.stream() + .filter(reservation -> theme.equals(reservation.getTheme())) + .filter(reservation -> date.equals(reservation.getDate())) + .anyMatch(reservation -> reservationTime.equals(reservation.getReservationTime())); + } + + @Override + public boolean existsByTime(ReservationTime reservationTime) { + return reservations.stream() + .anyMatch(reservation -> reservationTime.equals(reservation.getReservationTime())); + } + + @Override + public boolean existsByTheme(Theme theme) { + return reservations.stream() + .anyMatch(reservation -> theme.equals(reservation.getTheme())); + } + @Override public void delete(long id) { reservations.stream() diff --git a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java index a22896c61f..1008c221f7 100644 --- a/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java +++ b/src/test/java/roomescape/repository/CollectionReservationTimeRepository.java @@ -1,15 +1,20 @@ package roomescape.repository; +import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; public class CollectionReservationTimeRepository implements ReservationTimeRepository { private final List<ReservationTime> reservationTimes; private final AtomicLong atomicLong; + private final ReservationRepository reservationRepository; + public CollectionReservationTimeRepository() { this(new ArrayList<>()); @@ -20,8 +25,18 @@ public CollectionReservationTimeRepository(List<ReservationTime> reservationTime } public CollectionReservationTimeRepository(List<ReservationTime> reservationTimes, AtomicLong atomicLong) { + this(reservationTimes, atomicLong, null); + } + + public CollectionReservationTimeRepository(List<ReservationTime> reservationTimes, AtomicLong atomicLong, + ReservationRepository reservationRepository) { this.reservationTimes = reservationTimes; this.atomicLong = atomicLong; + this.reservationRepository = reservationRepository; + } + + public CollectionReservationTimeRepository(ReservationRepository reservationRepository) { + this(new ArrayList<>(), new AtomicLong(), reservationRepository); } @Override @@ -49,6 +64,18 @@ public List<ReservationTime> findAll() { return List.copyOf(reservationTimes); } + @Override + public List<ReservationTime> findUsedTimeByDateAndTheme(LocalDate date, Theme theme) { + if (reservationRepository == null) { + throw new UnsupportedOperationException("ReservationRepository 를 사용해 생성하지 않아 메서드를 사용할 수 없습니다."); + } + return reservationRepository.findAll().stream() + .filter(reservation -> date.equals(reservation.getDate())) + .filter(reservation -> theme.equals(reservation.getTheme())) + .map(Reservation::getReservationTime) + .toList(); + } + @Override public void delete(long id) { reservationTimes.stream() diff --git a/src/test/java/roomescape/service/AvailableTimeServiceTest.java b/src/test/java/roomescape/service/AvailableTimeServiceTest.java index 87b5427efd..24383b8577 100644 --- a/src/test/java/roomescape/service/AvailableTimeServiceTest.java +++ b/src/test/java/roomescape/service/AvailableTimeServiceTest.java @@ -14,20 +14,24 @@ import roomescape.dto.AvailableTimeResponse; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; import roomescape.repository.ReservationRepository; import roomescape.repository.ReservationTimeRepository; +import roomescape.repository.ThemeRepository; class AvailableTimeServiceTest { private AvailableTimeService availableTimeService; private ReservationRepository reservationRepository; private ReservationTimeRepository reservationTimeRepository; + private ThemeRepository themeRepository; @BeforeEach void init() { reservationRepository = new CollectionReservationRepository(); - reservationTimeRepository = new CollectionReservationTimeRepository(); - availableTimeService = new AvailableTimeService(reservationRepository, reservationTimeRepository); + reservationTimeRepository = new CollectionReservationTimeRepository(reservationRepository); + themeRepository = new CollectionThemeRepository(); + availableTimeService = new AvailableTimeService(reservationTimeRepository, themeRepository); } @DisplayName("날짜와 테마, 시간에 대한 예약 내역을 확인할 수 있다.") @@ -35,6 +39,7 @@ void init() { void findAvailableTimeTest() { //given Theme DEFUALT_THEME = new Theme(1L, "name", "description", "thumbnail"); + themeRepository.save(DEFUALT_THEME); ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(13, 0))); From a1ed68a191bf3435d283aa57bc00fa370fd72b57 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Mon, 6 May 2024 03:41:04 +0900 Subject: [PATCH 69/75] =?UTF-8?q?style:=20=EA=B8=B8=EC=9D=B4=EA=B0=80=20?= =?UTF-8?q?=EA=B8=B4=20SQL=20=EB=AC=B8=EC=9D=84=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B8=94=EB=A1=9D=EC=9C=BC=EB=A1=9C=20=ED=91=9C?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcTemplateReservationRepository.java | 11 ++++++----- .../JdbcTemplateReservationTimeRepository.java | 3 ++- .../repository/JdbcTemplateThemeRepository.java | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java index d6dffca4f7..c462b1f528 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationRepository.java @@ -46,7 +46,7 @@ private void save(Reservation reservation, KeyHolder keyHolder) { @Override public List<Reservation> findAll() { String query = """ - SELECT + SELECT r.id AS reservation_id, r.name AS reservation_name, r.date AS reservation_date, @@ -57,10 +57,11 @@ public List<Reservation> findAll() { t2.description AS description, t2.thumbnail AS thumbnail FROM reservation AS r - INNER JOIN reservation_time t - ON r.time_id = t.id - INNER JOIN theme t2 - ON t2.id = r.theme_id"""; + INNER JOIN reservation_time t + ON r.time_id = t.id + INNER JOIN theme t2 + ON t2.id = r.theme_id + """; return jdbcTemplate.query(query, reservationRowMapper); } diff --git a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java index da84250a1b..3e001eb47d 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateReservationTimeRepository.java @@ -65,7 +65,8 @@ public List<ReservationTime> findAll() { @Override public List<ReservationTime> findUsedTimeByDateAndTheme(LocalDate date, Theme theme) { String sql = """ - SELECT rt.id, start_at + SELECT + rt.id, start_at FROM reservation_time rt JOIN reservation r ON rt.id = r.time_id diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index dd95c751cb..c9871e1eda 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -29,7 +29,20 @@ public List<Theme> findAll() { @Override public List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { - String sql = "SELECT th.id AS id, th.name AS name, description, thumbnail, COUNT(*) AS count FROM theme th JOIN reservation r ON r.theme_id = th.id WHERE PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') AND PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') GROUP BY th.id ORDER BY count DESC LIMIT ?"; + String sql = """ + SELECT + th.id AS id, th.name AS name, description, thumbnail, COUNT(*) AS count + FROM theme th + JOIN reservation r + ON r.theme_id = th.id + WHERE + PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') + AND + PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') + GROUP BY th.id + ORDER BY count DESC + LIMIT ? + """; return jdbcTemplate.query(sql, themeRowMapper, start, end, count); } From 5d02747f5b9b1e61e4cd117f8e91e4fd54c7a0f6 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 12:03:37 +0900 Subject: [PATCH 70/75] =?UTF-8?q?test:=20=EC=9D=B8=EA=B8=B0=20=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CollectionThemeRepository.java | 42 +++++++++++++++-- .../roomescape/service/ThemeServiceTest.java | 46 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/test/java/roomescape/repository/CollectionThemeRepository.java b/src/test/java/roomescape/repository/CollectionThemeRepository.java index 2774877fc3..98ba2856f4 100644 --- a/src/test/java/roomescape/repository/CollectionThemeRepository.java +++ b/src/test/java/roomescape/repository/CollectionThemeRepository.java @@ -4,18 +4,32 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import roomescape.domain.Reservation; import roomescape.domain.Theme; public class CollectionThemeRepository implements ThemeRepository { private final List<Theme> themes; private final AtomicLong index; + private final CollectionReservationRepository reservationRepository; public CollectionThemeRepository() { - themes = new ArrayList<>(); - index = new AtomicLong(0); + this(new ArrayList<>(), new AtomicLong(0), null); + } + + private CollectionThemeRepository(List<Theme> themes, AtomicLong index, + CollectionReservationRepository reservationRepository) { + this.themes = themes; + this.index = index; + this.reservationRepository = reservationRepository; + } + + public CollectionThemeRepository(CollectionReservationRepository reservationRepository) { + this(new ArrayList<>(), new AtomicLong(0), reservationRepository); } @Override @@ -25,7 +39,29 @@ public List<Theme> findAll() { @Override public List<Theme> findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { - return null; + if (reservationRepository == null) { + throw new UnsupportedOperationException("ReservationRepository 를 사용해 생성하지 않아 메서드를 사용할 수 없습니다."); + } + Map<Long, Integer> collect = reservationRepository.findAll().stream() + .filter(reservation -> isAfterStart(start, reservation)) + .filter(reservation -> isBeforeEnd(end, reservation)) + .map(Reservation::getTheme) + .collect(Collectors.groupingBy(Theme::getId, Collectors.summingInt(value -> 1))); + return collect.keySet() + .stream() + .sorted((o1, o2) -> Integer.compare(collect.get(o2), collect.get(o1))) + .map(id -> findById(id).orElseThrow()) + .toList(); + } + + private boolean isAfterStart(LocalDate start, Reservation reservation) { + LocalDate date = reservation.getDate(); + return date.isAfter(start) || date.isEqual(start); + } + + private boolean isBeforeEnd(LocalDate end, Reservation reservation) { + LocalDate date = reservation.getDate(); + return date.isBefore(end) || date.isEqual(end); } @Override diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java index 8e1505d457..8605a8ed0e 100644 --- a/src/test/java/roomescape/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -9,6 +9,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.List; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,27 +17,27 @@ import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; +import roomescape.dto.ReservationRequest; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; import roomescape.exception.RoomescapeException; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; import roomescape.repository.CollectionThemeRepository; -import roomescape.repository.ReservationRepository; import roomescape.repository.ThemeRepository; class ThemeServiceTest { private ThemeRepository themeRepository; private CollectionReservationTimeRepository reservationTimeRepository; - private ReservationRepository reservationRepository; + private CollectionReservationRepository reservationRepository; private ThemeService themeService; @BeforeEach void initService() { - themeRepository = new CollectionThemeRepository(); reservationTimeRepository = new CollectionReservationTimeRepository(); reservationRepository = new CollectionReservationRepository(); + themeRepository = new CollectionThemeRepository(); themeService = new ThemeService(themeRepository, reservationRepository); } @@ -56,6 +57,45 @@ void findAllTest() { assertThat(themeResponses).hasSize(4); } + @DisplayName("인기 테마를 조회할 수 있다.") + @Test + void findAndOrderByPopularity() { + themeRepository = new CollectionThemeRepository(reservationRepository); + themeService = new ThemeService(themeRepository, reservationRepository); + LocalDate date = LocalDate.now().plusDays(1); + + addReservations(date); + + LocalDate end = date.plusDays(6); + List<Long> themeIds = themeService.findAndOrderByPopularity(date, end, 10) + .stream() + .map(ThemeResponse::id) + .toList(); + Assertions.assertThat(themeIds) + .containsExactly(2L, 1L, 3L); + } + + private void addReservations(LocalDate date) { + Theme theme1 = themeRepository.save(new Theme("name1", "description1", "thumbnail1")); + Theme theme2 = themeRepository.save(new Theme("name2", "description2", "thumbnail2")); + Theme theme3 = themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(1, 30))); + ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(2, 30))); + ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(3, 30))); + + ReservationService reservationService = new ReservationService(reservationRepository, reservationTimeRepository, + themeRepository); + + reservationService.save(new ReservationRequest(date, "name", reservationTime2.getId(), theme2.getId())); + reservationService.save(new ReservationRequest(date, "name", reservationTime1.getId(), theme2.getId())); + reservationService.save(new ReservationRequest(date, "name", reservationTime3.getId(), theme2.getId())); + + reservationService.save(new ReservationRequest(date, "name", reservationTime1.getId(), theme1.getId())); + reservationService.save(new ReservationRequest(date, "name", reservationTime2.getId(), theme1.getId())); + + reservationService.save(new ReservationRequest(date, "name", reservationTime1.getId(), theme3.getId())); + } + @DisplayName("테마, 시간이 하나 존재할 때") @Nested class OneThemeTest { From eb38c7da9921bebd5b0ea30e1bf97dc159c073c1 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 12:50:50 +0900 Subject: [PATCH 71/75] =?UTF-8?q?test:=20=EB=88=84=EB=9D=BD=EB=90=9C=20Rep?= =?UTF-8?q?ository=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...JdbcTemplateReservationRepositoryTest.java | 64 ++++++++++++-- ...TemplateReservationTimeRepositoryTest.java | 47 +++++++++- .../JdbcTemplateThemeRepositoryTest.java | 85 +++++++++++++++++++ 3 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java index 79dfd24631..54dcd71f38 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -1,5 +1,7 @@ package roomescape.repository; +import static org.junit.jupiter.api.Assertions.assertAll; + import java.time.LocalDate; import java.time.LocalTime; import java.util.List; @@ -26,17 +28,17 @@ class JdbcTemplateReservationRepositoryTest { @BeforeEach void init() { - jdbcTemplate.update("delete from reservation"); - jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); + jdbcTemplate.update("DELETE FROM reservation"); + jdbcTemplate.update("ALTER TABLE reservation ALTER COLUMN id RESTART WITH 1"); - jdbcTemplate.update("delete from reservation_time"); - jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); - jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); + jdbcTemplate.update("DELETE FROM reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("INSERT INTO reservation_time(start_at) VALUES('11:56')"); - jdbcTemplate.update("delete from theme"); - jdbcTemplate.update("ALTER TABLE theme alter column id restart with 1"); + jdbcTemplate.update("DELETE FROM theme"); + jdbcTemplate.update("ALTER TABLE theme ALTER COLUMN id RESTART WITH 1"); jdbcTemplate.update( - "insert into theme (name, description, thumbnail) values('name', 'description', 'thumbnail')"); + "INSERT INTO theme (name, description, thumbnail) VALUES('name', 'description', 'thumbnail')"); } @@ -78,4 +80,50 @@ void delete() { Assertions.assertThat(beforeSaveAndDelete) .containsExactlyElementsOf(afterSaveAndDelete); } + + @Test + @DisplayName("특정 테마에 특정 날짜 특정 시간에 예약 여부를 잘 반환하는지 확인한다.") + void existsByThemeAndDateAndTime() { + LocalDate date1 = LocalDate.now(); + LocalDate date2 = date1.plusDays(1); + reservationRepository.save(new Reservation("name", date1, DEFAULT_TIME, DEFAULT_THEME)); + + assertAll( + () -> Assertions.assertThat( + reservationRepository.existsByThemeAndDateAndTime(DEFAULT_THEME, date1, DEFAULT_TIME)) + .isTrue(), + () -> Assertions.assertThat( + reservationRepository.existsByThemeAndDateAndTime(DEFAULT_THEME, date2, DEFAULT_TIME)) + .isFalse() + ); + } + + @Test + @DisplayName("특정 시간에 예약이 있는지 확인한다.") + void existsByTime() { + LocalDate date = LocalDate.now(); + reservationRepository.save(new Reservation("name", date, DEFAULT_TIME, DEFAULT_THEME)); + + assertAll( + () -> Assertions.assertThat(reservationRepository.existsByTime(DEFAULT_TIME)) + .isTrue(), + () -> Assertions.assertThat( + reservationRepository.existsByTime(new ReservationTime(2L, LocalTime.of(12, 56)))) + .isFalse() + ); + } + + @Test + @DisplayName("특정 테마에 예약이 있는지 확인한다.") + void existsByTheme() { + LocalDate date = LocalDate.now(); + reservationRepository.save(new Reservation("name", date, DEFAULT_TIME, DEFAULT_THEME)); + + assertAll( + () -> Assertions.assertThat(reservationRepository.existsByTheme(DEFAULT_THEME)) + .isTrue(), + () -> Assertions.assertThat(reservationRepository.existsByTheme(new Theme(2L, DEFAULT_THEME))) + .isFalse() + ); + } } diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java index 71d364044d..8c29259eb2 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationTimeRepositoryTest.java @@ -1,5 +1,8 @@ package roomescape.repository; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.LocalDate; import java.time.LocalTime; import java.util.List; import org.assertj.core.api.Assertions; @@ -9,21 +12,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; +import roomescape.domain.Reservation; import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; @SpringBootTest class JdbcTemplateReservationTimeRepositoryTest { @Autowired private ReservationTimeRepository reservationTimeRepository; @Autowired + private ReservationRepository reservationRepository; + @Autowired + private ThemeRepository themeRepository; + @Autowired private JdbcTemplate jdbcTemplate; @BeforeEach void init() { - jdbcTemplate.update("delete from reservation"); - jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); - jdbcTemplate.update("delete from reservation_time"); - jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); + jdbcTemplate.update("DELETE FROM reservation"); + jdbcTemplate.update("ALTER TABLE reservation ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("DELETE FROM reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time ALTER COLUMN id RESTART WITH 1"); } @Test @@ -64,4 +73,34 @@ void delete() { Assertions.assertThat(beforeSaveAndDelete) .containsExactlyInAnyOrderElementsOf(afterSaveAndDelete); } + + @Test + @DisplayName("특정 시작 시간을 가지는 예약 시간이 있는지 여부를 잘 반환하는지 확인한다.") + void existsByStartAt() { + LocalTime time = LocalTime.of(11, 20); + reservationTimeRepository.save(new ReservationTime(time)); + + assertAll( + () -> Assertions.assertThat(reservationTimeRepository.existsByStartAt(time)) + .isTrue(), + () -> Assertions.assertThat(reservationTimeRepository.existsByStartAt(time.plusHours(1))) + .isFalse() + ); + } + + @Test + @DisplayName("특정 날짜와 테마에 예약이 있는 예약 시간의 목록을 잘 반환하는지 확인한다.") + void findUsedTimeByDateAndTheme() { + LocalDate date = LocalDate.of(2024, 12, 30); + Theme theme = new Theme("name", "description", "http://example.com"); + theme = themeRepository.save(theme); + ReservationTime time = new ReservationTime(LocalTime.of(12, 30)); + time = reservationTimeRepository.save(time); + reservationRepository.save(new Reservation("name", date, time, theme)); + + List<ReservationTime> response = reservationTimeRepository.findUsedTimeByDateAndTheme(date, theme); + + Assertions.assertThat(response) + .containsExactly(time); + } } diff --git a/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java new file mode 100644 index 0000000000..5f10e6cda7 --- /dev/null +++ b/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java @@ -0,0 +1,85 @@ +package roomescape.repository; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationTime; +import roomescape.domain.Theme; + +@SpringBootTest +class JdbcTemplateThemeRepositoryTest { + + @Autowired + private ThemeRepository themeRepository; + @Autowired + private ReservationRepository reservationRepository; + @Autowired + private ReservationTimeRepository reservationTimeRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void init() { + jdbcTemplate.update("DELETE FROM reservation"); + jdbcTemplate.update("ALTER TABLE reservation ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("DELETE FROM reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("DELETE FROM theme"); + jdbcTemplate.update("ALTER TABLE theme ALTER COLUMN id RESTART WITH 1"); + } + + @Test + @DisplayName("전체 테마 조회를 잘 하는지 확인") + void findAll() { + Theme theme = new Theme("name", "description", "http://example.com"); + theme = themeRepository.save(theme); + List<Theme> allTheme = themeRepository.findAll(); + + Assertions.assertThat(allTheme) + .containsExactly(theme); + } + + @Test + void findAndOrderByPopularity() { + Theme theme1 = themeRepository.save(new Theme("name1", "description1", "thumbnail1")); + Theme theme2 = themeRepository.save(new Theme("name2", "description2", "thumbnail2")); + Theme theme3 = themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + + ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(1, 30))); + ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(2, 30))); + ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(3, 30))); + + LocalDate date = LocalDate.now().plusDays(1); + reservationRepository.save(new Reservation("name", date, reservationTime2, theme2)); + reservationRepository.save(new Reservation("name", date, reservationTime1, theme2)); + reservationRepository.save(new Reservation("name", date, reservationTime3, theme2)); + + reservationRepository.save(new Reservation("name", date, reservationTime1, theme1)); + reservationRepository.save(new Reservation("name", date, reservationTime2, theme1)); + + reservationRepository.save(new Reservation("name", date, reservationTime1, theme3)); + + List<Theme> result = themeRepository.findAndOrderByPopularity(date, date.plusDays(1), 10); + Assertions.assertThat(result) + .containsExactly(theme2, theme1, theme3); + } + + @Test + @DisplayName("테마가 잘 지워지는지 확인") + void delete() { + Theme theme = themeRepository.save(new Theme("name1", "description1", "thumbnail")); + + themeRepository.delete(theme.getId()); + + Assertions.assertThat(themeRepository.findAll()) + .isEmpty(); + } +} From 9881fc7cb95394dad3b79728d6dba66c4590e2f2 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 12:54:01 +0900 Subject: [PATCH 72/75] =?UTF-8?q?docs:=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- REFACTORING_LIST.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/REFACTORING_LIST.md b/REFACTORING_LIST.md index 62a7673edf..2882536e81 100644 --- a/REFACTORING_LIST.md +++ b/REFACTORING_LIST.md @@ -4,9 +4,4 @@ 1. Repository.findAll() + Stream 을 사용한 코드 2. SQL 표준 작성 방식에 따라 대소문자 통일 -3. Repository 의 인메모리 구현체 대신 다른 종류의 테스트 더블 사용하기 - -### 우선순위 낮음 - -1. Service의 일부 검증 로직을 도메인으로 이동 - - 조회 도메인과 생성 도메인을 분리 +3. ~~Repository 의 인메모리 구현체 대신 다른 종류의 테스트 더블 사용하기~~ From 239c24149981fcc37076a19db38019927bd510b5 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 13:28:28 +0900 Subject: [PATCH 73/75] =?UTF-8?q?feat:=20=ED=85=8C=EB=A7=88=EC=9D=98=20thu?= =?UTF-8?q?mbnail=20=EC=9D=B4=20url=20=ED=98=95=ED=83=9C=EC=9D=B8=EC=A7=80?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/domain/Theme.java | 5 ++++ .../roomescape/exception/ExceptionType.java | 1 + .../controller/ReservationControllerTest.java | 2 +- .../roomescape/domain/ReservationTest.java | 2 +- .../java/roomescape/domain/ThemeTest.java | 26 +++++++++++-------- .../integration/AdminIntegrationTest.java | 22 ++++++++-------- ...JdbcTemplateReservationRepositoryTest.java | 4 +-- .../JdbcTemplateThemeRepositoryTest.java | 8 +++--- .../service/AvailableTimeServiceTest.java | 2 +- .../service/ReservationServiceTest.java | 2 +- .../service/ReservationTimeServiceTest.java | 2 +- .../roomescape/service/ThemeServiceTest.java | 20 +++++++------- 12 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 83f72ce50b..ac1d854e09 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -3,6 +3,7 @@ import static roomescape.exception.ExceptionType.EMPTY_DESCRIPTION; import static roomescape.exception.ExceptionType.EMPTY_NAME; import static roomescape.exception.ExceptionType.EMPTY_THUMBNAIL; +import static roomescape.exception.ExceptionType.NOT_URL_BASE_THUMBNAIL; import java.util.Objects; import roomescape.exception.RoomescapeException; @@ -43,6 +44,10 @@ private void validateThumbnail(String thumbnail) { if (thumbnail == null || thumbnail.isBlank()) { throw new RoomescapeException(EMPTY_THUMBNAIL); } + + if (!thumbnail.startsWith("http://") && !thumbnail.startsWith("https://")) { + throw new RoomescapeException(NOT_URL_BASE_THUMBNAIL); + } } public Theme(String name, String description, String thumbnail) { diff --git a/src/main/java/roomescape/exception/ExceptionType.java b/src/main/java/roomescape/exception/ExceptionType.java index e0d16e572c..7f71d71bcf 100644 --- a/src/main/java/roomescape/exception/ExceptionType.java +++ b/src/main/java/roomescape/exception/ExceptionType.java @@ -11,6 +11,7 @@ public enum ExceptionType { EMPTY_THEME(BAD_REQUEST, "테마는 필수값 입니다."), EMPTY_DESCRIPTION(BAD_REQUEST, "테마 설명은 필수값 입니다."), EMPTY_THUMBNAIL(BAD_REQUEST, "테마 썸네일은 필수값 입니다."), + NOT_URL_BASE_THUMBNAIL(BAD_REQUEST, "테마 썸네일이 url 형태가 아닙니다."), PAST_TIME_RESERVATION(BAD_REQUEST, "이미 지난 시간에 예약할 수 없습니다."), DUPLICATE_RESERVATION(BAD_REQUEST, "같은 시간에 이미 예약이 존재합니다."), DUPLICATE_RESERVATION_TIME(BAD_REQUEST, "이미 예약시간이 존재합니다."), diff --git a/src/test/java/roomescape/controller/ReservationControllerTest.java b/src/test/java/roomescape/controller/ReservationControllerTest.java index 277eedaba8..60a44b2504 100644 --- a/src/test/java/roomescape/controller/ReservationControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationControllerTest.java @@ -25,7 +25,7 @@ class ReservationControllerTest { private static final long TIME_ID = 1L; private static final LocalTime TIME = LocalTime.now(); private ReservationTime defaultTime = new ReservationTime(TIME_ID, TIME); - private Theme defualtTheme = new Theme("name", "description", "thumbnail"); + private Theme defualtTheme = new Theme("name", "description", "http://thumbnail"); private CollectionReservationRepository collectionReservationRepository; private ReservationController reservationController; diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index 6c20ebe0a0..1abdae0c0b 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -18,7 +18,7 @@ class ReservationTest { private static final LocalDate DEFAULT_DATE = LocalDate.now(); private static final ReservationTime DEFAULT_TIME = new ReservationTime(1L, LocalTime.now()); - private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "썸네일"); + private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "http://썸네일"); @DisplayName("생성 테스트") @Test diff --git a/src/test/java/roomescape/domain/ThemeTest.java b/src/test/java/roomescape/domain/ThemeTest.java index a5b3e088d4..c2b359ae87 100644 --- a/src/test/java/roomescape/domain/ThemeTest.java +++ b/src/test/java/roomescape/domain/ThemeTest.java @@ -7,6 +7,7 @@ import static roomescape.exception.ExceptionType.EMPTY_DESCRIPTION; import static roomescape.exception.ExceptionType.EMPTY_NAME; import static roomescape.exception.ExceptionType.EMPTY_THUMBNAIL; +import static roomescape.exception.ExceptionType.NOT_URL_BASE_THUMBNAIL; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -18,25 +19,28 @@ class ThemeTest { @Test void constructTest() { assertAll( - () -> assertThatThrownBy(() -> new Theme(null, "description", "thumbnail")) + () -> assertThatThrownBy(() -> new Theme(null, "description", "http://thumbnail")) .isInstanceOf(RoomescapeException.class) .hasMessage(EMPTY_NAME.getMessage()), - () -> assertThatThrownBy(() -> new Theme("name", null, "thumbnail")) + () -> assertThatThrownBy(() -> new Theme("name", null, "http://thumbnail")) .isInstanceOf(RoomescapeException.class) .hasMessage(EMPTY_DESCRIPTION.getMessage()), () -> assertThatThrownBy(() -> new Theme("name", "description", null)) .isInstanceOf(RoomescapeException.class) .hasMessage(EMPTY_THUMBNAIL.getMessage()), + () -> assertThatThrownBy(() -> new Theme("name", "description", "not url")) + .isInstanceOf(RoomescapeException.class) + .hasMessage(NOT_URL_BASE_THUMBNAIL.getMessage()), - () -> assertThatCode(() -> new Theme("name", "description", "thumbnail")) + () -> assertThatCode(() -> new Theme("name", "description", "http://thumbnail")) .doesNotThrowAnyException(), - () -> assertThatCode(() -> new Theme(null, "name", "description", "thumbnail")) + () -> assertThatCode(() -> new Theme(null, "name", "description", "http://thumbnail")) .doesNotThrowAnyException(), - () -> assertThatCode(() -> new Theme(1L, "name", "description", "thumbnail")) + () -> assertThatCode(() -> new Theme(1L, "name", "description", "http://thumbnail")) .doesNotThrowAnyException() ); } @@ -45,14 +49,14 @@ void constructTest() { @Test void equalsTest() { assertAll( - () -> assertThat(new Theme(1L, "name", "description", "thumbnail")) - .isEqualTo(new Theme(1L, "otherName", "otherDescription", "otherThumbnail")), + () -> assertThat(new Theme(1L, "name", "description", "http://thumbnail")) + .isEqualTo(new Theme(1L, "otherName", "otherDescription", "http://otherThumbnail")), - () -> assertThat(new Theme(1L, "sameName", "sameDescription", "sameThumbnail")) - .isNotEqualTo(new Theme(2L, "sameName", "sameDescription", "sameThumbnail")), + () -> assertThat(new Theme(1L, "sameName", "sameDescription", "http://sameThumbnail")) + .isNotEqualTo(new Theme(2L, "sameName", "sameDescription", "http://sameThumbnail")), - () -> assertThat(new Theme(1L, "sameName", "sameDescription", "sameThumbnail")) - .isNotEqualTo(new Theme(null, "sameName", "sameDescription", "sameThumbnail")) + () -> assertThat(new Theme(1L, "sameName", "sameDescription", "http://sameThumbnail")) + .isNotEqualTo(new Theme(null, "sameName", "sameDescription", "http://sameThumbnail")) ); } } diff --git a/src/test/java/roomescape/integration/AdminIntegrationTest.java b/src/test/java/roomescape/integration/AdminIntegrationTest.java index 2db65f8542..73d9bffb46 100644 --- a/src/test/java/roomescape/integration/AdminIntegrationTest.java +++ b/src/test/java/roomescape/integration/AdminIntegrationTest.java @@ -28,14 +28,14 @@ public class AdminIntegrationTest { @BeforeEach void init() { - jdbcTemplate.update("delete from reservation"); - jdbcTemplate.update("ALTER TABLE reservation alter column id restart with 1"); - jdbcTemplate.update("delete from reservation_time"); - jdbcTemplate.update("ALTER TABLE reservation_time alter column id restart with 1"); - jdbcTemplate.update("insert into reservation_time(start_at) values('11:56')"); - jdbcTemplate.update("delete from THEME"); - jdbcTemplate.update("ALTER TABLE THEME alter column id restart with 1"); - jdbcTemplate.update("insert into THEME values ( 1,'a','a','a')"); + jdbcTemplate.update("DELETE FROM reservation"); + jdbcTemplate.update("ALTER TABLE reservation ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("DELETE FROM reservation_time"); + jdbcTemplate.update("ALTER TABLE reservation_time ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("INSERT INTO reservation_time(start_at) VALUES('11:56')"); + jdbcTemplate.update("DELETE FROM theme"); + jdbcTemplate.update("ALTER TABLE theme ALTER COLUMN id RESTART WITH 1"); + jdbcTemplate.update("INSERT INTO theme VALUES ( 1,'name','description','http://thumbnail')"); RestAssured.port = port; } @@ -66,7 +66,7 @@ void adminReservationPageLoad() { @Test @DisplayName("관리자 예약 페이지가 잘 동작한다.") void adminReservationPageWork() { - Integer integer = jdbcTemplate.queryForObject("select id from reservation_time", + Integer integer = jdbcTemplate.queryForObject("SELECT id FROM reservation_time", Integer.class); System.out.println(integer); Map<String, Object> params = new HashMap<>(); @@ -117,7 +117,7 @@ void adminReservationPageWorkWithDB() { .then().log().all() .statusCode(201); - Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + Integer count = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM reservation", Integer.class); Assertions.assertThat(count).isEqualTo(1); RestAssured.given().log().all() @@ -125,7 +125,7 @@ void adminReservationPageWorkWithDB() { .then().log().all() .statusCode(204); - Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM reservation", Integer.class); Assertions.assertThat(countAfterDelete).isEqualTo(0); } diff --git a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java index 54dcd71f38..f51d126f1b 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateReservationRepositoryTest.java @@ -19,7 +19,7 @@ @SpringBootTest class JdbcTemplateReservationRepositoryTest { private static final ReservationTime DEFAULT_TIME = new ReservationTime(1L, LocalTime.of(11, 56)); - private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "썸네일"); + private static final Theme DEFAULT_THEME = new Theme(1L, "이름", "설명", "http://썸네일"); @Autowired private ReservationRepository reservationRepository; @@ -38,7 +38,7 @@ void init() { jdbcTemplate.update("DELETE FROM theme"); jdbcTemplate.update("ALTER TABLE theme ALTER COLUMN id RESTART WITH 1"); jdbcTemplate.update( - "INSERT INTO theme (name, description, thumbnail) VALUES('name', 'description', 'thumbnail')"); + "INSERT INTO theme (name, description, thumbnail) VALUES('name', 'description', 'http://thumbnail')"); } diff --git a/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java b/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java index 5f10e6cda7..60ea789407 100644 --- a/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java +++ b/src/test/java/roomescape/repository/JdbcTemplateThemeRepositoryTest.java @@ -49,9 +49,9 @@ void findAll() { @Test void findAndOrderByPopularity() { - Theme theme1 = themeRepository.save(new Theme("name1", "description1", "thumbnail1")); - Theme theme2 = themeRepository.save(new Theme("name2", "description2", "thumbnail2")); - Theme theme3 = themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + Theme theme1 = themeRepository.save(new Theme("name1", "description1", "http://thumbnail1")); + Theme theme2 = themeRepository.save(new Theme("name2", "description2", "http://thumbnail2")); + Theme theme3 = themeRepository.save(new Theme("name3", "description3", "http://thumbnail3")); ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(1, 30))); ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(2, 30))); @@ -75,7 +75,7 @@ void findAndOrderByPopularity() { @Test @DisplayName("테마가 잘 지워지는지 확인") void delete() { - Theme theme = themeRepository.save(new Theme("name1", "description1", "thumbnail")); + Theme theme = themeRepository.save(new Theme("name1", "description1", "http://thumbnail")); themeRepository.delete(theme.getId()); diff --git a/src/test/java/roomescape/service/AvailableTimeServiceTest.java b/src/test/java/roomescape/service/AvailableTimeServiceTest.java index 24383b8577..cc953821c4 100644 --- a/src/test/java/roomescape/service/AvailableTimeServiceTest.java +++ b/src/test/java/roomescape/service/AvailableTimeServiceTest.java @@ -38,7 +38,7 @@ void init() { @Test void findAvailableTimeTest() { //given - Theme DEFUALT_THEME = new Theme(1L, "name", "description", "thumbnail"); + Theme DEFUALT_THEME = new Theme(1L, "name", "description", "http://thumbnail"); themeRepository.save(DEFUALT_THEME); ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(11, 0))); ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 0))); diff --git a/src/test/java/roomescape/service/ReservationServiceTest.java b/src/test/java/roomescape/service/ReservationServiceTest.java index b28d9bced8..7780d186e3 100644 --- a/src/test/java/roomescape/service/ReservationServiceTest.java +++ b/src/test/java/roomescape/service/ReservationServiceTest.java @@ -35,7 +35,7 @@ class ReservationServiceTest { private ReservationService reservationService; private ReservationTime defaultTime = new ReservationTime(LocalTime.now()); - private Theme defaultTheme = new Theme("name", "description", "thumbnail"); + private Theme defaultTheme = new Theme("name", "description", "http://thumbnail"); @BeforeEach void initService() { diff --git a/src/test/java/roomescape/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/service/ReservationTimeServiceTest.java index 0b0cd90642..38c6c7f1bf 100644 --- a/src/test/java/roomescape/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/service/ReservationTimeServiceTest.java @@ -98,7 +98,7 @@ void usedReservationTimeDeleteTest() { "name", LocalDate.now(), new ReservationTime(1L, SAVED_TIME), - new Theme(1L, "name", "description", "thumbnail") + new Theme(1L, "name", "description", "http://thumbnail") )); //when & then diff --git a/src/test/java/roomescape/service/ThemeServiceTest.java b/src/test/java/roomescape/service/ThemeServiceTest.java index 8605a8ed0e..5ea3366dbf 100644 --- a/src/test/java/roomescape/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/service/ThemeServiceTest.java @@ -45,10 +45,10 @@ void initService() { @Test void findAllTest() { //given - themeRepository.save(new Theme("name1", "description1", "thumbnail1")); - themeRepository.save(new Theme("name2", "description2", "thumbnail2")); - themeRepository.save(new Theme("name3", "description3", "thumbnail3")); - themeRepository.save(new Theme("name4", "description4", "thumbnail4")); + themeRepository.save(new Theme("name1", "description1", "http://thumbnail1")); + themeRepository.save(new Theme("name2", "description2", "http://thumbnail2")); + themeRepository.save(new Theme("name3", "description3", "http://thumbnail3")); + themeRepository.save(new Theme("name4", "description4", "http://thumbnail4")); //when List<ThemeResponse> themeResponses = themeService.findAll(); @@ -76,9 +76,9 @@ void findAndOrderByPopularity() { } private void addReservations(LocalDate date) { - Theme theme1 = themeRepository.save(new Theme("name1", "description1", "thumbnail1")); - Theme theme2 = themeRepository.save(new Theme("name2", "description2", "thumbnail2")); - Theme theme3 = themeRepository.save(new Theme("name3", "description3", "thumbnail3")); + Theme theme1 = themeRepository.save(new Theme("name1", "description1", "http://thumbnail1")); + Theme theme2 = themeRepository.save(new Theme("name2", "description2", "http://thumbnail2")); + Theme theme3 = themeRepository.save(new Theme("name3", "description3", "http://thumbnail3")); ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(1, 30))); ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(2, 30))); ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(3, 30))); @@ -100,7 +100,7 @@ private void addReservations(LocalDate date) { @Nested class OneThemeTest { private ReservationTime defaultTime = new ReservationTime(LocalTime.now().plusMinutes(5)); - private Theme defaultTheme = new Theme("name", "description", "thumbnail"); + private Theme defaultTheme = new Theme("name", "description", "http://thumbnail"); @BeforeEach void addDefaultData() { @@ -112,7 +112,7 @@ void addDefaultData() { @Test void duplicatedThemeSaveFailTest() { assertThatThrownBy(() -> themeService.save(new ThemeRequest( - defaultTheme.getName(), "description", "thumbnail" + defaultTheme.getName(), "description", "http://thumbnail" ))).isInstanceOf(RoomescapeException.class) .hasMessage(DUPLICATE_THEME.getMessage()); } @@ -120,7 +120,7 @@ void duplicatedThemeSaveFailTest() { @DisplayName("다른 이름의 테마를 예약할 수 있다.") @Test void notDuplicatedThemeNameSaveTest() { - themeService.save(new ThemeRequest("otherName", "description", "thumbnail")); + themeService.save(new ThemeRequest("otherName", "description", "http://thumbnail")); assertThat(themeRepository.findAll()) .hasSize(2); From 20fdddef794de6a7aa1ba05f1b9f0c9c37c08188 Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 13:39:39 +0900 Subject: [PATCH 74/75] =?UTF-8?q?refactor:=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{AdminController.java => AdminPageController.java} | 2 +- .../{UserController.java => UserPageController.java} | 2 +- ...inControllerTest.java => AdminPageControllerTest.java} | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/roomescape/controller/{AdminController.java => AdminPageController.java} (94%) rename src/main/java/roomescape/controller/{UserController.java => UserPageController.java} (91%) rename src/test/java/roomescape/controller/{AdminControllerTest.java => AdminPageControllerTest.java} (80%) diff --git a/src/main/java/roomescape/controller/AdminController.java b/src/main/java/roomescape/controller/AdminPageController.java similarity index 94% rename from src/main/java/roomescape/controller/AdminController.java rename to src/main/java/roomescape/controller/AdminPageController.java index 4e9a8510fa..9ca15af369 100644 --- a/src/main/java/roomescape/controller/AdminController.java +++ b/src/main/java/roomescape/controller/AdminPageController.java @@ -6,7 +6,7 @@ @Controller @RequestMapping("/admin") -public class AdminController { +public class AdminPageController { @GetMapping public String mainPage() { return "admin/index"; diff --git a/src/main/java/roomescape/controller/UserController.java b/src/main/java/roomescape/controller/UserPageController.java similarity index 91% rename from src/main/java/roomescape/controller/UserController.java rename to src/main/java/roomescape/controller/UserPageController.java index 86ecc0f8ce..ebd3c6519a 100644 --- a/src/main/java/roomescape/controller/UserController.java +++ b/src/main/java/roomescape/controller/UserPageController.java @@ -4,7 +4,7 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller -public class UserController { +public class UserPageController { @GetMapping("/reservation") public String reservationPage() { return "reservation"; diff --git a/src/test/java/roomescape/controller/AdminControllerTest.java b/src/test/java/roomescape/controller/AdminPageControllerTest.java similarity index 80% rename from src/test/java/roomescape/controller/AdminControllerTest.java rename to src/test/java/roomescape/controller/AdminPageControllerTest.java index adeb4f8b2c..fdfae03c79 100644 --- a/src/test/java/roomescape/controller/AdminControllerTest.java +++ b/src/test/java/roomescape/controller/AdminPageControllerTest.java @@ -4,11 +4,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class AdminControllerTest { +class AdminPageControllerTest { @Test @DisplayName("관리자 메인 페이지 경로를 정해진 경로로 매핑한다.") void mainPage() { - AdminController adminController = new AdminController(); + AdminPageController adminController = new AdminPageController(); String mainPage = adminController.mainPage(); Assertions.assertThat(mainPage) .isEqualTo("admin/index"); @@ -17,7 +17,7 @@ void mainPage() { @Test @DisplayName("관리자 예약 정보 페이지 경로를 정해진 경로로 매핑한다.") void reservationPage() { - AdminController adminController = new AdminController(); + AdminPageController adminController = new AdminPageController(); String reservationPage = adminController.reservationPage(); Assertions.assertThat(reservationPage) .isEqualTo("admin/reservation-new"); @@ -26,7 +26,7 @@ void reservationPage() { @Test @DisplayName("시간 관리 페이지 경로를 정해진 경로로 매핑한다.") void reservationTimePage() { - AdminController adminController = new AdminController(); + AdminPageController adminController = new AdminPageController(); String reservationTimePage = adminController.reservationTimePage(); Assertions.assertThat(reservationTimePage) .isEqualTo("admin/time"); From e12b17e86f564fff407dd8b04ae65b24fc64668d Mon Sep 17 00:00:00 2001 From: robinjoon <robin980108@naver.com> Date: Tue, 7 May 2024 13:57:50 +0900 Subject: [PATCH 75/75] =?UTF-8?q?refactor:=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EC=8B=9C=EA=B0=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EB=AA=85=EC=84=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../controller/AvailableTimeController.java | 26 ------------------- .../controller/ReservationTimeController.java | 14 +++++++++- .../resources/static/js/user-reservation.js | 2 +- .../ReservationTimeControllerTest.java | 7 ++++- 5 files changed, 21 insertions(+), 30 deletions(-) delete mode 100644 src/main/java/roomescape/controller/AvailableTimeController.java diff --git a/README.md b/README.md index f3265c68e4..f81fffe0bc 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ ### Request -> GET /availableTimes?date=${date}&themeId=${themeId} +> GET /times/book-able?date=${date}&themeId=${themeId} ### response diff --git a/src/main/java/roomescape/controller/AvailableTimeController.java b/src/main/java/roomescape/controller/AvailableTimeController.java deleted file mode 100644 index ffc8f65829..0000000000 --- a/src/main/java/roomescape/controller/AvailableTimeController.java +++ /dev/null @@ -1,26 +0,0 @@ -package roomescape.controller; - -import java.time.LocalDate; -import java.util.List; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import roomescape.dto.AvailableTimeResponse; -import roomescape.service.AvailableTimeService; - -@RestController -@RequestMapping("/availableTimes") -public class AvailableTimeController { - - private final AvailableTimeService availableTimeService; - - public AvailableTimeController(AvailableTimeService availableTimeService) { - this.availableTimeService = availableTimeService; - } - - @GetMapping() - public List<AvailableTimeResponse> findByThemeAndDate(@RequestParam LocalDate date, @RequestParam long themeId) { - return availableTimeService.findByThemeAndDate(date, themeId); - } -} diff --git a/src/main/java/roomescape/controller/ReservationTimeController.java b/src/main/java/roomescape/controller/ReservationTimeController.java index 868e43f4b1..bc247105ad 100644 --- a/src/main/java/roomescape/controller/ReservationTimeController.java +++ b/src/main/java/roomescape/controller/ReservationTimeController.java @@ -1,6 +1,7 @@ package roomescape.controller; import java.net.URI; +import java.time.LocalDate; import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -9,18 +10,24 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import roomescape.dto.AvailableTimeResponse; import roomescape.dto.ReservationTimeRequest; import roomescape.dto.ReservationTimeResponse; +import roomescape.service.AvailableTimeService; import roomescape.service.ReservationTimeService; @RestController @RequestMapping("/times") public class ReservationTimeController { private final ReservationTimeService reservationTimeService; + private final AvailableTimeService availableTimeService; - public ReservationTimeController(ReservationTimeService reservationTimeService) { + public ReservationTimeController(ReservationTimeService reservationTimeService, + AvailableTimeService availableTimeService) { this.reservationTimeService = reservationTimeService; + this.availableTimeService = availableTimeService; } @PostMapping @@ -35,6 +42,11 @@ public List<ReservationTimeResponse> findAll() { return reservationTimeService.findAll(); } + @GetMapping("/book-able") + public List<AvailableTimeResponse> findByThemeAndDate(@RequestParam LocalDate date, @RequestParam long themeId) { + return availableTimeService.findByThemeAndDate(date, themeId); + } + @DeleteMapping("/{id}") public ResponseEntity<Void> delete(@PathVariable long id) { reservationTimeService.delete(id); diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js index 12a26c866a..27b4c5ef94 100644 --- a/src/main/resources/static/js/user-reservation.js +++ b/src/main/resources/static/js/user-reservation.js @@ -82,7 +82,7 @@ function checkDateAndTheme() { } function fetchAvailableTimes(date, themeId) { - fetch(`/availableTimes?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint + fetch(`/times/book-able?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java index c6082b30a5..9bf390eb6a 100644 --- a/src/test/java/roomescape/controller/ReservationTimeControllerTest.java +++ b/src/test/java/roomescape/controller/ReservationTimeControllerTest.java @@ -12,6 +12,8 @@ import roomescape.dto.ReservationTimeResponse; import roomescape.repository.CollectionReservationRepository; import roomescape.repository.CollectionReservationTimeRepository; +import roomescape.repository.CollectionThemeRepository; +import roomescape.service.AvailableTimeService; import roomescape.service.ReservationTimeService; class ReservationTimeControllerTest { @@ -25,7 +27,10 @@ void init() { CollectionReservationRepository reservationRepository = new CollectionReservationRepository(); ReservationTimeService reservationTimeService = new ReservationTimeService(reservationRepository, reservationTimeRepository); - reservationTimeController = new ReservationTimeController(reservationTimeService); + CollectionThemeRepository themeRepository = new CollectionThemeRepository(); + AvailableTimeService availableTimeService = new AvailableTimeService(reservationTimeRepository, + themeRepository); + reservationTimeController = new ReservationTimeController(reservationTimeService, availableTimeService); } @Test