-
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 all 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 |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# 기능 요구사항 | ||
|
||
## 예약 가능 시각 | ||
|
||
- [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,28 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
public class AdminViewController { | ||
|
||
@GetMapping( "/admin") | ||
public String adminMainPage() { | ||
return "admin/index"; | ||
} | ||
|
||
@GetMapping("/admin/reservation") | ||
public String reservationPage() { | ||
return "admin/reservation-new"; | ||
} | ||
|
||
@GetMapping("/admin/time") | ||
public String reservationTimePage() { | ||
return "admin/time"; | ||
} | ||
|
||
@GetMapping("/admin/theme") | ||
public String themePage() { | ||
return "admin/theme"; | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,57 @@ | ||
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; | ||
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.domain.Theme; | ||
import roomescape.dto.BookableTimeResponse; | ||
import roomescape.dto.BookableTimesRequest; | ||
import roomescape.dto.ReservationAddRequest; | ||
import roomescape.service.ReservationService; | ||
|
||
@RestController | ||
public class ReservationController { | ||
|
||
private final ReservationService reservationService; | ||
|
||
public ReservationController(ReservationService reservationService) { | ||
this.reservationService = reservationService; | ||
} | ||
|
||
@GetMapping("/reservations") | ||
public ResponseEntity<List<Reservation>> getReservationList() { | ||
return ResponseEntity.ok(reservationService.findAllReservation()); | ||
} | ||
|
||
@PostMapping("/reservations") | ||
public ResponseEntity<Reservation> addReservation(@RequestBody ReservationAddRequest reservationAddRequest) { | ||
Reservation reservation = reservationService.addReservation(reservationAddRequest); | ||
return ResponseEntity.created(URI.create("/reservation/" + reservation.getId())).body(reservation); | ||
} | ||
|
||
@DeleteMapping("/reservations/{id}") | ||
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,42 @@ | ||
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.ReservationTime; | ||
import roomescape.dto.ReservationTimeAddRequest; | ||
import roomescape.service.ReservationTimeService; | ||
|
||
@RestController | ||
public class ReservationTimeController { | ||
|
||
private final ReservationTimeService reservationTimeService; | ||
|
||
public ReservationTimeController(ReservationTimeService reservationTimeService) { | ||
this.reservationTimeService = reservationTimeService; | ||
} | ||
|
||
@GetMapping("/times") | ||
public ResponseEntity<List<ReservationTime>> getReservationTimeList() { | ||
return ResponseEntity.ok(reservationTimeService.findAllReservationTime()); | ||
} | ||
|
||
@PostMapping("/times") | ||
public ResponseEntity<ReservationTime> addReservationTime( | ||
@RequestBody ReservationTimeAddRequest reservationTimeAddRequest) { | ||
ReservationTime reservationTime = reservationTimeService.addReservationTime(reservationTimeAddRequest); | ||
return ResponseEntity.created(URI.create("/times/" + reservationTime.getId())).body(reservationTime); | ||
} | ||
|
||
@DeleteMapping("/times/{id}") | ||
public ResponseEntity<Void> deleteReservationTime(@PathVariable("id") Long id) { | ||
reservationTimeService.removeReservationTime(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
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,78 @@ | ||
package roomescape.domain; | ||
|
||
import java.time.LocalDate; | ||
import java.util.Objects; | ||
|
||
public class Reservation { | ||
|
||
private final Long id; | ||
private final Name name; | ||
private final ReservationDate date; | ||
private final ReservationTime time; | ||
private Theme theme; | ||
|
||
public Reservation(Long id, String name, LocalDate date, ReservationTime time, Theme theme) { | ||
this.id = id; | ||
this.name = new Name(name); | ||
this.date = new ReservationDate(date); | ||
this.time = time; | ||
this.theme = theme; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public Long getTimeId() { | ||
return time.getId(); | ||
} | ||
|
||
public Long getThemeId() { | ||
return theme.getId(); | ||
} | ||
|
||
public String getName() { | ||
return name.getName(); | ||
} | ||
|
||
public LocalDate getDate() { | ||
return date.getDate(); | ||
} | ||
|
||
public ReservationTime getTime() { | ||
return time; | ||
} | ||
|
||
public Theme getTheme() { | ||
return theme; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
Reservation that = (Reservation) o; | ||
return Objects.equals(id, that.id) && Objects.equals(name, that.name) | ||
&& Objects.equals(date, that.date) && Objects.equals(time, that.time) | ||
&& Objects.equals(theme, that.theme); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(id, name, date, time, theme); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Reservation{" + | ||
"id=" + id + | ||
", name='" + name + '\'' + | ||
", date=" + date + | ||
", time=" + time + | ||
'}'; | ||
} | ||
} |
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 설계를 대충한 부분이야
오늘 강의 들어보니 이러한 부분이 이번 미션의 핵심이었던 것 같은데 반성 중 .. 😭