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 all 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
263 changes: 263 additions & 0 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
@@ -0,0 +1,263 @@
# 기능 명세

## Admin

- [X] `localhost:8080/admin` 요청 시 어드민 메인 페이지를 응답한다.
- [X] `/admin/reservation` 요청 시 예약 관리 페이지를 응답한다.
- [X] `/admin/time` 요청 시 시간 관리 페이지를 응답한다.

## 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

- [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 명세

Comment on lines +48 to +49
Copy link
Member

Choose a reason for hiding this comment

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

👍👍

## reservations

### GET

```http
GET /reservations HTTP/1.1
```

```http
[
{
"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,
"startAt": "10:00"
}
}
]
```

### Post

```http
POST /reservations HTTP/1.1
content-type: application/json
{
"date": "2023-08-05",
"name": "브라운",
"themeId": 1,
"timeId": 1
}
```

```http
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,
"startAt" : "10:00"
}
}
```

### Delete

```http
DELETE /reservations/1 HTTP/1.1
```

```http
HTTP/1.1 204
```

## times

### GET

```http
GET /times HTTP/1.1
```

```http
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
}
]
```

### Post

```http
POST /times HTTP/1.1
content-type: application/json
{
"startAt": "10:00"
}
```

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

### Delete

```http
DELETE /times/1 HTTP/1.1
```

```http
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
```
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

runtimeOnly 'com.h2database:h2'

implementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/roomescape/controller/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 getIndexPage() {
return "admin/index";
}

@GetMapping("/reservation")
public String getReservationPage() {
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로 옮겼어 👍

43 changes: 43 additions & 0 deletions src/main/java/roomescape/controller/ReservationApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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;
import roomescape.service.dto.ReservationResponseDto;

@RestController
@RequestMapping("/reservations")
public class ReservationApiController {

private final ReservationService reservationService;

public ReservationApiController(ReservationService reservationService) {
this.reservationService = reservationService;
}

@GetMapping
public List<ReservationResponseDto> findReservations() {
return reservationService.findAllReservations();
}

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public ReservationResponseDto createReservation(@RequestBody ReservationRequestDto requestDto) {
return reservationService.createReservation(requestDto);
}
Comment on lines +32 to +36
Copy link
Member

@Libienz Libienz May 3, 2024

Choose a reason for hiding this comment

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

우리가 응답을 커스텀하는 정도는 현재에선 상태 코드뿐인 것 같아.
그런 점에서 객체를 던지면서 @ResponseStatus 이용하는 현재의 코드는 미션 볼륨에 핏한 구현이라고 생각되네.

그런데 ResponseEntity와 비교해서 어떤 장점이 있는지는 잘 모르겠네 🤔
재즈는 어떻게 생각??

Copy link
Member Author

Choose a reason for hiding this comment

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

난 저번 레벨 미션 리뷰어로부터 리뷰를 받고,
ResponseEntity가 정말 필요한 경우가 아니라면 객체를 바로 반환하고 @ResponseStatus를 사용하는 것이 낫다. 라는 기준이 생겼어.

나도 전에는 리비랑 같은 궁금증이 있었는데, 리뷰어로부터 근본적인 답변을 듣지는 못했지만
ResponseEntity를 사용하는 목적을 생각하면 응답 시 Status code, HttpHeader, Body를 더 유연하게 제어할 수 있다.
이러한 상황이 필요한게 아니면 사용할 필요가 없다. 라는 의견이었고,

이제는 나도 ㅋㅋ 컨트롤러 코드에 ResponseEntity가 섞여있는 것 보다, 확실하게 필요한 부분에만 적절히 사용하는게 코드를 볼 때 더 명확하지 않나 생각이 들어.

특히 단순히 void를 반환 한다거나, 상태 코드 200 같은 경우엔 더더욱 사용할 필요가 없다는 생각이야


@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
public void deleteReservation(@PathVariable long id) {
reservationService.deleteReservation(id);
}
}
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";
}
}
Loading