Skip to content

Commit 4a9270c

Browse files
authored
고생하셨습니다.
🎉 PR 머지 완료! 🎉
1 parent 8f2f447 commit 4a9270c

16 files changed

+378
-115
lines changed

README.md

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
### 컨트롤러 (Controller)
2-
- `AdminController`: `@Controller`를 사용하여 웹 페이지를 반환합니다. 사용자가 특정 경로로 접속했을 때, 해당하는 HTML 파일을 렌더링하여 보여줍니다.
3-
- `/`: `home.html` (홈 페이지)
4-
- `/reservation`: `reservation.html` (예약 관리 페이지)
5-
- `ReservationController`: `@RestController`를 사용하여 RESTful API를 제공합니다. HTTP 요청을 받아 `ReservationService`를 호출하고, 그 결과를 JSON 형태로 반환합니다.
6-
- `GET /reservations`: 모든 예약 목록을 조회합니다.
7-
- `POST /reservations`: 새로운 예약을 생성합니다.
8-
- `DELETE /reservations/{id}`: 특정 예약을 삭제합니다.
1+
## 📂 프로젝트 구조
92

10-
### 서비스 (Service)
11-
- `ReservationService`: 핵심 비즈니스 로직을 담당합니다.
12-
- 예약 데이터를 `List<Reservation>` 형태로 메모리에 저장하고 관리합니다.
13-
- `AtomicLong`을 사용하여 예약 ID를 순차적으로 생성합니다.
14-
- 주요 메서드:
15-
- `getAllReservations()`: 전체 예약 목록을 반환합니다.
16-
- `addReservation()`: 새로운 예약을 리스트에 추가하고, 생성된 예약 객체를 반환합니다.
17-
- `deleteReservation()`: ID를 기준으로 예약을 찾아 리스트에서 삭제합니다. 존재하지 않는 ID일 경우 `IllegalArgumentException`을 발생시킵니다.
3+
```
4+
.
5+
├── build.gradle
6+
├── src
7+
│ ├── main
8+
│ │ ├── java
9+
│ │ │ └── roomescape
10+
│ │ │ ├── controller
11+
│ │ │ │ ├── AdminController.java # 웹 페이지 라우팅
12+
│ │ │ │ └── ReservationController.java # 예약 API
13+
│ │ │ ├── dto # 데이터 전송 객체
14+
│ │ │ ├── model # 데이터 모델
15+
│ │ │ ├── repository # 데이터 접근
16+
│ │ │ └── service # 비즈니스 로직
17+
```
1818

1919
## 📝 API 엔드포인트
2020

@@ -44,8 +44,3 @@
4444
"time": "14:00"
4545
}
4646
```
47-
48-
## 📄 페이지
49-
50-
- ****: `http://localhost:8080/`
51-
- **예약 관리**: `http://localhost:8080/reservation`

build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ repositories {
1818

1919
// 이 'dependencies' 블록 안에 모든 의존성을 관리하세요.
2020
dependencies {
21+
// [필수 1] JdbcTemplate을 포함한 스프링 JDBC 스타터
22+
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
23+
24+
// [필수 2] H2 데이터베이스 드라이버
25+
runtimeOnly 'com.h2database:h2'
26+
2127
// [필수 1] 웹 서버 엔진
2228
implementation 'org.springframework.boot:spring-boot-starter-web'
2329

@@ -33,6 +39,10 @@ dependencies {
3339
// 테스트에 필요한 의존성
3440
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3541
testImplementation 'io.rest-assured:rest-assured:5.3.1'
42+
43+
44+
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
45+
runtimeOnly 'com.h2database:h2'
3646
}
3747

3848
test {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package roomescape.advice;
2+
3+
import org.springframework.http.HttpStatus;
4+
5+
public enum ErrorCode {
6+
// 1. 공통 에러
7+
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "COMMON-001", "잘못된 입력값입니다."),
8+
9+
// 2. 비즈니스 에러
10+
DUPLICATE_DATA(HttpStatus.CONFLICT, "BIZ-001", "이미 처리된 요청이거나 중복된 데이터입니다."),
11+
IDEMPOTENCY_KEY_MISMATCH(HttpStatus.BAD_REQUEST, "BIZ-002", "멱등성 키 불일치: 요청 내용이 다릅니다.");
12+
13+
private final HttpStatus httpStatus;
14+
private final String code;
15+
private final String message;
16+
17+
public HttpStatus getHttpStatus() {
18+
return httpStatus;
19+
}
20+
21+
public String getCode() {
22+
return code;
23+
}
24+
25+
public String getMessage() {
26+
return message;
27+
}
28+
29+
ErrorCode(HttpStatus httpStatus, String code, String message) {
30+
this.httpStatus = httpStatus;
31+
this.code = code;
32+
this.message = message;
33+
}
34+
}

src/main/java/roomescape/controller/GlobalExceptionHandler.java renamed to src/main/java/roomescape/advice/GlobalExceptionHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package roomescape.controller;
1+
package roomescape.controller; // (또는 roomescape.exception)
22

33
import org.springframework.http.ResponseEntity;
44
import org.springframework.web.bind.MethodArgumentNotValidException;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package roomescape.advice;
2+
3+
public class IdempotencyKeyMismatchException extends RuntimeException {
4+
5+
private final ErrorCode errorCode;
6+
7+
public IdempotencyKeyMismatchException() {
8+
super(ErrorCode.IDEMPOTENCY_KEY_MISMATCH.getMessage());
9+
this.errorCode = ErrorCode.IDEMPOTENCY_KEY_MISMATCH;
10+
}
11+
12+
public ErrorCode getErrorCode() {
13+
return errorCode;
14+
}
15+
}
16+

src/main/java/roomescape/controller/ReservationController.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public ReservationController(ReservationService service) {
2525

2626
@GetMapping
2727
public List<ReservationResponse> getAllReservations() {
28-
return service.getAllReservations().stream()
28+
return service.getAllReservations().stream()
2929
.map(ReservationResponse::from) // (::from은 ReservationResponse::from과 동일)
3030
.toList();
3131
}
@@ -39,12 +39,21 @@ public ResponseEntity<Void> deleteReservation(@PathVariable Long id) {
3939
@PostMapping
4040
public ResponseEntity<ReservationResponse> createReservation(
4141
@Valid @RequestBody ReservationCreateRequest requestDto
42+
//@RequestHeader(value = "Idempotency-Key", required = true) String idempotencyKey
4243
) {
44+
/*if(service.exitsKey(idempotencyKey)) {
45+
Reservation existingReservation = service.get(requestDto);
46+
ReservationResponse responseDto = ReservationResponse.from(existingReservation);
4347
44-
Reservation reservationToCreate = requestDto.toEntity();
48+
Reservation reservationToCreate = new Reservation(
49+
requestDto.name(),
50+
requestDto.date(),
51+
requestDto.time()
52+
);*/
4553

46-
Reservation savedReservation = service.addReservation(reservationToCreate);
54+
//Reservation savedReservation = service.addReservation(reservationToCreate);
4755

56+
Reservation savedReservation = service.addReservation(requestDto.toEntity());
4857
ReservationResponse responseDto = ReservationResponse.from(savedReservation);
4958
URI location = URI.create("/reservations/" + savedReservation.getId());
5059

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package roomescape.dto;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import roomescape.advice.ErrorCode;
5+
6+
public class ErrorResponse {
7+
private final String code;
8+
private final String message;
9+
10+
// 핵심: ErrorCode Enum만 던져주면 알아서 ResponseEntity를 만들어줌!
11+
public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
12+
return ResponseEntity
13+
.status(errorCode.getHttpStatus())
14+
.body(new ErrorResponse(errorCode.getCode(),errorCode.getMessage()));
15+
}
16+
17+
public ErrorResponse(String code, String message) {
18+
this.code = code;
19+
this.message = message;
20+
}
21+
}

src/main/java/roomescape/dto/ReservationCreateRequest.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ public record ReservationCreateRequest(
2323
LocalTime time
2424
) {
2525
public Reservation toEntity() {
26-
return new Reservation(name, date, time);
26+
return Reservation.create(name, date, time);
2727
}
2828
}
29-
30-

src/main/java/roomescape/model/Reservation.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,45 @@
44
import java.time.LocalTime;
55

66
public class Reservation {
7-
private Long id;
8-
private String name;
7+
Long id;
8+
String name;
99
private LocalDate date;
1010
private LocalTime time;
1111

12-
public Reservation(Long id, String name, LocalDate date, LocalTime time) {
12+
private Reservation(Long id, String name, LocalDate date, LocalTime time) {
1313
this.id = id;
1414
this.name = name;
1515
this.date = date;
1616
this.time = time;
1717
}
1818

19-
public Reservation(String name, LocalDate date, LocalTime time) {
20-
this(null, name, date, time);
19+
public static Reservation create(String name, LocalDate date, LocalTime time) {
20+
return new Reservation(null, name, date, time);
21+
}
22+
23+
public static Reservation of(Long id, String name, LocalDate date, LocalTime time) {
24+
return new Reservation(id, name, date, time);
25+
}
26+
27+
public Long getId() {
28+
return id;
29+
}
30+
31+
public String getName() {
32+
return name;
33+
}
34+
35+
public LocalDate getDate() {
36+
return date;
37+
}
38+
39+
public LocalTime getTime() {
40+
return time;
2141
}
2242

23-
public Long getId() { return id; }
24-
public String getName() { return name; }
25-
public LocalDate getDate() { return date; }
26-
public LocalTime getTime() { return time; }
43+
public Reservation(String name, LocalDate date, LocalTime time) {
44+
this.name = name;
45+
this.date = date;
46+
this.time = time;
47+
}
2748
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package roomescape.repository;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.dao.EmptyResultDataAccessException;
5+
import org.springframework.jdbc.core.JdbcTemplate;
6+
import org.springframework.stereotype.Repository;
7+
8+
@Repository
9+
public class IdempotencyRepository {
10+
private final JdbcTemplate jdbcTemplate;
11+
12+
@Autowired
13+
public IdempotencyRepository(JdbcTemplate jdbcTemplate) {
14+
this.jdbcTemplate = jdbcTemplate;
15+
}
16+
17+
public boolean exists(String key) {
18+
String sql = "SELECT count(*) FROM idempotency_keys WHERE id = ?";
19+
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, key);
20+
return count != null && count > 0;
21+
}
22+
23+
public void save(String key) {
24+
String sql = "INSERT INTO idempotency_keys (id) VALUES (?)";
25+
jdbcTemplate.update(sql, key);
26+
}
27+
28+
public Long getReservationId(String key) {
29+
try {
30+
String sql = "SELECT reservation_id FROM idempotency_keys WHERE id = ?";
31+
return jdbcTemplate.queryForObject(sql, Long.class, key);
32+
} catch (EmptyResultDataAccessException e) {
33+
return null;
34+
}
35+
}
36+
37+
public void save(String key, Long reservationId) {
38+
String sql = "INSERT INTO idempotency_keys (id, reservation_id) VALUES (?, ?)";
39+
jdbcTemplate.update(sql, key, reservationId);
40+
}
41+
42+
}

0 commit comments

Comments
 (0)