-
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 - 3단계 방탈출 사용자 예약] 리비(이근희) 미션 제출합니다. #3
base: libienz
Are you sure you want to change the base?
Changes from 52 commits
2622490
706c060
7033b39
faf9920
986ea9e
29f5ce3
ad9cb39
e7abc93
08da946
db26247
1fa2aa0
f7e82a6
2c35a3a
fc95a5b
57d1f09
7f5aeae
f624f87
5d5c64f
3650baa
48ab4f5
e86236f
130cf54
d28495c
e2cc555
3333508
bfa4381
3f2b90a
6deace6
097dcfc
79b8f13
f90f8ee
9167b47
2bc40d2
f8e1eeb
4675f58
8fc5de0
3c1d8f6
d1513c9
8716aa1
e665601
1220dfb
f94c579
3d6edf9
1d9aa04
2ac3886
e396b98
2793e21
c6fc316
92fd7b9
93cb710
87fa155
4e5d49f
c9ec69b
e09e2d0
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 |
---|---|---|
@@ -1,32 +1,35 @@ | ||
# 기능 요구사항 | ||
|
||
# 1~3단계 요구 사항 | ||
- [x] `localhost:8080/admin` get 요청 시 어드민 메인 페이지가 응답할 수 있다. | ||
- [x] `localhost:8080` get 요청 시 어드민 메인 페이지가 응답할 수 있다. | ||
- [x] `/admin/reservation` get 요청 시 예약 관리 페이지가 응답할 수 있다. | ||
- [x] 예약 관리 페이지 응답 시, 현재 예약 목록을 함께 보여준다. | ||
- [x] `/admin/reservation` post 요청 시 예약을 추가한다. | ||
- [x] `/reservations/{id}` delete 요청 시 예약을 삭제한다. | ||
- [x] id 값이 없는 경우 예외를 발생시킨다. | ||
|
||
# 4단계 요구사항 | ||
- [x] JdbcTemplate을 이용하여 DataSource객체에 접근하기 | ||
- [x] DataSource 객체를 이용하여 Connection 확인하기 | ||
- [x] Connection 객체를 이용하여 데이터베이스 이름 검증 | ||
- [x] Connection 객체를 이용하여 테이블 이름 검증 | ||
|
||
# 5단계 요구사항 | ||
- [x] db의 reservation을 조회 할 수 있다. | ||
|
||
# 6단계 요구사항 | ||
- [x] 예약 추가 api가 db와 연동된다. | ||
- [x] 예약 취소 api를 통해 db의 예약 정보를 삭제가능하다. | ||
|
||
# 7단계 요구사항 | ||
- [x] 시간 관리 페이지를 응답할 수 있다. | ||
- [x] 예약 시간 추가를 할 수 있다. | ||
- [x] 예약 시간 리스트 조회를 할 수 있다. | ||
- [x] 예약 시간 삭제를 할 수 있다. | ||
|
||
# 8단계 요구사항 | ||
- [x] 예약 페이지를 reservation.html로 변경 | ||
- [x] 방탈출 예약 시 정해진 시간만을 예약 가능합니다. | ||
## 예약 가능 시각 | ||
|
||
- [x] 예약 가능 시간은 null일 수 없다. | ||
- [x] 예약 가능 시간은 이미 등록되어 있는 시간과 겹칠 수 없다. | ||
|
||
## 예약 | ||
|
||
- [x] 예약 시각이 중복되는 예약이 존재하는 경우 예약을 할 수 없다. | ||
|
||
## 예약자 이름 | ||
|
||
- [x] 예약자 이름은 빈 문자열일 수 없다. | ||
- [x] 예약자 이름은 Null일 수 없다. | ||
|
||
## 예약 날짜 | ||
|
||
- [x] 예약 날짜는 오늘보다 이전일 수 없다. | ||
- [x] 예약 날짜는 null일 수 없다. | ||
|
||
## 예약 시간 | ||
|
||
- [x] 예약시간은 지정된 예약시간 중 하나여야 한다. | ||
|
||
### 2단계 요구사항 | ||
|
||
- [x] 테마를 조회할 수 있다 | ||
- [x] 테마를 추가할 수 있다 | ||
- [x] 테마를 삭제할 수 있다 | ||
|
||
### 3단계 요구사항 | ||
|
||
- [ ] 날짜와 테마를 기반으로 예약 가능한 시각과 예약 불가능한 시각을 응답할 수 있다. | ||
- [ ] `/`요청 시 인기 테마 페이지를 응답한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
public class ClientViewController { | ||
|
||
@GetMapping("/reservation") | ||
public String reservationPage() { | ||
return "reservation"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package roomescape.controller; | ||
|
||
import java.net.URI; | ||
import java.time.LocalDate; | ||
import java.util.List; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
|
@@ -10,6 +11,9 @@ | |
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import roomescape.domain.Reservation; | ||
import roomescape.domain.Theme; | ||
import roomescape.dto.BookableTimeResponse; | ||
import roomescape.dto.BookableTimesRequest; | ||
import roomescape.dto.ReservationAddRequest; | ||
import roomescape.service.ReservationService; | ||
|
||
|
@@ -38,4 +42,16 @@ public ResponseEntity<Void> removeReservation(@PathVariable("id") Long id) { | |
reservationService.removeReservation(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
|
||
@GetMapping("/reservations/bookable-times/{date}/{themeId}") | ||
public ResponseEntity<List<BookableTimeResponse>> getTimesWithStatus( | ||
@PathVariable("date") LocalDate date, | ||
@PathVariable("themeId") Long themeId) { | ||
return ResponseEntity.ok(reservationService.findBookableTimes(new BookableTimesRequest(date, themeId))); | ||
} | ||
|
||
@GetMapping("/reservations/theme-rank") | ||
public ResponseEntity<List<Theme>> getThemeRank() { | ||
return ResponseEntity.ok(reservationService.getThemeRanking()); | ||
} | ||
Comment on lines
+53
to
+56
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. ThemeController에서 이걸 구현해야 할 것 같은데 왜 여기서 했어? 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. 자원간의 연관관계를 잘못해석했어 😂 현재는 uri가 자원의 식별자임을 알게되었고 개선이 필요한 점 인지하게 되었음 👍 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package roomescape.controller; | ||
|
||
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.RestController; | ||
import roomescape.domain.Theme; | ||
import roomescape.dto.ThemeAddRequest; | ||
import roomescape.service.ThemeService; | ||
|
||
@RestController | ||
public class ThemeController { | ||
private final ThemeService themeService; | ||
|
||
public ThemeController(ThemeService themeService) { | ||
this.themeService = themeService; | ||
} | ||
|
||
@GetMapping("/themes") | ||
public ResponseEntity<List<Theme>> getThemeList() { | ||
return ResponseEntity.ok(themeService.findAllTheme()); | ||
} | ||
|
||
@PostMapping("/themes") | ||
public ResponseEntity<Theme> addTheme(@RequestBody ThemeAddRequest themeAddRequest) { | ||
Theme theme = themeService.addTheme(themeAddRequest); | ||
return ResponseEntity.created(URI.create("/themes" + theme.getId())).body(theme); | ||
} | ||
|
||
@DeleteMapping("/themes/{id}") | ||
public ResponseEntity<Void> deleteTheme(@PathVariable("id") Long id) { | ||
themeService.removeTheme(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package roomescape.domain; | ||
|
||
import java.util.Objects; | ||
|
||
public class Name { | ||
|
||
private final String name; | ||
|
||
public Name(String name) { | ||
validateNonBlank(name); | ||
this.name = name; | ||
} | ||
|
||
private void validateNonBlank(String name) { | ||
if (name == null || name.isEmpty()) { | ||
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. " " 과 같은 값도 검증이 필요할 거 같은데 isEmpty() 대신 isBlank()를 사용하면 가능! 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 NullPointerException("이름은 비어있을 수 없습니다."); | ||
} | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
Name name1 = (Name) o; | ||
return Objects.equals(name, name1.name); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(name); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package roomescape.domain; | ||
|
||
import java.time.LocalDate; | ||
import java.util.Objects; | ||
|
||
public class ReservationDate { | ||
|
||
private final LocalDate date; | ||
|
||
public ReservationDate(LocalDate date) { | ||
validate(date); | ||
this.date = date; | ||
} | ||
|
||
private void validate(LocalDate date) { | ||
validateNonNull(date); | ||
validateNonPastDate(date); | ||
} | ||
|
||
private void validateNonNull(LocalDate date) { | ||
if (date == null) { | ||
throw new NullPointerException("날짜는 null일 수 없습니다"); | ||
} | ||
} | ||
|
||
private void validateNonPastDate(LocalDate date) { | ||
if (date.isBefore(LocalDate.now())) { | ||
throw new IllegalArgumentException(date + ": 예약 날짜는 현재 보다 이전일 수 없습니다"); | ||
} | ||
} | ||
Comment on lines
+26
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. 나는 이 로직을 service 계층에서 이루어졌는데, 리비의 의견이 궁금! 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. ReservationDate 도메인을 어떻게 해석하는지에 따라 달라지는 문제라고 생각! 예약 날짜라는 도메인을 데이터만 담고있는 것으로 생각한다면 service계층에서의 검증이 적절한 것 같아 도메인 주도 개발측면에서 서비스는 물론 비즈니스 로직을 담는 계층이고 범용적으로 운용될 수 있지만 각각의 도메인 명세는 도메인 클래스에 있어야 자연스럽지 않나 하는 개인적인 생각이야 🧐 |
||
|
||
public LocalDate getDate() { | ||
return date; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
ReservationDate that = (ReservationDate) o; | ||
return Objects.equals(date, that.date); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(date); | ||
} | ||
} |
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.
많은 방법 중에 왜 PathVariable 을 사용하는 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.
쿼리 스트링을 이용하는 방법도 있잖아
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.
지금 다시 짠다면 쿼리파라미터를 이용하도록 할 듯!
구현이 급해서 api 설계를 대충한 부분이야
오늘 강의 들어보니 이러한 부분이 이번 미션의 핵심이었던 것 같은데 반성 중 .. 😭