-
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단계 제출 #8
base: robinjoon
Are you sure you want to change the base?
로빈 1단계 제출 #8
Changes from 68 commits
64335cd
9d575fe
258c9b6
dbb1b8b
202216d
bae62c9
fc59282
65733d0
1bbadab
a14d390
2da2b7a
4f222f4
6a4ebb7
60e2f88
80b2371
a5ca6d4
36fe37e
3e37f25
9d83ffe
6f1be1a
c75c485
4d4c985
44c1974
af7f7e6
31df8bd
564ecaf
550f4cd
d1c9bee
1cfec9a
37c3bc1
cb113fd
42c2415
8a42260
584df82
f6966e1
9adfa8d
24c83c6
5f25eaf
b4b52b4
cc62f4b
f690708
1e96e33
06bb27c
2255391
ee5b115
77a3e24
173699c
3689dc7
0516901
3c472a3
b55ca91
2789dae
c6f2992
6ec052d
ad587b5
ddf88b1
e36c497
3961bb6
1378a92
093f68b
1024c7e
258466c
8c4efbc
c7c5033
0391803
c7f2dad
2416896
e8e7a6f
62cef24
1561f7e
c9b2f37
92e8519
a1ed68a
5d02747
eb38c7d
9881fc7
239c241
20fddde
e12b17e
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,248 @@ | ||
# 요구사항 문서 | ||
|
||
- [x] API 명세를 현재 프론트엔드 코드가 잘 동작할 수 있도록 수정 | ||
- [x] 예약 시간에 대한 제약 조건 추가 | ||
- [x] 중복된 예약 시간 생성 요청 시 에러 | ||
- [x] ISO 8601 표준에 따른 hh:mm 포맷에 해당하지 않는 요청 시 에러 | ||
- [x] 예약이 있는 예약 시간을 삭제 요청 시 에러 | ||
- [x] 예약에 대한 제약 조건 추가 | ||
- [x] 동일한 날짜와 시간, 테마에 예약 생성 요청 시 에러 | ||
- [x] 존재하지 않는 시간에 예약 생성 요청 시 에러 | ||
- [x] ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러 | ||
- [x] 지나간 날짜와 시간의 예약 요청 시 에러 | ||
- [x] 이름이 비어있는 예약 요청 시 에러 | ||
- [x] 존재하지 않는 테마 예약 생성 요청시 에러 | ||
- [x] 테마 값이 비어있는 예약 요청 시 에러 | ||
|
||
- [x] 테마에 대한 제약 조건 추가 | ||
- [x] 테마 이름, 설명, 썸네일 이미자가 비어 있을 경우 에러 | ||
- [x] 중복된 이름의 테마 생성 요청시 에러 | ||
- [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-08-05", | ||
"time": { | ||
"id": 1, | ||
"startAt": "10:00" | ||
} | ||
"theme" : { | ||
"id": 1, | ||
"name": "이름", | ||
"description": "설명", | ||
"thumbnail": "썸네일" | ||
} | ||
} | ||
] | ||
``` | ||
|
||
## 예약 추가 API | ||
|
||
### Request | ||
|
||
> POST /reservations HTTP/1.1 | ||
> | ||
> content-type: application/json | ||
|
||
```JSON | ||
{ | ||
"date": "2023-08-05", | ||
"name": "브라운", | ||
"timeId": 1, | ||
"themeId": 1 | ||
} | ||
``` | ||
|
||
### Response | ||
|
||
> HTTP/1.1 201 | ||
> | ||
> Content-Type: application/json | ||
> Location: /reservations/{id} | ||
|
||
```JSON | ||
{ | ||
"id": 1, | ||
"name": "브라운", | ||
"date": "2023-08-05", | ||
"time": { | ||
"id": 1, | ||
"startAt": "10:00" | ||
}, | ||
"theme": { | ||
"id": 1, | ||
"name": "이름", | ||
"description": "설명", | ||
"thumbnail": "썸네일" | ||
} | ||
} | ||
``` | ||
|
||
## 예약 취소 API | ||
|
||
### Request | ||
|
||
> DELETE /reservations/1 HTTP/1.1 | ||
|
||
### Response | ||
|
||
> HTTP/1.1 204 | ||
|
||
## 시간 추가 API | ||
|
||
### request | ||
|
||
> POST /times HTTP/1.1 | ||
> content-type: application/json | ||
|
||
```JSON | ||
{ | ||
"startAt": "10:00" | ||
} | ||
``` | ||
|
||
### response | ||
|
||
> HTTP/1.1 201 | ||
> Content-Type: application/json | ||
> Location: /times/{id} | ||
|
||
```JSON | ||
{ | ||
"id": 1, | ||
"startAt": "10:00" | ||
} | ||
``` | ||
|
||
## 시간 조회 API | ||
|
||
### request | ||
|
||
> GET /times HTTP/1.1 | ||
|
||
### response | ||
|
||
> HTTP/1.1 200 | ||
> Content-Type: application/json | ||
|
||
```JSON | ||
[ | ||
{ | ||
"id": 1, | ||
"startAt": "10:00" | ||
} | ||
] | ||
``` | ||
|
||
## 시간 삭제 API | ||
|
||
### request | ||
|
||
> DELETE /times/1 HTTP/1.1 | ||
|
||
### response | ||
|
||
> HTTP/1.1 204 | ||
|
||
## 테마 조회 API | ||
|
||
### request | ||
|
||
> GET /themes HTTP/1.1 | ||
|
||
### response | ||
|
||
> HTTP/1.1 200 | ||
> Content-Type: application/json | ||
|
||
```json | ||
[ | ||
{ | ||
"id": 1, | ||
"name": "레벨2 탈출", | ||
"description": "우테코 레벨2를 탈출하는 내용입니다.", | ||
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" | ||
} | ||
] | ||
``` | ||
|
||
## 테마 추가 API | ||
|
||
### request | ||
|
||
> POST /themes HTTP/1.1 | ||
> content-type: application/json | ||
|
||
```json | ||
{ | ||
"name": "레벨2 탈출", | ||
"description": "우테코 레벨2를 탈출하는 내용입니다.", | ||
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" | ||
} | ||
``` | ||
|
||
### response | ||
|
||
> HTTP/1.1 201 | ||
> Location: /themes/1 | ||
> Content-Type: application/json | ||
|
||
```json | ||
{ | ||
"id": 1, | ||
"name": "레벨2 탈출", | ||
"description": "우테코 레벨2를 탈출하는 내용입니다.", | ||
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg" | ||
} | ||
``` | ||
|
||
## 테마 삭제 API | ||
|
||
### request | ||
|
||
> DELETE /themes/1 HTTP/1.1 | ||
|
||
### response | ||
|
||
> HTTP/1.1 204 | ||
|
||
## 예약 가능 시간 조회 API | ||
|
||
### Request | ||
|
||
> GET /availableTimes?date=${date}&themeId=${themeId} | ||
|
||
### response | ||
|
||
> HTTP/1.1 200 | ||
> Content-Type: application/json | ||
|
||
```json | ||
[ | ||
{ | ||
"id": 0, | ||
"startAt": "02:53", | ||
"isBooked": false | ||
} | ||
] | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package roomescape.config; | ||
|
||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | ||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; | ||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; | ||
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 { | ||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | ||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); | ||
Comment on lines
+14
to
+15
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.
|
||
|
||
@Bean | ||
public Jackson2ObjectMapperBuilderCustomizer localTimeSerializerCustomizer() { | ||
return builder -> builder.serializers(new LocalTimeSerializer(TIME_FORMATTER), | ||
new LocalDateSerializer(DATE_FORMATTER)) | ||
.deserializers(new LocalTimeDeserializer(TIME_FORMATTER), new LocalDateDeserializer(DATE_FORMATTER)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
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") | ||
public class AdminController { | ||
@GetMapping | ||
public String mainPage() { | ||
return "admin/index"; | ||
} | ||
|
||
@GetMapping("/reservation") | ||
public String reservationPage() { | ||
return "admin/reservation-new"; | ||
} | ||
|
||
@GetMapping("/time") | ||
public String reservationTimePage() { | ||
return "admin/time"; | ||
} | ||
|
||
@GetMapping("/theme") | ||
public String themePage() { | ||
return "admin/theme"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package roomescape.controller; | ||
|
||
import java.time.LocalDate; | ||
import java.util.List; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import roomescape.dto.AvailableTimeResponse; | ||
import roomescape.service.AvailableTimeService; | ||
|
||
@RestController | ||
@RequestMapping("/availableTimes") | ||
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.
좋은 점이라기 보단, 기존의 방탈출 시작 시간 자원과 이 API가 제공해야하는 자원이 전혀 다른 자원이라고 생각했습니다. 왜냐면 이 API가 제공하는 자원은 "예약" 이라는 행위의 영향을 받기 때문입니다. 그런데 이 결정이 일반적인 URI 설계는 아닌 것 같아서 확인해보는 중입니다. |
||
public class AvailableTimeController { | ||
|
||
private final AvailableTimeService availableTimeService; | ||
|
||
public AvailableTimeController(AvailableTimeService availableTimeService) { | ||
this.availableTimeService = availableTimeService; | ||
} | ||
|
||
@GetMapping() | ||
public List<AvailableTimeResponse> findByThemeAndDate(@RequestParam LocalDate date, @RequestParam long themeId) { | ||
return availableTimeService.findByThemeAndDate(date, themeId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
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.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import roomescape.dto.ReservationRequest; | ||
import roomescape.dto.ReservationResponse; | ||
import roomescape.service.ReservationService; | ||
|
||
@RestController | ||
@RequestMapping("/reservations") | ||
public class ReservationController { | ||
private final ReservationService reservationService; | ||
|
||
public ReservationController(ReservationService reservationService) { | ||
this.reservationService = reservationService; | ||
} | ||
|
||
@PostMapping | ||
public ResponseEntity<ReservationResponse> saveReservation(@RequestBody ReservationRequest reservationRequest) { | ||
ReservationResponse saved = reservationService.save(reservationRequest); | ||
return ResponseEntity.created(URI.create("/reservations/" + saved.id())) | ||
.body(saved); | ||
Comment on lines
+29
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. 지금은 바디만 필요한 상태인데 URI까지 담아서 리턴하는 이유가 있을까요?
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 reservationService.findAll(); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
public ResponseEntity<Void> delete(@PathVariable long id) { | ||
reservationService.delete(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
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.