[#41] 예약 스키마 재정의 #42

Merged
pricelees merged 41 commits from refactor/#41 into main 2025-09-09 00:43:39 +00:00
2 changed files with 69 additions and 271 deletions
Showing only changes of commit 5a9d992cb4 - Show all commits

View File

@ -2,150 +2,59 @@ package roomescape.reservation.docs
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.headers.Header
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springframework.http.HttpHeaders
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.auth.web.support.Admin
import roomescape.auth.web.support.LoginRequired
import roomescape.auth.web.support.MemberId
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.web.*
import java.time.LocalDate
import roomescape.reservation.web.PendingReservationCreateRequest
import roomescape.reservation.web.PendingReservationCreateResponse
import roomescape.reservation.web.ReservationCancelRequest
import roomescape.reservation.web.ReservationDetailRetrieveResponse
import roomescape.reservation.web.ReservationSummaryRetrieveListResponse
@Tag(name = "3. 예약 API", description = "예약 및 대기 정보를 추가 / 조회 / 삭제할 때 사용합니다.")
interface ReservationAPI {
@Admin
@Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"])
@LoginRequired
@Operation(summary = "결제 대기 예약 저장", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
fun createPendingReservation(
@MemberId @Parameter(hidden = true) memberId: Long,
@Valid @RequestBody request: PendingReservationCreateRequest
): ResponseEntity<CommonApiResponse<PendingReservationCreateResponse>>
@LoginRequired
@Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"])
@Operation(summary = "예약 확정", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findReservationsByMemberId(
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>>
@Admin
@Operation(summary = "관리자의 예약 검색", description = "특정 조건에 해당되는 예약 검색", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
)
fun searchReservations(
@RequestParam(required = false) themeId: Long?,
@RequestParam(required = false) memberId: Long?,
@RequestParam(required = false) dateFrom: LocalDate?,
@RequestParam(required = false) dateTo: LocalDate?
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
@Admin
@Operation(summary = "관리자의 예약 취소", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "204", description = "성공"),
)
fun cancelReservationByAdmin(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") reservationId: Long
fun confirmReservation(
@PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<Unit>>
@LoginRequired
@Operation(summary = "예약 추가", tags = ["로그인이 필요한 API"])
@ApiResponses(
ApiResponse(
responseCode = "201",
description = "성공",
useReturnTypeSchema = true,
headers = [Header(
name = HttpHeaders.LOCATION,
description = "생성된 예약 정보 URL",
schema = Schema(example = "/reservations/1")
)]
)
)
fun createReservationWithPayment(
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
@Admin
@Operation(summary = "관리자 예약 추가", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(
responseCode = "201",
description = "성공",
useReturnTypeSchema = true,
headers = [Header(
name = HttpHeaders.LOCATION,
description = "생성된 예약 정보 URL",
schema = Schema(example = "/reservations/1")
)],
)
)
fun createReservationByAdmin(
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
@Admin
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
@Operation(summary = "예약 취소", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
fun cancelReservation(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable reservationId: Long,
@Valid @RequestBody request: ReservationCancelRequest
): ResponseEntity<CommonApiResponse<Unit>>
@LoginRequired
@Operation(summary = "예약 대기 신청", tags = ["로그인이 필요한 API"])
@ApiResponses(
ApiResponse(
responseCode = "201",
description = "성공",
useReturnTypeSchema = true,
headers = [Header(
name = HttpHeaders.LOCATION,
description = "생성된 예약 정보 URL",
schema = Schema(example = "/reservations/1")
)]
)
)
fun createWaiting(
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
@MemberId @Parameter(hidden = true) memberId: Long,
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
@Operation(summary = "회원별 예약 요약 목록 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findSummaryByMemberId(
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>>
@LoginRequired
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "204", description = "성공"),
)
fun cancelWaitingByMember(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>>
@Operation(summary = "특정 예약에 대한 상세 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findDetailById(
@PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>>
@Admin
@Operation(summary = "대기 중인 예약 승인", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "200", description = "성공"),
)
fun confirmWaiting(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>>
@Admin
@Operation(summary = "대기 중인 예약 거절", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "204", description = "대기 중인 예약 거절 성공"),
)
fun rejectWaiting(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>>
}

View File

@ -6,170 +6,59 @@ import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.auth.web.support.MemberId
import roomescape.common.dto.response.CommonApiResponse
import roomescape.payment.infrastructure.client.PaymentApproveRequest
import roomescape.payment.infrastructure.client.PaymentApproveResponse
import roomescape.payment.infrastructure.client.TossPaymentClient
import roomescape.payment.web.PaymentCancelRequest
import roomescape.reservation.business.ReservationWriteService
import roomescape.reservation.business.ReservationFindService
import roomescape.reservation.business.ReservationWithPaymentService
import roomescape.reservation.business.ReservationService
import roomescape.reservation.docs.ReservationAPI
import java.net.URI
import java.time.LocalDate
@RestController
class ReservationController(
private val reservationWithPaymentService: ReservationWithPaymentService,
private val reservationFindService: ReservationFindService,
private val reservationWriteService: ReservationWriteService,
private val paymentClient: TossPaymentClient
private val reservationService: ReservationService
) : ReservationAPI {
@GetMapping("/reservations")
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
val response: ReservationRetrieveListResponse = reservationFindService.findReservations()
@PostMapping("/reservations/pending")
override fun createPendingReservation(
@MemberId @Parameter(hidden = true) memberId: Long,
@Valid @RequestBody request: PendingReservationCreateRequest
): ResponseEntity<CommonApiResponse<PendingReservationCreateResponse>> {
val response = reservationService.createPendingReservation(memberId, request)
return ResponseEntity.ok(CommonApiResponse(response))
}
@GetMapping("/reservations-mine")
override fun findReservationsByMemberId(
@PatchMapping("/reservations/{id}/confirm")
override fun confirmReservation(
@PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<Unit>> {
reservationService.confirmReservation(id)
return ResponseEntity.ok().body(CommonApiResponse())
}
@PostMapping("/reservations/{reservationId}/cancel")
override fun cancelReservation(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable reservationId: Long,
@Valid @RequestBody request: ReservationCancelRequest
): ResponseEntity<CommonApiResponse<Unit>> {
reservationService.cancelReservation(memberId, reservationId, request)
return ResponseEntity.noContent().build()
}
@GetMapping("/reservations/summary")
override fun findSummaryByMemberId(
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
val response: MyReservationRetrieveListResponse = reservationFindService.findReservationsByMemberId(memberId)
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>> {
val response = reservationService.findSummaryByMemberId(memberId)
return ResponseEntity.ok(CommonApiResponse(response))
}
@GetMapping("/reservations/search")
override fun searchReservations(
@RequestParam(required = false) themeId: Long?,
@RequestParam(required = false) memberId: Long?,
@RequestParam(required = false) dateFrom: LocalDate?,
@RequestParam(required = false) dateTo: LocalDate?
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
val response: ReservationRetrieveListResponse = reservationFindService.searchReservations(
themeId,
memberId,
dateFrom,
dateTo
)
@GetMapping("/reservations/{id}/detail")
override fun findDetailById(
@PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>> {
val response = reservationService.findDetailById(id)
return ResponseEntity.ok(CommonApiResponse(response))
}
@DeleteMapping("/reservations/{id}")
override fun cancelReservationByAdmin(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>> {
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
reservationWriteService.deleteReservation(reservationId, memberId)
return ResponseEntity.noContent().build()
}
val paymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(reservationId, memberId)
val paymentCancelResponse = paymentClient.cancel(paymentCancelRequest)
reservationWithPaymentService.updateCanceledTime(
paymentCancelRequest.paymentKey,
paymentCancelResponse.canceledAt
)
return ResponseEntity.noContent().build()
}
@PostMapping("/reservations")
override fun createReservationWithPayment(
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
val paymentRequest: PaymentApproveRequest = reservationCreateWithPaymentRequest.toPaymentApproveRequest()
val paymentResponse: PaymentApproveResponse = paymentClient.confirm(paymentRequest)
try {
val response: ReservationCreateResponse =
reservationWithPaymentService.createReservationAndPayment(
reservationCreateWithPaymentRequest,
paymentResponse,
memberId
)
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
.body(CommonApiResponse(response))
} catch (e: Exception) {
val cancelRequest = PaymentCancelRequest(
paymentRequest.paymentKey,
paymentRequest.amount,
e.message!!
)
val paymentCancelResponse = paymentClient.cancel(cancelRequest)
reservationWithPaymentService.createCanceledPayment(
paymentCancelResponse,
paymentResponse.approvedAt,
paymentRequest.paymentKey
)
throw e
}
}
@PostMapping("/reservations/admin")
override fun createReservationByAdmin(
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
@MemberId @Parameter(hidden = true) memberId: Long,
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
val response: ReservationCreateResponse =
reservationWriteService.createReservationByAdmin(adminReservationRequest, memberId)
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
.body(CommonApiResponse(response))
}
@GetMapping("/reservations/waiting")
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
val response: ReservationRetrieveListResponse = reservationFindService.findAllWaiting()
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/reservations/waiting")
override fun createWaiting(
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
@MemberId @Parameter(hidden = true) memberId: Long,
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
val response: ReservationCreateResponse = reservationWriteService.createWaiting(
waitingCreateRequest,
memberId
)
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
.body(CommonApiResponse(response))
}
@DeleteMapping("/reservations/waiting/{id}")
override fun cancelWaitingByMember(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>> {
reservationWriteService.deleteWaiting(reservationId, memberId)
return ResponseEntity.noContent().build()
}
@PostMapping("/reservations/waiting/{id}/confirm")
override fun confirmWaiting(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>> {
reservationWriteService.confirmWaiting(reservationId, memberId)
return ResponseEntity.ok().build()
}
@PostMapping("/reservations/waiting/{id}/reject")
override fun rejectWaiting(
@MemberId @Parameter(hidden = true) memberId: Long,
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<Unit>> {
reservationWriteService.deleteWaiting(reservationId, memberId)
return ResponseEntity.noContent().build()
}
}