-
Notifications
You must be signed in to change notification settings - Fork 0
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
로빈 #1
base: robinjoon
Are you sure you want to change the base?
로빈 #1
Changes from all commits
7041d02
ffe11dc
4e47c32
1234c01
3f11120
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,82 @@ | ||
# 요구사항 문서 | ||
|
||
- [x] localhost:8080/admin 요청 시 어드민 메인 페이지가 응답할 수 있도록 구현한다. | ||
- [x] 어드민 메인 페이지는 templates/admin/index.html 파일을 이용한다. | ||
- [x] localhost:8080/admin/reservation 요청 시 아래 화면과 같이 예약 관리 페이지가 응답할 수 있도록 구현한다. | ||
- [x] 페이지는 templates/admin/reservation-legacy.html 파일을 이용한다. | ||
- [x] 예약 조회 API 명세를 따라 예약 관리 페이지 로드 시 호출되는 예약 목록 조회 API를 구현한다. | ||
- [x] API 명세를 따라 예약 추가 API 와 삭제 API를 구현한다. | ||
- [x] 예약 추가와 취소가 잘 동작한다. | ||
- [x] 이상의 요구 사항을 데이터베이스와 연동하도록 한다. | ||
|
||
# API 명세 | ||
|
||
## 예약 조회 API | ||
|
||
### Request | ||
|
||
> GET /reservations HTTP/1.1 | ||
|
||
### Response | ||
|
||
> HTTP/1.1 200 | ||
> | ||
> Content-Type: application/json | ||
|
||
``` JSON | ||
[ | ||
{ | ||
"id": 1, | ||
"name": "브라운", | ||
"date": "2023-01-01", | ||
"time": "10:00" | ||
}, | ||
{ | ||
"id": 2, | ||
"name": "브라운", | ||
"date": "2023-01-02", | ||
"time": "11:00" | ||
} | ||
] | ||
``` | ||
Comment on lines
+16
to
+41
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. 자세한 명세 👍 |
||
|
||
## 예약 추가 API | ||
|
||
### Request | ||
|
||
> POST /reservations HTTP/1.1 | ||
> | ||
> content-type: application/json | ||
|
||
```JSON | ||
{ | ||
"date": "2023-08-05", | ||
"name": "브라운", | ||
"time": "15:40" | ||
} | ||
``` | ||
|
||
### Response | ||
|
||
> HTTP/1.1 200 | ||
> | ||
> Content-Type: application/json | ||
|
||
```JSON | ||
{ | ||
"id": 1, | ||
"name": "브라운", | ||
"date": "2023-08-05", | ||
"time": "15:40" | ||
} | ||
``` | ||
|
||
## 예약 취소 API | ||
|
||
### Request | ||
|
||
> DELETE /reservations/1 HTTP/1.1 | ||
|
||
### Response | ||
|
||
> HTTP/1.1 200 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package roomescape.config; | ||
|
||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | ||
import java.time.format.DateTimeFormatter; | ||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class TimeFormatterConfig { | ||
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. 이 클래스는 무슨 역할을 하는건가요? (궁금) 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.
Spring Boot 에서 LocalDate, LocalTime, LocalDateTime 등의 일부 타입을 JSON 으로 바꿔주는 규칙을 이미 만들어두고 있는데, 이를 커스텀하기 위한 설정을 해주는 클래스에요. https://www.baeldung.com/spring-boot-customize-jackson-objectmapper |
||
private static final String TIME_FORMAT = "HH:mm"; | ||
|
||
@Bean | ||
public Jackson2ObjectMapperBuilderCustomizer localTimeSerializerCustomizer() { | ||
return builder -> builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT))); | ||
} | ||
} | ||
Comment on lines
+9
to
+17
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,19 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
@RequestMapping("/admin") | ||
Comment on lines
+7
to
+8
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. 망쵸한테도 물어봤던 부분인데 이렇게 공통되는 경로를 빼놓는 것에 대해서 어떻게 생각해?
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. |
||
public class AdminController { | ||
@GetMapping | ||
public String mainPage() { | ||
return "admin/index"; | ||
} | ||
|
||
@GetMapping("/reservation") | ||
public String reservationPage() { | ||
return "admin/reservation-legacy"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package roomescape.controller; | ||
|
||
import java.util.List; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import roomescape.domain.Reservation; | ||
import roomescape.dto.ReservationRequest; | ||
import roomescape.dto.ReservationResponse; | ||
import roomescape.repository.ReservationRepository; | ||
|
||
@RestController | ||
@RequestMapping("/reservations") | ||
public class ReservationController { | ||
private final ReservationRepository reservationRepository; | ||
|
||
public ReservationController(ReservationRepository reservationRepository) { | ||
this.reservationRepository = reservationRepository; | ||
} | ||
|
||
@PostMapping | ||
public ReservationResponse saveReservation(@RequestBody ReservationRequest reservationRequest) { | ||
Reservation reservation = reservationRepository.save(reservationRequest); | ||
return toResponse(reservation); | ||
} | ||
|
||
private ReservationResponse toResponse(Reservation reservation) { | ||
return new ReservationResponse(reservation.getId(), | ||
reservation.getName(), reservation.getDate(), reservation.getTime()); | ||
} | ||
Comment on lines
+31
to
+34
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. ReservationResponse.from(reservation)과 같은 코드는 어떻게 생각!? 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.
글쎄, 컨트롤러가 자신이 응답해야 하는 형태로 데이터를 가공하는건데, Controller의 역할로 부여하는 것이 그렇게 이상한 건 아닌 것 같아. |
||
|
||
@GetMapping | ||
public List<ReservationResponse> findAllReservations() { | ||
return reservationRepository.findAll() | ||
.stream() | ||
.map(this::toResponse) | ||
.toList(); | ||
} | ||
|
||
@DeleteMapping("/{reservationId}") | ||
public void delete(@PathVariable long reservationId) { | ||
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. long 사용 👍 |
||
reservationRepository.delete(reservationId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package roomescape.domain; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
|
||
public class Reservation implements Comparable<Reservation> { | ||
private final long id; | ||
private final String name; | ||
private final LocalDateTime dateTime; | ||
Comment on lines
+7
to
+10
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. 엔티티랑 도메인을 구분하지 않았군 👍 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.
지금 시점에서는 이를 구분하는게 아무 의미가 없이 구조만 복잡해지는 것 같아. |
||
|
||
public Reservation(long id, Reservation reservationBeforeSave) { | ||
this(id, reservationBeforeSave.name, reservationBeforeSave.dateTime); | ||
} | ||
|
||
public Reservation(long id, String name, LocalDateTime dateTime) { | ||
this.id = id; | ||
this.name = name; | ||
this.dateTime = dateTime; | ||
} | ||
|
||
public Reservation(long id, String name, LocalDate date, LocalTime time) { | ||
this(id, name, LocalDateTime.of(date, time)); | ||
} | ||
|
||
@Override | ||
public int compareTo(Reservation other) { | ||
return dateTime.compareTo(other.dateTime); | ||
} | ||
|
||
public boolean hasSameId(long id) { | ||
return this.id == id; | ||
} | ||
|
||
public long getId() { | ||
return id; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public LocalDate getDate() { | ||
return dateTime.toLocalDate(); | ||
} | ||
|
||
public LocalTime getTime() { | ||
return dateTime.toLocalTime(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package roomescape.dto; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
public record ReservationRequest(LocalDate date, String name, LocalTime time) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package roomescape.dto; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
public record ReservationResponse(long id, String name, LocalDate date, LocalTime time) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package roomescape.repository; | ||
|
||
import java.sql.Date; | ||
import java.sql.PreparedStatement; | ||
import java.sql.Time; | ||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
import java.util.List; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.jdbc.support.GeneratedKeyHolder; | ||
import org.springframework.jdbc.support.KeyHolder; | ||
import org.springframework.stereotype.Repository; | ||
import roomescape.domain.Reservation; | ||
import roomescape.dto.ReservationRequest; | ||
|
||
@Repository | ||
public class JdbcTemplateReservationRepository implements ReservationRepository { | ||
private final JdbcTemplate jdbcTemplate; | ||
|
||
public JdbcTemplateReservationRepository(JdbcTemplate jdbcTemplate) { | ||
this.jdbcTemplate = jdbcTemplate; | ||
} | ||
|
||
@Override | ||
public Reservation save(ReservationRequest reservationRequest) { | ||
KeyHolder keyHolder = new GeneratedKeyHolder(); | ||
Reservation reservation = fromRequest(reservationRequest); | ||
jdbcTemplate.update(con -> { | ||
PreparedStatement preparedStatement = con.prepareStatement( | ||
"insert into reservation (name, date,time) values ( ?,?,? )", new String[]{"id"}); | ||
preparedStatement.setString(1, reservation.getName()); | ||
preparedStatement.setDate(2, Date.valueOf(reservation.getDate())); | ||
preparedStatement.setTime(3, Time.valueOf(reservation.getTime())); | ||
return preparedStatement; | ||
}, keyHolder); | ||
return new Reservation(keyHolder.getKey().longValue(), reservation); | ||
Comment on lines
+26
to
+36
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. jdbcTemplate의 기술적인 한계인 것 같음 |
||
} | ||
|
||
@Override | ||
public List<Reservation> findAll() { | ||
return jdbcTemplate.query("select * from reservation", (rs, rowNum) -> { | ||
long id = rs.getLong("id"); | ||
String name = rs.getString("name"); | ||
LocalDate date = rs.getDate("date").toLocalDate(); | ||
LocalTime time = rs.getTime("time").toLocalTime(); | ||
return new Reservation(id, name, date, time); | ||
}); | ||
} | ||
|
||
@Override | ||
public void delete(long reservationId) { | ||
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. 기능이 null을 필요로 하는게 아니면 Long을 사용할 필요가 없죠 |
||
jdbcTemplate.update("delete from reservation where id = ?", reservationId); | ||
} | ||
|
||
private Reservation fromRequest(ReservationRequest reservationRequest) { | ||
long id = 1L; | ||
String name = reservationRequest.name(); | ||
LocalDate date = reservationRequest.date(); | ||
Comment on lines
+55
to
+58
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. req res dto 변환 로직들을 안으로 숨길 수도 있을 것 같아요 |
||
LocalTime time = reservationRequest.time(); | ||
return new Reservation(id, name, date, time); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package roomescape.repository; | ||
|
||
import java.util.List; | ||
import roomescape.domain.Reservation; | ||
import roomescape.dto.ReservationRequest; | ||
|
||
public interface ReservationRepository { | ||
Reservation save(ReservationRequest reservationRequest); | ||
|
||
List<Reservation> findAll(); | ||
|
||
void delete(long reservationId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
spring.h2.console.enabled=true | ||
spring.datasource.url=jdbc:h2:mem:product |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CREATE TABLE IF NOT EXISTS 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) | ||
); |
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.
resource 마다 request, response 명세를 꼼꼼하게 잘 작성해주셨네요 👍