-
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-6 단계 제출 #8
base: main
Are you sure you want to change the base?
켈리 1-6 단계 제출 #8
Changes from all commits
0b0ebe0
5e2a676
50ab539
b378849
090da4c
e94d84e
c3acdfb
3ecafa2
721df43
f137a33
2eca83c
76a90b0
d1ed4c3
ff663c8
2e8474e
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,12 @@ | ||
# 웹 페이지 조회 | ||
|
||
- [X] 어드민 페이지 조회 | ||
- [X] 예약 페이지 조회 | ||
|
||
# API | ||
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. 켈리~ |
||
|
||
- [X] 예약 정보 전체 조회 | ||
- [X] 예약 정보 저장 | ||
- [X] 예약자 이름 검증 | ||
- 예약자 이름은 `1`이상 `5`이하 문자열만 허용 | ||
- [X] 예약 정보 삭제 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package roomescape.config; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
import roomescape.repository.H2ReservationRepository; | ||
import roomescape.repository.ReservationRepository; | ||
|
||
@Configuration | ||
public class AppConfig { | ||
|
||
private final NamedParameterJdbcTemplate template; | ||
|
||
public AppConfig(final NamedParameterJdbcTemplate template) { | ||
this.template = template; | ||
} | ||
|
||
@Bean | ||
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. @component 대신 @bean을 직접 등록한 이유가 있나요?? 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 ReservationRepository reservationRepository() { | ||
return new H2ReservationRepository(template); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package roomescape.controller; | ||
|
||
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.RestController; | ||
import roomescape.domain.Reservation; | ||
import roomescape.dto.ReservationResponse; | ||
import roomescape.dto.SaveReservationRequest; | ||
import roomescape.repository.ReservationRepository; | ||
|
||
import java.util.List; | ||
Comment on lines
+3
to
+14
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. 구글 자바 컨벤션에서 non-static import는 빈 줄 없이 한 블록으로 구분되어야 할 거에요~~~ |
||
|
||
@RestController | ||
public class AdminApiController { | ||
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가 reservation 관련 하나만 있어서인가요? |
||
private final ReservationRepository reservationRepository; | ||
|
||
public AdminApiController(final ReservationRepository reservationRepository) { | ||
this.reservationRepository = reservationRepository; | ||
} | ||
|
||
@GetMapping("/reservations") | ||
public List<ReservationResponse> getReservations() { | ||
return reservationRepository.findAll() | ||
.stream() | ||
.map(ReservationResponse::from) | ||
.toList(); | ||
} | ||
|
||
@PostMapping("/reservations") | ||
public ReservationResponse saveReservation(@RequestBody final SaveReservationRequest request) { | ||
Reservation reservation = request.toReservation(); | ||
Reservation savedReservation = reservationRepository.save(reservation); | ||
|
||
return ReservationResponse.from(savedReservation); | ||
} | ||
|
||
@DeleteMapping("/reservations/{reservation-id}") | ||
public void deleteReservation(@PathVariable("reservation-id") final 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. 여기도 pathvariable value를 null을 받고 싶은 의도가 있으신건가요? |
||
reservationRepository.deleteById(reservationId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
public class AdminWebController { | ||
|
||
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. 클래스 선언 이후 공백 줄이 대부분 없는 것으로 보이는데, |
||
@GetMapping("/admin") | ||
public String getAdminPage() { | ||
return "/admin/index"; | ||
} | ||
|
||
@GetMapping("/admin/reservation") | ||
public String getReservationPage() { | ||
return "/admin/reservation-legacy"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package roomescape.domain; | ||
|
||
public class ClientName { | ||
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. 1단계에서 학습한 부분을 잊지 않았군요. 꼼꼼하게 원시 값을 포장했네요 👍 |
||
private static final int MAXIMUM_ENABLE_NAME_LENGTH = 5; | ||
|
||
private final String value; | ||
|
||
public ClientName(final String value) { | ||
validateClientName(value); | ||
this.value = value; | ||
} | ||
|
||
private void validateClientName(final String value) { | ||
if (value == null || value.isEmpty() || value.length() > MAXIMUM_ENABLE_NAME_LENGTH) { | ||
throw new IllegalArgumentException("예약자 이름은 1글자 이상 5글자 이하여야 합니다."); | ||
} | ||
} | ||
|
||
public String getValue() { | ||
return value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package roomescape.domain; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
|
||
public class Reservation { | ||
private static final Long DEFAULT_ID_VALUE = 0L; | ||
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. 이렇게 기본 값을 명시하면 어떤 점이 좋은지 궁금해요 |
||
|
||
private final Long id; | ||
private final ClientName clientName; | ||
private final LocalDate date; | ||
private final LocalTime time; | ||
|
||
public Reservation(final ClientName clientName, final LocalDate date, final LocalTime time) { | ||
this(DEFAULT_ID_VALUE, clientName, date, time); | ||
} | ||
|
||
public Reservation(final Long id, final ClientName clientName, final LocalDate date, final LocalTime time) { | ||
validateReservationDateAndTime(date, time); | ||
this.id = id; | ||
this.clientName = clientName; | ||
this.date = date; | ||
this.time = time; | ||
} | ||
|
||
private void validateReservationDateAndTime(final LocalDate date, final LocalTime time) { | ||
LocalDateTime reservationLocalDateTime = LocalDateTime.of(date, time); | ||
if (reservationLocalDateTime.isBefore(LocalDateTime.now())) { | ||
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. 이런 메소드가 있는 걸 처음 알았네요 |
||
throw new IllegalArgumentException("현재 날짜보다 이전 날짜를 예약할 수 없습니다."); | ||
Comment on lines
+27
to
+30
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 initializeIndex(final Long reservationId) { | ||
return new Reservation(reservationId, clientName, date, time); | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public ClientName getClientName() { | ||
return clientName; | ||
} | ||
|
||
public LocalDate getDate() { | ||
return date; | ||
} | ||
|
||
public LocalTime getTime() { | ||
return time; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package roomescape.dto; | ||
|
||
import roomescape.domain.Reservation; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
public record ReservationResponse(Long id, String name, LocalDate date, LocalTime time) { | ||
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. 클라이언트 http 응답을 null을 주지는 않을 것 같은데 Long을 사용하신 이유가 있을까용 |
||
public static ReservationResponse from(final Reservation reservation) { | ||
return new ReservationResponse( | ||
reservation.getId(), | ||
reservation.getClientName().getValue(), | ||
reservation.getDate(), | ||
reservation.getTime() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package roomescape.dto; | ||
|
||
import roomescape.domain.ClientName; | ||
import roomescape.domain.Reservation; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
public record SaveReservationRequest(LocalDate date, String name, LocalTime time) { | ||
public Reservation toReservation() { | ||
return new Reservation( | ||
new ClientName(name), | ||
date, | ||
time | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package roomescape.exception; | ||
|
||
public record ErrorResponse(String message) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package roomescape.exception; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
import java.util.NoSuchElementException; | ||
|
||
@RestControllerAdvice | ||
public class ReservationExceptionHandler { | ||
|
||
@ExceptionHandler | ||
protected ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) { | ||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); | ||
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. 요렇게 전달하면 어디서 어떻게 처리가 되나요??? |
||
} | ||
|
||
@ExceptionHandler | ||
protected ResponseEntity<ErrorResponse> handleNoSuchElementException(NoSuchElementException e) { | ||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package roomescape.repository; | ||
|
||
import org.springframework.jdbc.core.RowMapper; | ||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; | ||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
import org.springframework.jdbc.support.GeneratedKeyHolder; | ||
import org.springframework.jdbc.support.KeyHolder; | ||
import org.springframework.stereotype.Repository; | ||
import roomescape.domain.ClientName; | ||
import roomescape.domain.Reservation; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@Repository | ||
public class H2ReservationRepository implements ReservationRepository { | ||
private final NamedParameterJdbcTemplate template; | ||
|
||
public H2ReservationRepository(final NamedParameterJdbcTemplate template) { | ||
this.template = template; | ||
} | ||
|
||
@Override | ||
public List<Reservation> findAll() { | ||
String sql = "SELECT id, name, date, time FROM reservation"; | ||
|
||
List<Reservation> reservations = template.query(sql, itemRowMapper()); | ||
return reservations; | ||
} | ||
|
||
private RowMapper<Reservation> itemRowMapper() { | ||
return ((rs, rowNum) -> new Reservation( | ||
rs.getLong("id"), | ||
new ClientName(rs.getString("name")), | ||
rs.getDate("date").toLocalDate(), | ||
rs.getTime("time").toLocalTime() | ||
)); | ||
} | ||
|
||
@Override | ||
public Reservation save(final Reservation reservation) { | ||
String sql = "INSERT INTO reservation(name, date, time) VALUES (:name, :date, :time)"; | ||
MapSqlParameterSource param = new MapSqlParameterSource() | ||
.addValue("name", reservation.getClientName().getValue()) | ||
.addValue("date", reservation.getDate()) | ||
.addValue("time", reservation.getTime()); | ||
KeyHolder keyHolder = new GeneratedKeyHolder(); | ||
template.update(sql, param, keyHolder); | ||
|
||
long savedReservationId = keyHolder.getKey().longValue(); | ||
|
||
return reservation.initializeIndex(savedReservationId); | ||
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. 오호 이렇게도 할 수 있군요 👍 |
||
} | ||
|
||
@Override | ||
public void deleteById(final 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이 옳다고 생각합니다. (내부 동작과 시그니처의 일관성) |
||
String sql = "DELETE FROM reservation WHERE id = :id"; | ||
Map<String, Long> param = Map.of("id", reservationId); | ||
|
||
template.update(sql, param); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package roomescape.repository; | ||
|
||
import roomescape.domain.Reservation; | ||
|
||
import java.util.List; | ||
|
||
public interface ReservationRepository { | ||
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. 인터페이스로 분리해둔 이유를 듣고 싶어요 |
||
List<Reservation> findAll(); | ||
|
||
Reservation save(Reservation reservation); | ||
|
||
void deleteById(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: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) | ||
); |
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.
주석을 달아두니 보기 좋군요