Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5277a36
feat: Spring Web, Thymeleaf 의존성 추가
gjtjrl303 Apr 30, 2025
3ae6863
docs: README 작성
gjtjrl303 Apr 30, 2025
6e05245
feat: 2단계 테스트 코드 작성
gjtjrl303 Apr 30, 2025
363baa0
feat: homeController 구현
gjtjrl303 Apr 30, 2025
0a84565
feat: Reservation 객체 구현
gjtjrl303 Apr 30, 2025
0c7afc6
feat: Reservation dto 객체 구현
gjtjrl303 Apr 30, 2025
2f99f81
feat: ReservationController 구현
gjtjrl303 Apr 30, 2025
4ceb2a8
refactor: @RequestMapping → @GetMapping으로 변경 (/ 경로)변경
gjtjrl303 May 3, 2025
698ae7e
feat: MemoryReservationRepository 구현
gjtjrl303 May 3, 2025
e09efa9
refactor: Reservation 클래스의 필드 final 제거하여 유연한 값 변경 가능하게 수정
gjtjrl303 May 3, 2025
e597d1b
refactor: controller에 있던 예약 저장 로직을 repository로 이동
gjtjrl303 May 3, 2025
d11c3a4
refactor: ReservationResponse DTO를 class에서 record로 변경
gjtjrl303 May 3, 2025
6f40799
feat: ReservationService 구현
gjtjrl303 May 7, 2025
16b13ed
feat: ReservationRequestValidator 구현
gjtjrl303 May 7, 2025
9203e52
refactor: API 전용 컨트롤러와 페이지 컨트롤러 분리
gjtjrl303 May 7, 2025
34444aa
feat: NotFoundReservationException 구현
gjtjrl303 May 7, 2025
a0685fd
feat: test코드 구현
gjtjrl303 May 7, 2025
a10ee63
docs: README 작성
gjtjrl303 May 7, 2025
f5d9211
feat: ErrorResult 객체 구현
gjtjrl303 May 7, 2025
c670ca7
refactor: Reservation 기본 생성자 삭제
gjtjrl303 May 7, 2025
958dd27
delete: ReservationController 삭제
gjtjrl303 May 7, 2025
ca8c4f1
feat: ReservationExceptionHandler 구현
gjtjrl303 May 7, 2025
6f5afca
feat: ReservationRequest dto 구현
gjtjrl303 May 7, 2025
984c1fa
feat: 충돌 해결
gjtjrl303 May 7, 2025
b98bf11
feat: 충돌 해결
gjtjrl303 May 7, 2025
99190e7
refactor: 사용하지 않는 코드 제거
gjtjrl303 May 7, 2025
6dd45ef
docs: 도메인 특화 용어 제거
gjtjrl303 May 10, 2025
809e2db
refactor: ErrorResult class에서 record로 변경
gjtjrl303 May 10, 2025
a3d38dd
feat: GlobalExceptionHandler를 통해 전역 및 예상치 못한 예외 처리 추가
gjtjrl303 May 10, 2025
8e49f4e
refactor: 테스트 코드에서 과거 날짜 → 현재 이후 날짜로 수정
gjtjrl303 May 10, 2025
5a9fc02
feat: Reservation 관련 커스텀 예외 구현
gjtjrl303 May 10, 2025
6e028d7
refactor: dto 패키지 위치 controller로 이동
gjtjrl303 May 10, 2025
ef07788
refactor: ExceptionHandler GlobalExceptionHandler로 통합
gjtjrl303 May 10, 2025
967b876
refactor: Reservation 검증 로직 entity내부로 이동
gjtjrl303 May 10, 2025
ac439b1
feat: ReservationDTO 변환을 위한 mapper 구현
gjtjrl303 May 10, 2025
7af7815
refactor: mapper를 통해 DTO 변환하도록 변경
gjtjrl303 May 10, 2025
05e844e
feat: 서비스 Reservation 반환 DTO 구현
gjtjrl303 May 10, 2025
fa64bd3
refactor: 존재하는 id 추가할때 커스텀 예외를 던지도록 변경
gjtjrl303 May 10, 2025
e5e2d78
feat: 서비스 save메서드를 위한 dto 구현
gjtjrl303 May 10, 2025
d0f88e9
delete: ReservationMapper 삭제
gjtjrl303 May 11, 2025
7001478
feat: Reservation 엔티티에 필드 검증 로직 추가
gjtjrl303 May 11, 2025
cd2ce20
refactor: ReservationRequest의 검증 로직을 메서드로 분리
gjtjrl303 May 11, 2025
660d181
refactor: DTO 변환을 Mapper에서 객체 내부 메서드로 이동
gjtjrl303 May 11, 2025
59454c7
refactor: 일관된 예외 메시지 대신 e.getMessage()로 상세 원인 제공
gjtjrl303 May 11, 2025
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
141 changes: 141 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,144 @@
- /reservation: 예약 관리 페이지 (reservation.html) 응답
- /reservations: 예약 데이터 JSON 응답

## 3단계 - 예약 등록
- `POST /reservations`: 새로운 예약 정보를 등록합니다.
- 요청 본문에 예약자의 이름, 날짜, 시간이 포함됩니다.
- 성공 시 `201 Created` 응답 및 Location 헤더 반환

## 4단계 - 예외 처리
- 클라이언트 요청의 문제로 예외가 발생할 경우 `400 Bad Request`를 반환합니다.
- 처리되는 예외 예시:
- 예약 입력값 누락 (name, date, time 중 하나라도 누락)
- 존재하지 않는 예약 ID로 삭제 요청
- JSON 파싱 오류 (날짜/시간 형식이 올바르지 않음)

# 📌 API 명세서

---

## GET /reservations

- 설명: 모든 예약 목록 조회
- 응답: `200 OK`
- 응답 본문 예시:

```json
[
{
"id": 1,
"name": "브라운",
"date": "2023-08-05",
"time": "15:40"
},
{
"id": 2,
"name": "죠르디",
"date": "2023-08-05",
"time": "17:00"
}
// ... 더 많은 예약
]

```
| 필드 | 타입 | 설명 |
| ------ | --------------------- | ------ |
| `id` | `number` | 예약 식별자 |
| `name` | `string` | 예약자 이름 |
| `date` | `string (YYYY-MM-DD)` | 예약 날짜 |
| `time` | `string (HH:mm)` | 예약 시간 |

---
## POST /reservations
- 설명: 예약 추가
- 응답: `201 Created`
- 응답 본문 예시:
``` json
{
"id": 1,
"name": "브라운",
"date": "2023-08-05",
"time": "15:40"
}
```
| 필드 | 타입 | 필수 여부 | 설명 |
| ------ | --------------------- | ----- | ------ |
| `name` | `string` | ✅ | 예약자 이름 |
| `date` | `string (YYYY-MM-DD)` | ✅ | 예약 날짜 |
| `time` | `string (HH:mm)` | ✅ | 예약 시간 |
---
## DELETE /reservations/{id}
- 설명: 예약 삭제
- 응답: `204 No Content`
- 응답 본문 예시: 없음

---

## ❗ 예외 응답 명세

### 잘못된 요청 (필드 누락 등)

- 상태 코드: `400 Bad Request`
- 발생 조건:
- 예약 요청에서 name, date, time 중 하나라도 누락되거나
- 빈 문자열 또는 null 값이 들어온 경우

응답 예시:
```json
{
"code": "BAD_REQUEST",
"message": "이름은 필수입니다."
}
```
### 존재하지 않는 예약 ID
- 상태 코드: `400 Bad Request`
- 발생 조건: 존재하지 않는 ID를 가진 예약을 삭제할 때

응답 예시:
```json
{
"code": "NOT_FOUND_RESERVATION",
"message": "해당 예약이 존재하지 않습니다."
}
```

### JSON 파싱 오류
- 상태 코드: `400 Bad Request`
- 발생 조건: 요청 JSON이 잘못된 형식일 경우

응답 예시:
```json
{
"code": "INVALID_JSON",
"message": "형식이 옳바르지 않습니다"
}
```

### 존재하는 ID 중복 예약
- 상태 코드: `409 CONFLICT`
- 발생 조건: 중복된 ID의 Reservation을 예약할 경우

응답 예시:
```json
{
"code": "RESERVATION_ALREADY_EXISTS",
"message": "이미 ID가 존재하는 예약은 저장할 수 없습니다.
}
```

### 예상하지 못한 오류
- 상태 코드: `500 Internal Server Error`
- 발생 조건: 서버 내부에서 예상하지 못한 예외가 발생한 경우

응답 예시:
```json
{
"code": "INTERNAL_SERVER_ERROR",
"message": "예상치 못한 오류 발생"
}
```





Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
public class PageController {

Choose a reason for hiding this comment

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

정적 페이지를 제공하는 역할에 맞는 더 적합한 네이밍이네요.
섬세하시군요!👍


@GetMapping("/")
public String home() {
return "home";
}

@GetMapping("/reservation")
public String reservation() {
return "reservation";
}
}

45 changes: 45 additions & 0 deletions src/main/java/roomescape/controller/ReservationApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package roomescape.controller;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.controller.dto.ReservationRequest;
import roomescape.controller.dto.ReservationResponse;
import roomescape.service.ReservationService;
import roomescape.service.dto.ReservationResult;

import java.net.URI;
import java.util.List;

@RestController
public class ReservationApiController {

private final ReservationService reservationService;

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

@GetMapping("/reservations")
public ResponseEntity<List<ReservationResponse>> findAll() {
List<ReservationResult> all = reservationService.findAll();
List<ReservationResponse> reservationResponses = all.stream()
.map(ReservationResponse::from)
.toList();
return ResponseEntity.ok(reservationResponses);
}

@PostMapping("/reservations")
public ResponseEntity<ReservationResponse> save(@RequestBody ReservationRequest reservationRequest) {
ReservationResult result = reservationService.save(reservationRequest.toCommand());
return ResponseEntity.created(URI.create("/reservations/" + result.id()))
.contentType(MediaType.APPLICATION_JSON)
.body(ReservationResponse.from(result));
}

@DeleteMapping("/reservations/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
reservationService.delete(id);
return ResponseEntity.noContent().build();
}
}
43 changes: 0 additions & 43 deletions src/main/java/roomescape/controller/ReservationController.java

This file was deleted.

48 changes: 48 additions & 0 deletions src/main/java/roomescape/controller/dto/ReservationRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package roomescape.controller.dto;

import roomescape.service.dto.SaveReservationCommand;

import java.time.LocalDate;
import java.time.LocalTime;

public class ReservationRequest {
private final String name;
private final LocalDate date;
private final LocalTime time;

public ReservationRequest(String name, LocalDate date, LocalTime time) {
validate(name, date, time);
this.name = name;
this.date = date;
this.time = time;
}

public SaveReservationCommand toCommand() {
return new SaveReservationCommand(name, date, time);
}

public String getName() {
return name;
}

public LocalDate getDate() {
return date;
}

public LocalTime getTime() {
return time;
}

private static void validate(String name, LocalDate date, LocalTime time) {

Choose a reason for hiding this comment

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

static일 필요는 없겠어요!

Suggested change
private static void validate(String name, LocalDate date, LocalTime time) {
private void validate(String name, LocalDate date, LocalTime time) {

if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 필수입니다.");
}
if (date == null) {
throw new IllegalArgumentException("날짜는 필수입니다.");
}
if (time == null) {
throw new IllegalArgumentException("시간은 필수입니다.");
}
}
}

22 changes: 22 additions & 0 deletions src/main/java/roomescape/controller/dto/ReservationResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package roomescape.controller.dto;

import roomescape.service.dto.ReservationResult;

import java.time.LocalDate;
import java.time.LocalTime;

public record ReservationResponse(
Long id,
String name,
LocalDate date,
LocalTime time
) {

public static ReservationResponse from(ReservationResult result) {
return new ReservationResponse(
result.id(),
result.name(),
result.date(),
result.time()
);
}}
30 changes: 25 additions & 5 deletions src/main/java/roomescape/domain/Reservation.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ public class Reservation {
private LocalTime time;

public Reservation(Long id, String name, LocalDate date, LocalTime time) {
validateName(name);
validateDate(date);
validateTime(time);
this.id = id;
this.name = name;
this.date = date;
this.time = time;
}

public Reservation(String name, LocalDate date, LocalTime time) {
this.id = null;
this.name = name;
this.date = date;
this.time = time;
this(null, name, date, time);
}

public Reservation withId(Long id) {
Expand All @@ -31,7 +31,6 @@ public Reservation withId(Long id) {
public Long getId() {
return id;
}

public String getName() {
return name;
}
Expand All @@ -43,4 +42,25 @@ public LocalDate getDate() {
public LocalTime getTime() {
return time;
}

private void validateName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 필수입니다.");
}
}

private void validateDate(LocalDate date) {
if (date == null) {
throw new IllegalArgumentException("날짜는 필수입니다.");
}
if (date.isBefore(LocalDate.now())) {
throw new IllegalArgumentException("예약 날짜는 오늘 이후여야 합니다.");
}
}

private void validateTime(LocalTime time) {
if (time == null) {
throw new IllegalArgumentException("시간은 필수입니다.");
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/roomescape/exception/ErrorResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package roomescape.exception;

public record ErrorResult(
String code,
String message
) {}
Loading