diff --git a/src/main/kotlin/roomescape/payment/docs/PaymentAPI.kt b/src/main/kotlin/roomescape/payment/docs/PaymentAPI.kt index 98633997..5afb5d7a 100644 --- a/src/main/kotlin/roomescape/payment/docs/PaymentAPI.kt +++ b/src/main/kotlin/roomescape/payment/docs/PaymentAPI.kt @@ -8,8 +8,12 @@ import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam +import roomescape.auth.web.support.Authenticated +import roomescape.auth.web.support.CurrentUser import roomescape.auth.web.support.LoginRequired import roomescape.auth.web.support.MemberId +import roomescape.auth.web.support.UserOnly +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentConfirmRequest @@ -17,7 +21,7 @@ import roomescape.payment.web.PaymentCreateResponse interface PaymentAPI { - @LoginRequired + @UserOnly @Operation(summary = "결제 승인", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun confirmPayment( @@ -25,11 +29,10 @@ interface PaymentAPI { @Valid @RequestBody request: PaymentConfirmRequest ): ResponseEntity> - @LoginRequired @Operation(summary = "결제 취소", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun cancelPayment( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @Valid @RequestBody request: PaymentCancelRequest ): ResponseEntity> } diff --git a/src/main/kotlin/roomescape/payment/web/PaymentController.kt b/src/main/kotlin/roomescape/payment/web/PaymentController.kt index 9e3dc906..3923a39c 100644 --- a/src/main/kotlin/roomescape/payment/web/PaymentController.kt +++ b/src/main/kotlin/roomescape/payment/web/PaymentController.kt @@ -7,7 +7,9 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import roomescape.auth.web.support.CurrentUser import roomescape.auth.web.support.MemberId +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse import roomescape.payment.business.PaymentService import roomescape.payment.docs.PaymentAPI @@ -29,10 +31,10 @@ class PaymentController( @PostMapping("/payments/cancel") override fun cancelPayment( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @Valid @RequestBody request: PaymentCancelRequest ): ResponseEntity> { - paymentService.cancel(memberId, request) + paymentService.cancel(user.id, request) return ResponseEntity.ok(CommonApiResponse()) } diff --git a/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt b/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt index dd3e6612..2d25ff0a 100644 --- a/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt +++ b/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt @@ -8,45 +8,46 @@ import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody +import roomescape.auth.web.support.CurrentUser import roomescape.auth.web.support.LoginRequired import roomescape.auth.web.support.MemberId +import roomescape.auth.web.support.UserOnly +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse import roomescape.reservation.web.* interface ReservationAPI { - @LoginRequired @Operation(summary = "결제 대기 예약 저장", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun createPendingReservation( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @Valid @RequestBody request: PendingReservationCreateRequest ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "예약 확정", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun confirmReservation( @PathVariable("id") id: Long ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "예약 취소", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun cancelReservation( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @PathVariable reservationId: Long, @Valid @RequestBody request: ReservationCancelRequest ): ResponseEntity> - @LoginRequired @Operation(summary = "회원별 예약 요약 목록 조회", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findSummaryByMemberId( - @MemberId @Parameter(hidden = true) memberId: Long + @CurrentUser user: CurrentUserContext, ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "특정 예약에 대한 상세 조회", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findDetailById( diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationController.kt b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt index b5e60372..dedf02f5 100644 --- a/src/main/kotlin/roomescape/reservation/web/ReservationController.kt +++ b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt @@ -1,10 +1,10 @@ package roomescape.reservation.web -import io.swagger.v3.oas.annotations.Parameter import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import roomescape.auth.web.support.MemberId +import roomescape.auth.web.support.CurrentUser +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse import roomescape.reservation.business.ReservationService import roomescape.reservation.docs.ReservationAPI @@ -16,10 +16,10 @@ class ReservationController( @PostMapping("/reservations/pending") override fun createPendingReservation( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @Valid @RequestBody request: PendingReservationCreateRequest ): ResponseEntity> { - val response = reservationService.createPendingReservation(memberId, request) + val response = reservationService.createPendingReservation(user.id, request) return ResponseEntity.ok(CommonApiResponse(response)) } @@ -35,20 +35,20 @@ class ReservationController( @PostMapping("/reservations/{reservationId}/cancel") override fun cancelReservation( - @MemberId @Parameter(hidden = true) memberId: Long, + @CurrentUser user: CurrentUserContext, @PathVariable reservationId: Long, @Valid @RequestBody request: ReservationCancelRequest ): ResponseEntity> { - reservationService.cancelReservation(memberId, reservationId, request) + reservationService.cancelReservation(user.id, reservationId, request) return ResponseEntity.ok().body(CommonApiResponse()) } @GetMapping("/reservations/summary") override fun findSummaryByMemberId( - @MemberId @Parameter(hidden = true) memberId: Long + @CurrentUser user: CurrentUserContext, ): ResponseEntity> { - val response = reservationService.findSummaryByMemberId(memberId) + val response = reservationService.findSummaryByMemberId(user.id) return ResponseEntity.ok(CommonApiResponse(response)) } diff --git a/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt b/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt index 95e2cc2d..1ae6df4b 100644 --- a/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt +++ b/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt @@ -4,27 +4,31 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import jakarta.validation.Valid +import org.aspectj.internal.lang.annotation.ajcPrivileged import org.springframework.format.annotation.DateTimeFormat import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam +import roomescape.admin.infrastructure.persistence.Privilege import roomescape.auth.web.support.Admin +import roomescape.auth.web.support.AdminOnly import roomescape.auth.web.support.LoginRequired +import roomescape.auth.web.support.UserOnly import roomescape.common.dto.response.CommonApiResponse import roomescape.schedule.web.* import java.time.LocalDate interface ScheduleAPI { - @LoginRequired + @UserOnly @Operation(summary = "입력된 날짜에 가능한 테마 목록 조회", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "입력된 날짜에 가능한 테마 목록 조회", useReturnTypeSchema = true)) fun findAvailableThemes( @RequestParam("date") @DateTimeFormat(pattern = "yyyy-MM-dd") date: LocalDate ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "입력된 날짜, 테마에 대한 모든 시간 조회", tags = ["로그인이 필요한 API"]) @ApiResponses( ApiResponse( @@ -38,7 +42,7 @@ interface ScheduleAPI { @RequestParam("themeId") themeId: Long ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "일정을 Hold 상태로 변경", tags = ["로그인이 필요한 API"]) @ApiResponses( ApiResponse( @@ -51,21 +55,21 @@ interface ScheduleAPI { @PathVariable("id") id: Long ): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.READ_DETAIL) @Operation(summary = "일정 상세 조회", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "감사 정보를 포함하여 일정 상세 조회", useReturnTypeSchema = true)) fun findScheduleDetail( @PathVariable("id") id: Long ): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.CREATE) @Operation(summary = "일정 생성", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun createSchedule( @Valid @RequestBody request: ScheduleCreateRequest ): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.UPDATE) @Operation(summary = "일정 수정", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun updateSchedule( @@ -73,7 +77,7 @@ interface ScheduleAPI { @Valid @RequestBody request: ScheduleUpdateRequest ): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.DELETE) @Operation(summary = "일정 삭제", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true)) fun deleteSchedule( diff --git a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt index 284d7684..36587af1 100644 --- a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt +++ b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt @@ -8,35 +8,38 @@ import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody +import roomescape.admin.infrastructure.persistence.Privilege import roomescape.auth.web.support.Admin +import roomescape.auth.web.support.AdminOnly import roomescape.auth.web.support.LoginRequired +import roomescape.auth.web.support.UserOnly import roomescape.common.dto.response.CommonApiResponse import roomescape.theme.web.* @Tag(name = "5. 관리자 테마 API", description = "관리자 페이지에서 테마를 조회 / 추가 / 삭제할 때 사용합니다.") interface ThemeAPIV2 { - @Admin + @AdminOnly(privilege = Privilege.READ_SUMMARY) @Operation(summary = "모든 테마 조회", description = "관리자 페이지에서 요약된 테마 목록을 조회합니다.", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findAdminThemes(): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.READ_DETAIL) @Operation(summary = "테마 상세 조회", description = "해당 테마의 상세 정보를 조회합니다.", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findAdminThemeDetail(@PathVariable("id") id: Long): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.CREATE) @Operation(summary = "테마 추가", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun createTheme(@Valid @RequestBody themeCreateRequest: ThemeCreateRequest): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.DELETE) @Operation(summary = "테마 삭제", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true)) fun deleteTheme(@PathVariable id: Long): ResponseEntity> - @Admin + @AdminOnly(privilege = Privilege.UPDATE) @Operation(summary = "테마 수정", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun updateTheme( @@ -44,12 +47,12 @@ interface ThemeAPIV2 { @Valid @RequestBody themeUpdateRequest: ThemeUpdateRequest ): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "예약 페이지에서 모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findUserThemes(): ResponseEntity> - @LoginRequired + @UserOnly @Operation(summary = "예약 페이지에서 입력한 날짜에 가능한 테마 조회", description = "입력한 날짜에 가능한 테마를 조회합니다.", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findThemesByIds(request: ThemeListRetrieveRequest): ResponseEntity> diff --git a/src/test/kotlin/roomescape/payment/PaymentAPITest.kt b/src/test/kotlin/roomescape/payment/PaymentAPITest.kt index 9ae9b620..69017655 100644 --- a/src/test/kotlin/roomescape/payment/PaymentAPITest.kt +++ b/src/test/kotlin/roomescape/payment/PaymentAPITest.kt @@ -145,8 +145,8 @@ class PaymentAPITest( PaymentMethod.entries.filter { it !in supportedMethod }.forEach { test("결제 수단: ${it.koreanName}") { val reservation = dummyInitializer.createConfirmReservation( - adminToken = authUtil.loginAsAdmin(), - reserverToken = authUtil.loginAsUser() + adminToken = authUtil.defaultAdminLogin(), + reserverToken = authUtil.defaultUserLogin() ) val request = PaymentFixture.confirmRequest @@ -163,7 +163,7 @@ class PaymentAPITest( ) runTest( - token = authUtil.loginAsUser(), + token = authUtil.defaultUserLogin(), using = { body(PaymentFixture.confirmRequest) }, @@ -182,7 +182,7 @@ class PaymentAPITest( context("결제를 취소한다.") { test("정상 취소") { - val token = authUtil.loginAsAdmin() + val token = authUtil.defaultAdminLogin() val confirmRequest = PaymentFixture.confirmRequest val reservation = dummyInitializer.createConfirmReservation( adminToken = token, @@ -230,7 +230,7 @@ class PaymentAPITest( } test("예약에 대한 결제 정보가 없으면 실패한다.") { - val token = authUtil.loginAsAdmin() + val token = authUtil.defaultAdminLogin() val reservation = dummyInitializer.createConfirmReservation( adminToken = token, reserverToken = token, @@ -282,8 +282,8 @@ class PaymentAPITest( val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount) val reservation: ReservationEntity = dummyInitializer.createPendingReservation( - adminToken = authUtil.loginAsAdmin(), - reserverToken = authUtil.loginAsUser(), + adminToken = authUtil.defaultAdminLogin(), + reserverToken = authUtil.defaultUserLogin(), ) val method = if (easyPayDetail != null) { @@ -305,7 +305,7 @@ class PaymentAPITest( } returns clientResponse runTest( - token = authUtil.loginAsUser(), + token = authUtil.defaultUserLogin(), using = { body(request) },