Skip to content
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단계 방탈출 사용자 예약] 재즈(함석명) 미션 제출합니다. #4

Open
wants to merge 48 commits into
base: seokmyungham
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e9839c2
delete(RoomescapeApplicationTest): 테스트 삭제
seokmyungham May 2, 2024
da08d06
migrate first mission code
seokmyungham Apr 30, 2024
035c771
docs: 기능 및 api 명세 작성
seokmyungham May 1, 2024
084c8a8
feat(controller): api 명세에 맞춰 응답 상태 코드 수정
seokmyungham May 1, 2024
ddbd79f
feat(ReservationTimeRequestDto): 잘못된 입력 검증
seokmyungham May 1, 2024
fe2d58d
feat(ReservationTimeRepository): 테이블에 시간 존재 여부 확인
seokmyungham May 1, 2024
39d5385
feat(ReservationTimeService): 중복된 시간 생성 검증
seokmyungham May 1, 2024
ef02237
feat(ReservationRepository): 시간 아이디에 해당하는 예약 존재 여부 확인
seokmyungham May 1, 2024
05bf12d
feat(ReservationTimeService): 예약이 존재하는 시간 삭제 시도 검증
seokmyungham May 1, 2024
1c8ca4d
feat(ReservationTimeRepository): 테이블에 시간 존재 여부 확인
seokmyungham May 1, 2024
f6ddf1c
feat(ReservationTimeService): 존재하지 않는 아이디 삭제 시도 검증
seokmyungham May 1, 2024
d3fea55
feat(ReservationTimeApiController): ExceptionHandler 추가
seokmyungham May 1, 2024
0915bd4
feat(ReservationRequestDto): 잘못된 입력 검증
seokmyungham May 1, 2024
1c9957e
feat(ReservationRepository): 예약 아이디에 해당하는 예약 존재 여부 확인
seokmyungham May 1, 2024
af3fa40
feat(ReservationService): 존재하지 않는 아이디 삭제 검증
seokmyungham May 1, 2024
ea5a8a6
feat(ReservationRepository): 특정 날짜와 시간에 예약 존재 여부 확인
seokmyungham May 1, 2024
f65e2a6
feat(ReservationService): 중복된 날짜와 시간에 예약 생성 검증
seokmyungham May 1, 2024
b7561fa
feat(ReservationService): 존재하지 않는 시간 아이디로 예약 생성 검증
seokmyungham May 1, 2024
cb936aa
feat(ReservationService): 지나간 날짜와 시간에 대한 예약 생성 검증
seokmyungham May 1, 2024
bddf8c0
refactor(ReservationServiceTest): 호출되지 않은 메서드 검증
seokmyungham May 1, 2024
ffe91f3
refactor(ReservationTimeServiceTest): 테스트에 필요한 조건 명시적으로 추가
seokmyungham May 1, 2024
d9cb606
feat(ReservationApiController): ExceptionHandler 추가
seokmyungham May 1, 2024
68562ec
feat(GlobalExceptionHandler): ControllerAdvice 추가
seokmyungham May 1, 2024
03b2a6d
refactor(ReservationTimeService): 아이디 존재 여부를 먼저 확인하도록 검증 순서 변경
seokmyungham May 1, 2024
855c386
docs: 테마 관련 기능 및 api 명세 추가
seokmyungham May 1, 2024
9acebf3
feat(AdminController): 테마 페이지 매핑 핸들러 추가
seokmyungham May 1, 2024
950583f
feat(Theme): 테마 도메인 생성
seokmyungham May 1, 2024
6a45b7d
feat(ThemeRequestDto): 잘못된 입력 시 예외 발생
seokmyungham May 1, 2024
79c3649
feat(ThemeRepository): 테마 저장, 삭제 및 조회
seokmyungham May 1, 2024
befb697
feat(ThemeService): 테마 저장, 삭제 및 조회
seokmyungham May 2, 2024
df1d0d5
feat(ThemeApiController): 테마 api GET, POST, DELETE 핸들러
seokmyungham May 2, 2024
74f87ee
feat(ReservationRequestDto): 테마 아이디 존재 여부와 자연수 여부 검증
seokmyungham May 2, 2024
3d9a043
feat(ReservationResponseDto): api 명세에 맞게 응답 추가
seokmyungham May 2, 2024
d1937cc
feat(JdbcReservationRepository): 테이블 스키마를 따라 테마 정보 추가
seokmyungham May 2, 2024
e81ab58
docs: 구현 완료 사항 반영
seokmyungham May 2, 2024
5cf59c2
feat(ReservationService): 테마, 날짜, 시간이 동일한 예약 존재 여부 검증
seokmyungham May 2, 2024
b7af5a2
refactor(ReservationApiControllerTest): api 명세 변경에 따른 테스트 수정
seokmyungham May 2, 2024
ff15845
feat: 사용자 예약 구현
seokmyungham May 2, 2024
671017c
feat: 인기 테마 조회 기능
seokmyungham May 2, 2024
d4201ba
feat: HttpMessageNotReadableException, Exception 핸들러 추가
seokmyungham May 2, 2024
ea1d1fa
fix(ThemeService): 날짜 오류 수정
seokmyungham May 2, 2024
ea4db56
feat: 샘플 데이터 생성
seokmyungham May 2, 2024
1b981df
docs: 구현 완료 사항 반영
seokmyungham May 2, 2024
509b904
refactor(ReservationTimeService): 도메인을 사용하도록 변경
seokmyungham May 2, 2024
19ae311
fix: 아이디 변경
seokmyungham May 2, 2024
c18a929
refactor(Repository): 존재 여부 확인 SQL 개선
seokmyungham May 2, 2024
5a93fc4
refactor(MissionStepTest): 인수 테스트 리팩토링
seokmyungham May 2, 2024
2303839
delete: RoomescapeApplicationTest 테스트 삭제
seokmyungham May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 142 additions & 11 deletions README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 명세서 너무 깔끔하게 정리했는데? 👍 👍 👍

Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,44 @@
- [X] `/admin/reservation` 요청 시 예약 관리 페이지를 응답한다.
- [X] `/admin/time` 요청 시 시간 관리 페이지를 응답한다.

## Reservation API
## Reservation

- [X] `/reservations` `GET` 요청 시 예약 목록을 조회하고 API 명세에 맞게 응답을 반환한다.
- [X] `/reservations` `POST` 요청 시 예약을 추가하고 API 명세에 맞게 응답을 반환한다.
- [x] 예약자 명, 테마 아이디, 예약 날짜, 시간 아이디가 비어있으면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 예약 날짜가 형식에 맞지 않는다면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 테마 아이디가 자연수가 아니라면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 예약 시간 아이디가 자연수가 아니라면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 현재보다 이전 날짜 및 시간이라면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 예약 시간 아이디가 시간 테이블에 없으면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 해당 테마에 같은 날짜와 시간의 예약이 존재하면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] `/reservations/{id}` `DELETE` 요청 시 예약을 삭제하고 API 명세에 맞게 응답을 반환한다.
- [x] 존재하지 않는 아이디를 삭제하려고 하면 예외를 발생시키고 상태코드 400을 반환한다.

## Time API
## Time

- [X] `/times` `GET` 요청 시 시간 목록을 조회하고 API 명세에 맞게 응답을 반환한다.
- [X] `/times` `POST` 요청 시 시간을 추가하고 API 명세에 맞게 응답을 반환한다.
- [X] 시작 시간이 null이라면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] 시작 시간이 형식에 맞지 않는다면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] 시작 시간이 중복이라면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] `/times/{id}` `DELETE` 요청 시 시간을 삭제하고 API 명세에 맞게 응답을 반환한다.
- [X] 예약이 존재하는 시간을 삭제하려고 하면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] 존재하지 않는 아이디를 삭제하려고 하면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] `/times/availability?date=${date}&themeId=${themeId}` `GET` 요청 시 시간 목록을 조회하고 API 명세에 맞게 응답을 반환한다.
- [X] 날짜, 테마 아이디가 비어있으면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] 예약 날짜가 형식에 맞지 않는다면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] 테마 아이디가 자연수가 아니라면 예외를 발생시키고 상태코드 400을 반환한다.

### Theme

- [x] `/themes` `GET` 요청 시 테마 목록을 조회하고 API 명세에 맞게 응답을 반환한다.
- [x] `/themes` `POST` 요청 시 테마를 추가하고 API 명세에 맞게 응답을 반환한다.
- [x] 테마 이름, 테마 설명, 테마 썸네일이 비어있다면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] `/themes` `DELETE` 요청 시 테마를 삭제하고 API 명세에 맞게 응답을 반환한다.
- [x] 예약이 존재하는 테마를 삭제하려고 하면 예외를 발생시키고 상태코드 400을 반환한다.
- [x] 존재하지 않는 아이디를 삭제하려고 하면 예외를 발생시키고 상태코드 400을 반환한다.
- [X] `/themes/rank` `GET` 요청 시 예약 순서로 인기 테마 결과를 API 명세에 맞게 반환한다.

# API 명세

Expand All @@ -33,6 +60,12 @@ GET /reservations HTTP/1.1
{
"id": 1,
"name": "브라운",
"theme": {
"id": 1,
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
},
"date": "2023-08-05",
"time": {
"id": 1,
Expand All @@ -47,21 +80,26 @@ GET /reservations HTTP/1.1
```http
POST /reservations HTTP/1.1
content-type: application/json

{
"date": "2023-08-05",
"name": "브라운",
"themeId": 1,
"timeId": 1
}
```

```http
HTTP/1.1 200
HTTP/1.1 201
Content-Type: application/json

{
"id": 1,
"name": "브라운",
"theme": {
"id": 1,
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
},
"date": "2023-08-05",
"time" : {
"id": 1,
Expand All @@ -77,7 +115,7 @@ DELETE /reservations/1 HTTP/1.1
```

```http
HTTP/1.1 200
HTTP/1.1 204
```

## times
Expand All @@ -89,13 +127,30 @@ GET /times HTTP/1.1
```

```http
HTTP/1.1 200
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 1,
"startAt": "10:00"
}
]
```

### GET

```http
GET /times/availability?date="2024-12-20"&themeId=1 HTTP/1.1
```

```http
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 1,
"startAt": "10:00"
"alreadyBooked": true
}
]
```
Expand All @@ -105,16 +160,14 @@ Content-Type: application/json
```http
POST /times HTTP/1.1
content-type: application/json

{
"startAt": "10:00"
}
```

```http
HTTP/1.1 200
HTTP/1.1 201
Content-Type: application/json

{
"id": 1,
"startAt": "10:00"
Expand All @@ -128,5 +181,83 @@ DELETE /times/1 HTTP/1.1
```

```http
HTTP/1.1 200
HTTP/1.1 204
```

## theme

### Get

```http
GET /themes HTTP/1.1
```

```http
HTTP/1.1 200
Content-Type: application/json

[
{
"id": 1,
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
}
]
```

### Get

```http
GET /themes/rank HTTP/1.1
```

```http
HTTP/1.1 200
Content-Type: application/json

[
{
"id": 1,
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
}
]
```

### Post

```http
POST /themes HTTP/1.1
content-type: application/json

{
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
}
```

```http
HTTP/1.1 201
Location: /themes/1
Content-Type: application/json

{
"id": 1,
"name": "레벨2 탈출",
"description": "우테코 레벨2를 탈출하는 내용입니다.",
"thumbnail": "https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg"
}
```

### Delete

```http
DELETE /themes/1 HTTP/1.1
```

```http
HTTP/1.1 204
```
7 changes: 6 additions & 1 deletion src/main/java/roomescape/controller/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ public String getIndexPage() {

@GetMapping("/reservation")
public String getReservationPage() {
return "admin/reservation";
return "admin/reservation-new";
}

@GetMapping("/time")
public String getReservationTimePage() {
return "admin/time";
}

@GetMapping("/theme")
public String getThemePage() {
return "admin/theme";
}
}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/controller/HomeController.java
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 HomeController {

@GetMapping("/")
public String getIndexPage() {
return "index";
}
}
Comment on lines +6 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HomeController 따로 명시 안해줘도 될 것 같은데, 한 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시간이 없어 미처 변경하지 못했는데 지금은 UserController로 옮겼어 👍

Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package roomescape.controller;

import java.util.List;
import org.springframework.http.HttpStatus;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import roomescape.service.ReservationService;
import roomescape.service.dto.ReservationRequestDto;
Expand All @@ -27,11 +29,13 @@ public List<ReservationResponseDto> findReservations() {
return reservationService.findAllReservations();
}

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public ReservationResponseDto createReservation(@RequestBody ReservationRequestDto requestDto) {
return reservationService.createReservation(requestDto);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
public void deleteReservation(@PathVariable long id) {
reservationService.deleteReservation(id);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package roomescape.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/reservation")
public class ReservationController {

@GetMapping
public String getReservationPage() {
return "reservation";
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package roomescape.controller;

import java.util.List;
import org.springframework.http.HttpStatus;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import roomescape.service.ReservationTimeService;
import roomescape.service.dto.AvailabilityOfTimeRequestDto;
import roomescape.service.dto.ReservationTimeRequestDto;
import roomescape.service.dto.ReservationTimeResponseDto;

Expand All @@ -27,11 +31,19 @@ public List<ReservationTimeResponseDto> findReservationTimes() {
return reservationTimeService.findAllReservationTimes();
}

@GetMapping("/availability")
public List<ReservationTimeResponseDto> findReservationTimesAvailability(@RequestParam String date,
@RequestParam Long themeId) {
return reservationTimeService.findReservationTimesAvailability(new AvailabilityOfTimeRequestDto(date, themeId));
}
Comment on lines +34 to +38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api 엔드포인트를 /times/availability로 정의했군 👍
availability 같은 경우는 자원이라고 해석하기엔 위화감이 잇는 것 같아
URI가 유니크하게 식별할 수 있는 자원을 의미한다면 api 설계가 바뀌어도 좋을 것 같다는 개인적인 생각

어떤 크루는 /times/available?date=...&themeId=...와 같이 times 자원에 대한 필터링을 쿼리파라미터로 두었는데

나는 이쪽이 훨씬 합리적인 것 같더라
내가 REST를 잘 모르기도 해서 재즈의 의견이 궁금하군 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나도 잘 모르는데.. ㅎㅎ
/times/availability로 정했을 때 리뷰어에게 피드백이 오진 않았는데,
/times/availabilty, /times/available 등등
path를 어떤 식으로 지어야 할 지 모르겠다고 여쭤보니, 함수나 변수 이름을 짓는 것과 다를 게 없다고 말해주셨어

REST 규칙 안에서 가져오려고 하는 대상이 무엇인지만 명확하게 알 수 있다면 뭐든 괜찮은 것 같아
나도 지금은 생각이 바껴서 times/available으로 수정했어 😀


@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public ReservationTimeResponseDto createReservationTime(@RequestBody ReservationTimeRequestDto requestDto) {
return reservationTimeService.createReservationTime(requestDto);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
public void deleteReservationTime(@PathVariable long id) {
reservationTimeService.deleteReservationTime(id);
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/roomescape/controller/ThemeApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package roomescape.controller;

import java.util.List;
import org.springframework.http.HttpStatus;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import roomescape.service.ThemeService;
import roomescape.service.dto.ThemeRequestDto;
import roomescape.service.dto.ThemeResponseDto;

@RestController
@RequestMapping("/themes")
public class ThemeApiController {

private final ThemeService themeService;

public ThemeApiController(ThemeService themeService) {
this.themeService = themeService;
}

@GetMapping
public List<ThemeResponseDto> findAllThemes() {
return themeService.findAllThemes();
}

@GetMapping("/rank")
public List<ThemeResponseDto> findTopBookedThemes() {
return themeService.findTopBookedThemes();
}

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public ThemeResponseDto createTheme(@RequestBody ThemeRequestDto requestDto) {
return themeService.createTheme(requestDto);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
public void deleteTheme(@PathVariable long id) {
themeService.deleteTheme(id);
}
}
Loading