From 13b5d177b6ea5e62b5159c91999654ee29c7a4bf Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 8 Nov 2024 01:11:54 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20step1=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EB=A9=94=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ .../java/roomescape/controller/HomeController.java | 13 +++++++++++++ src/main/resources/application.properties | 3 +++ src/main/resources/sql/schema.sql | 8 ++++++++ 4 files changed, 28 insertions(+) create mode 100644 src/main/java/roomescape/controller/HomeController.java create mode 100644 src/main/resources/sql/schema.sql diff --git a/build.gradle b/build.gradle index 57267157c..a24379532 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,10 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-devtools' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' } diff --git a/src/main/java/roomescape/controller/HomeController.java b/src/main/java/roomescape/controller/HomeController.java new file mode 100644 index 000000000..dd7c53624 --- /dev/null +++ b/src/main/java/roomescape/controller/HomeController.java @@ -0,0 +1,13 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HomeController { + + @GetMapping("/") + public String home() { + return "home"; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..f597afeef 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console +spring.datasource.url=jdbc:h2:mem:database \ No newline at end of file diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql new file mode 100644 index 000000000..8d9ab2754 --- /dev/null +++ b/src/main/resources/sql/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE reservation +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + date VARCHAR(255) NOT NULL, + time VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); From 06f5ee5c93af099ddaf929e394ec40841e43c979 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 8 Nov 2024 01:54:13 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20step2=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=84=EC=9A=B0=EA=B8=B0,=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 40 +++++++++++++++++++ .../java/roomescape/entity/Reservation.java | 23 +++++++++++ src/test/java/roomescape/MissionStepTest.java | 16 ++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/main/java/roomescape/controller/ReservationController.java create mode 100644 src/main/java/roomescape/entity/Reservation.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java new file mode 100644 index 000000000..c333f60cf --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -0,0 +1,40 @@ +package roomescape.controller; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import roomescape.entity.Reservation; + +@RestController +public class ReservationController { + + private List reservations = new ArrayList<>(); + + @GetMapping("/reservation") + public String reservation() { + return "reservation"; + } + + @GetMapping("/reservations") + public List reservations() { + reservations = List.of( + new Reservation( + 1, + "브라운", + "2023-01-01", + "10:00" + ), + new Reservation( + 2, + "브라운", + "2023-01-02", + "11:00" + ) + ); + return reservations; + } + + + +} diff --git a/src/main/java/roomescape/entity/Reservation.java b/src/main/java/roomescape/entity/Reservation.java new file mode 100644 index 000000000..e039be1cf --- /dev/null +++ b/src/main/java/roomescape/entity/Reservation.java @@ -0,0 +1,23 @@ +package roomescape.entity; + +public class Reservation { + private long id; + private String date; + private String time; + private String description; + + public Reservation(String date, String time, String description) { + this.date = date; + this.time = time; + this.description = description; + } + public Reservation(long id, String date, String time, String description) { + this.id = id; + } + + public long getId() { return id; } + public String getDate() { return date; } + public String getTime() { return time; } + public String getDescription() { return description; } +} + diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cf4efbe91..38964009b 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,5 +1,7 @@ package roomescape; +import static org.hamcrest.Matchers.is; + import io.restassured.RestAssured; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -16,4 +18,18 @@ public class MissionStepTest { .then().log().all() .statusCode(200); } + + @Test + void 이단계() { + RestAssured.given().log().all() + .when().get("/reservation") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(2)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } } From cfdca16d1fcc12f0c8a202318829b87805fd04e9 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 8 Nov 2024 21:40:51 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20step3=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/controller/HomeController.java | 13 ----- .../controller/ReservationController.java | 53 +++++++++++++------ .../java/roomescape/entity/Reservation.java | 19 ++++--- src/test/java/roomescape/MissionStepTest.java | 47 ++++++++++++++-- 4 files changed, 92 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/roomescape/controller/HomeController.java diff --git a/src/main/java/roomescape/controller/HomeController.java b/src/main/java/roomescape/controller/HomeController.java deleted file mode 100644 index dd7c53624..000000000 --- a/src/main/java/roomescape/controller/HomeController.java +++ /dev/null @@ -1,13 +0,0 @@ -package roomescape.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class HomeController { - - @GetMapping("/") - public String home() { - return "home"; - } -} diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index c333f60cf..736e0f607 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,40 +1,59 @@ package roomescape.controller; +import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +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.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import roomescape.entity.Reservation; -@RestController +@Controller public class ReservationController { private List reservations = new ArrayList<>(); + private AtomicLong index = new AtomicLong(1); + + @GetMapping("/") + public String home() { + return "home"; + } + @GetMapping("/reservation") public String reservation() { return "reservation"; } @GetMapping("/reservations") - public List reservations() { - reservations = List.of( - new Reservation( - 1, - "브라운", - "2023-01-01", - "10:00" - ), - new Reservation( - 2, - "브라운", - "2023-01-02", - "11:00" - ) - ); + @ResponseBody + public List read() { return reservations; } + @PostMapping("/reservations") + @ResponseBody + public ResponseEntity create(@RequestBody Reservation reservation) { + Reservation newReservation = reservation.toEntity(reservation, index.getAndIncrement()); + reservations.add(newReservation); + return ResponseEntity + .created(URI.create("/reservations/" + newReservation.getId())) + .body(newReservation); + } + @DeleteMapping("/reservations/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable("id") Long id) { + reservations.removeIf(reservation -> reservation.getId() == id); + } } diff --git a/src/main/java/roomescape/entity/Reservation.java b/src/main/java/roomescape/entity/Reservation.java index e039be1cf..43925915a 100644 --- a/src/main/java/roomescape/entity/Reservation.java +++ b/src/main/java/roomescape/entity/Reservation.java @@ -2,22 +2,27 @@ public class Reservation { private long id; + private String name; private String date; private String time; - private String description; - public Reservation(String date, String time, String description) { + + public Reservation(String name, String date, String time) { + this.name = name; this.date = date; this.time = time; - this.description = description; - } - public Reservation(long id, String date, String time, String description) { - this.id = id; } public long getId() { return id; } + public String getName() { return name; } public String getDate() { return date; } public String getTime() { return time; } - public String getDescription() { return description; } + + private void setId(long id) { this.id = id; } + + public Reservation toEntity(Reservation reservation, Long id) { + reservation.setId(id); + return reservation; + } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 38964009b..2d1bd560b 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -3,6 +3,10 @@ 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.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; @@ -12,7 +16,8 @@ public class MissionStepTest { @Test - void 일단계() { + @DisplayName("1단계 홈 화면 불러오기") + void step1() { RestAssured.given().log().all() .when().get("/") .then().log().all() @@ -20,7 +25,8 @@ public class MissionStepTest { } @Test - void 이단계() { + @DisplayName("2단계 예약 목록 조회하기") + void step2() { RestAssured.given().log().all() .when().get("/reservation") .then().log().all() @@ -30,6 +36,41 @@ public class MissionStepTest { .when().get("/reservations") .then().log().all() .statusCode(200) - .body("size()", is(2)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + .body("size()", is(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } + + @Test + @DisplayName("3단계 예약 목록 추가 및 삭제하기") + void step3() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "15:40"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1") + .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(204); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); } } From 28cd4eeb19022b13c7a50a6061e6a13658383a26 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 8 Nov 2024 22:40:25 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20step4=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EC=8B=9C=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 17 ++++++++++++-- .../java/roomescape/entity/Reservation.java | 4 ++++ .../NotFoundReservationException.java | 7 ++++++ src/test/java/roomescape/MissionStepTest.java | 23 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/main/java/roomescape/exception/NotFoundReservationException.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 736e0f607..0c95b535f 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -7,8 +7,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import roomescape.entity.Reservation; +import roomescape.exception.NotFoundReservationException; @Controller public class ReservationController { @@ -43,6 +44,9 @@ public List read() { @PostMapping("/reservations") @ResponseBody public ResponseEntity create(@RequestBody Reservation reservation) { + if (reservation == null || reservation.isEmpty()) { + throw new IllegalArgumentException("reservation cannot be empty"); + } Reservation newReservation = reservation.toEntity(reservation, index.getAndIncrement()); reservations.add(newReservation); @@ -54,6 +58,15 @@ public ResponseEntity create(@RequestBody Reservation reservation) @DeleteMapping("/reservations/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable("id") Long id) { - reservations.removeIf(reservation -> reservation.getId() == id); + Reservation target = reservations.stream() + .filter(reservation -> reservation.getId() == id).findAny() + .orElseThrow(() -> new NotFoundReservationException(id)); + + reservations.remove(target); + } + + @ExceptionHandler({NotFoundReservationException.class, IllegalArgumentException.class}) + public ResponseEntity handleException(Exception e) { + return ResponseEntity.badRequest().build(); } } diff --git a/src/main/java/roomescape/entity/Reservation.java b/src/main/java/roomescape/entity/Reservation.java index 43925915a..0b34f45df 100644 --- a/src/main/java/roomescape/entity/Reservation.java +++ b/src/main/java/roomescape/entity/Reservation.java @@ -24,5 +24,9 @@ public Reservation toEntity(Reservation reservation, Long id) { reservation.setId(id); return reservation; } + + public boolean isEmpty() { + return name == null || date == null || time == null || name.isEmpty() || date.isEmpty() || time.isEmpty(); + } } diff --git a/src/main/java/roomescape/exception/NotFoundReservationException.java b/src/main/java/roomescape/exception/NotFoundReservationException.java new file mode 100644 index 000000000..00fe415af --- /dev/null +++ b/src/main/java/roomescape/exception/NotFoundReservationException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class NotFoundReservationException extends RuntimeException { + private static final String message = "cannot find reservation"; + + public NotFoundReservationException(Long id) { super(message + " with id: " + id); } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 2d1bd560b..18933ad6f 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -73,4 +73,27 @@ void step3() { .statusCode(200) .body("size()", is(0)); } + + @Test + @DisplayName("4단계 예약 목록 추가 및 삭제시 예외처리") + void step4() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", ""); + params.put("time", ""); + + // 필요한 인자가 없는 경우 + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(400); + + // 삭제할 예약이 없는 경우 + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(400); + } } From d04bd4653e1859801f27b69641b0fdcd3f8bb6b9 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 8 Nov 2024 23:06:39 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20step5=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=97=B0=EB=8F=99?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../roomescape/RoomescapeApplication.java | 1 - src/main/resources/application.properties | 4 +++- src/test/java/roomescape/MissionStepTest.java | 20 +++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a24379532..53ad2f3fb 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 702706791..2ca0f743f 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -8,5 +8,4 @@ public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); } - } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f597afeef..21a31d7b7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,5 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2-console -spring.datasource.url=jdbc:h2:mem:database \ No newline at end of file +spring.datasource.url=jdbc:h2:mem:database +spring.datasource.username=sa +spring.sql.init.schema-locations=classpath:sql/schema.sql diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 18933ad6f..85cd64c28 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,14 +1,19 @@ package roomescape; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import java.sql.Connection; +import java.sql.SQLException; import java.util.HashMap; import java.util.Map; 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 org.springframework.test.annotation.DirtiesContext; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -96,4 +101,19 @@ void step4() { .then().log().all() .statusCode(400); } + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + @DisplayName("5단계 JdbcTemplate을 이용하여 h2 데이터베이스 연동하기") + void step5() { + try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { + assertThat(connection).isNotNull(); + assertThat(connection.getCatalog()).isEqualTo("DATABASE"); + assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } } From dff01001388a3c46667de77fe5c81be0c1437fe3 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Sun, 10 Nov 2024 14:24:03 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20step6,=20step7=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=B6=94=EA=B0=80,=20=EC=82=AD=EC=A0=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../controller/ReservationController.java | 71 ++++-- src/main/java/roomescape/dto/Reservation.java | 30 +++ .../java/roomescape/entity/Reservation.java | 32 --- src/main/resources/application.properties | 4 +- src/test/java/roomescape/MissionStepTest.java | 227 +++++++++++------- 6 files changed, 225 insertions(+), 142 deletions(-) create mode 100644 src/main/java/roomescape/dto/Reservation.java delete mode 100644 src/main/java/roomescape/entity/Reservation.java diff --git a/build.gradle b/build.gradle index 53ad2f3fb..da136e045 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-jdbc' runtimeOnly 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 0c95b535f..577dbb6c9 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,12 +1,21 @@ package roomescape.controller; +import jakarta.validation.Valid; import java.net.URI; -import java.util.ArrayList; +import java.sql.PreparedStatement; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +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.Controller; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; @@ -15,15 +24,22 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; -import roomescape.entity.Reservation; +import roomescape.dto.Reservation; import roomescape.exception.NotFoundReservationException; @Controller public class ReservationController { - private List reservations = new ArrayList<>(); + @Autowired + JdbcTemplate jdbcTemplate; - private AtomicLong index = new AtomicLong(1); + private final RowMapper rowMapper = (resultSet, rowNum) -> + new Reservation( + resultSet.getLong("id"), + resultSet.getString("name"), + resultSet.getString("date"), + resultSet.getString("time") + ); @GetMapping("/") public String home() { @@ -37,35 +53,46 @@ public String reservation() { @GetMapping("/reservations") @ResponseBody - public List read() { - return reservations; + public List findAll() { + return jdbcTemplate.query("select id, name, date, time from reservation", rowMapper); } @PostMapping("/reservations") @ResponseBody - public ResponseEntity create(@RequestBody Reservation reservation) { - if (reservation == null || reservation.isEmpty()) { - throw new IllegalArgumentException("reservation cannot be empty"); - } - Reservation newReservation = reservation.toEntity(reservation, index.getAndIncrement()); - reservations.add(newReservation); + public ResponseEntity create(@RequestBody @Valid Reservation reservation) { + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + String insertSql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)"; + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"}); + ps.setString(1, reservation.getName()); + ps.setString(2, reservation.getDate()); + ps.setString(3, reservation.getTime()); + return ps; + }, keyHolder); + + Long generatedId = keyHolder.getKey().longValue(); + reservation.setId(generatedId); - return ResponseEntity - .created(URI.create("/reservations/" + newReservation.getId())) - .body(newReservation); + URI location = URI.create("/reservations/" + generatedId); + + return ResponseEntity.created(location).body(reservation); } @DeleteMapping("/reservations/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(@PathVariable("id") Long id) { - Reservation target = reservations.stream() - .filter(reservation -> reservation.getId() == id).findAny() - .orElseThrow(() -> new NotFoundReservationException(id)); + public ResponseEntity delete(@PathVariable("id") Long id) { + int rowsAffected = jdbcTemplate.update("delete from reservation where id = ?", id); + + if (rowsAffected == 0) { + throw new NotFoundReservationException(id); + } - reservations.remove(target); + return ResponseEntity.noContent().build(); } - @ExceptionHandler({NotFoundReservationException.class, IllegalArgumentException.class}) + @ExceptionHandler({NotFoundReservationException.class, MethodArgumentNotValidException.class}) public ResponseEntity handleException(Exception e) { return ResponseEntity.badRequest().build(); } diff --git a/src/main/java/roomescape/dto/Reservation.java b/src/main/java/roomescape/dto/Reservation.java new file mode 100644 index 000000000..859eb50d3 --- /dev/null +++ b/src/main/java/roomescape/dto/Reservation.java @@ -0,0 +1,30 @@ +package roomescape.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Reservation { + private long id; + + @NotBlank + private String name; + + @NotBlank + private String date; + + @NotBlank + private String time; + + public Reservation(String name, String date, String time) { + this.name = name; + this.date = date; + this.time = time; + } +} diff --git a/src/main/java/roomescape/entity/Reservation.java b/src/main/java/roomescape/entity/Reservation.java deleted file mode 100644 index 0b34f45df..000000000 --- a/src/main/java/roomescape/entity/Reservation.java +++ /dev/null @@ -1,32 +0,0 @@ -package roomescape.entity; - -public class Reservation { - private long id; - private String name; - private String date; - private String time; - - - public Reservation(String name, String date, String time) { - this.name = name; - this.date = date; - this.time = time; - } - - public long getId() { return id; } - public String getName() { return name; } - public String getDate() { return date; } - public String getTime() { return time; } - - private void setId(long id) { this.id = id; } - - public Reservation toEntity(Reservation reservation, Long id) { - reservation.setId(id); - return reservation; - } - - public boolean isEmpty() { - return name == null || date == null || time == null || name.isEmpty() || date.isEmpty() || time.isEmpty(); - } -} - diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 21a31d7b7..c55ceb151 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2-console -spring.datasource.url=jdbc:h2:mem:database +spring.datasource.url=jdbc:h2:mem:database;DATABASE_TO_UPPER=false spring.datasource.username=sa -spring.sql.init.schema-locations=classpath:sql/schema.sql +spring.sql.init.schema-locations=classpath:sql/schema.sql \ No newline at end of file diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 85cd64c28..68b33f476 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -8,112 +8,167 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; 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 org.springframework.test.annotation.DirtiesContext; +import roomescape.dto.Reservation; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { - @Test - @DisplayName("1단계 홈 화면 불러오기") - void step1() { - RestAssured.given().log().all() + @Nested + public class SpringMvcTest { + + @Test + @DisplayName("1단계 홈 화면 불러오기") + void step1() { + RestAssured.given().log().all() .when().get("/") .then().log().all() .statusCode(200); - } + } - @Test - @DisplayName("2단계 예약 목록 조회하기") - void step2() { - RestAssured.given().log().all() - .when().get("/reservation") - .then().log().all() - .statusCode(200); - - RestAssured.given().log().all() - .when().get("/reservations") - .then().log().all() - .statusCode(200) - .body("size()", is(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. - } + @Test + @DisplayName("2단계 예약 목록 조회하기") + void step2() { + RestAssured.given().log().all() + .when().get("/reservation") + .then().log().all() + .statusCode(200); - @Test - @DisplayName("3단계 예약 목록 추가 및 삭제하기") - void step3() { - Map params = new HashMap<>(); - params.put("name", "브라운"); - params.put("date", "2023-08-05"); - params.put("time", "15:40"); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(params) - .when().post("/reservations") - .then().log().all() - .statusCode(201) - .header("Location", "/reservations/1") - .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(204); - - RestAssured.given().log().all() - .when().get("/reservations") - .then().log().all() - .statusCode(200) - .body("size()", is(0)); - } + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } + + @Test + @DisplayName("3단계 예약 목록 추가 및 삭제하기") + void step3() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "15:40"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1") + .body("id", is(1)); - @Test - @DisplayName("4단계 예약 목록 추가 및 삭제시 예외처리") - void step4() { - Map params = new HashMap<>(); - params.put("name", "브라운"); - params.put("date", ""); - params.put("time", ""); - - // 필요한 인자가 없는 경우 - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(params) - .when().post("/reservations") - .then().log().all() - .statusCode(400); - - // 삭제할 예약이 없는 경우 - RestAssured.given().log().all() - .when().delete("/reservations/1") - .then().log().all() - .statusCode(400); + 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(204); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); + } + + @Test + @DisplayName("4단계 예약 목록 추가 및 삭제시 예외처리") + void step4() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", ""); + params.put("time", ""); + + // 필요한 인자가 없는 경우 + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(400); + + // 삭제할 예약이 없는 경우 + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(400); + } } - @Autowired - private JdbcTemplate jdbcTemplate; - - @Test - @DisplayName("5단계 JdbcTemplate을 이용하여 h2 데이터베이스 연동하기") - void step5() { - try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { - assertThat(connection).isNotNull(); - assertThat(connection.getCatalog()).isEqualTo("DATABASE"); - assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue(); - } catch (SQLException e) { - throw new RuntimeException(e); + + + @Nested + public class SpringJdbcTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + @DisplayName("5단계 JdbcTemplate을 이용하여 h2 데이터베이스 연동하기") + void step5() { + try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { + assertThat(connection).isNotNull(); + assertThat(connection.getCatalog()).isEqualTo("database"); + assertThat(connection.getMetaData().getTables(null, null, "reservation", null).next()).isTrue(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("6단계 데이터베이스 조회하기") + void step6() { + jdbcTemplate.update("INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)", "브라운", "2023-08-05", "15:40"); + + List reservations = RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200).extract() + .jsonPath().getList(".", Reservation.class); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + + assertThat(reservations.size()).isEqualTo(count); + } + + @Test + @DisplayName("7단계 데이터베이스 추가 및 삭제하기") + void step7() { + Reservation reservation = new Reservation("브라운", "2023-08-05", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(reservation) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1"); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(count).isEqualTo(1); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(204); + + Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(countAfterDelete).isEqualTo(0); } + } } From 932a14b7fe7918eba9f7de924f1dcce2a6b1d648 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Fri, 15 Nov 2024 23:40:48 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20step8=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=20=EC=B6=94=EA=B0=80,=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 31 ++++---- .../controller/ScheduleController.java | 78 +++++++++++++++++++ .../roomescape/dto/RequestScheduleDTO.java | 22 ++++++ .../{Reservation.java => ReservationDTO.java} | 7 +- .../roomescape/dto/ResponseScheduleDTO.java | 20 +++++ .../repository/ReservationRepository.java | 5 ++ .../repository/ScheduleRepository.java | 5 ++ .../service/ReservationService.java | 5 ++ .../roomescape/service/ScheduleService.java | 5 ++ src/main/resources/sql/schema.sql | 7 ++ src/main/resources/templates/home.html | 2 +- .../resources/templates/new-reservation.html | 10 +-- src/main/resources/templates/reservation.html | 10 +-- src/main/resources/templates/time.html | 2 +- src/test/java/roomescape/MissionStepTest.java | 45 +++++++++-- 15 files changed, 216 insertions(+), 38 deletions(-) create mode 100644 src/main/java/roomescape/controller/ScheduleController.java create mode 100644 src/main/java/roomescape/dto/RequestScheduleDTO.java rename src/main/java/roomescape/dto/{Reservation.java => ReservationDTO.java} (70%) create mode 100644 src/main/java/roomescape/dto/ResponseScheduleDTO.java create mode 100644 src/main/java/roomescape/repository/ReservationRepository.java create mode 100644 src/main/java/roomescape/repository/ScheduleRepository.java create mode 100644 src/main/java/roomescape/service/ReservationService.java create mode 100644 src/main/java/roomescape/service/ScheduleService.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 577dbb6c9..dface7bcd 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -4,10 +4,8 @@ import java.net.URI; import java.sql.PreparedStatement; import java.util.List; +import java.util.NoSuchElementException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.core.JdbcTemplate; @@ -24,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; -import roomescape.dto.Reservation; +import roomescape.dto.ReservationDTO; import roomescape.exception.NotFoundReservationException; @Controller @@ -33,8 +31,8 @@ public class ReservationController { @Autowired JdbcTemplate jdbcTemplate; - private final RowMapper rowMapper = (resultSet, rowNum) -> - new Reservation( + private final RowMapper rowMapper = (resultSet, rowNum) -> + new ReservationDTO( resultSet.getLong("id"), resultSet.getString("name"), resultSet.getString("date"), @@ -53,47 +51,46 @@ public String reservation() { @GetMapping("/reservations") @ResponseBody - public List findAll() { + public List findAll() { return jdbcTemplate.query("select id, name, date, time from reservation", rowMapper); } @PostMapping("/reservations") @ResponseBody - public ResponseEntity create(@RequestBody @Valid Reservation reservation) { + public ResponseEntity create(@RequestBody @Valid ReservationDTO reservationDTO) { KeyHolder keyHolder = new GeneratedKeyHolder(); String insertSql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)"; jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"}); - ps.setString(1, reservation.getName()); - ps.setString(2, reservation.getDate()); - ps.setString(3, reservation.getTime()); + ps.setString(1, reservationDTO.getName()); + ps.setString(2, reservationDTO.getDate()); + ps.setString(3, reservationDTO.getTime()); return ps; }, keyHolder); Long generatedId = keyHolder.getKey().longValue(); - reservation.setId(generatedId); + reservationDTO.setId(generatedId); URI location = URI.create("/reservations/" + generatedId); - return ResponseEntity.created(location).body(reservation); + return ResponseEntity.created(location).body(reservationDTO); } @DeleteMapping("/reservations/{id}") - @ResponseStatus(HttpStatus.NO_CONTENT) public ResponseEntity delete(@PathVariable("id") Long id) { int rowsAffected = jdbcTemplate.update("delete from reservation where id = ?", id); if (rowsAffected == 0) { - throw new NotFoundReservationException(id); + throw new NoSuchElementException(); } return ResponseEntity.noContent().build(); } - @ExceptionHandler({NotFoundReservationException.class, MethodArgumentNotValidException.class}) - public ResponseEntity handleException(Exception e) { + @ExceptionHandler({NoSuchElementException.class, MethodArgumentNotValidException.class}) + public ResponseEntity handleException(Exception e) { return ResponseEntity.badRequest().build(); } } diff --git a/src/main/java/roomescape/controller/ScheduleController.java b/src/main/java/roomescape/controller/ScheduleController.java new file mode 100644 index 000000000..a4ae1a96b --- /dev/null +++ b/src/main/java/roomescape/controller/ScheduleController.java @@ -0,0 +1,78 @@ +package roomescape.controller; + +import jakarta.validation.Valid; +import java.net.URI; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.NoSuchElementException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.Controller; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +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.ResponseBody; +import roomescape.dto.RequestScheduleDTO; +import roomescape.dto.ResponseScheduleDTO; + +@Controller +public class ScheduleController { + + @Autowired + JdbcTemplate jdbcTemplate; + + private final RowMapper rowMapper = (rs, rowNum) -> + ResponseScheduleDTO.createResponseTimeDTO(rs.getLong("id"), rs.getString("time")); + + @GetMapping("/times") + @ResponseBody + public ResponseEntity> getAllSchedules() { + String selectSql = "select * from schedule"; + List schedules = jdbcTemplate.query(selectSql, rowMapper); + return new ResponseEntity<>(schedules, HttpStatus.OK); + } + + @PostMapping("/times") + @ResponseBody + public ResponseEntity createSchedule(@RequestBody @Valid RequestScheduleDTO request) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + + String insertSql = "INSERT INTO schedule (time) VALUES (?)"; + jdbcTemplate.update((connection) -> { + PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"}); + ps.setString(1, request.getTime()); + return ps; + }, keyHolder); + + Long generatedId = keyHolder.getKey().longValue(); + ResponseScheduleDTO response = ResponseScheduleDTO.createResponseTimeDTO(generatedId, request.getTime()); + URI location = URI.create("/times/" + generatedId); + return ResponseEntity.created(location).body(response); + } + + @DeleteMapping("/times/{scheduleId}") + @ResponseBody + public ResponseEntity deleteSchedule(@PathVariable Long scheduleId) { + String deleteSql = "DELETE FROM schedule WHERE id = ?"; + + int rowsAffected = jdbcTemplate.update(deleteSql, scheduleId); + if (rowsAffected == 0) { + throw new NoSuchElementException(); + } + return ResponseEntity.noContent().build(); + } + + @ExceptionHandler({NoSuchElementException.class, MethodArgumentNotValidException.class}) + public ResponseEntity handleException(Exception e) { + return ResponseEntity.badRequest().build(); + } +} diff --git a/src/main/java/roomescape/dto/RequestScheduleDTO.java b/src/main/java/roomescape/dto/RequestScheduleDTO.java new file mode 100644 index 000000000..ddf182065 --- /dev/null +++ b/src/main/java/roomescape/dto/RequestScheduleDTO.java @@ -0,0 +1,22 @@ +package roomescape.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RequestScheduleDTO { + + @NotBlank + private String time; + + public static RequestScheduleDTO createRequestTimeDTO(final String time) { + return new RequestScheduleDTO(time); + } +} diff --git a/src/main/java/roomescape/dto/Reservation.java b/src/main/java/roomescape/dto/ReservationDTO.java similarity index 70% rename from src/main/java/roomescape/dto/Reservation.java rename to src/main/java/roomescape/dto/ReservationDTO.java index 859eb50d3..0151d9de6 100644 --- a/src/main/java/roomescape/dto/Reservation.java +++ b/src/main/java/roomescape/dto/ReservationDTO.java @@ -1,6 +1,7 @@ package roomescape.dto; import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,8 +10,8 @@ @Getter @Setter @AllArgsConstructor -@NoArgsConstructor -public class Reservation { +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReservationDTO { private long id; @NotBlank @@ -22,7 +23,7 @@ public class Reservation { @NotBlank private String time; - public Reservation(String name, String date, String time) { + public ReservationDTO(String name, String date, String time) { this.name = name; this.date = date; this.time = time; diff --git a/src/main/java/roomescape/dto/ResponseScheduleDTO.java b/src/main/java/roomescape/dto/ResponseScheduleDTO.java new file mode 100644 index 000000000..ca7af97bc --- /dev/null +++ b/src/main/java/roomescape/dto/ResponseScheduleDTO.java @@ -0,0 +1,20 @@ +package roomescape.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ResponseScheduleDTO { + + private long id; + + private String time; + + public static ResponseScheduleDTO createResponseTimeDTO(final long id, final String time) { + return new ResponseScheduleDTO(id, time); + } +} diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java new file mode 100644 index 000000000..ba394169c --- /dev/null +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -0,0 +1,5 @@ +package roomescape.repository; + +public class ReservationRepository { + +} diff --git a/src/main/java/roomescape/repository/ScheduleRepository.java b/src/main/java/roomescape/repository/ScheduleRepository.java new file mode 100644 index 000000000..3374f9c2c --- /dev/null +++ b/src/main/java/roomescape/repository/ScheduleRepository.java @@ -0,0 +1,5 @@ +package roomescape.repository; + +public class ScheduleRepository { + +} diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java new file mode 100644 index 000000000..2f810dee1 --- /dev/null +++ b/src/main/java/roomescape/service/ReservationService.java @@ -0,0 +1,5 @@ +package roomescape.service; + +public class ReservationService { + +} diff --git a/src/main/java/roomescape/service/ScheduleService.java b/src/main/java/roomescape/service/ScheduleService.java new file mode 100644 index 000000000..869b1ce30 --- /dev/null +++ b/src/main/java/roomescape/service/ScheduleService.java @@ -0,0 +1,5 @@ +package roomescape.service; + +public class ScheduleService { + +} diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index 8d9ab2754..1076085ea 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -6,3 +6,10 @@ CREATE TABLE reservation time VARCHAR(255) NOT NULL, PRIMARY KEY (id) ); + +CREATE TABLE schedule +( + id BIGINT NOT NULL AUTO_INCREMENT, + time VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 7ddf752a6..bf759dd94 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -27,7 +27,7 @@
  • - 예약관리 + 예약관리
  • diff --git a/src/main/resources/templates/new-reservation.html b/src/main/resources/templates/new-reservation.html index ae49f5ad0..711ff32cc 100644 --- a/src/main/resources/templates/new-reservation.html +++ b/src/main/resources/templates/new-reservation.html @@ -9,7 +9,7 @@ - + @@ -29,7 +29,7 @@
    • - 예약관리 + 예약관리
    • @@ -42,7 +42,7 @@

      예약 관리

      - +
      @@ -55,7 +55,7 @@

      예약 관리

      - +
      @@ -63,7 +63,7 @@

      예약 관리

      - + \ No newline at end of file diff --git a/src/main/resources/templates/reservation.html b/src/main/resources/templates/reservation.html index 7c0eb9fc4..4ac837f19 100644 --- a/src/main/resources/templates/reservation.html +++ b/src/main/resources/templates/reservation.html @@ -9,7 +9,7 @@ - + @@ -29,7 +29,7 @@
      • - 예약관리 + 예약관리
      • @@ -42,7 +42,7 @@

        예약 관리

        - +
        @@ -55,7 +55,7 @@

        예약 관리

        - +
        @@ -63,7 +63,7 @@

        예약 관리

        - + \ No newline at end of file diff --git a/src/main/resources/templates/time.html b/src/main/resources/templates/time.html index 4670aeb0f..70c6dfc22 100644 --- a/src/main/resources/templates/time.html +++ b/src/main/resources/templates/time.html @@ -28,7 +28,7 @@
        • - 예약관리 + 예약관리
        • diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 68b33f476..7a29935b8 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -17,7 +17,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.DirtiesContext; -import roomescape.dto.Reservation; +import roomescape.dto.ReservationDTO; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -134,25 +134,25 @@ void step5() { void step6() { jdbcTemplate.update("INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)", "브라운", "2023-08-05", "15:40"); - List reservations = RestAssured.given().log().all() + List reservationDTOS = RestAssured.given().log().all() .when().get("/reservations") .then().log().all() .statusCode(200).extract() - .jsonPath().getList(".", Reservation.class); + .jsonPath().getList(".", ReservationDTO.class); Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); - assertThat(reservations.size()).isEqualTo(count); + assertThat(reservationDTOS.size()).isEqualTo(count); } @Test @DisplayName("7단계 데이터베이스 추가 및 삭제하기") void step7() { - Reservation reservation = new Reservation("브라운", "2023-08-05", "10:00"); + ReservationDTO reservationDTO = new ReservationDTO("브라운", "2023-08-05", "10:00"); RestAssured.given().log().all() .contentType(ContentType.JSON) - .body(reservation) + .body(reservationDTO) .when().post("/reservations") .then().log().all() .statusCode(201) @@ -169,6 +169,39 @@ void step7() { Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); assertThat(countAfterDelete).isEqualTo(0); } + } + + @Nested + public class SpringCoreTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + @DisplayName("8단계 시간 추가, 삭제 및 조회하기") + void step8() { + Map params = new HashMap<>(); + params.put("time", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/times") + .then().log().all() + .statusCode(201) + .header("Location", "/times/1"); + + RestAssured.given().log().all() + .when().get("/times") + .then().log().all() + .statusCode(200) + .body("size()", is(1)); + + RestAssured.given().log().all() + .when().delete("/times/1") + .then().log().all() + .statusCode(204); + } } } From 6905d66b3d12b033e1fd5867bbb1faf23c8c8ee0 Mon Sep 17 00:00:00 2001 From: GY102912 Date: Sat, 16 Nov 2024 00:31:21 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20step9=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EC=A1=B4=20=EC=BD=94=EB=93=9C=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 --- .../controller/ReservationController.java | 46 ++++++++++++------- ...uleController.java => TimeController.java} | 31 ++++++------- .../roomescape/dto/RequestScheduleDTO.java | 22 --------- .../java/roomescape/dto/ReservationDTO.java | 31 ------------- .../roomescape/dto/ResponseScheduleDTO.java | 20 -------- .../java/roomescape/entity/Reservation.java | 19 ++++++++ src/main/java/roomescape/entity/Time.java | 18 ++++++++ ...uleRepository.java => TimeRepository.java} | 2 +- ...{ScheduleService.java => TimeService.java} | 2 +- src/main/resources/sql/schema.sql | 19 ++++---- src/main/resources/templates/home.html | 2 +- .../resources/templates/new-reservation.html | 10 ++-- src/main/resources/templates/reservation.html | 10 ++-- src/main/resources/templates/time.html | 2 +- src/test/java/roomescape/MissionStepTest.java | 31 ++++++++++--- 15 files changed, 131 insertions(+), 134 deletions(-) rename src/main/java/roomescape/controller/{ScheduleController.java => TimeController.java} (65%) delete mode 100644 src/main/java/roomescape/dto/RequestScheduleDTO.java delete mode 100644 src/main/java/roomescape/dto/ReservationDTO.java delete mode 100644 src/main/java/roomescape/dto/ResponseScheduleDTO.java create mode 100644 src/main/java/roomescape/entity/Reservation.java create mode 100644 src/main/java/roomescape/entity/Time.java rename src/main/java/roomescape/repository/{ScheduleRepository.java => TimeRepository.java} (50%) rename src/main/java/roomescape/service/{ScheduleService.java => TimeService.java} (50%) diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index dface7bcd..e96a724e6 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.NoSuchElementException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -21,9 +20,8 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; -import roomescape.dto.ReservationDTO; -import roomescape.exception.NotFoundReservationException; +import roomescape.entity.Reservation; +import roomescape.entity.Time; @Controller public class ReservationController { @@ -31,12 +29,14 @@ public class ReservationController { @Autowired JdbcTemplate jdbcTemplate; - private final RowMapper rowMapper = (resultSet, rowNum) -> - new ReservationDTO( + private final RowMapper rowMapper = (resultSet, rowNum) -> + Reservation.create( resultSet.getLong("id"), resultSet.getString("name"), resultSet.getString("date"), - resultSet.getString("time") + Time.create( + resultSet.getLong("time_id"), + resultSet.getString("time_value")) ); @GetMapping("/") @@ -46,36 +46,50 @@ public String home() { @GetMapping("/reservation") public String reservation() { - return "reservation"; + return "new-reservation"; } @GetMapping("/reservations") @ResponseBody - public List findAll() { - return jdbcTemplate.query("select id, name, date, time from reservation", rowMapper); + public List findAll() { + String selectSql = """ + SELECT + r.id as reservation_id, + r.name, + r.date, + t.id as time_id, + t.time as time_value + FROM reservation as r inner join time as t on r.time_id = t.id + """; + return jdbcTemplate.query(selectSql, rowMapper); } @PostMapping("/reservations") @ResponseBody - public ResponseEntity create(@RequestBody @Valid ReservationDTO reservationDTO) { + public ResponseEntity create(@RequestBody @Valid Reservation reservation) { KeyHolder keyHolder = new GeneratedKeyHolder(); String insertSql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)"; jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"}); - ps.setString(1, reservationDTO.getName()); - ps.setString(2, reservationDTO.getDate()); - ps.setString(3, reservationDTO.getTime()); + ps.setString(1, reservation.getName()); + ps.setString(2, reservation.getDate()); + ps.setString(3, String.valueOf(reservation.getTime().getId())); return ps; }, keyHolder); Long generatedId = keyHolder.getKey().longValue(); - reservationDTO.setId(generatedId); + Reservation response = Reservation.create( + generatedId, + reservation.getName(), + reservation.getDate(), + reservation.getTime() + ); URI location = URI.create("/reservations/" + generatedId); - return ResponseEntity.created(location).body(reservationDTO); + return ResponseEntity.created(location).body(response); } @DeleteMapping("/reservations/{id}") diff --git a/src/main/java/roomescape/controller/ScheduleController.java b/src/main/java/roomescape/controller/TimeController.java similarity index 65% rename from src/main/java/roomescape/controller/ScheduleController.java rename to src/main/java/roomescape/controller/TimeController.java index a4ae1a96b..59275b187 100644 --- a/src/main/java/roomescape/controller/ScheduleController.java +++ b/src/main/java/roomescape/controller/TimeController.java @@ -21,32 +21,31 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; -import roomescape.dto.RequestScheduleDTO; -import roomescape.dto.ResponseScheduleDTO; +import roomescape.entity.Time; @Controller -public class ScheduleController { +public class TimeController { @Autowired JdbcTemplate jdbcTemplate; - private final RowMapper rowMapper = (rs, rowNum) -> - ResponseScheduleDTO.createResponseTimeDTO(rs.getLong("id"), rs.getString("time")); + private final RowMapper