diff --git a/build.gradle b/build.gradle index 57267157c..d2b20c563 100644 --- a/build.gradle +++ b/build.gradle @@ -14,8 +14,14 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' testImplementation 'io.rest-assured:rest-assured:5.3.1' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' } test { diff --git a/src/main/java/roomescape/PreInit.java b/src/main/java/roomescape/PreInit.java new file mode 100644 index 000000000..20bc487dd --- /dev/null +++ b/src/main/java/roomescape/PreInit.java @@ -0,0 +1,23 @@ +package roomescape; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import roomescape.domain.Dto.RequestDto; +import roomescape.domain.Repository.ReservationRepository; + +@Component +@RequiredArgsConstructor +public class PreInit { + + private final ReservationRepository reservationRepository; +// @PostConstruct +// public void init() { +// RequestDto requestDto1 = new RequestDto(); +// requestDto1.setName("ju"); +// requestDto1.setTime("asdasd"); +// requestDto1.setDate("asdasd"); +// reservationRepository.save(requestDto1.toReservation()); +// +// } +} diff --git a/src/main/java/roomescape/domain/Dto/RequestDto.java b/src/main/java/roomescape/domain/Dto/RequestDto.java new file mode 100644 index 000000000..6ea1bcdd1 --- /dev/null +++ b/src/main/java/roomescape/domain/Dto/RequestDto.java @@ -0,0 +1,25 @@ +package roomescape.domain.Dto; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import roomescape.domain.Model.Reservation; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Data +public class RequestDto { + private String name; + private String date; + private String time; + + public Reservation toReservation() { + LocalDateTime reservationDateTime = LocalDateTime.of( + LocalDate.parse(date), + LocalTime.parse(time) + ); + return new Reservation(name, reservationDateTime); + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/domain/Dto/ResponseDto.java b/src/main/java/roomescape/domain/Dto/ResponseDto.java new file mode 100644 index 000000000..de5d2eb9b --- /dev/null +++ b/src/main/java/roomescape/domain/Dto/ResponseDto.java @@ -0,0 +1,34 @@ +package roomescape.domain.Dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import roomescape.domain.Model.Reservation; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +public class ResponseDto { + private Long id; + private String name; + private String date; + private String time; + + public ResponseDto(Long id, String name, String date, String time) { + this.id = id; + this.name = name; + this.date = date; + this.time = time; + } + + public static ResponseDto makeResponse(Reservation reservation) { + LocalDateTime reservationDateTime = reservation.getLocalDateTime(); + return new ResponseDto( + reservation.getId(), + reservation.getName(), + reservationDateTime.toLocalDate().toString(), + reservationDateTime.toLocalTime().toString() + ); + } + +} \ No newline at end of file diff --git a/src/main/java/roomescape/domain/Model/Reservation.java b/src/main/java/roomescape/domain/Model/Reservation.java new file mode 100644 index 000000000..4a393d268 --- /dev/null +++ b/src/main/java/roomescape/domain/Model/Reservation.java @@ -0,0 +1,26 @@ +package roomescape.domain.Model; + +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +public class Reservation { + private Long id; + private String name; + private LocalDateTime localDateTime; + + public Reservation() { + } + + public Reservation(Long id, String name, LocalDateTime localDateTime) { + this.id = id; + this.name = name; + this.localDateTime = localDateTime; + } + public Reservation(String name, LocalDateTime localDateTime) { + this.name = name; + this.localDateTime = localDateTime; + } +} diff --git a/src/main/java/roomescape/domain/Repository/BasicRepository.java b/src/main/java/roomescape/domain/Repository/BasicRepository.java new file mode 100644 index 000000000..88852795f --- /dev/null +++ b/src/main/java/roomescape/domain/Repository/BasicRepository.java @@ -0,0 +1,14 @@ +package roomescape.domain.Repository; + +import roomescape.domain.Model.Reservation; + +import java.util.List; + + +public interface BasicRepository { + public Reservation save(Reservation reservation); + + public List findAll(); + + public void deleteReservation(Long id); +} diff --git a/src/main/java/roomescape/domain/Repository/JdbcReservationRepository.java b/src/main/java/roomescape/domain/Repository/JdbcReservationRepository.java new file mode 100644 index 000000000..bf16e2fc3 --- /dev/null +++ b/src/main/java/roomescape/domain/Repository/JdbcReservationRepository.java @@ -0,0 +1,56 @@ +package roomescape.domain.Repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.Model.Reservation; +import roomescape.domain.exception.NotFoundReservationException; + +import java.sql.PreparedStatement; +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JdbcReservationRepository implements BasicRepository{ + private final JdbcTemplate jdbcTemplate; + + public JdbcReservationRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public List findAll() { + String sql = "SELECT * FROM reservation"; + return jdbcTemplate.query(sql, (rs, rowNum) -> new Reservation( + rs.getLong("id"), + rs.getString("name"), + rs.getObject("reservation_date_time", LocalDateTime.class) + )); + } + + @Override + public Reservation save(Reservation reservation) { + String sql = "INSERT INTO reservation (name, reservation_date_time) VALUES (?, ?)"; + + GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, reservation.getName()); + ps.setObject(2, reservation.getLocalDateTime()); + return ps; + }, keyHolder); + + long pk = keyHolder.getKey().longValue(); + return new Reservation(pk, reservation.getName(), reservation.getLocalDateTime()); + } + + @Override + public void deleteReservation(final Long id) { + String sql = "DELETE FROM reservation WHERE id = ?"; + int updateCount = jdbcTemplate.update(sql, id); + if (updateCount == 0) { + throw new NotFoundReservationException("예약을 찾을 수 없습니다."); + } + } +} diff --git a/src/main/java/roomescape/domain/Repository/ReservationRepository.java b/src/main/java/roomescape/domain/Repository/ReservationRepository.java new file mode 100644 index 000000000..2ec17f109 --- /dev/null +++ b/src/main/java/roomescape/domain/Repository/ReservationRepository.java @@ -0,0 +1,36 @@ +package roomescape.domain.Repository; + +import org.springframework.stereotype.Repository; +import roomescape.domain.Model.Reservation; +import roomescape.domain.exception.NoDataException; +import roomescape.domain.exception.NotFoundReservationException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Repository +public class ReservationRepository { + private static final HashMap store = new HashMap(); + private static Long sequence = 0L; + + + public Reservation save(Reservation reservation) { + reservation.setId(++sequence); + store.put(reservation.getId(),reservation); + return reservation; + } + + public List findAll() { + return new ArrayList<>(store.values()); + } + + public Reservation deleteReservation(Long id){ + if (store.containsKey(id)) { + Reservation removeReservation = store.get(id); + store.remove(id); + return removeReservation; + } + throw new NotFoundReservationException("해당 id를 찾지 못하였습니다."); + } +} diff --git a/src/main/java/roomescape/domain/exception/NoDataException.java b/src/main/java/roomescape/domain/exception/NoDataException.java new file mode 100644 index 000000000..39daa82b0 --- /dev/null +++ b/src/main/java/roomescape/domain/exception/NoDataException.java @@ -0,0 +1,7 @@ +package roomescape.domain.exception; + +public class NoDataException extends RuntimeException{ + public NoDataException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/domain/exception/NotFoundReservationException.java b/src/main/java/roomescape/domain/exception/NotFoundReservationException.java new file mode 100644 index 000000000..d8516153c --- /dev/null +++ b/src/main/java/roomescape/domain/exception/NotFoundReservationException.java @@ -0,0 +1,7 @@ +package roomescape.domain.exception; + +public class NotFoundReservationException extends RuntimeException{ + public NotFoundReservationException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/domain/exceptionHandler/ExceptionHandlerData.java b/src/main/java/roomescape/domain/exceptionHandler/ExceptionHandlerData.java new file mode 100644 index 000000000..4a27bb976 --- /dev/null +++ b/src/main/java/roomescape/domain/exceptionHandler/ExceptionHandlerData.java @@ -0,0 +1,20 @@ +package roomescape.domain.exceptionHandler; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import roomescape.domain.exception.NoDataException; +import roomescape.domain.exception.NotFoundReservationException; + +@ControllerAdvice +public class ExceptionHandlerData { + @ExceptionHandler(NotFoundReservationException.class) + public ResponseEntity handleExceptionNotFound(NotFoundReservationException e) { + return ResponseEntity.badRequest().build(); + } + + @ExceptionHandler(NoDataException.class) + public ResponseEntity handleExceptionNoData(NoDataException e) { + return ResponseEntity.badRequest().build(); + } +} diff --git a/src/main/java/roomescape/web/Controller/HomeController.java b/src/main/java/roomescape/web/Controller/HomeController.java new file mode 100644 index 000000000..bdfdde910 --- /dev/null +++ b/src/main/java/roomescape/web/Controller/HomeController.java @@ -0,0 +1,16 @@ +package roomescape.web.Controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/") +public class HomeController { + + @GetMapping + public String home() { + return "home"; + } + +} diff --git a/src/main/java/roomescape/web/Controller/ReservationController.java b/src/main/java/roomescape/web/Controller/ReservationController.java new file mode 100644 index 000000000..fd5c5d359 --- /dev/null +++ b/src/main/java/roomescape/web/Controller/ReservationController.java @@ -0,0 +1,64 @@ +package roomescape.web.Controller; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.apache.catalina.connector.Response; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import roomescape.domain.Dto.RequestDto; +import roomescape.domain.Dto.ResponseDto; +import roomescape.domain.Model.Reservation; +import roomescape.domain.Repository.BasicRepository; + + +import java.lang.reflect.Member; +import java.net.URI; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; + +@Controller +public class ReservationController { + private final BasicRepository basicRepository; + + public ReservationController(@Qualifier("jdbcReservationRepository") BasicRepository basicRepository) { + this.basicRepository = basicRepository; + } + + @GetMapping("/reservation") + public String reservation(){ + return "reservation"; + } + + @GetMapping ("/reservations") + @ResponseBody + public ResponseEntity> checkReservation(){ + List reservations = basicRepository.findAll(); + List responseDtos = reservations.stream() + .map(ResponseDto::makeResponse) + .toList(); + return ResponseEntity.ok(responseDtos); + } + @PostMapping("/reservations") + @ResponseBody + public ResponseEntity addReservation(@RequestBody RequestDto requestDto){ + + Reservation reservation = basicRepository.save(requestDto.toReservation()); + ResponseDto responseDto = ResponseDto.makeResponse(reservation); + URI location = URI.create("/reservations/"+responseDto.getId()); + return ResponseEntity.created(location).body(responseDto); + } + + @DeleteMapping("/reservations/{id}") + public ResponseEntity deleteReservation(@PathVariable Long id) { + basicRepository.deleteReservation(id); + return ResponseEntity.noContent().build(); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..b63619d73 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 diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..5b5431b00 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS reservation; + +CREATE TABLE reservation +( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + reservation_date_time TIMESTAMP NOT NULL +); \ No newline at end of file diff --git a/src/test/java/roomescape/JdbcTest.java b/src/test/java/roomescape/JdbcTest.java new file mode 100644 index 000000000..a2df9c520 --- /dev/null +++ b/src/test/java/roomescape/JdbcTest.java @@ -0,0 +1,82 @@ +package roomescape; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +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.domain.Dto.ResponseDto; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +public class JdbcTest { + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + void 오단계() { + 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 + void 육단계() { + jdbcTemplate.update( + "INSERT INTO reservation (name, reservation_date_time) VALUES (?, ?) ", + "브라운", "2023-08-05T15:40" + ); + + List reservations = RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200).extract() + .jsonPath().getList(".", ResponseDto.class); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + + assertThat(reservations.size()).isEqualTo(count); + } + + @Test + void 칠단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .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); + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cf4efbe91..4d7f8ac59 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,14 +1,28 @@ package roomescape; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; +import roomescape.domain.Dto.ResponseDto; +import roomescape.domain.Model.Reservation; + + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { - @Test void 일단계() { RestAssured.given().log().all() @@ -16,4 +30,74 @@ 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(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } + + @Test + void 삼단계() { + 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)); + } + + @Test + void 사단계() { + 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); + } }