Skip to content

[2 - 4단계 방탈출 결제 / 배포] 이든(최승준) 미션 제출합니다. #154

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

Merged
merged 50 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e19f767
feat: Payment Entity 구현 및 연관관계 설정
PgmJun Jun 7, 2024
d2a50bf
feat: 결제 시, 결제정보(Payment) DB 저장 로직 추가
PgmJun Jun 7, 2024
20dc304
refactor: size 검증 테스트 isEqualTo() -> hasSize() 로 리팩토링
PgmJun Jun 7, 2024
96e6786
fix: EntityGraph로 LazyInitializationException 오류 해결
PgmJun Jun 7, 2024
925bd01
feat: 내 예약정보 조회에서 paymentKey, amount 확인 로직 추가
PgmJun Jun 7, 2024
1744936
test: Payment 객체 추가로 인한 테스트 로직 변경 사항 반영
PgmJun Jun 7, 2024
58bae37
feat: 예약대기에서 예약 상태로 변경되면 '결제대기' 상태의 예약자가 되도록 구
PgmJun Jun 7, 2024
b3541e5
fix: 회원 비밀번호 제한 범위 오류 수정
PgmJun Jun 7, 2024
739da66
fix: ReservationStatus에 Enumerated 애노테이션 적용
PgmJun Jun 7, 2024
f27ac68
fix: 결제 대기 상태 예약은 내 예약 조회에서 조회되지 않던 오류 수정
PgmJun Jun 7, 2024
f110a47
docs: 예약 삭제 관련 TODO 추가
PgmJun Jun 7, 2024
ddd1a04
refactor: properties -> yml 로 프로퍼티 파일 변경
PgmJun Jun 7, 2024
51f7006
refactor: 회원도 결제 대기 상태라면 예약 삭제 가능하도록 변경
PgmJun Jun 7, 2024
41d465f
feat: 결제 환불 기능 구현
PgmJun Jun 7, 2024
c182a02
fix: 결제 상태 메시지 설정 오류 수정
PgmJun Jun 7, 2024
e0f2ddf
refactor: Payment 테이블에 requestedAt, approvedAt 데이터도 저장하도록 변경
PgmJun Jun 8, 2024
c14525f
feat: 설정한 가격에 따라 다른 금액으로 결제되는 기능 구현
PgmJun Jun 8, 2024
30a775d
feat: 예약정보, 결제정보 Soft Delete 로 처리로 변경
PgmJun Jun 8, 2024
0b07436
feat: 결제 대기 상태의 예약에 대한 결제 기능 구현
PgmJun Jun 9, 2024
35d8c77
feat: 취소된 예약 삭제 기능 구현
PgmJun Jun 9, 2024
8091dd6
feat: 예약 삭제, 예약 취소 API 분리
PgmJun Jun 10, 2024
678eacc
fix: 결제 대기 상태에서 예약을 삭제하면 payment 정보 삭제하지 않도록 수정
PgmJun Jun 10, 2024
b6acee3
test: 예약 취소, 삭제 테스트 코드 분리
PgmJun Jun 10, 2024
1670f8d
feat: Swagger API Operation 추가
PgmJun Jun 10, 2024
a2d04ab
fix: validateReservationStatusForDelete 검증 로직 오류 수정
PgmJun Jun 10, 2024
f98e930
fix: 결제 취소 시간/상태 기록하고 있지 않던 로직 오류 수정
PgmJun Jun 11, 2024
142a568
refactor: 결제 대기 예약 결제 후, DB 반영 딜레이 1초 대기 로직 추가
PgmJun Jun 11, 2024
d1cba2c
test: 결제 취소 시간/상태 기록하고 있지 않던 로직 오류 수정함에 따라 관련 테스트 코드도 수정
PgmJun Jun 11, 2024
22636f2
docs: ERD 추가
PgmJun Jun 12, 2024
1a2cdb8
refactor: ReservationWaiting 연관관계 엔티티에 LAZY 로딩 적용
PgmJun Jun 12, 2024
0b8b54f
fix: delete 기능에 빠져있던 Transactional 처리 추가
PgmJun Jun 12, 2024
bd1ecbe
refactor: indent 초과 로직 메서드 분리
PgmJun Jun 12, 2024
d764ed0
refactor: 내부 동작 예측이 불가능한 생성자 메서드를 정적팩토리 메서드로 변경하여 이름 부여
PgmJun Jun 12, 2024
cff5dc9
refactor: ThemePrice에 값 객체 적용
PgmJun Jun 12, 2024
aff4625
refactor: 엔티티 기본 생성자 접근제어자 protected 로 변경
PgmJun Jun 12, 2024
b9f12de
refactor: 메서드명 적절하게 변경
PgmJun Jun 12, 2024
f962d3a
refactor: 미사용 메서드 제거
PgmJun Jun 12, 2024
8c8b39f
refactor: 의도가 불분명한 changeMember 메서드 대신 정적팩토리 메서드 fromDifferentMember …
PgmJun Jun 12, 2024
ffa92df
refactor: TODO 제거
PgmJun Jun 13, 2024
a8d23ed
test: 결제 대기 예약 결제 테스트 추가
PgmJun Jun 13, 2024
c011941
docs: Swagger JWT 인증 처리 추가
PgmJun Jun 14, 2024
896ba67
refactor: 의미가 드러나지 않는 매직 리터럴 상수 처리
PgmJun Jun 14, 2024
352b659
refactor: 인덴트 2 -> 1로 수정
PgmJun Jun 15, 2024
3600823
refactor: 테마 가격 범위 검증 기능 구현
PgmJun Jun 15, 2024
bbfc9ef
refactor: VO 객체는 record 사용하도록 변경
PgmJun Jun 15, 2024
8f10eb7
feat: 로컬(local), 운영(prod), 테스트(test) 환경 분리
PgmJun Jun 17, 2024
efa6ff0
refactor: 레코드 Custom Constructor 적용
PgmJun Jun 19, 2024
e70f01a
refactor: Swagger에서 JWT 요청 정보 제거하는 로직을 @Parameter(hidden=true)로 일일히 처…
PgmJun Jun 19, 2024
67a5431
refactor: 메서드 매개변수에서 Optional 사용하는 로직 제거
PgmJun Jun 19, 2024
6894fa9
refactor: DB NamingStrategy 설정 제거
PgmJun Jun 19, 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# ERD

<img width="1183" alt="image" src="https://github.com/woowacourse/spring-roomescape-payment/assets/84304802/6de95d67-f13e-4b18-b2d5-dc0e27bf515a">

# 기능 명세

## 1단계 - 에약 시 결제 단계 추가
Expand Down Expand Up @@ -318,7 +322,7 @@ Content-Type: application/json
Request

```
DELETE /reservations/1?memberId=1
DELETE /reservations/1
Cookie: token=hello.example.token
```

Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

Choose a reason for hiding this comment

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

질문) springdoc과 spring rest docs 중에 기술을 선택한 근거가 궁금해요.

Copy link
Author

@PgmJun PgmJun Jun 12, 2024

Choose a reason for hiding this comment

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

RestDocs는 사용해본 경험이 없기 때문에 학습에 대한 시간적 리소스가 필요한 반면,
Swagger는 사용해본 경험이 있어서 빠르게 적용할 수 있다는 생각에 선택하였습니다.

물론 미션이기 때문에 안 써본 기술을 학습해서 적용해보는 것도 중요하지만 문서화 기술은 조금 다른 범주라고 생각되었습니다.
문서화 도구는 미션 이후 따로 공부하거나, 레벨3 프로젝트 때 학습하여 적용해보는 등 아무때나 충분히 학습 및 사용가능한 하나의 기술이라고 생각합니다.
반면 결제 관련 부분에 대한 저의 고민과 설계를 현업자인 호돌에게 리뷰받을 수 있는 기회는 이번 리뷰활동이 마지막이라고 생각합니다.
때문에 결제 관련 설계에 더 시간적 리소스를 투자하고자 하였고,
이 과정에서 빠르게 적용하여 미션 요구사항을 충족시킬 수 있는 Swagger를 선택하게 되었습니다.

물론 Swagger와 RestDocs는 각 기술의 장단점이 있으며 그것또한 비교하며 선택해야 하지만,
이번엔 그 부분을 충분히 학습하고 고려해보지 못한 점이 아쉬움으로 남긴합니다.
RestDocs 에 대해서는 따로 학습하여 적용해보고 Swagger와의 장단점을 비교해보겠습니다!


implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/roomescape/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package roomescape.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.utils.SpringDocUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import roomescape.config.auth.LoginMember;

@Configuration
public class SwaggerConfig {

private static final String SECURITY_SCHEME_NAME = "bearerAuth";

@Bean
public OpenAPI openAPI() {
SpringDocUtils.getConfig().addAnnotationsToIgnore(LoginMember.class);

return new OpenAPI()
.addSecurityItem(new SecurityRequirement()
.addList(SECURITY_SCHEME_NAME))
.components(new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.info(apiInfo());
}

private Info apiInfo() {
return new Info()
.title("Room Escape API Docs")
.description("방탈출 예약 시스템 서버 REST API 문서")
.version("1.0.0");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.login;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
Expand All @@ -19,6 +21,7 @@

import java.net.URI;

@Tag(name = "Login")
@RestController
public class LoginController {
private final LoginService loginService;
Expand All @@ -30,6 +33,7 @@ public LoginController(LoginService loginService, AuthCookieHandler authCookieHa
}

@PostMapping("/login")
@Operation(summary = "로그인", description = "회원정보를 통해 로그인을 수행한다.")
public ResponseEntity<Void> login(@RequestBody @Valid LoginRequest request, HttpServletResponse response) {
String token = loginService.login(request);
Cookie cookie = authCookieHandler.createCookie(token);
Expand All @@ -39,20 +43,23 @@ public ResponseEntity<Void> login(@RequestBody @Valid LoginRequest request, Http

@RoleAllowed
@GetMapping("/login/check")
@Operation(summary = "[회원] 로그인 검증", description = "JWT 토큰을 통해 로그인 여부를 검사한다.")

Choose a reason for hiding this comment

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

의견) 문서에서 로그인이 필요한 엔드포인트에서 LoginMember이 인자로 노출되고 있는데요, springdocs의 auth 기능을 통해 사용할 수 있게 하면 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

그 부분에 대한 처리를 놓쳤네요..!
중요한 부분 짚어주셔서 감사합니다:)
처리해보겠습니다!

public ResponseEntity<LoginCheckResponse> loginCheck(@LoginMember Member member) {
LoginCheckResponse response = loginService.loginCheck(member);
return ResponseEntity.ok().body(response);
}

@RoleAllowed
@PostMapping("/logout")
@Operation(summary = "[회원] 로그아웃", description = "로그아웃을 수행한다.")
public ResponseEntity<Void> logout(HttpServletResponse response) {
Cookie cookie = authCookieHandler.deleteCookie();
response.addCookie(cookie);
return ResponseEntity.ok().build();
}

@PostMapping("/signup")
@Operation(summary = "회원가입", description = "회원가입을 수행한다.")
public ResponseEntity<SignupResponse> signup(@RequestBody @Valid SignupRequest request) {
SignupResponse response = loginService.signup(request);
return ResponseEntity.created(URI.create("/members/" + response.getId())).body(response);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.member;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -8,6 +10,7 @@
import roomescape.service.member.MemberService;
import roomescape.service.member.dto.MemberListResponse;

@Tag(name = "Member")
@RestController
public class MemberController {
private final MemberService memberService;
Expand All @@ -18,6 +21,7 @@ public MemberController(MemberService memberService) {

@RoleAllowed(MemberRole.ADMIN)
@GetMapping("/members")
@Operation(summary = "[관리자] 전체 회원 정보 조회", description = "전체 회원 정보를 조회한다.")
public ResponseEntity<MemberListResponse> findAllMember() {
MemberListResponse response = memberService.findAllMember();
return ResponseEntity.ok().body(response);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.reservation;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
Expand All @@ -21,13 +23,15 @@
import roomescape.service.reservation.dto.AdminReservationRequest;
import roomescape.service.reservation.dto.ReservationListResponse;
import roomescape.service.reservation.dto.ReservationMineListResponse;
import roomescape.service.reservation.dto.ReservationPaymentRequest;
import roomescape.service.reservation.dto.ReservationRequest;
import roomescape.service.reservation.dto.ReservationResponse;
import roomescape.service.reservation.dto.ReservationSaveInput;

import java.net.URI;
import java.time.LocalDate;

@Tag(name = "Reservation")
@RestController
@Validated
public class ReservationController {
Expand All @@ -41,6 +45,7 @@ public ReservationController(ReservationService reservationService, MemberServic

@RoleAllowed(MemberRole.ADMIN)
@GetMapping("/reservations")
@Operation(summary = "[관리자] 예약 정보 검색", description = "회원ID, 테마ID, 시작일, 종료일로 예약 정보를 검색한다.")
public ResponseEntity<ReservationListResponse> searchReservation(
@RequestParam(required = false) Long memberId,
@RequestParam(required = false) Long themeId,
Expand All @@ -52,13 +57,15 @@ public ResponseEntity<ReservationListResponse> searchReservation(

@RoleAllowed
@GetMapping("/reservations-mine")
@Operation(summary = "[회원] 내 예약 정보 조회", description = "내 예약 정보를 조회한다.")
public ResponseEntity<ReservationMineListResponse> findMyReservation(@LoginMember Member member) {
ReservationMineListResponse response = reservationService.findMyReservation(member);
return ResponseEntity.ok().body(response);
}

@RoleAllowed
@PostMapping("/reservations")
@Operation(summary = "[회원] 예약 추가", description = "결제 및 예약을 수행한다.")
public ResponseEntity<ReservationResponse> saveReservation(@RequestBody @Valid ReservationRequest request,
@LoginMember Member member) {
ReservationSaveInput reservationSaveInput = request.toReservationSaveInput();
Expand All @@ -69,22 +76,47 @@ public ResponseEntity<ReservationResponse> saveReservation(@RequestBody @Valid R
return ResponseEntity.created(URI.create("/reservations/" + response.getId())).body(response);
}

@RoleAllowed
@PostMapping("/reservations/{reservationId}/payment")
@Operation(summary = "[회원] 예약 결제", description = "결제 대기 상태의 예약을 결제한다.")
public ResponseEntity<ReservationResponse> payReservation(@PathVariable Long reservationId,
@RequestBody @Valid ReservationPaymentRequest request,
@LoginMember Member member) {
PaymentConfirmInput paymentConfirmInput = request.toPaymentConfirmInput();

ReservationResponse response = reservationService.payReservation(
reservationId, paymentConfirmInput, member);
return ResponseEntity.ok(response);
}

@RoleAllowed(MemberRole.ADMIN)
@PostMapping("/admin/reservations")
@Operation(summary = "[관리자] 예약 추가", description = "결제 없이 예약을 수행한다.")
public ResponseEntity<ReservationResponse> saveAdminReservation(@RequestBody @Valid AdminReservationRequest request) {
ReservationSaveInput reservationSaveInput = request.toReservationSaveInput();
Member member = memberService.findById(request.getMemberId());

ReservationSaveInput reservationSaveInput = request.toReservationSaveInput();
ReservationResponse response = reservationService.saveReservationWithoutPayment(reservationSaveInput, member);
return ResponseEntity.created(URI.create("/reservations/" + response.getId())).body(response);
}

@RoleAllowed(MemberRole.ADMIN)
@RoleAllowed
@DeleteMapping("/reservations/{reservationId}/cancel")
@Operation(summary = "[회원] 예약 취소", description = "예약을 취소하고 결제 금액을 환불한다.")
public ResponseEntity<Void> cancelReservation(
@PathVariable @NotNull(message = "reservationId 값이 null일 수 없습니다.") Long reservationId,
@LoginMember Member member) {
reservationService.cancelReservation(reservationId, member);
return ResponseEntity.noContent().build();
}

@RoleAllowed
@DeleteMapping("/reservations/{reservationId}")
@Operation(summary = "[회원] 예약 삭제", description = "취소 또는 결제 대기 상태의 예약 정보를 삭제한다.")
public ResponseEntity<Void> deleteReservation(
@PathVariable @NotNull(message = "reservationId 값이 null일 수 없습니다.") Long reservationId,
@RequestParam @NotNull(message = "memberId 값이 null일 수 없습니다.") Long memberId) {
reservationService.deleteReservation(reservationId, memberId);
@LoginMember Member member) {
reservationService.deleteReservation(reservationId, member);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.reservationtime;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
Expand All @@ -22,6 +24,7 @@
import java.net.URI;
import java.time.LocalDate;

@Tag(name = "Reservation Time")
@RestController
@Validated
public class ReservationTimeController {
Expand All @@ -33,12 +36,14 @@ public ReservationTimeController(ReservationTimeService reservationTimeService)

@RoleAllowed(MemberRole.ADMIN)
@GetMapping("/times")
@Operation(summary = "[관리자] 전체 예약 가능 시간 조회", description = "전체 예약 가능 시간 정보를 조회한다.")
public ResponseEntity<ReservationTimeListResponse> findAllReservationTime() {
ReservationTimeListResponse response = reservationTimeService.findAllReservationTime();
return ResponseEntity.ok().body(response);
}

@GetMapping("/times/available")
@Operation(summary = "날짜/테마의 예약 가능 시간 조회", description = "특정 날짜/테마의 예약 가능한 시간 정보를 조회한다.")
public ResponseEntity<ReservationTimeAvailableListResponse> findAllAvailableReservationTime(
@RequestParam LocalDate date, @RequestParam Long themeId) {
ReservationTimeAvailableListResponse response =
Expand All @@ -48,6 +53,7 @@ public ResponseEntity<ReservationTimeAvailableListResponse> findAllAvailableRese

@RoleAllowed(MemberRole.ADMIN)
@PostMapping("/times")
@Operation(summary = "[관리자] 예약 가능 시간 추가", description = "예약 가능 시간을 추가한다.")
public ResponseEntity<ReservationTimeResponse> saveReservationTime(
@RequestBody @Valid ReservationTimeRequest request) {
ReservationTimeResponse response = reservationTimeService.saveReservationTime(request);
Expand All @@ -56,6 +62,7 @@ public ResponseEntity<ReservationTimeResponse> saveReservationTime(

@RoleAllowed(MemberRole.ADMIN)
@DeleteMapping("/times/{timeId}")
@Operation(summary = "[관리자] 예약 가능 시간 삭제", description = "예약 가능 시간을 삭제한다.")
public ResponseEntity<Void> deleteReservationTime(
@PathVariable @NotNull(message = "timeId 값이 null일 수 없습니다.") Long timeId) {
reservationTimeService.deleteReservationTime(timeId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.reservationwaiting;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -19,6 +21,7 @@

import java.net.URI;

@Tag(name = "Reservation Waiting")
@RestController
public class ReservationWaitingController {
private final ReservationWaitingService reservationWaitingService;
Expand All @@ -29,13 +32,15 @@ public ReservationWaitingController(ReservationWaitingService reservationWaiting

@RoleAllowed(MemberRole.ADMIN)
@GetMapping("/reservations/waitings")
@Operation(summary = "[관리자] 전체 예약 대기 정보 조회", description = "모든 예약 대기 정보를 조회한다.")
public ResponseEntity<ReservationWaitingListResponse> findAllReservationWaiting() {
ReservationWaitingListResponse response = reservationWaitingService.findAllReservationWaiting();
return ResponseEntity.ok().body(response);
}

@RoleAllowed
@PostMapping("/reservations/waitings")
@Operation(summary = "[회원] 예약 대기 추가", description = "예약 대기를 수행한다.")
public ResponseEntity<ReservationWaitingResponse> saveReservationWaiting(
@RequestBody ReservationWaitingRequest request, @LoginMember Member member) {
ReservationWaitingResponse response = reservationWaitingService.saveReservationWaiting(request, member);
Expand All @@ -44,6 +49,7 @@ public ResponseEntity<ReservationWaitingResponse> saveReservationWaiting(

@RoleAllowed
@DeleteMapping("/reservations/{reservationId}/waitings")
@Operation(summary = "[회원] 예약 대기 삭제", description = "자신의 예약 대기를 삭제한다.")
public ResponseEntity<Void> deleteReservation(
@PathVariable @NotNull(message = "reservationId 값이 null일 수 없습니다.") Long reservationId,
@LoginMember Member member) {
Expand All @@ -53,6 +59,7 @@ public ResponseEntity<Void> deleteReservation(

@RoleAllowed(MemberRole.ADMIN)
@DeleteMapping("/admin/reservations/waitings/{waitingId}")
@Operation(summary = "[관리자] 예약 대기 삭제", description = "예약 대기를 삭제한다.")
public ResponseEntity<Void> deleteAdminReservation(
@PathVariable @NotNull(message = "waitingId 값이 null일 수 없습니다.") Long waitingId) {
reservationWaitingService.deleteAdminReservationWaiting(waitingId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomescape.controller.theme;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
Expand All @@ -18,6 +20,7 @@

import java.net.URI;

@Tag(name = "Theme")
@RestController
public class ThemeController {
private final ThemeService themeService;
Expand All @@ -27,26 +30,30 @@ public ThemeController(ThemeService themeService) {
}

@GetMapping("/themes")
@Operation(summary = "전체 테마 조회", description = "전체 테마 정보를 조회한다.")
public ResponseEntity<ThemeListResponse> findAllTheme() {
ThemeListResponse response = themeService.findAllTheme();
return ResponseEntity.ok().body(response);
}

@GetMapping("/themes/popular")
@Operation(summary = "인기 테마 조회", description = "TOP10 인기 테마를 조회한다.")
public ResponseEntity<ThemeListResponse> findAllPopularTheme() {
ThemeListResponse response = themeService.findAllPopularTheme();
return ResponseEntity.ok().body(response);
}

@RoleAllowed(MemberRole.ADMIN)
@PostMapping("/themes")
@Operation(summary = "[관리자] 테마 추가", description = "테마를 추가한다.")
public ResponseEntity<ThemeResponse> saveTheme(@RequestBody @Valid ThemeRequest request) {
ThemeResponse response = themeService.saveTheme(request);
return ResponseEntity.created(URI.create("/themes/" + response.getId())).body(response);
}

@RoleAllowed(MemberRole.ADMIN)
@DeleteMapping("/themes/{themeId}")
@Operation(summary = "[관리자] 테마 삭제", description = "테마를 삭제한다.")
public ResponseEntity<Void> deleteTheme(
@PathVariable @NotNull(message = "themeId 값이 null일 수 없습니다.") Long themeId) {
themeService.deleteTheme(themeId);
Expand Down
Loading