-
Notifications
You must be signed in to change notification settings - Fork 0
[1~6단계 - 방탈출 예약 관리] 커찬(이충안) 미션 제출합니다. #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: leegwichan
Are you sure you want to change the base?
Changes from all commits
2605aec
0aa0084
3cd2f0f
9f586f0
b260d19
88f8159
c4eb0c8
e87d88c
2c36f1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package roomescape; | ||
|
|
||
| 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 showMainPage() { | ||
| return "index"; | ||
| } | ||
|
|
||
| @GetMapping("/reservation") | ||
| public String showReservationPage() { | ||
| return "admin/reservation-legacy"; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package roomescape; | ||
|
|
||
| public record Reservation( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reservation을 record로 만든 이유가 궁금해
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 Reservation의 역할은 DTO와 크게 다르지 않다고 생각해. 이후에 추가적인 요구사항이나, 추가적인 작업이 필요하다면 class로 바꿀 것 같아. |
||
| Long id, | ||
| String name, | ||
| String date, | ||
| String time) { | ||
|
|
||
| public Reservation toEntity(Long id) { | ||
| return new Reservation(id, this.name, this.date, this.time); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package roomescape; | ||
|
|
||
| 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; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/reservations") | ||
| public class ReservationController { | ||
|
|
||
| private final ReservationRepository reservationRepository; | ||
|
|
||
| public ReservationController(ReservationRepository reservationRepository) { | ||
| this.reservationRepository = reservationRepository; | ||
| } | ||
|
|
||
| @GetMapping | ||
| public ResponseEntity<List<Reservation>> listReservations() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. listReservations 보다 좀 더 좋은 이름이 있지 않을까?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.slipp.net/questions/79 -> 포비(자바지기)에게 이야기 해보세요. 는 농담이고, 네이밍 컨벤션을 찾아봤는데 위 자료가 보였고, 납득 가능한 것 같아서 사용했어 |
||
| return ResponseEntity.ok(reservationRepository.findAll()); | ||
| } | ||
|
|
||
| @PostMapping | ||
| public ResponseEntity<Reservation> createReservation(@RequestBody Reservation reservation) { | ||
| Reservation newReservation = reservationRepository.create(reservation); | ||
|
|
||
| URI location = URI.create("/reservations/" + newReservation.id()); | ||
| return ResponseEntity.created(location).body(newReservation); | ||
|
Comment on lines
+33
to
+34
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 나는 뭔가 문자열을 + 연산하는게 껄끄럽다고 해야하나 암튼 그래서 아래처럼 처리했는데 맘에 드는 방식대로 해도 괜찮을 듯! @PostMapping
public ResponseEntity<ReservationResponse> save(@RequestBody final ReservationRequest reservationRequest) {
final ReservationResponse reservationResponse = reservationService.save(reservationRequest);
final URI uri = UriComponentsBuilder.fromPath("/reservations/{id}")
.buildAndExpand(reservationResponse.id())
.toUri();
return ResponseEntity.created(uri)
.body(reservationResponse);
} |
||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity<Void> deleteReservation(@PathVariable Long id) { | ||
| reservationRepository.deleteById(id); | ||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package roomescape; | ||
|
|
||
| import java.sql.Connection; | ||
| import java.sql.PreparedStatement; | ||
| import java.sql.SQLException; | ||
| 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; | ||
|
|
||
| @Repository | ||
| public class ReservationRepository { | ||
|
|
||
| private static final String TABLE_NAME = "reservation"; | ||
| private static final RowMapper<Reservation> ROW_MAPPER = (resultSet, rowNum) -> new Reservation( | ||
| resultSet.getLong("id"), | ||
| resultSet.getString("name"), | ||
| resultSet.getString("date"), | ||
| resultSet.getString("time")); | ||
|
|
||
| private final JdbcTemplate jdbcTemplate; | ||
|
|
||
| public ReservationRepository(JdbcTemplate jdbcTemplate) { | ||
| this.jdbcTemplate = jdbcTemplate; | ||
| } | ||
|
|
||
| public List<Reservation> findAll() { | ||
| return jdbcTemplate.query("SELECT id, name, date, time FROM %s".formatted(TABLE_NAME), ROW_MAPPER); | ||
| } | ||
|
|
||
| public Reservation create(Reservation reservation) { | ||
| KeyHolder keyHolder = new GeneratedKeyHolder(); | ||
| jdbcTemplate.update(connection -> insertQuery(connection, reservation), keyHolder); | ||
|
|
||
| Long id = keyHolder.getKey().longValue(); | ||
| return reservation.toEntity(id); | ||
| } | ||
|
|
||
| private PreparedStatement insertQuery(Connection connection, Reservation reservation) throws SQLException { | ||
| PreparedStatement preparedStatement = connection.prepareStatement( | ||
| "INSERT INTO %s (name, date, time) VALUES (?, ?, ?)".formatted(TABLE_NAME), new String[]{"id"}); | ||
| preparedStatement.setString(1, reservation.name()); | ||
| preparedStatement.setString(2, reservation.date()); | ||
| preparedStatement.setString(3, reservation.time()); | ||
| return preparedStatement; | ||
| } | ||
|
|
||
| public void deleteById(Long id) { | ||
| Reservation foundReservation = findById(id); | ||
| jdbcTemplate.update("DELETE FROM %s WHERE id = ?".formatted(TABLE_NAME), foundReservation.id()); | ||
| } | ||
|
|
||
| private Reservation findById(Long id) { | ||
| Reservation reservation = jdbcTemplate.queryForObject( | ||
| "SELECT id, name, date, time FROM %s WHERE id = ?".formatted(TABLE_NAME), ROW_MAPPER, id); | ||
|
|
||
| if (reservation == null) { | ||
| throw new IllegalStateException("해당 예약이 없습니다."); | ||
| } | ||
| return reservation; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| spring.h2.console.enabled=true | ||
| spring.h2.console.path=/h2-console | ||
| spring.datasource.url=jdbc:h2:mem:database |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| ); |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package roomescape.acceptance; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 테스트가 LMS에서 제공하는 테스트 밖에 없는 거 같은데 다른 테스트도 추가해주!!!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이번 단위에서는 크게 복잡하지 않아서, 다른 테스트는 추가하지 않았음! 아마 다음 미션부터는 의식적으로 테스트를 짤 것 같아. 대신에 LMS에서 제공하는 테스트를 가공해서 작성했어! |
||
|
|
||
| import io.restassured.RestAssured; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.boot.test.context.SpringBootTest; | ||
| import org.springframework.boot.test.web.server.LocalServerPort; | ||
|
|
||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||
| class AdminPageAcceptanceTest { | ||
|
|
||
| @LocalServerPort | ||
| int port; | ||
|
|
||
| @BeforeEach | ||
| public void setUp() { | ||
| RestAssured.port = port; | ||
| } | ||
|
|
||
| @DisplayName("어드민 메인 페이지 조회") | ||
| @Test | ||
| void get_welcomePage() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get 다음은 스네이크케이스고 welcomePage는 카멜케이스인데 통일해주!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일부러 통일 안했음! get 요청으로 날린다는 것을 강조하기 위해, get_welcomPage() 라고 했어! |
||
| RestAssured.given().log().all() | ||
| .when().get("/admin") | ||
| .then().log().all() | ||
| .statusCode(200); | ||
| } | ||
|
|
||
| @DisplayName("어드민 예약 관리 페이지 조회") | ||
| @Test | ||
| void get_reservationPage() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| RestAssured.given().log().all() | ||
| .when().get("/admin/reservation") | ||
| .then().log().all() | ||
| .statusCode(200); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package roomescape.acceptance; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.hamcrest.Matchers.is; | ||
|
|
||
| import io.restassured.RestAssured; | ||
| import io.restassured.http.ContentType; | ||
| 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.web.server.LocalServerPort; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.test.context.jdbc.Sql; | ||
| import roomescape.Reservation; | ||
|
|
||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||
| @Sql(scripts = "/truncate.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 @SQL 애노테이션을 좀 더 쉽게 테스트 할 수 있겠군 👍 배워갑니다 |
||
| class ReservationAcceptanceTest { | ||
|
|
||
| @LocalServerPort | ||
| private int port; | ||
| @Autowired | ||
| private JdbcTemplate jdbcTemplate; | ||
|
|
||
| @BeforeEach | ||
| public void setUp() { | ||
| RestAssured.port = port; | ||
| } | ||
|
|
||
| @DisplayName("전체 예약 조회") | ||
| @Test | ||
| void get_reservations() { | ||
| insertDefaultData(); | ||
|
|
||
| RestAssured.given().log().all() | ||
| .when().get("/reservations") | ||
| .then().log().all() | ||
| .statusCode(200) | ||
| .body("size()", is(1)); | ||
| } | ||
|
|
||
| @DisplayName("예약 추가") | ||
| @Test | ||
| void post_reservation() { | ||
| Reservation reservation = new Reservation(null, "브라운", "2023-08-05", "15:40"); | ||
| Reservation expectedReservation = new Reservation(1L, "브라운", "2023-08-05", "15:40"); | ||
|
|
||
| Reservation createdReservation = RestAssured.given().log().all() | ||
| .contentType(ContentType.JSON).body(reservation) | ||
| .when().post("/reservations") | ||
| .then().log().all() | ||
| .statusCode(201) | ||
| .header("Location", "/reservations/1") | ||
| .extract().as(Reservation.class); | ||
|
|
||
| assertThat(createdReservation).isEqualTo(expectedReservation); | ||
| assertThat(countReservation()).isEqualTo(1); | ||
|
Comment on lines
+58
to
+59
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에 createdReservation 처럼 아래 countReservation 메서드도 변수로 만든다음 테스트하는건 어떻게 생각해??
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나는 거꾸로 |
||
| } | ||
|
|
||
| @DisplayName("예약 삭제") | ||
| @Test | ||
| void delete_reservation() { | ||
| insertDefaultData(); | ||
|
|
||
| RestAssured.given().log().all() | ||
| .when().delete("/reservations/1") | ||
| .then().log().all() | ||
| .statusCode(204); | ||
|
|
||
| Integer countAfterDelete = countReservation(); | ||
| assertThat(countAfterDelete).isZero(); | ||
| } | ||
|
|
||
| private void insertDefaultData() { | ||
| jdbcTemplate.update("INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)", | ||
| "브라운", "2023-08-05", "15:40"); | ||
| } | ||
|
|
||
| private Integer countReservation() { | ||
| return jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이걸 놓치네... (나는 이런 부분이 약한듯...) |
||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| TRUNCATE TABLE reservation RESTART IDENTITY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
나도 1~3 단계에선 하지 않았지만 API 명세라던가 기능구현 목록을 추가해주는게 좋을 거 같아!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
응! 리뷰어한테 같은 피드백을 받았어!
그래서 9단계까지 끝내고 README에 명세서를 추가했어!