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단계 제출 #8

Open
wants to merge 79 commits into
base: robinjoon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
64335cd
feat: 이전 미션 코드 가져오기
robinjoon Apr 30, 2024
9d575fe
docs: 요구사항 변경에 따른 README.md 수정
robinjoon Apr 30, 2024
258c9b6
fix: 시간 생성 API 응답 변경
robinjoon Apr 30, 2024
dbb1b8b
fix: 예약 생성 API 응답 변경
robinjoon Apr 30, 2024
202216d
fix: 예약, 예약 시간 삭제 API 응답 변경
robinjoon Apr 30, 2024
bae62c9
fix: 누락된 설정 파일 추가
robinjoon Apr 30, 2024
fc59282
fix: hh:MM 형식이 아닌 시간 생성 요청시 예외 처리
robinjoon Apr 30, 2024
65733d0
refactor : Repository 가 DTO를 사용하지 않도록 변경
robinjoon Apr 30, 2024
1bbadab
feat : ReservationTime 의 startAt 에 대한 null 검증 추가 및 존재하지 않는 시간에 예약 생성 …
robinjoon Apr 30, 2024
a14d390
feat : 중복된 예약 시간 생성 요청 시 에러 구현
robinjoon Apr 30, 2024
2da2b7a
feat : 예약이 있는 예약 시간을 삭제 요청 시 에러 구현
robinjoon Apr 30, 2024
4f222f4
docs : 존재하지 않는 자원을 삭제하는 경우 예외처리 삭제
robinjoon Apr 30, 2024
6a4ebb7
feat: 동일한 날짜와 시간에 예약 생성 요청 시 에러 구현
robinjoon Apr 30, 2024
60e2f88
feat: ISO 8601 표준에 따른 YYYY-MM-dd 포맷에 해당하지 않는 날짜가 포함된 예약 생성 요청 시 에러
robinjoon Apr 30, 2024
80b2371
feat: 지나간 날짜와 시간의 예약 요청 시 에러 구현
robinjoon Apr 30, 2024
a5ca6d4
feat: 이름이 비어있는 예약 요청 시 에러 구현
robinjoon Apr 30, 2024
36fe37e
refactor: 커스텀 예외 도입
robinjoon Apr 30, 2024
3e37f25
docs: api 명세 수정
robinjoon Apr 30, 2024
9d83ffe
docs: api 명세 추가
robinjoon Apr 30, 2024
6f1be1a
docs: 기능 목록 추가
robinjoon Apr 30, 2024
c75c485
feat: 변경된 페이지로 반환하도록 수정
robinjoon Apr 30, 2024
4d4c985
feat: 테마 CRD 구현
robinjoon Apr 30, 2024
44c1974
feat: 기존 에약 API 에 테마 추가
robinjoon Apr 30, 2024
af7f7e6
docs: 누락된 문서 작성
robinjoon Apr 30, 2024
31df8bd
docs: 이미 해결한 Todo 삭제
robinjoon Apr 30, 2024
564ecaf
docs: 이미 해결한 Todo 삭제
robinjoon Apr 30, 2024
550f4cd
fix: equals 메서드가 id만으로 비교하도록 수정
robinjoon Apr 30, 2024
d1c9bee
fix: 중복 예약 검증 로직이 테마도 확인하도록 변경
robinjoon Apr 30, 2024
1cfec9a
docs: 사용자 예약 기능 및 API 명세 추가
robinjoon Apr 30, 2024
37c3bc1
feat: 사용자 예약 기능 구현
robinjoon Apr 30, 2024
cb113fd
test : 도메인 로직 테스트
zangsu May 1, 2024
42c2415
refactor : 도메인에게 위임할 수 있는 책임을 도메인으로 이동
zangsu May 1, 2024
8a42260
test : ThemeService 단위 테스트 작성
zangsu May 1, 2024
584df82
style : 누락된 컨벤션 적용
zangsu May 1, 2024
f6966e1
refactor : 기존의 테스트 개선
zangsu May 1, 2024
9adfa8d
refactor : Reservation 생성에서 검증 추가
zangsu May 1, 2024
24c83c6
test : ReservationService 단위테스트 작성
zangsu May 1, 2024
5f25eaf
refactor : CollectionReservationRepository 가 다른 Repository 를 의존하지 않도록 변경
zangsu May 1, 2024
b4b52b4
refactor : getter 대신 구현된 도메인 메서드를 사용하도록 변경
zangsu May 1, 2024
cc62f4b
refactor : 해결된 TODO 삭제
zangsu May 1, 2024
f690708
style: 누락된 static import 처리
zangsu May 2, 2024
1e96e33
style: 처리된 todo 삭제
zangsu May 2, 2024
06bb27c
test: ReservationTimeService 로직 테스트
zangsu May 2, 2024
2255391
test: AvailableTimeService 로직 테스트
zangsu May 2, 2024
ee5b115
style: 컨벤션 적용
zangsu May 2, 2024
77a3e24
fix: 테스트 전에 theme 테이블도 초기화
zangsu May 2, 2024
173699c
Merge pull request #1 from zangsu/pair-refactor
robinjoon May 2, 2024
3689dc7
docs: 인기 테마 기능 추가
robinjoon May 2, 2024
0516901
feat: 인기 테마 기능 구현
robinjoon May 2, 2024
3c472a3
Merge branch 'robinjoon' into step3-2
robinjoon May 2, 2024
b55ca91
Merge pull request #2 from robinjoon/step3-2
robinjoon May 2, 2024
2789dae
refactor: ExceptionType 네이밍 변경
zangsu May 2, 2024
c6f2992
Merge pull request #3 from zangsu/robinjoon
robinjoon May 2, 2024
6ec052d
refactor: ResultSet 사용 방식 변경
robinjoon May 2, 2024
ad587b5
docs: 해결한 미션 가이드 Todo 삭제
robinjoon May 2, 2024
ddf88b1
docs: "TODO : NAME_EMPYT 재사용 고민" 삭제
robinjoon May 2, 2024
e36c497
docs: "//todo : long 으로 변경" 삭제
robinjoon May 2, 2024
3961bb6
docs: "//todo : 변수명 고민" 삭제
robinjoon May 2, 2024
1378a92
docs: "//TODO : 테스트 생성" 삭제
robinjoon May 2, 2024
093f68b
docs: "//Todo 테스트코드 작성" 삭제
robinjoon May 2, 2024
1024c7e
docs: "todo [로빈] 이 테스트를 작성하다 보니 Response dto 에도 NotNull 검증은 필요할 것 같다는…
robinjoon May 2, 2024
258466c
docs: "TODO : 도메인에게 넘길 수 있을 것 같은데" 삭제
robinjoon May 2, 2024
8c4efbc
docs: "Todo : [로빈] Response 가 변환 로직을 가지고 있으면 아래 코드도 간단해 질 것 같음" 삭제
robinjoon May 2, 2024
c7c5033
refactor : 도메인으로 이동할 수 있는 책임을 이동
robinjoon May 2, 2024
0391803
docs: "Todo 상태코드 고민해보기"
robinjoon May 2, 2024
c7f2dad
refactor: getter 대신 도메인 로직을 사용하도록 변경
robinjoon May 2, 2024
2416896
docs: 해결된 todo 삭제
robinjoon May 2, 2024
e8e7a6f
style: 컨벤션 적용
robinjoon May 2, 2024
62cef24
docs: 누락된 체크 표시 추가
robinjoon May 4, 2024
1561f7e
docs: 리팩토링 대상 목록 정리
robinjoon May 5, 2024
c9b2f37
refactor: RowMapper 구현 클래스 추가 및 sql 스타일 통일
robinjoon May 5, 2024
92e8519
refactor: Stream 대신 SQL을 사용하도록 수정
robinjoon May 5, 2024
a1ed68a
style: 길이가 긴 SQL 문을 텍스트 블록으로 표현
robinjoon May 5, 2024
5d02747
test: 인기 테마 조회 기능 테스트 추가
robinjoon May 7, 2024
eb38c7d
test: 누락된 Repository 테스트 추가
robinjoon May 7, 2024
9881fc7
docs: 리팩토링 목록 수정
robinjoon May 7, 2024
239c241
feat: 테마의 thumbnail 이 url 형태인지 검증하는 기능 추가
robinjoon May 7, 2024
20fddde
refactor: 정적 페이지를 반환하는 컨트롤러 이름 수정
robinjoon May 7, 2024
e12b17e
refactor: 예약 가능한 시간 조회 API 명세 수정
robinjoon May 7, 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
248 changes: 248 additions & 0 deletions README.md
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] 사용자 예약 기능 추가
- [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 /times/book-able?date=${date}&themeId=${themeId}

### response

> HTTP/1.1 200
> Content-Type: application/json

```json
[
{
"id": 0,
"startAt": "02:53",
"isBooked": false
}
]
```
7 changes: 7 additions & 0 deletions REFACTORING_LIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 리팩토링 대상 목록

### 우선순위 높음

1. Repository.findAll() + Stream 을 사용한 코드
2. SQL 표준 작성 방식에 따라 대소문자 통일
3. ~~Repository 의 인메모리 구현체 대신 다른 종류의 테스트 더블 사용하기~~
23 changes: 23 additions & 0 deletions src/main/java/roomescape/config/TimeFormatterConfig.java
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
Copy link
Member

Choose a reason for hiding this comment

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

저도 이런걸 추가하려다 말았는데... 로빈은 이 부분을 따로 만들어둔 이유가 뭔가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

저도 이런걸 추가하려다 말았는데... 로빈은 이 부분을 따로 만들어둔 이유가 뭔가요?

  1. 응답이나 요청의 시간 포맷이 API 마다 다른 경우는 거의 없다고 판단.

    • 따라서 전역적으로 시간 포맷을 결정해야 한다고 생각했음.
    • 만약, 특정 API에서 시간 포맷이 달라진다면 그곳에서만 추가적인 설정이 가능하기 때문에 대응이 가능함.
  2. 예외를 잡을 수 있음

    • Spring에서 핸들러의 메서드 파라미터를 역직렬화하는 과정에서 오류가 있다면 지정된 예외를 던짐.
    • 그리고 그 예외는 ControllerAdvice 에서 잡을 수 있음.
    • 따라서 잘못된 포맷이 입력되었을 경우의 예외 처리를 할 수 있음.


@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));
}
}
29 changes: 29 additions & 0 deletions src/main/java/roomescape/controller/AdminPageController.java
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 AdminPageController {
@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";
}
}
43 changes: 43 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
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
Copy link
Member

Choose a reason for hiding this comment

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

지금은 바디만 필요한 상태인데 URI까지 담아서 리턴하는 이유가 있을까요?

@ResponseStatus(HttpStatus.CREATED) 를 사용하면 ResponseEntity 없이도 201을 넘길 수 있답니닷...!
저는 이번에 알아서 이걸 사용해봤는데... 로빈은 알고 있지만 안썼을 수도 있겠다 싶어서 궁금하네용

Copy link
Member Author

Choose a reason for hiding this comment

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

지금은 바디만 필요한 상태인데 URI까지 담아서 리턴하는 이유가 있을까요?

@ResponseStatus(HttpStatus.CREATED) 를 사용하면 ResponseEntity 없이도 201을 넘길 수 있답니닷...! 저는 이번에 알아서 이걸 사용해봤는데... 로빈은 알고 있지만 안썼을 수도 있겠다 싶어서 궁금하네용

현재 테스트코드에 반영되었는지는 모르겠지만(워낙 시간이 없었어서...) 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();
}
}
Loading