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단계 방탈출 사용자 예약] 페드로(류형욱) 미션 제출합니다. #33

Merged
merged 46 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
27cbcb0
feat: 어드민 코드 이전
eunjungL Apr 30, 2024
47665fb
docs(README): 1단계 요구사항 추가
eunjungL Apr 30, 2024
319e6c8
feat(ReservationService): 지난 시점에 대한 예약 예외 처리 구현
eunjungL Apr 30, 2024
5f6f8ff
remove(/test/repository): 사용하지 않는 FakeDao 제거
eunjungL Apr 30, 2024
4d62cca
fix(ReservationControllerTest): 테스트 시 항상 미래의 날자를 사용하도록 수정
eunjungL Apr 30, 2024
63bec82
feat(ReservationService): 중복된 예약 예외처리 구현
eunjungL Apr 30, 2024
1a350aa
feat(Reservation): 예약 생성 시 예약자명이 공백일 경우 예외 처리 구현
eunjungL Apr 30, 2024
bbeb00a
feat(GlobalExceptionHandler): BadRequest handler 추가
eunjungL Apr 30, 2024
4ccbd4b
feat(GlobalExceptionHandler): 유효하지 않은 날짜 또는 시간에 대한 예외 처리 구현
eunjungL Apr 30, 2024
01a4652
test(/test/controller): 잘못된 형식의 본문 요청 시 예외 처리 구현
eunjungL Apr 30, 2024
c677f67
test(ReservationServiceTest): 등록되지 않은 시간에 대한 예약 생성 테스트 코드 추가
eunjungL Apr 30, 2024
425c94f
feat(ReservationTimeService): 중복된 예약 시간 생성 예외 처리 구현
eunjungL Apr 30, 2024
4c3b7a1
feat(ReservationTimeService): 시간 삭제 시 참조된 예약이 있을 때 예외 처리 구현
eunjungL Apr 30, 2024
b19bdd5
docs(README): 2단계 요구사항 추가
eunjungL Apr 30, 2024
fbd7f15
feat(AdminController): 테마 목록 페이지 응답 구현
eunjungL Apr 30, 2024
be0322f
feat(AdminController): 예약 시 테마를 지정할 수 있는 예약 페이지 응답 구현
eunjungL Apr 30, 2024
a19edca
feat(ThemeController): 테마 추가 기능 구현
eunjungL Apr 30, 2024
92bc053
feat(ThemeController): 테마 조회 기능 구현
eunjungL Apr 30, 2024
d2af8ac
feat(ThemeController): 테마 삭제 구현
eunjungL Apr 30, 2024
41f4b4f
feat(ReservationController): 방탈출 예약 시 테마 정보 포함하는 예약 기능 구현
eunjungL May 1, 2024
4e5a8ab
feat(ReservationService): 동시간대 예약 시 예외 처리 구현
eunjungL May 1, 2024
7034dfc
feat(ThemeService): 중복된 이름의 테마 추가 시 예외 처리 구현
eunjungL May 1, 2024
35294cb
feat(Theme): 테마 추가 시 이름, 설명, 썸네일 중 하나라도 빈 경우 예외 처리 구현
eunjungL May 1, 2024
0423fa0
feat(ThemeService): 테마 삭제 시 참조된 예약이 있을 때 예외 처리 구현
eunjungL May 1, 2024
a4ef28d
fix(js): js 코드 API 명세에 맞게 수정
eunjungL May 1, 2024
41587b5
docs(README): 3단계 기능 요구사항 추가
eunjungL May 1, 2024
5c7b024
feat(UserController): 사용자 예약 페이지 구현
eunjungL May 1, 2024
2cff101
feat(ThemeController): 지난 일주일 간 인기 테마 조회 구현
eunjungL May 1, 2024
2936539
test(ThemeServiceTest): 인기 테마 조회 테스트 구현
eunjungL May 1, 2024
3d4ab2b
docs(README): 누락된 3단계 요구사항 추가
eunjungL May 2, 2024
7b2c065
feat(ReservationTimeController): 선택된 테마에 따라 예약 가능 여부가 포함된 시간 조회 기능 구현
eunjungL May 2, 2024
2a6e14d
refactor(ReservationTimeController): 쿼리 파라미터에 따른 핸들러 분리
eunjungL May 2, 2024
f0fcb30
refactor: 패키지 구조 정리
eunjungL May 2, 2024
c90785b
refactor: 메서드 정의 순서 변경
eunjungL May 2, 2024
c9923d0
refactor(ThemeService): 테마별 예약 횟수 분류 메서드 분리
eunjungL May 2, 2024
4a8bc9b
refactor(/test): 테스트 픽스쳐 분리
eunjungL May 2, 2024
6937af0
fix(ReservationTimeController): GetMapping에 누락된 / 문자 추가
hw0603 May 7, 2024
7a6a868
fix(ThemeService): 인기 테마 조회 시 날짜 범위 오류 수정
hw0603 May 7, 2024
aade4c3
refactor(ThemeService): 인기 테마 조회 시 ThemeRepository에 접근하지 않고 Reservati…
hw0603 May 7, 2024
9f46f9c
refactor(ReservationService): 중복 예약 검증 시 전체탐색하지 않고 쿼리를 통해 검증하도록 변경
hw0603 May 7, 2024
ac1adb5
refactor(Dao): RowMapper를 static 변수로 변경
hw0603 May 7, 2024
630ff06
refactor(ReservationDao): 존재 여부 확인 메서드들에서 SELECT EXIST() 를 사용하도록 변경
hw0603 May 7, 2024
2f628e4
fix(ThemeControllerTest): 인기 테마 조회 요청 테스트 시 잘못 지정된 path 수정
hw0603 May 7, 2024
07f4c95
refactor(/test): 테스트에서 사용되는 픽스쳐들을 모두 Fixtures 클래스로 이동
hw0603 May 7, 2024
7834789
refactor(/test): Optional 값 검증 시 .isPresent().isTrue() 가 아닌 .isPresen…
hw0603 May 7, 2024
0fc2cdd
refactor(/exception): 예외 클래스 세분화
hw0603 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
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 1단계 - 예외 처리와 응답
## 예약
- [x] 지나간 날짜와 시간에 대한 예약 생성 시 예외가 발생한다.
- [x] 중복 예약 시 예외가 발생한다.
- [x] 예약 생성 시 예약자명이 비어있으면 예외가 발생한다.
- [x] 날짜가 유효하지 않은 값일 시 예외가 발생한다.
- [x] 등록되지 않은 시간에 대한 예약 생성 시 예외가 발생한다.
- [x] 요청 본문이 JSON 형식이 아닐 경우 예외가 발생한다.

## 시간
- [x] 중복된 시간 생성 시 예외가 발생한다.
- [x] 시간 삭제 시 참조된 예약이 있으면 예외가 발생한다.
- [x] 시간이 유효하지 않은 값일 시 예외가 발생한다.
- [x] 요청 본문이 JSON 형식이 아닐 경우 예외가 발생한다.

# 2단계 - 테마 추가
## 테마
- [x] 관리자는 테마를 추가할 수 있다.
- [x] 중복된 테마 이름으로 추가 시 예외가 발생한다.
- [x] 테마 추가 시, 테마 이름, 설명, 썸네일 중 하나라도 비어 있으면 예외가 발생한다.
- [x] 관리자는 테마를 조회할 수 있다.
- [x] 관리자는 테마를 삭제할 수 있다.
- [x] 테마 삭제 시 참조된 예약이 있으면 예외가 발생한다.

## 예약
- [x] 방탈출 예약 시 테마 정보를 포함하여 예약을 추가한다.
- [x] 등록되지 않은 테마에 대한 예약 생성 시 예외가 발생한다.
- [x] 동시간대에 이미 예약된 테마를 예약하는 경우 예외가 발생한다.

## 화면
- [x] `/admin/theme` 요청 시 `templates/admin/theme.html`가 응답한다.
- [x] `/admin/reservation` 요청 시 `templates/admin/reservation-new.html`가 응답한다.

# 3단계 - 사용자 기능
## 예약
- [x] `/reservation` 요청 시 `templates/reservation.html`가 응답한다.

## 테마
- [x] `/` 요청 시 `templates/index.html`가 응답한다.
- [x] 최근 일주일을 기준으로 예약이 많은 상위 10개 테마를 조회할 수 있다.

## 시간
- [x] 선택된 테마에 따라 예약 가능 여부가 포함된 시간을 조회한다.
1 change: 0 additions & 1 deletion src/main/java/roomescape/RoomescapeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public class RoomescapeApplication {
public static void main(String[] args) {
SpringApplication.run(RoomescapeApplication.class, args);
}

}
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 readAdmin() {

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

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

@GetMapping("/theme")
public String readTheme() {
return "admin/theme";
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package roomescape.controller;

import org.springframework.web.bind.annotation.*;
import roomescape.dto.reservation.ReservationCreateRequest;
import roomescape.dto.reservation.ReservationResponse;
import roomescape.service.dto.reservation.ReservationCreateRequest;
import roomescape.service.dto.reservation.ReservationResponse;
import roomescape.service.ReservationService;

import java.util.List;
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/roomescape/controller/ReservationTimeController.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package roomescape.controller;

import org.springframework.web.bind.annotation.*;
import roomescape.domain.ReservationTime;
import roomescape.dto.reservationtime.ReservationTimeCreateRequest;
import roomescape.dto.reservationtime.ReservationTimeResponse;
import roomescape.service.dto.reservationtime.ReservationTimeCreateRequest;
import roomescape.service.dto.reservationtime.ReservationTimeResponse;
import roomescape.service.ReservationTimeService;

import java.time.LocalDate;
import java.util.List;

@RestController
Expand All @@ -23,6 +23,14 @@ public List<ReservationTimeResponse> readTimes() {
return reservationTimeService.readReservationTimes();
}

@GetMapping(params = {"date", "themeId"})
Copy link

Choose a reason for hiding this comment

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

params는 생략 가능할 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

현재 GET /times 에 대한 핸들러가 2개이고, 쿼리 파라미터의 여부에 따라서 다른 핸들러가 호출되도록 해 두었습니다.
이 경우에도 params = {} 부분의 생략이 가능한가요?

@RequestMapping("/times")
public class ReservationTimeController {
    // ...

    @GetMapping  // 쿼리스트링 없을 때 전체 조회
    public List<ReservationTimeResponse> readTimes() {
        return reservationTimeService.readReservationTimes();
    }

    @GetMapping(params = {"date", "themeId"})  // 쿼리스트링 있으면 필터링해서 조회
    public List<ReservationTimeResponse> readTimes(
            @RequestParam(value = "date") LocalDate date,
            @RequestParam(value = "themeId") Long themeId
    ) {
        return reservationTimeService.readReservationTimes(date, themeId);
    }

생략할 경우 충돌로 인해 다음과 같은 오류가 발생합니다.

...: Ambiguous mapping. Cannot map 'reservationTimeController' method 
roomescape.controller.ReservationTimeController#readTimes(LocalDate, Long)
to {GET [/times]}: There is already 'reservationTimeController' bean method

Copy link

Choose a reason for hiding this comment

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

아이고 같은 경로의 API가 존재했군요. 죄송합니다 😅

public List<ReservationTimeResponse> readTimes(
@RequestParam(value = "date") LocalDate date,
@RequestParam(value = "themeId") Long themeId
) {
return reservationTimeService.readReservationTimes(date, themeId);
}

@GetMapping("{id}")
public ReservationTimeResponse readTime(@PathVariable Long id) {
return reservationTimeService.readReservationTime(id);
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/roomescape/controller/ThemeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.service.dto.theme.ThemeCreateRequest;
import roomescape.service.dto.theme.ThemeResponse;
import roomescape.service.ThemeService;

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

@RestController
@RequestMapping("/themes")
public class ThemeController {
private final ThemeService themeService;

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

@PostMapping
public ResponseEntity<ThemeResponse> createTheme(@RequestBody ThemeCreateRequest themeCreateRequest) {
ThemeResponse theme = themeService.createTheme(themeCreateRequest);
return ResponseEntity.created(URI.create("/themes/" + theme.id()))
.body(theme);
}

@GetMapping
public ResponseEntity<List<ThemeResponse>> readThemes() {
List<ThemeResponse> themes = themeService.readThemes();
return ResponseEntity.ok(themes);
Comment on lines +28 to +31
Copy link

Choose a reason for hiding this comment

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

개인적인 생각이지만 200을 줄거면 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로 감싸는 것을 선호하는 편이였습니다.

메서드의 시그니처에서 ResponseEntity 를 반환하고 있는 것을 보면 '아 이 메서드는 웹 요청에 응답하기 위한 메서드이구나'를 바로 파악할 수 있고, 한 RestController 내에서 반환하는 타입이 List, void, ResponseDto 등으로 모두 상이한 것은 어색하기도 하고 추상화 수준이 맞지 않는다고 생각했어요.
(심지어 헤더에 명시적으로 값을 넣어야 하거나, 200 이 아닌 응답을 해야 할 경우에는 한 컨트롤러 내에 ResponseEntity로 감싸진 반환값과 감싸지지 않은 반환값이 같이 존재하게 되니까요🥲)

직전 미션에서 작성했던 ReservationController의 경우, 요구사항에서 예약 추가 요청 시 200을 응답하게 되어 있기도 했었고 이번 미션 진행 시 페어와 자신의 코드 중 선택하여 진행하고, 지난 코드는 수정하지 않는다 라는 조건이 있어 ResponseEntity로 감싸지 않았습니다.

하지만 테마 추가 시에는 201 CREATED를 응답하도록 되어 있기도 하고, 반환값의 형태를 통일하는게 읽기 좋을 것 같다고 생각하여 모두 감싸 주었어요.

범블비는 한 컨트롤러 내의 메서드에서 ResponseEntity, List, DTO, void 등의 상이한 반환값을 가지고 있는 상태는 크게 신경쓰지 않으시는 것인지 궁금합니다!

Copy link

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.

좋은 의견 공유 감사합니다👍

}

@GetMapping("/{id}")
public ResponseEntity<ThemeResponse> readTheme(@PathVariable Long id) {
ThemeResponse themeResponse = themeService.readTheme(id);
return ResponseEntity.ok(themeResponse);
}

@GetMapping("/popular")
public ResponseEntity<List<ThemeResponse>> readPopularThemes() {
List<ThemeResponse> themeResponses = themeService.readPopularThemes();
return ResponseEntity.ok(themeResponses);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTheme(@PathVariable Long id) {
themeService.deleteTheme(id);
return ResponseEntity.noContent().build();
}
}
15 changes: 15 additions & 0 deletions src/main/java/roomescape/controller/UserReservationController.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 UserReservationController {
@GetMapping
public String readUserReservation() {
return "reservation";
}
}
33 changes: 29 additions & 4 deletions src/main/java/roomescape/domain/Reservation.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.domain;

import roomescape.exception.BadRequestException;

import java.time.LocalDate;

public class Reservation {
Copy link

Choose a reason for hiding this comment

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

  public boolean isDuplicated(Reservation other) {
        return date.equals(other.date)
                && time.getId().equals(other.time.getId())
                && theme.getId().equals(other.theme.getId());
    }

Reservation에서는 이런 코드를 찾지 못했는데 혹시 어디를 말씀하시는걸까요?
아니면 exist 쿼리로 바꾸면서 제거된 부분일까요?

id가 부여된 객체의 동등성은 id로 해야겠죠. 코멘트를 남긴 의도는 exist 쿼리 등을 활용해서 존재하면 안 될 객체는 생성하지 않는 게 나을 것 같다는 의도였습니다. 잘 변경해주신 것 같네요.

Copy link
Member Author

Choose a reason for hiding this comment

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

아니면 exist 쿼리로 바꾸면서 제거된 부분일까요?

넵 맞습니다! EXIST 쿼리를 활용하는 형태로 변경하면서 사라진 부분이였어요.

처음에는 EXIST 와 같은 쿼리도 '기능'을 DB에 의존적인 쿼리로 해결하는 부분이라고 생각하여 최대한 지양하려고 했는데, 범블비의 코멘트를 보고 생각이 조금 바뀌었습니다.

어차피 도메인의 영속화를 위해 관계형 DB를 사용하기로 결정했다면, DB에서 제공하는 기능들을 잘 활용하는 것도 DB를 DB답게 사용하는 일인 것 같아요. 물론 말씀해 주신 대로 로직과 관련된 부분을 최대한 도메인에 둬야 한다는 것에는 백번 동의합니다👍

Expand All @@ -8,20 +10,39 @@ public class Reservation {
private final String name;
private final LocalDate date;
private final ReservationTime time;
private final Theme theme;

public Reservation(Long id, String name, LocalDate date, ReservationTime time) {
public Reservation(Long id, String name, LocalDate date, ReservationTime time, Theme theme) {
validateName(name);
this.id = id;
this.name = name;
this.date = date;
this.time = time;
this.theme = theme;
}

private void validateName(String name) {
if (name.isBlank()) {
throw new BadRequestException("이름은 공백일 수 없습니다.");
Copy link

Choose a reason for hiding this comment

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

도메인에서 "Request"의 존재를 알아야할까요?

Copy link
Member Author

@hw0603 hw0603 May 7, 2024

Choose a reason for hiding this comment

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

작성하면서도 고민을 했던 부분이였는데요, 처음에는 IllegalArgumentException을 던지도록 했었지만 Exception Handler 에서 내장 예외를 잡아버릴 경우 해당 예외의 상세 메시지가 클라이언트로 전달되게 됩니다.

개발자가 작성한 유효성 검증 로직이 실패하여 명시적으로 던져지는 내장 예외라면 큰 문제는 없지만, 내장 예외는 그 특성상 의도하지 않은 곳에서도 던져질 수 있기 때문에 예외에 포함되는 메시지를 클라이언트에게 그대로 노출시키는 것은 잠재적으로 위험하다고 생각했습니다.

물론 도메인에서는 IllegalArgumentException을 던지고, 사용하는 쪽에서 커스텀 예외로 컨버팅하여 던지는 것이 제일 좋겠지만 이 경우 해당 도메인을 생성할 때 마다 try~catch 문으로 감싸 줘야 하는 불편이 생깁니다.

위 사항들을 종합적으로 판단했을 때, 도메인 객체 역시 현재 프로젝트 내에 종속적인 객체라고 볼 수 있으므로 프로젝트에 전역적으로 사용할 커스텀 예외를 작성하고, 도메인에서 그 예외를 던지는 것이 크게 어색하지 않다고 판단하여 지금처럼 구현해 두었습니다.

혹시 더 좋은 방법이 있을까요?

Copy link

Choose a reason for hiding this comment

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

  • 전역적으로 사용할 거라면 request란 단어가 포함되는 것이 적절할까 생각해보면 좋을 것 같아요.
  • 개인적으로는 객체의 네이밍을 할 때와 비슷하게 구체적인 예외 클래스를 사용하는 걸 선호하는 편입니다. 예외에도 경중이 있는데 모두 같은 예외를 사용하다보면 이를 나눠야 될 때 힘들더라구요.

Copy link
Member Author

@hw0603 hw0603 May 7, 2024

Choose a reason for hiding this comment

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

클라이언트에서 웹서버로의 요청 뿐만 아니라, 객체 간의 메시지 전달 역시 request라고 볼 수 있다고 생각하여 request 라는 단어를 사용하는 데 큰 무리가 없다고 판단했었어요. 하지만 코드를 읽는 사람에 따라 어색하게 느껴질 수도 있겠네요🥲

확실히 예외가 발생한 이유에 따라 서로 다른 예외를 던지도록 해 두는 것이 유지보수 측면에서 좋을 것 같아요. BadRequestException 의 생성자는 protected로 변경하고, 이를 상속받은 구체 예외 클래스들을 던지도록 변경해 두었습니다!

}
}

public Reservation(String name, LocalDate date, ReservationTime reservationTime) {
this(null, name, date, reservationTime);
public Reservation(String name, LocalDate date, ReservationTime reservationTime, Theme theme) {
this(null, name, date, reservationTime, theme);
}

public Reservation(Long id, Reservation reservation) {
this(id, reservation.getName(), reservation.getDate(), reservation.getTime());
this(id, reservation.name, reservation.date, reservation.time, reservation.theme);
}

public boolean isDuplicated(Reservation other) {
return date.equals(other.date)
&& time.getId().equals(other.time.getId())
&& theme.getId().equals(other.theme.getId());
}

public boolean isSameUser(Reservation other) {
return name.equals(other.getName());
}

public Long getId() {
Expand All @@ -39,4 +60,8 @@ public LocalDate getDate() {
public ReservationTime getTime() {
return time;
}

public Theme getTheme() {
return theme;
}
}
4 changes: 4 additions & 0 deletions src/main/java/roomescape/domain/ReservationTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public ReservationTime(LocalTime startAt) {
this(null, startAt);
}

public boolean isDuplicated(ReservationTime other) {
return startAt.equals(other.startAt);
}

public Long getId() {
return id;
}
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/roomescape/domain/Theme.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package roomescape.domain;

import roomescape.exception.BadRequestException;

public class Theme {

private final Long id;
private final String name;
private final String description;
private final String thumbnail;

public Theme(Long id, String name, String description, String thumbnail) {
validateNotBlank(name, description, thumbnail);
this.id = id;
this.name = name;
this.description = description;
this.thumbnail = thumbnail;
}

private void validateNotBlank(String name, String description, String thumbnail) {
if (name.isBlank() || description.isBlank() || thumbnail.isBlank()) {
throw new BadRequestException("테마의 정보는 비어있을 수 없습니다.");
}
}

public Theme(String name, String description, String thumbnail) {
this(null, name, description, thumbnail);
}

public Theme(Long id, Theme theme) {
this(id, theme.name, theme.description, theme.thumbnail);
}

public boolean isDuplicated(Theme theme) {
return name.equals(theme.name);
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getDescription() {
return description;
}

public String getThumbnail() {
return thumbnail;
}
}
14 changes: 0 additions & 14 deletions src/main/java/roomescape/dto/reservation/ReservationResponse.java

This file was deleted.

This file was deleted.

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

public class BadRequestException extends RuntimeException {

public BadRequestException(String message) {
super(message);
}
}
17 changes: 17 additions & 0 deletions src/main/java/roomescape/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import roomescape.exception.dto.ErrorResponse;

import java.time.DateTimeException;

@RestControllerAdvice
public class GlobalExceptionHandler {

Expand All @@ -16,9 +18,24 @@ public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNot
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(data);
}

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(BadRequestException exception) {
ErrorResponse data = new ErrorResponse(HttpStatus.BAD_REQUEST, exception.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(data);
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableException exception) {
if (exception.getRootCause() instanceof DateTimeException) {
return handleDateTimeParseException();
}

ErrorResponse data = new ErrorResponse(HttpStatus.BAD_REQUEST, "요청에 잘못된 형식의 값이 있습니다.");
return ResponseEntity.badRequest().body(data);
}

private ResponseEntity<ErrorResponse> handleDateTimeParseException() {
ErrorResponse data = new ErrorResponse(HttpStatus.BAD_REQUEST, "잘못된 형식의 날짜 혹은 시간입니다.");
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(data);
}
}
Loading