generated from pricelees/issue-pr-template
[#18] 코드 정리 및 일부 컨벤션 통일 #19
@ -1,17 +0,0 @@
|
||||
package roomescape.common.dto.response
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
|
||||
@Schema(name = "API 성공 응답")
|
||||
@JvmRecord
|
||||
data class RoomescapeApiResponse<T>(
|
||||
val data: T? = null
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun <T> success(data: T): RoomescapeApiResponse<T> = RoomescapeApiResponse(data)
|
||||
|
||||
@JvmStatic
|
||||
fun success(): RoomescapeApiResponse<Void> = RoomescapeApiResponse(null)
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package roomescape.common.dto.response
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.common.exception.ErrorType
|
||||
|
||||
@Schema(name = "API 에러 응답")
|
||||
@JvmRecord
|
||||
data class RoomescapeErrorResponse(
|
||||
val errorType: ErrorType,
|
||||
val message: String
|
||||
)
|
||||
@ -1,31 +0,0 @@
|
||||
package roomescape.member.web
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
|
||||
fun MemberEntity.toResponse(): MemberResponse = MemberResponse(
|
||||
id = id!!,
|
||||
name = name
|
||||
)
|
||||
|
||||
@Schema(name = "회원 조회 응답", description = "회원 정보 조회 응답시 사용됩니다.")
|
||||
data class MemberResponse(
|
||||
@field:Schema(description = "회원의 고유 번호")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "회원의 이름")
|
||||
val name: String
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromEntity(member: MemberEntity): MemberResponse {
|
||||
return MemberResponse(member.id!!, member.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "회원 목록 조회 응답", description = "모든 회원의 정보 조회 응답시 사용됩니다.")
|
||||
data class MembersResponse(
|
||||
@field:Schema(description = "모든 회원의 ID 및 이름")
|
||||
val members: List<MemberResponse>
|
||||
)
|
||||
@ -1,7 +0,0 @@
|
||||
package roomescape.payment.infrastructure.client
|
||||
|
||||
@JvmRecord
|
||||
data class TossPaymentErrorResponse(
|
||||
val code: String,
|
||||
val message: String
|
||||
)
|
||||
@ -1,66 +0,0 @@
|
||||
package roomescape.payment.web
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.reservation.web.ReservationResponse
|
||||
import roomescape.reservation.web.toResponse
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
class PaymentApprove {
|
||||
@JvmRecord
|
||||
data class Request(
|
||||
@JvmField val paymentKey: String,
|
||||
@JvmField val orderId: String,
|
||||
@JvmField val amount: Long,
|
||||
@JvmField val paymentType: String
|
||||
)
|
||||
|
||||
@JvmRecord
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class Response(
|
||||
@JvmField val paymentKey: String,
|
||||
@JvmField val orderId: String,
|
||||
@JvmField val approvedAt: OffsetDateTime,
|
||||
@JvmField val totalAmount: Long
|
||||
)
|
||||
}
|
||||
|
||||
class PaymentCancel {
|
||||
@JvmRecord
|
||||
data class Request(
|
||||
@JvmField val paymentKey: String,
|
||||
@JvmField val amount: Long,
|
||||
@JvmField val cancelReason: String
|
||||
)
|
||||
|
||||
@JvmRecord
|
||||
@JsonDeserialize(using = PaymentCancelResponseDeserializer::class)
|
||||
data class Response(
|
||||
@JvmField val cancelStatus: String,
|
||||
@JvmField val cancelReason: String,
|
||||
@JvmField val cancelAmount: Long,
|
||||
@JvmField val canceledAt: OffsetDateTime
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmRecord
|
||||
data class ReservationPaymentResponse(
|
||||
val id: Long,
|
||||
val orderId: String,
|
||||
val paymentKey: String,
|
||||
val totalAmount: Long,
|
||||
val reservation: ReservationResponse,
|
||||
val approvedAt: OffsetDateTime
|
||||
)
|
||||
|
||||
fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = ReservationPaymentResponse(
|
||||
id = this.id!!,
|
||||
orderId = this.orderId,
|
||||
paymentKey = this.paymentKey,
|
||||
totalAmount = this.totalAmount,
|
||||
reservation = this.reservation.toResponse(),
|
||||
approvedAt = this.approvedAt
|
||||
)
|
||||
@ -1,74 +0,0 @@
|
||||
package roomescape.reservation.business
|
||||
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
||||
import roomescape.reservation.web.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
@Service
|
||||
class ReservationTimeService(
|
||||
private val reservationTimeRepository: ReservationTimeRepository,
|
||||
private val reservationRepository: ReservationRepository
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun findTimeById(id: Long): ReservationTimeEntity = reservationTimeRepository.findByIdOrNull(id)
|
||||
?: throw RoomescapeException(
|
||||
ErrorType.RESERVATION_TIME_NOT_FOUND,
|
||||
"[reservationTimeId: $id]",
|
||||
HttpStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllTimes(): ReservationTimesResponse = reservationTimeRepository.findAll()
|
||||
.toResponses()
|
||||
|
||||
@Transactional
|
||||
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
|
||||
val startAt: LocalTime = reservationTimeRequest.startAt
|
||||
|
||||
if (reservationTimeRepository.existsByStartAt(startAt)) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
|
||||
)
|
||||
}
|
||||
|
||||
return ReservationTimeEntity(startAt = startAt)
|
||||
.also { reservationTimeRepository.save(it) }
|
||||
.toResponse()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun removeTimeById(id: Long) {
|
||||
val reservationTime: ReservationTimeEntity = findTimeById(id)
|
||||
reservationRepository.findByReservationTime(reservationTime)
|
||||
.also {
|
||||
if (it.isNotEmpty()) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
|
||||
)
|
||||
}
|
||||
reservationTimeRepository.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
|
||||
val allTimes = reservationTimeRepository.findAll()
|
||||
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
|
||||
|
||||
return ReservationTimeInfosResponse(allTimes.map { time ->
|
||||
val alreadyBooked: Boolean = reservations.any { reservation -> reservation.reservationTime.id == time.id }
|
||||
time.toInfoResponse(alreadyBooked)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import java.time.LocalDate
|
||||
|
||||
@Schema(name = "관리자 예약 저장 요청", description = "관리자의 예약 저장 요청시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class AdminReservationRequest(
|
||||
@JvmField @field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
||||
val date: LocalDate,
|
||||
@JvmField @field:Schema(description = "예약 시간 ID.", example = "1")
|
||||
val timeId: Long,
|
||||
@JvmField @field:Schema(description = "테마 ID", example = "1")
|
||||
val themeId: Long,
|
||||
@JvmField @field:Schema(description = "회원 ID", example = "1")
|
||||
val memberId: Long
|
||||
)
|
||||
|
||||
@Schema(name = "회원의 예약 저장 요청", description = "회원의 예약 요청시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationRequest(
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
||||
val date: LocalDate,
|
||||
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 시간 ID.", example = "1")
|
||||
val timeId: Long,
|
||||
|
||||
@JvmField @field:Schema(description = "테마 ID", example = "1")
|
||||
val themeId: Long,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 키")
|
||||
val paymentKey: String,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 주문번호.")
|
||||
val orderId: String,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 금액")
|
||||
val amount: Long,
|
||||
|
||||
@field:Schema(description = "결제 타입", example = "NORMAL")
|
||||
val paymentType: String
|
||||
) {
|
||||
@get:JsonIgnore
|
||||
val paymentRequest: PaymentApprove.Request
|
||||
get() = PaymentApprove.Request(paymentKey, orderId, amount, paymentType)
|
||||
}
|
||||
|
||||
@Schema(name = "예약 대기 저장 요청", description = "회원의 예약 대기 요청시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class WaitingRequest(
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
||||
val date: LocalDate,
|
||||
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 시간 ID", example = "1")
|
||||
val timeId: Long,
|
||||
|
||||
@JvmField
|
||||
@field:Schema(description = "테마 ID", example = "1")
|
||||
val themeId: Long
|
||||
)
|
||||
@ -1,103 +0,0 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.member.web.MemberResponse
|
||||
import roomescape.member.web.toResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.theme.web.ThemeResponse
|
||||
import roomescape.theme.web.toResponse
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
@Schema(name = "회원의 예약 및 대기 응답", description = "회원의 예약 및 대기 정보 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class MyReservationResponse(
|
||||
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "테마 이름")
|
||||
val themeName: String,
|
||||
|
||||
@field:Schema(description = "예약 날짜", type = "string", example = "2022-12-31")
|
||||
val date: LocalDate,
|
||||
|
||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
||||
val time: LocalTime,
|
||||
|
||||
@field:Schema(description = "예약 상태", type = "string")
|
||||
val status: ReservationStatus,
|
||||
|
||||
@field:Schema(description = "예약 대기 상태일 때의 대기 순번. 확정된 예약은 0의 값을 가집니다.")
|
||||
val rank: Long,
|
||||
|
||||
@field:Schema(description = "결제 키. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||
val paymentKey: String?,
|
||||
|
||||
@field:Schema(description = "결제 금액. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||
val amount: Long?
|
||||
)
|
||||
|
||||
@Schema(name = "회원의 예약 및 대기 목록 조회 응답", description = "회원의 예약 및 대기 목록 조회 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class MyReservationsResponse(
|
||||
@field:Schema(description = "현재 로그인한 회원의 예약 및 대기 목록")
|
||||
val reservations: List<MyReservationResponse>
|
||||
)
|
||||
|
||||
@Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationResponse(
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "예약 날짜", type = "string", example = "2022-12-31")
|
||||
val date: LocalDate,
|
||||
|
||||
@field:Schema(description = "예약한 회원 정보")
|
||||
@field:JsonProperty("member")
|
||||
val member: MemberResponse,
|
||||
|
||||
@field:Schema(description = "예약 시간 정보")
|
||||
@field:JsonProperty("time")
|
||||
val time: ReservationTimeResponse,
|
||||
|
||||
@field:Schema(description = "예약한 테마 정보")
|
||||
@field:JsonProperty("theme")
|
||||
val theme: ThemeResponse,
|
||||
|
||||
@field:Schema(description = "예약 상태", type = "string")
|
||||
val status: ReservationStatus
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun from(reservation: ReservationEntity): ReservationResponse {
|
||||
return ReservationResponse(
|
||||
reservation.id!!,
|
||||
reservation.date,
|
||||
reservation.member.toResponse(),
|
||||
reservation.reservationTime.toResponse(),
|
||||
reservation.theme.toResponse(),
|
||||
reservation.reservationStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ReservationEntity.toResponse(): ReservationResponse = ReservationResponse(
|
||||
id = this.id!!,
|
||||
date = this.date,
|
||||
member = this.member.toResponse(),
|
||||
time = this.reservationTime.toResponse(),
|
||||
theme = this.theme.toResponse(),
|
||||
status = this.reservationStatus
|
||||
)
|
||||
|
||||
@Schema(name = "예약 목록 조회 응답", description = "모든 예약 정보 조회 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationsResponse(
|
||||
@field:Schema(description = "모든 예약 및 대기 목록")
|
||||
val reservations: List<ReservationResponse>
|
||||
)
|
||||
@ -1,51 +0,0 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import roomescape.common.dto.response.CommonApiResponse
|
||||
import roomescape.reservation.business.ReservationTimeService
|
||||
import roomescape.reservation.docs.ReservationTimeAPI
|
||||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
|
||||
@RestController
|
||||
class ReservationTimeController(
|
||||
private val reservationTimeService: ReservationTimeService
|
||||
) : ReservationTimeAPI {
|
||||
|
||||
@GetMapping("/times")
|
||||
override fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>> {
|
||||
val response: ReservationTimesResponse = reservationTimeService.findAllTimes()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@PostMapping("/times")
|
||||
override fun saveTime(
|
||||
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
|
||||
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>> {
|
||||
val response: ReservationTimeResponse = reservationTimeService.addTime(reservationTimeRequest)
|
||||
|
||||
return ResponseEntity
|
||||
.created(URI.create("/times/${response.id}"))
|
||||
.body(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@DeleteMapping("/times/{id}")
|
||||
override fun removeTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
reservationTimeService.removeTimeById(id)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@GetMapping("/times/filter")
|
||||
override fun findAllAvailableReservationTimes(
|
||||
@RequestParam date: LocalDate,
|
||||
@RequestParam themeId: Long
|
||||
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>> {
|
||||
val response: ReservationTimeInfosResponse = reservationTimeService.findAllAvailableTimesByDateAndTheme(date, themeId)
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import java.time.LocalTime
|
||||
|
||||
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationTimeRequest(
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 시간. HH:mm 형식으로 입력해야 합니다.", type = "string", example = "09:00")
|
||||
val startAt: LocalTime
|
||||
)
|
||||
|
||||
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationTimeResponse(
|
||||
@JvmField
|
||||
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
||||
val startAt: LocalTime
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun from(reservationTime: ReservationTimeEntity): ReservationTimeResponse {
|
||||
return ReservationTimeResponse(reservationTime.id!!, reservationTime.startAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
|
||||
this.id!!, this.startAt
|
||||
)
|
||||
|
||||
@Schema(name = "예약 시간 정보 목록 응답", description = "모든 예약 시간 조회 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ReservationTimesResponse(
|
||||
@field:Schema(description = "모든 시간 목록")
|
||||
val times: List<ReservationTimeResponse>
|
||||
)
|
||||
|
||||
fun List<ReservationTimeEntity>.toResponses(): ReservationTimesResponse = ReservationTimesResponse(
|
||||
this.map { it.toResponse() }
|
||||
)
|
||||
|
||||
@Schema(name = "특정 테마, 날짜에 대한 시간 정보 응답", description = "특정 날짜와 테마에 대해, 예약 가능 여부를 포함한 시간 정보를 저장합니다.")
|
||||
@JvmRecord
|
||||
data class ReservationTimeInfoResponse(
|
||||
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
||||
val startAt: LocalTime,
|
||||
|
||||
@field:Schema(description = "이미 예약이 완료된 시간인지 여부")
|
||||
val alreadyBooked: Boolean
|
||||
)
|
||||
|
||||
fun ReservationTimeEntity.toInfoResponse(alreadyBooked: Boolean): ReservationTimeInfoResponse = ReservationTimeInfoResponse(
|
||||
id = this.id!!,
|
||||
startAt = this.startAt,
|
||||
alreadyBooked = alreadyBooked
|
||||
)
|
||||
|
||||
@Schema(name = "예약 시간 정보 목록 응답", description = "특정 테마, 날짜에 대한 모든 예약 가능 시간 정보를 저장합니다.")
|
||||
@JvmRecord
|
||||
data class ReservationTimeInfosResponse(
|
||||
@field:Schema(description = "특정 테마, 날짜에 대한 예약 가능 여부를 포함한 시간 목록")
|
||||
val times: List<ReservationTimeInfoResponse>
|
||||
)
|
||||
@ -4,7 +4,7 @@ import org.springframework.stereotype.Service
|
||||
import roomescape.auth.infrastructure.jwt.JwtHandler
|
||||
import roomescape.auth.web.LoginCheckResponse
|
||||
import roomescape.auth.web.LoginRequest
|
||||
import roomescape.auth.web.TokenResponse
|
||||
import roomescape.auth.web.LoginResponse
|
||||
import roomescape.member.business.MemberService
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
|
||||
@ -13,15 +13,15 @@ class AuthService(
|
||||
private val memberService: MemberService,
|
||||
private val jwtHandler: JwtHandler
|
||||
) {
|
||||
fun login(request: LoginRequest): TokenResponse {
|
||||
val member: MemberEntity = memberService.findMemberByEmailAndPassword(
|
||||
fun login(request: LoginRequest): LoginResponse {
|
||||
val member: MemberEntity = memberService.findByEmailAndPassword(
|
||||
request.email,
|
||||
request.password
|
||||
)
|
||||
|
||||
val accessToken: String = jwtHandler.createToken(member.id!!)
|
||||
|
||||
return TokenResponse(accessToken)
|
||||
return LoginResponse(accessToken)
|
||||
}
|
||||
|
||||
fun checkLogin(memberId: Long): LoginCheckResponse {
|
||||
@ -24,7 +24,7 @@ class AuthController(
|
||||
override fun login(
|
||||
@Valid @RequestBody loginRequest: LoginRequest,
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
val response: TokenResponse = authService.login(loginRequest)
|
||||
val response: LoginResponse = authService.login(loginRequest)
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.SET_COOKIE, response.toResponseCookie())
|
||||
@ -4,27 +4,19 @@ import io.swagger.v3.oas.annotations.media.Schema
|
||||
import jakarta.validation.constraints.Email
|
||||
import jakarta.validation.constraints.NotBlank
|
||||
|
||||
@JvmRecord
|
||||
data class TokenResponse(
|
||||
data class LoginResponse(
|
||||
val accessToken: String
|
||||
)
|
||||
|
||||
|
||||
@Schema(name = "로그인 체크 응답", description = "로그인 상태 체크 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class LoginCheckResponse(
|
||||
@field:Schema(description = "로그인된 회원의 이름")
|
||||
val name: String
|
||||
)
|
||||
|
||||
@Schema(name = "로그인 요청", description = "로그인 요청 시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class LoginRequest(
|
||||
@Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com")
|
||||
@field:Schema(description = "필수 값이며, 이메일 형식으로 입력해야 합니다.", example = "abc123@gmail.com")
|
||||
val email: String,
|
||||
|
||||
@NotBlank(message = "비밀번호는 공백일 수 없습니다.")
|
||||
@field:Schema(description = "최소 1글자 이상 입력해야 합니다.")
|
||||
val password: String
|
||||
)
|
||||
@ -3,7 +3,7 @@ package roomescape.auth.web.support
|
||||
import jakarta.servlet.http.Cookie
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.http.ResponseCookie
|
||||
import roomescape.auth.web.TokenResponse
|
||||
import roomescape.auth.web.LoginResponse
|
||||
|
||||
const val ACCESS_TOKEN_COOKIE_NAME = "accessToken"
|
||||
|
||||
@ -11,7 +11,7 @@ fun HttpServletRequest.accessTokenCookie(): Cookie = this.cookies
|
||||
?.firstOrNull { it.name == ACCESS_TOKEN_COOKIE_NAME }
|
||||
?: Cookie(ACCESS_TOKEN_COOKIE_NAME, "")
|
||||
|
||||
fun TokenResponse.toResponseCookie(): String = accessTokenCookie(this.accessToken, 1800)
|
||||
fun LoginResponse.toResponseCookie(): String = accessTokenCookie(this.accessToken, 1800)
|
||||
.toString()
|
||||
|
||||
fun expiredAccessTokenCookie(): String = accessTokenCookie("", 0)
|
||||
@ -1,11 +1,7 @@
|
||||
package roomescape.common.exception
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
enum class ErrorType(
|
||||
@JvmField val description: String
|
||||
val description: String
|
||||
) {
|
||||
// 400 Bad Request
|
||||
REQUEST_DATA_BLANK("요청 데이터에 유효하지 않은 값(null OR 공백)이 포함되어있습니다."),
|
||||
@ -30,7 +26,7 @@ enum class ErrorType(
|
||||
// 404 Not Found
|
||||
MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."),
|
||||
RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."),
|
||||
RESERVATION_TIME_NOT_FOUND("예약 시간(ReservationTime) 정보가 존재하지 않습니다."),
|
||||
TIME_NOT_FOUND("예약 시간(Time) 정보가 존재하지 않습니다."),
|
||||
THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."),
|
||||
PAYMENT_NOT_FOUND("결제(Payment) 정보가 존재하지 않습니다."),
|
||||
|
||||
@ -54,18 +50,4 @@ enum class ErrorType(
|
||||
PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."),
|
||||
PAYMENT_SERVER_ERROR("결제 서버에서 에러가 발생하였습니다. 잠시 후 다시 시도해주세요.")
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@JsonCreator
|
||||
fun from(@JsonProperty("errorType") errorType: String): ErrorType {
|
||||
return entries.toTypedArray()
|
||||
.firstOrNull { it.name == errorType }
|
||||
?: throw RoomescapeException(
|
||||
INVALID_REQUEST_DATA,
|
||||
"[ErrorType: ${errorType}]",
|
||||
HttpStatus.BAD_REQUEST
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,9 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
import org.springframework.web.client.ResourceAccessException
|
||||
import roomescape.common.dto.response.CommonErrorResponse
|
||||
|
||||
@RestControllerAdvice
|
||||
@ -26,15 +24,6 @@ class ExceptionControllerAdvice(
|
||||
.body(CommonErrorResponse(e.errorType))
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResourceAccessException::class)
|
||||
fun handleResourceAccessException(e: ResourceAccessException): ResponseEntity<CommonErrorResponse> {
|
||||
logger.error(e) { "message: ${e.message}" }
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(CommonErrorResponse(ErrorType.PAYMENT_SERVER_ERROR))
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [HttpMessageNotReadableException::class])
|
||||
fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): ResponseEntity<CommonErrorResponse> {
|
||||
logger.error(e) { "message: ${e.message}" }
|
||||
@ -56,15 +45,6 @@ class ExceptionControllerAdvice(
|
||||
.body(CommonErrorResponse(ErrorType.INVALID_REQUEST_DATA, messages))
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [HttpRequestMethodNotSupportedException::class])
|
||||
fun handleHttpRequestMethodNotSupportedException(e: HttpRequestMethodNotSupportedException): ResponseEntity<CommonErrorResponse> {
|
||||
logger.error(e) { "message: ${e.message}" }
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
.body(CommonErrorResponse(ErrorType.METHOD_NOT_ALLOWED))
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [Exception::class])
|
||||
fun handleException(e: Exception): ResponseEntity<CommonErrorResponse> {
|
||||
logger.error(e) { "message: ${e.message}" }
|
||||
@ -8,17 +8,17 @@ import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.member.web.MembersResponse
|
||||
import roomescape.member.web.toResponse
|
||||
import roomescape.member.web.MemberRetrieveListResponse
|
||||
import roomescape.member.web.toRetrieveResponse
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class MemberService(
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
fun readAllMembers(): MembersResponse = MembersResponse(
|
||||
fun findMembers(): MemberRetrieveListResponse = MemberRetrieveListResponse(
|
||||
memberRepository.findAll()
|
||||
.map { it.toResponse() }
|
||||
.map { it.toRetrieveResponse() }
|
||||
.toList()
|
||||
)
|
||||
|
||||
@ -29,7 +29,7 @@ class MemberService(
|
||||
HttpStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
fun findMemberByEmailAndPassword(email: String, password: String): MemberEntity =
|
||||
fun findByEmailAndPassword(email: String, password: String): MemberEntity =
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
?: throw RoomescapeException(
|
||||
ErrorType.MEMBER_NOT_FOUND,
|
||||
@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.springframework.http.ResponseEntity
|
||||
import roomescape.auth.web.support.Admin
|
||||
import roomescape.common.dto.response.CommonApiResponse
|
||||
import roomescape.member.web.MembersResponse
|
||||
import roomescape.member.web.MemberRetrieveListResponse
|
||||
|
||||
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
|
||||
interface MemberAPI {
|
||||
@ -20,5 +20,5 @@ interface MemberAPI {
|
||||
useReturnTypeSchema = true
|
||||
)
|
||||
)
|
||||
fun readAllMembers(): ResponseEntity<CommonApiResponse<MembersResponse>>
|
||||
fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>>
|
||||
}
|
||||
@ -3,7 +3,7 @@ package roomescape.member.infrastructure.persistence
|
||||
import jakarta.persistence.*
|
||||
|
||||
@Entity
|
||||
@Table(name = "member")
|
||||
@Table(name = "members")
|
||||
class MemberEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -13,8 +13,8 @@ class MemberController(
|
||||
) : MemberAPI {
|
||||
|
||||
@GetMapping("/members")
|
||||
override fun readAllMembers(): ResponseEntity<CommonApiResponse<MembersResponse>> {
|
||||
val response: MembersResponse = memberService.readAllMembers()
|
||||
override fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>> {
|
||||
val response: MemberRetrieveListResponse = memberService.findMembers()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
21
src/main/kotlin/roomescape/member/web/MemberDTO.kt
Normal file
21
src/main/kotlin/roomescape/member/web/MemberDTO.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package roomescape.member.web
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
|
||||
fun MemberEntity.toRetrieveResponse(): MemberRetrieveResponse = MemberRetrieveResponse(
|
||||
id = id!!,
|
||||
name = name
|
||||
)
|
||||
|
||||
data class MemberRetrieveResponse(
|
||||
@field:Schema(description = "회원 식별자")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "회원 이름")
|
||||
val name: String
|
||||
)
|
||||
|
||||
data class MemberRetrieveListResponse(
|
||||
val members: List<MemberRetrieveResponse>
|
||||
)
|
||||
@ -5,14 +5,15 @@ import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.ReservationPaymentResponse
|
||||
import roomescape.payment.web.toReservationPaymentResponse
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import roomescape.payment.web.PaymentCreateResponse
|
||||
import roomescape.payment.web.toCreateResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@ -22,10 +23,10 @@ class PaymentService(
|
||||
private val canceledPaymentRepository: CanceledPaymentRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun savePayment(
|
||||
paymentResponse: PaymentApprove.Response,
|
||||
fun createPayment(
|
||||
paymentResponse: PaymentApproveResponse,
|
||||
reservation: ReservationEntity
|
||||
): ReservationPaymentResponse = PaymentEntity(
|
||||
): PaymentCreateResponse = PaymentEntity(
|
||||
orderId = paymentResponse.orderId,
|
||||
paymentKey = paymentResponse.paymentKey,
|
||||
totalAmount = paymentResponse.totalAmount,
|
||||
@ -33,7 +34,7 @@ class PaymentService(
|
||||
approvedAt = paymentResponse.approvedAt
|
||||
).also {
|
||||
paymentRepository.save(it)
|
||||
}.toReservationPaymentResponse()
|
||||
}.toCreateResponse()
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun isReservationPaid(
|
||||
@ -41,8 +42,8 @@ class PaymentService(
|
||||
): Boolean = paymentRepository.existsByReservationId(reservationId)
|
||||
|
||||
@Transactional
|
||||
fun saveCanceledPayment(
|
||||
cancelInfo: PaymentCancel.Response,
|
||||
fun createCanceledPayment(
|
||||
cancelInfo: PaymentCancelResponse,
|
||||
approvedAt: OffsetDateTime,
|
||||
paymentKey: String
|
||||
): CanceledPaymentEntity = CanceledPaymentEntity(
|
||||
@ -53,9 +54,8 @@ class PaymentService(
|
||||
canceledAt = cancelInfo.canceledAt
|
||||
).also { canceledPaymentRepository.save(it) }
|
||||
|
||||
|
||||
@Transactional
|
||||
fun cancelPaymentByAdmin(reservationId: Long): PaymentCancel.Request {
|
||||
fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest {
|
||||
val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId)
|
||||
?: throw RoomescapeException(
|
||||
ErrorType.PAYMENT_NOT_FOUND,
|
||||
@ -65,7 +65,7 @@ class PaymentService(
|
||||
// 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다.
|
||||
val canceled: CanceledPaymentEntity = cancelPayment(paymentKey)
|
||||
|
||||
return PaymentCancel.Request(paymentKey, canceled.cancelAmount, canceled.cancelReason)
|
||||
return PaymentCancelRequest(paymentKey, canceled.cancelAmount, canceled.cancelReason)
|
||||
}
|
||||
|
||||
private fun cancelPayment(
|
||||
@ -5,23 +5,23 @@ import com.fasterxml.jackson.core.TreeNode
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import java.io.IOException
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
class PaymentCancelResponseDeserializer(
|
||||
vc: Class<PaymentCancel.Response>? = null
|
||||
) : StdDeserializer<PaymentCancel.Response>(vc) {
|
||||
vc: Class<PaymentCancelResponse>? = null
|
||||
) : StdDeserializer<PaymentCancelResponse>(vc) {
|
||||
@Throws(IOException::class)
|
||||
override fun deserialize(
|
||||
jsonParser: JsonParser,
|
||||
deserializationContext: DeserializationContext?
|
||||
): PaymentCancel.Response {
|
||||
): PaymentCancelResponse {
|
||||
val cancels: JsonNode = jsonParser.codec.readTree<TreeNode>(jsonParser)
|
||||
.get("cancels")
|
||||
.get(0) as JsonNode
|
||||
|
||||
return PaymentCancel.Response(
|
||||
return PaymentCancelResponse(
|
||||
cancels.get("cancelStatus").asText(),
|
||||
cancels.get("cancelReason").asText(),
|
||||
cancels.get("cancelAmount").asLong(),
|
||||
@ -4,8 +4,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
@ConfigurationProperties(prefix = "payment")
|
||||
data class PaymentProperties(
|
||||
@JvmField val apiBaseUrl: String,
|
||||
@JvmField val confirmSecretKey: String,
|
||||
@JvmField val readTimeout: Int,
|
||||
@JvmField val connectTimeout: Int
|
||||
val apiBaseUrl: String,
|
||||
val confirmSecretKey: String,
|
||||
val readTimeout: Int,
|
||||
val connectTimeout: Int
|
||||
)
|
||||
@ -12,15 +12,16 @@ import org.springframework.stereotype.Component
|
||||
import org.springframework.web.client.RestClient
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import java.io.IOException
|
||||
import java.util.Map
|
||||
|
||||
@Component
|
||||
class TossPaymentClient(
|
||||
private val log: KLogger = KotlinLogging.logger {},
|
||||
tossPaymentClientBuilder: RestClient.Builder
|
||||
private val objectMapper: ObjectMapper,
|
||||
tossPaymentClientBuilder: RestClient.Builder,
|
||||
) {
|
||||
companion object {
|
||||
private const val CONFIRM_URL: String = "/v1/payments/confirm"
|
||||
@ -29,7 +30,7 @@ class TossPaymentClient(
|
||||
|
||||
private val tossPaymentClient: RestClient = tossPaymentClientBuilder.build()
|
||||
|
||||
fun confirmPayment(paymentRequest: PaymentApprove.Request): PaymentApprove.Response {
|
||||
fun confirm(paymentRequest: PaymentApproveRequest): PaymentApproveResponse {
|
||||
logPaymentInfo(paymentRequest)
|
||||
|
||||
return tossPaymentClient.post()
|
||||
@ -41,11 +42,11 @@ class TossPaymentClient(
|
||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
||||
)
|
||||
.body(PaymentApprove.Response::class.java)
|
||||
.body(PaymentApproveResponse::class.java)
|
||||
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
fun cancelPayment(cancelRequest: PaymentCancel.Request): PaymentCancel.Response {
|
||||
fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse {
|
||||
logPaymentCancelInfo(cancelRequest)
|
||||
val param = Map.of<String, String>("cancelReason", cancelRequest.cancelReason)
|
||||
|
||||
@ -58,18 +59,18 @@ class TossPaymentClient(
|
||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
||||
)
|
||||
.body(PaymentCancel.Response::class.java)
|
||||
.body(PaymentCancelResponse::class.java)
|
||||
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
private fun logPaymentInfo(paymentRequest: PaymentApprove.Request) {
|
||||
private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) {
|
||||
log.info {
|
||||
"결제 승인 요청: paymentKey=${paymentRequest.paymentKey}, orderId=${paymentRequest.orderId}, " +
|
||||
"amount=${paymentRequest.amount}, paymentType=${paymentRequest.paymentType}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun logPaymentCancelInfo(cancelRequest: PaymentCancel.Request) {
|
||||
private fun logPaymentCancelInfo(cancelRequest: PaymentCancelRequest) {
|
||||
log.info {
|
||||
"결제 취소 요청: paymentKey=${cancelRequest.paymentKey}, amount=${cancelRequest.amount}, " +
|
||||
"cancelReason=${cancelRequest.cancelReason}"
|
||||
@ -96,7 +97,6 @@ class TossPaymentClient(
|
||||
res: ClientHttpResponse
|
||||
): TossPaymentErrorResponse {
|
||||
val body = res.body
|
||||
val objectMapper = ObjectMapper()
|
||||
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
|
||||
body.close()
|
||||
return errorResponse
|
||||
@ -0,0 +1,24 @@
|
||||
package roomescape.payment.infrastructure.client
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
data class TossPaymentErrorResponse(
|
||||
val code: String,
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class PaymentApproveRequest(
|
||||
val paymentKey: String,
|
||||
val orderId: String,
|
||||
val amount: Long,
|
||||
val paymentType: String
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class PaymentApproveResponse(
|
||||
val paymentKey: String,
|
||||
val orderId: String,
|
||||
val totalAmount: Long,
|
||||
val approvedAt: OffsetDateTime
|
||||
)
|
||||
@ -4,7 +4,7 @@ import jakarta.persistence.*
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "canceled_payment")
|
||||
@Table(name = "canceled_payments")
|
||||
class CanceledPaymentEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -5,7 +5,7 @@ import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "payment")
|
||||
@Table(name = "payments")
|
||||
class PaymentEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
40
src/main/kotlin/roomescape/payment/web/PaymentDTO.kt
Normal file
40
src/main/kotlin/roomescape/payment/web/PaymentDTO.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package roomescape.payment.web
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||
import roomescape.reservation.web.toRetrieveResponse
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
data class PaymentCancelRequest(
|
||||
val paymentKey: String,
|
||||
val amount: Long,
|
||||
val cancelReason: String
|
||||
)
|
||||
|
||||
@JsonDeserialize(using = PaymentCancelResponseDeserializer::class)
|
||||
data class PaymentCancelResponse(
|
||||
val cancelStatus: String,
|
||||
val cancelReason: String,
|
||||
val cancelAmount: Long,
|
||||
val canceledAt: OffsetDateTime
|
||||
)
|
||||
|
||||
data class PaymentCreateResponse(
|
||||
val id: Long,
|
||||
val orderId: String,
|
||||
val paymentKey: String,
|
||||
val totalAmount: Long,
|
||||
val reservation: ReservationRetrieveResponse,
|
||||
val approvedAt: OffsetDateTime
|
||||
)
|
||||
|
||||
fun PaymentEntity.toCreateResponse(): PaymentCreateResponse = PaymentCreateResponse(
|
||||
id = this.id!!,
|
||||
orderId = this.orderId,
|
||||
paymentKey = this.paymentKey,
|
||||
totalAmount = this.totalAmount,
|
||||
reservation = this.reservation.toRetrieveResponse(),
|
||||
approvedAt = this.approvedAt
|
||||
)
|
||||
@ -18,40 +18,40 @@ import java.time.LocalDateTime
|
||||
@Transactional
|
||||
class ReservationService(
|
||||
private val reservationRepository: ReservationRepository,
|
||||
private val reservationTimeService: ReservationTimeService,
|
||||
private val timeService: TimeService,
|
||||
private val memberService: MemberService,
|
||||
private val themeService: ThemeService,
|
||||
) {
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllReservations(): ReservationsResponse {
|
||||
fun findReservations(): ReservationRetrieveListResponse {
|
||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||
.confirmed()
|
||||
.build()
|
||||
|
||||
|
||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
||||
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllWaiting(): ReservationsResponse {
|
||||
fun findAllWaiting(): ReservationRetrieveListResponse {
|
||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||
.waiting()
|
||||
.build()
|
||||
|
||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
||||
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||
}
|
||||
|
||||
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> {
|
||||
return reservationRepository.findAll(spec).map { it.toResponse() }
|
||||
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationRetrieveResponse> {
|
||||
return reservationRepository.findAll(spec).map { it.toRetrieveResponse() }
|
||||
}
|
||||
|
||||
fun removeReservationById(reservationId: Long, memberId: Long) {
|
||||
fun deleteReservation(reservationId: Long, memberId: Long) {
|
||||
validateIsMemberAdmin(memberId)
|
||||
reservationRepository.deleteById(reservationId)
|
||||
}
|
||||
|
||||
fun addReservation(request: ReservationRequest, memberId: Long): ReservationEntity {
|
||||
fun addReservation(request: ReservationCreateWithPaymentRequest, memberId: Long): ReservationEntity {
|
||||
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
||||
return getReservationForSave(
|
||||
request.timeId,
|
||||
@ -64,7 +64,7 @@ class ReservationService(
|
||||
}
|
||||
}
|
||||
|
||||
fun addReservationByAdmin(request: AdminReservationRequest): ReservationResponse {
|
||||
fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse {
|
||||
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
||||
|
||||
return addReservationWithoutPayment(
|
||||
@ -76,7 +76,7 @@ class ReservationService(
|
||||
)
|
||||
}
|
||||
|
||||
fun addWaiting(request: WaitingRequest, memberId: Long): ReservationResponse {
|
||||
fun createWaiting(request: WaitingCreateRequest, memberId: Long): ReservationRetrieveResponse {
|
||||
validateMemberAlreadyReserve(request.themeId, request.timeId, request.date, memberId)
|
||||
return addReservationWithoutPayment(
|
||||
request.themeId,
|
||||
@ -93,11 +93,10 @@ class ReservationService(
|
||||
date: LocalDate,
|
||||
memberId: Long,
|
||||
status: ReservationStatus
|
||||
): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status)
|
||||
): ReservationRetrieveResponse = getReservationForSave(timeId, themeId, date, memberId, status)
|
||||
.also {
|
||||
reservationRepository.save(it)
|
||||
}.toResponse()
|
||||
|
||||
}.toRetrieveResponse()
|
||||
|
||||
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
|
||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||
@ -127,10 +126,10 @@ class ReservationService(
|
||||
|
||||
private fun validateDateAndTime(
|
||||
requestDate: LocalDate,
|
||||
requestReservationTime: ReservationTimeEntity
|
||||
requestTime: TimeEntity
|
||||
) {
|
||||
val now = LocalDateTime.now()
|
||||
val request = LocalDateTime.of(requestDate, requestReservationTime.startAt)
|
||||
val request = LocalDateTime.of(requestDate, requestTime.startAt)
|
||||
|
||||
if (request.isBefore(now)) {
|
||||
throw RoomescapeException(
|
||||
@ -148,15 +147,15 @@ class ReservationService(
|
||||
memberId: Long,
|
||||
status: ReservationStatus
|
||||
): ReservationEntity {
|
||||
val time = reservationTimeService.findTimeById(timeId)
|
||||
val theme = themeService.findThemeById(themeId)
|
||||
val time = timeService.findById(timeId)
|
||||
val theme = themeService.findById(themeId)
|
||||
val member = memberService.findById(memberId)
|
||||
|
||||
validateDateAndTime(date, time)
|
||||
|
||||
return ReservationEntity(
|
||||
date = date,
|
||||
reservationTime = time,
|
||||
time = time,
|
||||
theme = theme,
|
||||
member = member,
|
||||
reservationStatus = status
|
||||
@ -164,12 +163,12 @@ class ReservationService(
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findFilteredReservations(
|
||||
fun searchReservations(
|
||||
themeId: Long?,
|
||||
memberId: Long?,
|
||||
dateFrom: LocalDate?,
|
||||
dateTo: LocalDate?
|
||||
): ReservationsResponse {
|
||||
): ReservationRetrieveListResponse {
|
||||
validateDateForSearch(dateFrom, dateTo)
|
||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||
.confirmed()
|
||||
@ -179,7 +178,7 @@ class ReservationService(
|
||||
.dateEndAt(dateTo)
|
||||
.build()
|
||||
|
||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
||||
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||
}
|
||||
|
||||
private fun validateDateForSearch(startFrom: LocalDate?, endAt: LocalDate?) {
|
||||
@ -195,11 +194,11 @@ class ReservationService(
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findMemberReservations(memberId: Long): MyReservationsResponse {
|
||||
return MyReservationsResponse(reservationRepository.findMyReservations(memberId))
|
||||
fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse {
|
||||
return MyReservationRetrieveListResponse(reservationRepository.findAllById(memberId))
|
||||
}
|
||||
|
||||
fun approveWaiting(reservationId: Long, memberId: Long) {
|
||||
fun confirmWaiting(reservationId: Long, memberId: Long) {
|
||||
validateIsMemberAdmin(memberId)
|
||||
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
|
||||
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT)
|
||||
@ -207,7 +206,7 @@ class ReservationService(
|
||||
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
||||
}
|
||||
|
||||
fun cancelWaiting(reservationId: Long, memberId: Long) {
|
||||
fun deleteWaiting(reservationId: Long, memberId: Long) {
|
||||
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
||||
it.isWaiting() && it.isSameMember(memberId)
|
||||
}?.let {
|
||||
@ -215,7 +214,7 @@ class ReservationService(
|
||||
} ?: throw throwReservationNotFound(reservationId)
|
||||
}
|
||||
|
||||
fun denyWaiting(reservationId: Long, memberId: Long) {
|
||||
fun rejectWaiting(reservationId: Long, memberId: Long) {
|
||||
validateIsMemberAdmin(memberId)
|
||||
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
||||
it.isWaiting()
|
||||
@ -3,11 +3,12 @@ package roomescape.reservation.business
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.payment.business.PaymentService
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.web.ReservationRequest
|
||||
import roomescape.reservation.web.ReservationResponse
|
||||
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@Service
|
||||
@ -16,31 +17,31 @@ class ReservationWithPaymentService(
|
||||
private val reservationService: ReservationService,
|
||||
private val paymentService: PaymentService
|
||||
) {
|
||||
fun addReservationWithPayment(
|
||||
request: ReservationRequest,
|
||||
paymentInfo: PaymentApprove.Response,
|
||||
fun createReservationAndPayment(
|
||||
request: ReservationCreateWithPaymentRequest,
|
||||
paymentInfo: PaymentApproveResponse,
|
||||
memberId: Long
|
||||
): ReservationResponse {
|
||||
): ReservationRetrieveResponse {
|
||||
val reservation: ReservationEntity = reservationService.addReservation(request, memberId)
|
||||
|
||||
return paymentService.savePayment(paymentInfo, reservation)
|
||||
return paymentService.createPayment(paymentInfo, reservation)
|
||||
.reservation
|
||||
}
|
||||
|
||||
fun saveCanceledPayment(
|
||||
cancelInfo: PaymentCancel.Response,
|
||||
fun createCanceledPayment(
|
||||
cancelInfo: PaymentCancelResponse,
|
||||
approvedAt: OffsetDateTime,
|
||||
paymentKey: String
|
||||
) {
|
||||
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey)
|
||||
paymentService.createCanceledPayment(cancelInfo, approvedAt, paymentKey)
|
||||
}
|
||||
|
||||
fun removeReservationWithPayment(
|
||||
fun deleteReservationAndPayment(
|
||||
reservationId: Long,
|
||||
memberId: Long
|
||||
): PaymentCancel.Request {
|
||||
val paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId)
|
||||
reservationService.removeReservationById(reservationId, memberId)
|
||||
): PaymentCancelRequest {
|
||||
val paymentCancelRequest = paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||
reservationService.deleteReservation(reservationId, memberId)
|
||||
|
||||
return paymentCancelRequest
|
||||
}
|
||||
@ -48,7 +49,6 @@ class ReservationWithPaymentService(
|
||||
@Transactional(readOnly = true)
|
||||
fun isNotPaidReservation(reservationId: Long): Boolean = !paymentService.isReservationPaid(reservationId)
|
||||
|
||||
|
||||
fun updateCanceledTime(
|
||||
paymentKey: String,
|
||||
canceledAt: OffsetDateTime
|
||||
@ -0,0 +1,73 @@
|
||||
package roomescape.reservation.business
|
||||
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||
import roomescape.reservation.web.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
@Service
|
||||
class TimeService(
|
||||
private val timeRepository: TimeRepository,
|
||||
private val reservationRepository: ReservationRepository
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun findById(id: Long): TimeEntity = timeRepository.findByIdOrNull(id)
|
||||
?: throw RoomescapeException(
|
||||
ErrorType.TIME_NOT_FOUND,
|
||||
"[timeId: $id]",
|
||||
HttpStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findTimes(): TimeRetrieveListResponse = timeRepository.findAll().toRetrieveListResponse()
|
||||
|
||||
@Transactional
|
||||
fun createTime(timeCreateRequest: TimeCreateRequest): TimeCreateResponse {
|
||||
val startAt: LocalTime = timeCreateRequest.startAt
|
||||
|
||||
if (timeRepository.existsByStartAt(startAt)) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
|
||||
)
|
||||
}
|
||||
|
||||
return TimeEntity(startAt = startAt)
|
||||
.also { timeRepository.save(it) }
|
||||
.toCreateResponse()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteTime(id: Long) {
|
||||
val time: TimeEntity = findById(id)
|
||||
reservationRepository.findByTime(time)
|
||||
.also {
|
||||
if (it.isNotEmpty()) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
|
||||
)
|
||||
}
|
||||
timeRepository.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findTimesWithAvailability(date: LocalDate, themeId: Long): TimeWithAvailabilityListResponse {
|
||||
val allTimes = timeRepository.findAll()
|
||||
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
|
||||
|
||||
return TimeWithAvailabilityListResponse(allTimes.map { time ->
|
||||
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
|
||||
|
||||
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -26,33 +26,33 @@ interface ReservationAPI {
|
||||
@Admin
|
||||
@Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getAllReservations(): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
||||
fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||
|
||||
@LoginRequired
|
||||
@Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getMemberReservations(
|
||||
fun findReservationsByMemberId(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long
|
||||
): ResponseEntity<CommonApiResponse<MyReservationsResponse>>
|
||||
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "관리자의 예약 검색", description = "특정 조건에 해당되는 예약 검색", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
|
||||
)
|
||||
fun getReservationBySearching(
|
||||
fun searchReservations(
|
||||
@RequestParam(required = false) themeId: Long?,
|
||||
@RequestParam(required = false) memberId: Long?,
|
||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
||||
@RequestParam(required = false) dateTo: LocalDate?
|
||||
): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "관리자의 예약 취소", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "204", description = "성공"),
|
||||
)
|
||||
fun removeReservation(
|
||||
fun cancelReservationByAdmin(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
@ -67,10 +67,10 @@ interface ReservationAPI {
|
||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
||||
)
|
||||
)
|
||||
fun saveReservation(
|
||||
@Valid @RequestBody reservationRequest: ReservationRequest,
|
||||
fun createReservationWithPayment(
|
||||
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
||||
@MemberId @Parameter(hidden = true) memberId: Long
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "관리자 예약 추가", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ -82,14 +82,14 @@ interface ReservationAPI {
|
||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))],
|
||||
)
|
||||
)
|
||||
fun saveReservationByAdmin(
|
||||
@Valid @RequestBody adminReservationRequest: AdminReservationRequest,
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
||||
fun createReservationByAdmin(
|
||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getAllWaiting(): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
||||
fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||
|
||||
@LoginRequired
|
||||
@Operation(summary = "예약 대기 신청", tags = ["로그인이 필요한 API"])
|
||||
@ -101,17 +101,17 @@ interface ReservationAPI {
|
||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
||||
)
|
||||
)
|
||||
fun saveWaiting(
|
||||
@Valid @RequestBody waitingRequest: WaitingRequest,
|
||||
fun createWaiting(
|
||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||
|
||||
@LoginRequired
|
||||
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "204", description = "성공"),
|
||||
)
|
||||
fun deleteWaiting(
|
||||
fun cancelWaitingByMember(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
@ -121,7 +121,7 @@ interface ReservationAPI {
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "200", description = "성공"),
|
||||
)
|
||||
fun approveWaiting(
|
||||
fun confirmWaiting(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
@ -131,7 +131,7 @@ interface ReservationAPI {
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "204", description = "대기 중인 예약 거절 성공"),
|
||||
)
|
||||
fun denyWaiting(
|
||||
fun rejectWaiting(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
@ -12,39 +12,39 @@ import org.springframework.web.bind.annotation.RequestParam
|
||||
import roomescape.auth.web.support.Admin
|
||||
import roomescape.auth.web.support.LoginRequired
|
||||
import roomescape.common.dto.response.CommonApiResponse
|
||||
import roomescape.reservation.web.ReservationTimeInfosResponse
|
||||
import roomescape.reservation.web.ReservationTimeRequest
|
||||
import roomescape.reservation.web.ReservationTimeResponse
|
||||
import roomescape.reservation.web.ReservationTimesResponse
|
||||
import roomescape.reservation.web.TimeCreateRequest
|
||||
import roomescape.reservation.web.TimeCreateResponse
|
||||
import roomescape.reservation.web.TimeRetrieveListResponse
|
||||
import roomescape.reservation.web.TimeWithAvailabilityListResponse
|
||||
import java.time.LocalDate
|
||||
|
||||
@Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.")
|
||||
interface ReservationTimeAPI {
|
||||
interface TimeAPI {
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>>
|
||||
fun findTimes(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true))
|
||||
fun saveTime(
|
||||
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
|
||||
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>>
|
||||
fun createTime(
|
||||
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
|
||||
): ResponseEntity<CommonApiResponse<TimeCreateResponse>>
|
||||
|
||||
@Admin
|
||||
@Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true))
|
||||
fun removeTime(
|
||||
fun deleteTime(
|
||||
@PathVariable id: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
|
||||
@LoginRequired
|
||||
@Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun findAllAvailableReservationTimes(
|
||||
fun findTimesWithAvailability(
|
||||
@RequestParam date: LocalDate,
|
||||
@RequestParam themeId: Long
|
||||
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>>
|
||||
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>>
|
||||
}
|
||||
@ -1,14 +1,13 @@
|
||||
package roomescape.reservation.infrastructure.persistence
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import jakarta.persistence.*
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import java.time.LocalDate
|
||||
|
||||
@Entity
|
||||
@Table(name = "reservation")
|
||||
@Table(name = "reservations")
|
||||
class ReservationEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -18,7 +17,7 @@ class ReservationEntity(
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "time_id", nullable = false)
|
||||
var reservationTime: ReservationTimeEntity,
|
||||
var time: TimeEntity,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "theme_id", nullable = false)
|
||||
@ -40,14 +39,8 @@ class ReservationEntity(
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(description = "예약 상태를 나타냅니다.", allowableValues = ["CONFIRMED", "CONFIRMED_PAYMENT_REQUIRED", "WAITING"])
|
||||
enum class ReservationStatus {
|
||||
@Schema(description = "결제가 완료된 예약")
|
||||
CONFIRMED,
|
||||
|
||||
@Schema(description = "결제가 필요한 예약")
|
||||
CONFIRMED_PAYMENT_REQUIRED,
|
||||
|
||||
@Schema(description = "대기 중인 예약")
|
||||
WAITING
|
||||
}
|
||||
@ -5,12 +5,12 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import roomescape.reservation.web.MyReservationResponse
|
||||
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||
import java.time.LocalDate
|
||||
|
||||
interface ReservationRepository
|
||||
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
|
||||
fun findByReservationTime(reservationTime: ReservationTimeEntity): List<ReservationEntity>
|
||||
fun findByTime(time: TimeEntity): List<ReservationEntity>
|
||||
|
||||
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
|
||||
|
||||
@ -33,7 +33,7 @@ interface ReservationRepository
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ReservationEntity r
|
||||
WHERE r.theme.id = r2.theme.id
|
||||
AND r.reservationTime.id = r2.reservationTime.id
|
||||
AND r.time.id = r2.time.id
|
||||
AND r.date = r2.date
|
||||
AND r.reservationStatus != 'WAITING'
|
||||
)
|
||||
@ -42,13 +42,13 @@ interface ReservationRepository
|
||||
fun isExistConfirmedReservation(@Param("id") reservationId: Long): Boolean
|
||||
|
||||
@Query("""
|
||||
SELECT new roomescape.reservation.web.MyReservationResponse(
|
||||
SELECT new roomescape.reservation.web.MyReservationRetrieveResponse(
|
||||
r.id,
|
||||
t.name,
|
||||
r.date,
|
||||
r.reservationTime.startAt,
|
||||
r.time.startAt,
|
||||
r.reservationStatus,
|
||||
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.reservationTime = r.reservationTime AND r2.id < r.id),
|
||||
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.time = r.time AND r2.id < r.id),
|
||||
p.paymentKey,
|
||||
p.totalAmount
|
||||
)
|
||||
@ -58,5 +58,5 @@ interface ReservationRepository
|
||||
ON p.reservation = r
|
||||
WHERE r.member.id = :memberId
|
||||
""")
|
||||
fun findMyReservations(memberId: Long): List<MyReservationResponse>
|
||||
fun findAllById(memberId: Long): List<MyReservationRetrieveResponse>
|
||||
}
|
||||
@ -22,7 +22,7 @@ class ReservationSearchSpecification(
|
||||
|
||||
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.equal(root.get<ReservationTimeEntity>("reservationTime").get<Long>("id"), timeId)
|
||||
cb.equal(root.get<TimeEntity>("time").get<Long>("id"), timeId)
|
||||
}
|
||||
})
|
||||
|
||||
@ -4,8 +4,8 @@ import jakarta.persistence.*
|
||||
import java.time.LocalTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "reservation_time")
|
||||
class ReservationTimeEntity(
|
||||
@Table(name = "times")
|
||||
class TimeEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long? = null,
|
||||
@ -3,6 +3,6 @@ package roomescape.reservation.infrastructure.persistence
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import java.time.LocalTime
|
||||
|
||||
interface ReservationTimeRepository : JpaRepository<ReservationTimeEntity, Long> {
|
||||
interface TimeRepository : JpaRepository<TimeEntity, Long> {
|
||||
fun existsByStartAt(startAt: LocalTime): Boolean
|
||||
}
|
||||
@ -7,9 +7,10 @@ import org.springframework.web.bind.annotation.*
|
||||
import roomescape.auth.web.support.MemberId
|
||||
import roomescape.common.dto.response.CommonApiResponse
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||
import roomescape.payment.infrastructure.client.TossPaymentClient
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.reservation.business.ReservationService
|
||||
import roomescape.reservation.business.ReservationWithPaymentService
|
||||
import roomescape.reservation.docs.ReservationAPI
|
||||
@ -23,46 +24,50 @@ class ReservationController(
|
||||
private val paymentClient: TossPaymentClient
|
||||
) : ReservationAPI {
|
||||
@GetMapping("/reservations")
|
||||
override fun getAllReservations(): ResponseEntity<CommonApiResponse<ReservationsResponse>> {
|
||||
val response: ReservationsResponse = reservationService.findAllReservations()
|
||||
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||
val response: ReservationRetrieveListResponse = reservationService.findReservations()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@GetMapping("/reservations-mine")
|
||||
override fun getMemberReservations(
|
||||
override fun findReservationsByMemberId(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long
|
||||
): ResponseEntity<CommonApiResponse<MyReservationsResponse>> {
|
||||
val response: MyReservationsResponse = reservationService.findMemberReservations(memberId)
|
||||
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
|
||||
val response: MyReservationRetrieveListResponse = reservationService.findReservationsByMemberId(memberId)
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@GetMapping("/reservations/search")
|
||||
override fun getReservationBySearching(
|
||||
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<ReservationsResponse>> {
|
||||
val response: ReservationsResponse = reservationService.findFilteredReservations(themeId, memberId, dateFrom, dateTo)
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||
val response: ReservationRetrieveListResponse = reservationService.searchReservations(
|
||||
themeId,
|
||||
memberId,
|
||||
dateFrom,
|
||||
dateTo
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@DeleteMapping("/reservations/{id}")
|
||||
override fun removeReservation(
|
||||
override fun cancelReservationByAdmin(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
||||
reservationService.removeReservationById(reservationId, memberId)
|
||||
reservationService.deleteReservation(reservationId, memberId)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
val paymentCancelRequest = reservationWithPaymentService.removeReservationWithPayment(
|
||||
reservationId, memberId)
|
||||
val paymentCancelResponse = paymentClient.cancelPayment(paymentCancelRequest)
|
||||
val paymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(reservationId, memberId)
|
||||
val paymentCancelResponse = paymentClient.cancel(paymentCancelRequest)
|
||||
reservationWithPaymentService.updateCanceledTime(paymentCancelRequest.paymentKey,
|
||||
paymentCancelResponse.canceledAt)
|
||||
|
||||
@ -70,56 +75,56 @@ class ReservationController(
|
||||
}
|
||||
|
||||
@PostMapping("/reservations")
|
||||
override fun saveReservation(
|
||||
@Valid @RequestBody reservationRequest: ReservationRequest,
|
||||
override fun createReservationWithPayment(
|
||||
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
||||
@MemberId @Parameter(hidden = true) memberId: Long
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
||||
val paymentRequest: PaymentApprove.Request = reservationRequest.paymentRequest
|
||||
val paymentResponse: PaymentApprove.Response = paymentClient.confirmPayment(paymentRequest)
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||
val paymentRequest: PaymentApproveRequest = reservationCreateWithPaymentRequest.toPaymentApproveRequest()
|
||||
val paymentResponse: PaymentApproveResponse = paymentClient.confirm(paymentRequest)
|
||||
|
||||
try {
|
||||
val reservationResponse: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
|
||||
reservationRequest,
|
||||
val reservationRetrieveResponse: ReservationRetrieveResponse = reservationWithPaymentService.createReservationAndPayment(
|
||||
reservationCreateWithPaymentRequest,
|
||||
paymentResponse,
|
||||
memberId
|
||||
)
|
||||
return ResponseEntity.created(URI.create("/reservations/${reservationResponse.id}"))
|
||||
.body(CommonApiResponse(reservationResponse))
|
||||
return ResponseEntity.created(URI.create("/reservations/${reservationRetrieveResponse.id}"))
|
||||
.body(CommonApiResponse(reservationRetrieveResponse))
|
||||
} catch (e: RoomescapeException) {
|
||||
val cancelRequest = PaymentCancel.Request(paymentRequest.paymentKey,
|
||||
val cancelRequest = PaymentCancelRequest(paymentRequest.paymentKey,
|
||||
paymentRequest.amount, e.message!!)
|
||||
val paymentCancelResponse = paymentClient.cancelPayment(cancelRequest)
|
||||
reservationWithPaymentService.saveCanceledPayment(paymentCancelResponse, paymentResponse.approvedAt,
|
||||
val paymentCancelResponse = paymentClient.cancel(cancelRequest)
|
||||
reservationWithPaymentService.createCanceledPayment(paymentCancelResponse, paymentResponse.approvedAt,
|
||||
paymentRequest.paymentKey)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/reservations/admin")
|
||||
override fun saveReservationByAdmin(
|
||||
@Valid @RequestBody adminReservationRequest: AdminReservationRequest
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
||||
val response: ReservationResponse =
|
||||
reservationService.addReservationByAdmin(adminReservationRequest)
|
||||
override fun createReservationByAdmin(
|
||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||
val response: ReservationRetrieveResponse =
|
||||
reservationService.createReservationByAdmin(adminReservationRequest)
|
||||
|
||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
||||
.body(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@GetMapping("/reservations/waiting")
|
||||
override fun getAllWaiting(): ResponseEntity<CommonApiResponse<ReservationsResponse>> {
|
||||
val response: ReservationsResponse = reservationService.findAllWaiting()
|
||||
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||
val response: ReservationRetrieveListResponse = reservationService.findAllWaiting()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@PostMapping("/reservations/waiting")
|
||||
override fun saveWaiting(
|
||||
@Valid @RequestBody waitingRequest: WaitingRequest,
|
||||
override fun createWaiting(
|
||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
||||
val response: ReservationResponse = reservationService.addWaiting(
|
||||
waitingRequest,
|
||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||
val response: ReservationRetrieveResponse = reservationService.createWaiting(
|
||||
waitingCreateRequest,
|
||||
memberId
|
||||
)
|
||||
|
||||
@ -128,31 +133,31 @@ class ReservationController(
|
||||
}
|
||||
|
||||
@DeleteMapping("/reservations/waiting/{id}")
|
||||
override fun deleteWaiting(
|
||||
override fun cancelWaitingByMember(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
reservationService.cancelWaiting(reservationId, memberId)
|
||||
reservationService.deleteWaiting(reservationId, memberId)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@PostMapping("/reservations/waiting/{id}/approve")
|
||||
override fun approveWaiting(
|
||||
@PostMapping("/reservations/waiting/{id}/confirm")
|
||||
override fun confirmWaiting(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
reservationService.approveWaiting(reservationId, memberId)
|
||||
reservationService.confirmWaiting(reservationId, memberId)
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
@PostMapping("/reservations/waiting/{id}/deny")
|
||||
override fun denyWaiting(
|
||||
@PostMapping("/reservations/waiting/{id}/reject")
|
||||
override fun rejectWaiting(
|
||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||
@PathVariable("id") reservationId: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
reservationService.denyWaiting(reservationId, memberId)
|
||||
reservationService.rejectWaiting(reservationId, memberId)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||
import java.time.LocalDate
|
||||
|
||||
data class AdminReservationCreateRequest(
|
||||
val date: LocalDate,
|
||||
val timeId: Long,
|
||||
val themeId: Long,
|
||||
val memberId: Long
|
||||
)
|
||||
|
||||
data class ReservationCreateWithPaymentRequest(
|
||||
val date: LocalDate,
|
||||
val timeId: Long,
|
||||
val themeId: Long,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 키")
|
||||
val paymentKey: String,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 주문번호.")
|
||||
val orderId: String,
|
||||
|
||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 금액")
|
||||
val amount: Long,
|
||||
|
||||
@field:Schema(description = "결제 타입", example = "NORMAL")
|
||||
val paymentType: String
|
||||
)
|
||||
|
||||
fun ReservationCreateWithPaymentRequest.toPaymentApproveRequest(): PaymentApproveRequest = PaymentApproveRequest(
|
||||
paymentKey, orderId, amount, paymentType
|
||||
)
|
||||
|
||||
data class WaitingCreateRequest(
|
||||
val date: LocalDate,
|
||||
val timeId: Long,
|
||||
val themeId: Long
|
||||
)
|
||||
@ -0,0 +1,60 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.member.web.MemberRetrieveResponse
|
||||
import roomescape.member.web.toRetrieveResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.theme.web.ThemeResponse
|
||||
import roomescape.theme.web.toResponse
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
data class MyReservationRetrieveResponse(
|
||||
val id: Long,
|
||||
val themeName: String,
|
||||
val date: LocalDate,
|
||||
val time: LocalTime,
|
||||
val status: ReservationStatus,
|
||||
@field:Schema(description = "대기 순번. 확정된 예약은 0의 값을 가집니다.")
|
||||
val rank: Long,
|
||||
@field:Schema(description = "결제 키. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||
val paymentKey: String?,
|
||||
@field:Schema(description = "결제 금액. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||
val amount: Long?
|
||||
)
|
||||
|
||||
data class MyReservationRetrieveListResponse(
|
||||
@field:Schema(description = "현재 로그인한 회원의 예약 및 대기 목록")
|
||||
val reservations: List<MyReservationRetrieveResponse>
|
||||
)
|
||||
|
||||
data class ReservationRetrieveResponse(
|
||||
val id: Long,
|
||||
val date: LocalDate,
|
||||
|
||||
@field:JsonProperty("member")
|
||||
val member: MemberRetrieveResponse,
|
||||
|
||||
@field:JsonProperty("time")
|
||||
val time: TimeCreateResponse,
|
||||
|
||||
@field:JsonProperty("theme")
|
||||
val theme: ThemeResponse,
|
||||
|
||||
val status: ReservationStatus
|
||||
)
|
||||
|
||||
fun ReservationEntity.toRetrieveResponse(): ReservationRetrieveResponse = ReservationRetrieveResponse(
|
||||
id = this.id!!,
|
||||
date = this.date,
|
||||
member = this.member.toRetrieveResponse(),
|
||||
time = this.time.toCreateResponse(),
|
||||
theme = this.theme.toResponse(),
|
||||
status = this.reservationStatus
|
||||
)
|
||||
|
||||
data class ReservationRetrieveListResponse(
|
||||
val reservations: List<ReservationRetrieveResponse>
|
||||
)
|
||||
51
src/main/kotlin/roomescape/reservation/web/TimeController.kt
Normal file
51
src/main/kotlin/roomescape/reservation/web/TimeController.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import roomescape.common.dto.response.CommonApiResponse
|
||||
import roomescape.reservation.business.TimeService
|
||||
import roomescape.reservation.docs.TimeAPI
|
||||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
|
||||
@RestController
|
||||
class TimeController(
|
||||
private val timeService: TimeService
|
||||
) : TimeAPI {
|
||||
|
||||
@GetMapping("/times")
|
||||
override fun findTimes(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>> {
|
||||
val response: TimeRetrieveListResponse = timeService.findTimes()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@PostMapping("/times")
|
||||
override fun createTime(
|
||||
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
|
||||
): ResponseEntity<CommonApiResponse<TimeCreateResponse>> {
|
||||
val response: TimeCreateResponse = timeService.createTime(timeCreateRequest)
|
||||
|
||||
return ResponseEntity
|
||||
.created(URI.create("/times/${response.id}"))
|
||||
.body(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@DeleteMapping("/times/{id}")
|
||||
override fun deleteTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
timeService.deleteTime(id)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@GetMapping("/times/search")
|
||||
override fun findTimesWithAvailability(
|
||||
@RequestParam date: LocalDate,
|
||||
@RequestParam themeId: Long
|
||||
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>> {
|
||||
val response: TimeWithAvailabilityListResponse = timeService.findTimesWithAvailability(date, themeId)
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
}
|
||||
55
src/main/kotlin/roomescape/reservation/web/TimeDTO.kt
Normal file
55
src/main/kotlin/roomescape/reservation/web/TimeDTO.kt
Normal file
@ -0,0 +1,55 @@
|
||||
package roomescape.reservation.web
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import java.time.LocalTime
|
||||
|
||||
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
|
||||
data class TimeCreateRequest(
|
||||
@field:Schema(description = "시간", type = "string", example = "09:00")
|
||||
val startAt: LocalTime
|
||||
)
|
||||
|
||||
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
|
||||
data class TimeCreateResponse(
|
||||
@field:Schema(description = "시간 식별자")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "시간")
|
||||
val startAt: LocalTime
|
||||
)
|
||||
|
||||
fun TimeEntity.toCreateResponse(): TimeCreateResponse = TimeCreateResponse(this.id!!, this.startAt)
|
||||
|
||||
data class TimeRetrieveResponse(
|
||||
@field:Schema(description = "시간 식별자.")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "시간")
|
||||
val startAt: LocalTime
|
||||
)
|
||||
|
||||
fun TimeEntity.toRetrieveResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt)
|
||||
|
||||
data class TimeRetrieveListResponse(
|
||||
val times: List<TimeRetrieveResponse>
|
||||
)
|
||||
|
||||
fun List<TimeEntity>.toRetrieveListResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse(
|
||||
this.map { it.toRetrieveResponse() }
|
||||
)
|
||||
|
||||
data class TimeWithAvailabilityResponse(
|
||||
@field:Schema(description = "시간 식별자")
|
||||
val id: Long,
|
||||
|
||||
@field:Schema(description = "시간")
|
||||
val startAt: LocalTime,
|
||||
|
||||
@field:Schema(description = "예약 가능 여부")
|
||||
val isAvailable: Boolean
|
||||
)
|
||||
|
||||
data class TimeWithAvailabilityListResponse(
|
||||
val times: List<TimeWithAvailabilityResponse>
|
||||
)
|
||||
@ -19,7 +19,7 @@ class ThemeService(
|
||||
private val themeRepository: ThemeRepository
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun findThemeById(id: Long): ThemeEntity = themeRepository.findByIdOrNull(id)
|
||||
fun findById(id: Long): ThemeEntity = themeRepository.findByIdOrNull(id)
|
||||
?: throw RoomescapeException(
|
||||
ErrorType.THEME_NOT_FOUND,
|
||||
"[themeId: $id]",
|
||||
@ -27,22 +27,21 @@ class ThemeService(
|
||||
)
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllThemes(): ThemesResponse = themeRepository.findAll()
|
||||
fun findThemes(): ThemesResponse = themeRepository.findAll()
|
||||
.toResponse()
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getMostReservedThemesByCount(count: Int): ThemesResponse {
|
||||
fun findMostReservedThemes(count: Int): ThemesResponse {
|
||||
val today = LocalDate.now()
|
||||
val startDate = today.minusDays(7)
|
||||
val endDate = today.minusDays(1)
|
||||
|
||||
return themeRepository.findTopNThemeBetweenStartDateAndEndDate(startDate, endDate, count)
|
||||
return themeRepository.findPopularThemes(startDate, endDate, count)
|
||||
.toResponse()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun save(request: ThemeRequest): ThemeResponse {
|
||||
fun createTheme(request: ThemeRequest): ThemeResponse {
|
||||
if (themeRepository.existsByName(request.name)) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.THEME_DUPLICATED,
|
||||
@ -61,7 +60,7 @@ class ThemeService(
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteById(id: Long) {
|
||||
fun deleteTheme(id: Long) {
|
||||
if (themeRepository.isReservedTheme(id)) {
|
||||
throw RoomescapeException(
|
||||
ErrorType.THEME_IS_USED_CONFLICT,
|
||||
@ -23,11 +23,11 @@ interface ThemeAPI {
|
||||
@LoginRequired
|
||||
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"])
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
||||
fun findThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
||||
|
||||
@Operation(summary = "가장 많이 예약된 테마 조회")
|
||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||
fun getMostReservedThemes(
|
||||
fun findMostReservedThemes(
|
||||
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
||||
): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
||||
|
||||
@ -36,7 +36,7 @@ interface ThemeAPI {
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
|
||||
)
|
||||
fun saveTheme(
|
||||
fun createTheme(
|
||||
@Valid @RequestBody request: ThemeRequest,
|
||||
): ResponseEntity<CommonApiResponse<ThemeResponse>>
|
||||
|
||||
@ -45,7 +45,7 @@ interface ThemeAPI {
|
||||
@ApiResponses(
|
||||
ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
|
||||
)
|
||||
fun removeTheme(
|
||||
fun deleteTheme(
|
||||
@PathVariable id: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>>
|
||||
}
|
||||
@ -3,7 +3,7 @@ package roomescape.theme.infrastructure.persistence
|
||||
import jakarta.persistence.*
|
||||
|
||||
@Entity
|
||||
@Table(name = "theme")
|
||||
@Table(name = "themes")
|
||||
class ThemeEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -14,10 +14,9 @@ interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
|
||||
GROUP BY r.theme.id
|
||||
ORDER BY COUNT(r.theme.id) DESC, t.id ASC
|
||||
LIMIT :limit
|
||||
|
||||
"""
|
||||
)
|
||||
fun findTopNThemeBetweenStartDateAndEndDate(startDate: LocalDate, endDate: LocalDate, limit: Int): List<ThemeEntity>
|
||||
fun findPopularThemes(startDate: LocalDate, endDate: LocalDate, limit: Int): List<ThemeEntity>
|
||||
|
||||
fun existsByName(name: String): Boolean
|
||||
|
||||
@ -15,36 +15,36 @@ class ThemeController(
|
||||
) : ThemeAPI {
|
||||
|
||||
@GetMapping("/themes")
|
||||
override fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
||||
val response: ThemesResponse = themeService.findAllThemes()
|
||||
override fun findThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
||||
val response: ThemesResponse = themeService.findThemes()
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@GetMapping("/themes/most-reserved-last-week")
|
||||
override fun getMostReservedThemes(
|
||||
override fun findMostReservedThemes(
|
||||
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
||||
): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
||||
val response: ThemesResponse = themeService.getMostReservedThemesByCount(count)
|
||||
val response: ThemesResponse = themeService.findMostReservedThemes(count)
|
||||
|
||||
return ResponseEntity.ok(CommonApiResponse(response))
|
||||
}
|
||||
|
||||
@PostMapping("/themes")
|
||||
override fun saveTheme(
|
||||
override fun createTheme(
|
||||
@RequestBody @Valid request: ThemeRequest
|
||||
): ResponseEntity<CommonApiResponse<ThemeResponse>> {
|
||||
val themeResponse: ThemeResponse = themeService.save(request)
|
||||
val themeResponse: ThemeResponse = themeService.createTheme(request)
|
||||
|
||||
return ResponseEntity.created(URI.create("/themes/${themeResponse.id}"))
|
||||
.body(CommonApiResponse(themeResponse))
|
||||
}
|
||||
|
||||
@DeleteMapping("/themes/{id}")
|
||||
override fun removeTheme(
|
||||
override fun deleteTheme(
|
||||
@PathVariable id: Long
|
||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||
themeService.deleteById(id)
|
||||
themeService.deleteTheme(id)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
@ -7,7 +7,6 @@ import org.hibernate.validator.constraints.URL
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
|
||||
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
|
||||
@JvmRecord
|
||||
data class ThemeRequest(
|
||||
@field:Schema(description = "필수 값이며, 최대 20글자까지 입력 가능합니다.")
|
||||
@NotBlank
|
||||
@ -26,7 +25,6 @@ data class ThemeRequest(
|
||||
)
|
||||
|
||||
@Schema(name = "테마 정보", description = "테마 추가 및 조회 응답에 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ThemeResponse(
|
||||
@field:Schema(description = "테마 번호. 테마를 식별할 때 사용합니다.")
|
||||
val id: Long,
|
||||
@ -39,14 +37,7 @@ data class ThemeResponse(
|
||||
|
||||
@field:Schema(description = "테마 썸네일 이미지 URL")
|
||||
val thumbnail: String
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun from(themeEntity: ThemeEntity): ThemeResponse {
|
||||
return ThemeResponse(themeEntity.id!!, themeEntity.name, themeEntity.description, themeEntity.thumbnail)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
|
||||
id = this.id!!,
|
||||
@ -55,9 +46,7 @@ fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
|
||||
thumbnail = this.thumbnail
|
||||
)
|
||||
|
||||
|
||||
@Schema(name = "테마 목록 조회 응답", description = "모든 테마 목록 조회 응답시 사용됩니다.")
|
||||
@JvmRecord
|
||||
data class ThemesResponse(
|
||||
@field:Schema(description = "모든 테마 목록")
|
||||
val themes: List<ThemeResponse>
|
||||
@ -1,68 +1,68 @@
|
||||
insert into reservation_time(start_at)
|
||||
insert into times(start_at)
|
||||
values ('15:00');
|
||||
insert into reservation_time(start_at)
|
||||
insert into times(start_at)
|
||||
values ('16:00');
|
||||
insert into reservation_time(start_at)
|
||||
insert into times(start_at)
|
||||
values ('17:00');
|
||||
insert into reservation_time(start_at)
|
||||
insert into times(start_at)
|
||||
values ('18:00');
|
||||
|
||||
insert into theme(name, description, thumbnail)
|
||||
insert into themes(name, description, thumbnail)
|
||||
values ('테스트1', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||
insert into theme(name, description, thumbnail)
|
||||
insert into themes(name, description, thumbnail)
|
||||
values ('테스트2', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||
insert into theme(name, description, thumbnail)
|
||||
insert into themes(name, description, thumbnail)
|
||||
values ('테스트3', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||
insert into theme(name, description, thumbnail)
|
||||
insert into themes(name, description, thumbnail)
|
||||
values ('테스트4', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||
|
||||
insert into member(name, email, password, role)
|
||||
insert into members(name, email, password, role)
|
||||
values ('어드민', 'a@a.a', 'a', 'ADMIN');
|
||||
insert into member(name, email, password, role)
|
||||
insert into members(name, email, password, role)
|
||||
values ('1호', '1@1.1', '1', 'MEMBER');
|
||||
insert into member(name, email, password, role)
|
||||
insert into members(name, email, password, role)
|
||||
values ('2호', '2@2.2', '2', 'MEMBER');
|
||||
insert into member(name, email, password, role)
|
||||
insert into members(name, email, password, role)
|
||||
values ('3호', '3@3.3', '3', 'MEMBER');
|
||||
insert into member(name, email, password, role)
|
||||
insert into members(name, email, password, role)
|
||||
values ('4호', '4@4.4', '4', 'MEMBER');
|
||||
|
||||
-- 예약: 결제 완료
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (1, DATEADD('DAY', -1, CURRENT_DATE()) - 1, 1, 1, 'CONFIRMED');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (2, DATEADD('DAY', -2, CURRENT_DATE()) - 2, 3, 2, 'CONFIRMED');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (3, DATEADD('DAY', -3, CURRENT_DATE()), 2, 2, 'CONFIRMED');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (4, DATEADD('DAY', -4, CURRENT_DATE()), 1, 2, 'CONFIRMED');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (5, DATEADD('DAY', -5, CURRENT_DATE()), 1, 3, 'CONFIRMED');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (2, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'CONFIRMED');
|
||||
|
||||
-- 예약: 결제 대기
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (2, DATEADD('DAY', 8, CURRENT_DATE()), 2, 4, 'CONFIRMED_PAYMENT_REQUIRED');
|
||||
|
||||
-- 예약 대기
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (3, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (4, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
||||
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||
values (5, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||
|
||||
-- 결제 정보
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-1', 'paymentKey-1', 10000, 1, CURRENT_DATE);
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-2', 'paymentKey-2', 20000, 2, CURRENT_DATE);
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-3', 'paymentKey-3', 30000, 3, CURRENT_DATE);
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-4', 'paymentKey-4', 40000, 4, CURRENT_DATE);
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-5', 'paymentKey-5', 50000, 5, CURRENT_DATE);
|
||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||
values ('orderId-6', 'paymentKey-6', 60000, 6, CURRENT_DATE);
|
||||
@ -97,7 +97,7 @@ function checkDateAndTheme() {
|
||||
|
||||
function fetchAvailableTimes(date, themeId) {
|
||||
|
||||
fetch(`/times/filter?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint
|
||||
fetch(`/times/search?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -121,12 +121,12 @@ function renderAvailableTimes(times) {
|
||||
timeSlots.innerHTML = '<div class="no-times">선택할 수 있는 시간이 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
times.data.reservationTimes.forEach(time => {
|
||||
times.data.times.forEach(time => {
|
||||
const startAt = time.startAt;
|
||||
const timeId = time.timeId;
|
||||
const alreadyBooked = time.alreadyBooked;
|
||||
const timeId = time.id;
|
||||
const isAvailable = time.isAvailable;
|
||||
|
||||
const div = createSlot('time', startAt, timeId, alreadyBooked); // createSlot('time', 시작 시간, time id, 예약 여부)
|
||||
const div = createSlot('time', startAt, timeId, isAvailable); // createSlot('time', 시작 시간, time id, 예약 여부)
|
||||
timeSlots.appendChild(div);
|
||||
});
|
||||
}
|
||||
@ -139,7 +139,7 @@ function checkDateAndThemeAndTime() {
|
||||
const waitButton = document.getElementById("wait-button");
|
||||
|
||||
if (selectedDate && selectedThemeElement && selectedTimeElement) {
|
||||
if (selectedTimeElement.getAttribute('data-time-booked') === 'true') {
|
||||
if (selectedTimeElement.getAttribute('data-time-booked') === 'false') {
|
||||
// 선택된 시간이 이미 예약된 경우
|
||||
reserveButton.classList.add("disabled");
|
||||
waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화
|
||||
|
||||
@ -38,7 +38,7 @@ function approve(event) {
|
||||
const row = event.target.closest('tr');
|
||||
const id = row.cells[0].textContent;
|
||||
|
||||
const endpoint = `/reservations/waiting/${id}/approve`
|
||||
const endpoint = `/reservations/waiting/${id}/confirm`
|
||||
return fetch(endpoint, {
|
||||
method: 'POST'
|
||||
}).then(response => {
|
||||
@ -51,7 +51,7 @@ function deny(event) {
|
||||
const row = event.target.closest('tr');
|
||||
const id = row.cells[0].textContent;
|
||||
|
||||
const endpoint = `/reservations/waiting/${id}/deny`
|
||||
const endpoint = `/reservations/waiting/${id}/reject`
|
||||
return fetch(endpoint, {
|
||||
method: 'POST'
|
||||
}).then(response => {
|
||||
|
||||
@ -17,7 +17,6 @@ import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.util.JwtFixture
|
||||
import roomescape.util.MemberFixture
|
||||
|
||||
|
||||
class AuthServiceTest : BehaviorSpec({
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberService: MemberService = MemberService(memberRepository)
|
||||
@ -43,7 +43,6 @@ class AuthControllerTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = userRequest,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
header {
|
||||
@ -66,7 +65,6 @@ class AuthControllerTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = userRequest,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
||||
@ -83,7 +81,6 @@ class AuthControllerTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = invalidRequest,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
jsonPath("$.message", containsString("이메일 형식이 일치하지 않습니다."))
|
||||
@ -97,7 +94,6 @@ class AuthControllerTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = invalidRequest,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
jsonPath("$.message", containsString("비밀번호는 공백일 수 없습니다."))
|
||||
@ -116,7 +112,6 @@ class AuthControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
jsonPath("$.data.name", equalTo(user.name))
|
||||
@ -134,7 +129,6 @@ class AuthControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
||||
@ -153,7 +147,6 @@ class AuthControllerTest(
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -170,7 +163,6 @@ class AuthControllerTest(
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
header {
|
||||
@ -8,7 +8,7 @@ import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import jakarta.servlet.http.Cookie
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import roomescape.auth.web.TokenResponse
|
||||
import roomescape.auth.web.LoginResponse
|
||||
|
||||
class CookieUtilsTest : FunSpec({
|
||||
context("HttpServletRequest에서 accessToken 쿠키를 가져온다.") {
|
||||
@ -45,9 +45,9 @@ class CookieUtilsTest : FunSpec({
|
||||
}
|
||||
|
||||
context("TokenResponse를 쿠키로 반환한다.") {
|
||||
val tokenResponse = TokenResponse("test-token")
|
||||
val loginResponse = LoginResponse("test-token")
|
||||
|
||||
val result: String = tokenResponse.toResponseCookie()
|
||||
val result: String = loginResponse.toResponseCookie()
|
||||
|
||||
result.split("; ") shouldContainAll listOf(
|
||||
"accessToken=test-token",
|
||||
@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import roomescape.member.web.MemberController
|
||||
import roomescape.member.web.MembersResponse
|
||||
import roomescape.member.web.MemberRetrieveListResponse
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.RoomescapeApiTest
|
||||
import kotlin.random.Random
|
||||
@ -35,14 +35,13 @@ class MemberControllerTest(
|
||||
val result: String = runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}.andReturn().response.contentAsString
|
||||
|
||||
val response: MembersResponse = readValue(
|
||||
val response: MemberRetrieveListResponse = readValue(
|
||||
responseJson = result,
|
||||
valueType = MembersResponse::class.java
|
||||
valueType = MemberRetrieveListResponse::class.java
|
||||
)
|
||||
|
||||
assertSoftly(response.members) {
|
||||
@ -59,7 +58,6 @@ class MemberControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -74,7 +72,6 @@ class MemberControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -13,7 +13,7 @@ import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.util.PaymentFixture
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@ -29,7 +29,7 @@ class PaymentServiceTest : FunSpec({
|
||||
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
|
||||
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
paymentService.cancelPaymentByAdmin(reservationId)
|
||||
paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||
}
|
||||
|
||||
assertSoftly(exception) {
|
||||
@ -51,7 +51,7 @@ class PaymentServiceTest : FunSpec({
|
||||
} returns null
|
||||
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
paymentService.cancelPaymentByAdmin(reservationId)
|
||||
paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||
}
|
||||
|
||||
assertSoftly(exception) {
|
||||
@ -79,7 +79,7 @@ class PaymentServiceTest : FunSpec({
|
||||
cancelAmount = paymentEntity.totalAmount,
|
||||
)
|
||||
|
||||
val result: PaymentCancel.Request = paymentService.cancelPaymentByAdmin(reservationId)
|
||||
val result: PaymentCancelRequest = paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||
|
||||
assertSoftly(result) {
|
||||
this.paymentKey shouldBe paymentKey
|
||||
@ -1,4 +1,4 @@
|
||||
package roomescape.payment.web.support
|
||||
package roomescape.payment.infrastructure.client
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
@ -6,24 +6,22 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import roomescape.payment.SampleTossPaymentConst
|
||||
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
|
||||
class PaymentCancelResponseDeserializerTest : StringSpec({
|
||||
|
||||
val objectMapper: ObjectMapper = jacksonObjectMapper().registerModule(
|
||||
SimpleModule().addDeserializer(
|
||||
PaymentCancel.Response::class.java,
|
||||
PaymentCancelResponse::class.java,
|
||||
PaymentCancelResponseDeserializer()
|
||||
)
|
||||
)
|
||||
|
||||
"결제 취소 응답을 역직렬화하여 PaymentCancelResponse 객체를 생성한다" {
|
||||
val cancelResponseJson: String = SampleTossPaymentConst.cancelJson
|
||||
val cancelResponse: PaymentCancel.Response = objectMapper.readValue(
|
||||
val cancelResponse: PaymentCancelResponse = objectMapper.readValue(
|
||||
cancelResponseJson,
|
||||
PaymentCancel.Response::class.java
|
||||
PaymentCancelResponse::class.java
|
||||
)
|
||||
|
||||
assertSoftly(cancelResponse) {
|
||||
@ -1,35 +1,26 @@
|
||||
package roomescape.payment
|
||||
package roomescape.payment.infrastructure.client
|
||||
|
||||
import roomescape.payment.SampleTossPaymentConst.amount
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
object SampleTossPaymentConst {
|
||||
@JvmField
|
||||
val paymentKey: String = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1"
|
||||
|
||||
@JvmField
|
||||
val orderId: String = "MC4wODU4ODQwMzg4NDk0"
|
||||
|
||||
@JvmField
|
||||
val amount: Long = 1000L
|
||||
|
||||
@JvmField
|
||||
val paymentType: String = "카드"
|
||||
|
||||
@JvmField
|
||||
val cancelReason: String = "테스트 결제 취소"
|
||||
|
||||
@JvmField
|
||||
val paymentRequest: PaymentApprove.Request = PaymentApprove.Request(
|
||||
val paymentRequest: PaymentApproveRequest = PaymentApproveRequest(
|
||||
paymentKey,
|
||||
orderId,
|
||||
amount,
|
||||
paymentType
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val paymentRequestJson: String = """
|
||||
{
|
||||
"paymentKey": "$paymentKey",
|
||||
@ -39,21 +30,18 @@ object SampleTossPaymentConst {
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@JvmField
|
||||
val cancelRequest: PaymentCancel.Request = PaymentCancel.Request(
|
||||
val cancelRequest: PaymentCancelRequest = PaymentCancelRequest(
|
||||
paymentKey,
|
||||
amount,
|
||||
cancelReason
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val cancelRequestJson: String = """
|
||||
{
|
||||
"cancelReason": "$cancelReason"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@JvmField
|
||||
val tossPaymentErrorJson: String = """
|
||||
{
|
||||
"code": "ERROR_CODE",
|
||||
@ -61,7 +49,6 @@ object SampleTossPaymentConst {
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@JvmField
|
||||
val confirmJson: String = """
|
||||
{
|
||||
"mId": "tosspayments",
|
||||
@ -127,7 +114,6 @@ object SampleTossPaymentConst {
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@JvmField
|
||||
val cancelJson: String = """
|
||||
{
|
||||
"mId": "tosspayments",
|
||||
@ -206,7 +192,3 @@ object SampleTossPaymentConst {
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println((amount / 1.1).roundToLong())
|
||||
}
|
||||
@ -16,9 +16,8 @@ import org.springframework.test.web.client.response.MockRestResponseCreators.wit
|
||||
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.payment.SampleTossPaymentConst
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
|
||||
@RestClientTest(TossPaymentClient::class)
|
||||
class TossPaymentClientTest(
|
||||
@ -48,7 +47,7 @@ class TossPaymentClientTest(
|
||||
|
||||
// when
|
||||
val paymentRequest = SampleTossPaymentConst.paymentRequest
|
||||
val paymentResponse: PaymentApprove.Response = client.confirmPayment(paymentRequest)
|
||||
val paymentResponse: PaymentApproveResponse = client.confirm(paymentRequest)
|
||||
|
||||
assertSoftly(paymentResponse) {
|
||||
this.paymentKey shouldBe paymentRequest.paymentKey
|
||||
@ -70,7 +69,7 @@ class TossPaymentClientTest(
|
||||
|
||||
// then
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
client.confirmPayment(paymentRequest)
|
||||
client.confirm(paymentRequest)
|
||||
}
|
||||
|
||||
assertSoftly(exception) {
|
||||
@ -102,8 +101,8 @@ class TossPaymentClientTest(
|
||||
}
|
||||
|
||||
// when
|
||||
val cancelRequest: PaymentCancel.Request = SampleTossPaymentConst.cancelRequest
|
||||
val cancelResponse: PaymentCancel.Response = client.cancelPayment(cancelRequest)
|
||||
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
||||
val cancelResponse: PaymentCancelResponse = client.cancel(cancelRequest)
|
||||
|
||||
assertSoftly(cancelResponse) {
|
||||
this.cancelStatus shouldBe "DONE"
|
||||
@ -121,11 +120,11 @@ class TossPaymentClientTest(
|
||||
}
|
||||
|
||||
// when
|
||||
val cancelRequest: PaymentCancel.Request = SampleTossPaymentConst.cancelRequest
|
||||
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
||||
|
||||
// then
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
client.cancelPayment(cancelRequest)
|
||||
client.cancel(cancelRequest)
|
||||
}
|
||||
|
||||
assertSoftly(exception) {
|
||||
@ -94,7 +94,7 @@ class PaymentRepositoryTest(
|
||||
return ReservationFixture.create().also {
|
||||
entityManager.persist(it.member)
|
||||
entityManager.persist(it.theme)
|
||||
entityManager.persist(it.reservationTime)
|
||||
entityManager.persist(it.time)
|
||||
entityManager.persist(it)
|
||||
|
||||
entityManager.flush()
|
||||
@ -13,19 +13,19 @@ import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.theme.business.ThemeService
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ReservationServiteTest : FunSpec({
|
||||
class ReservationServiceTest : FunSpec({
|
||||
|
||||
val reservationRepository: ReservationRepository = mockk()
|
||||
val reservationTimeService: ReservationTimeService = mockk()
|
||||
val timeService: TimeService = mockk()
|
||||
val memberService: MemberService = mockk()
|
||||
val themeService: ThemeService = mockk()
|
||||
val reservationService = ReservationService(
|
||||
reservationRepository,
|
||||
reservationTimeService,
|
||||
timeService,
|
||||
memberService,
|
||||
themeService
|
||||
)
|
||||
@ -51,7 +51,7 @@ class ReservationServiteTest : FunSpec({
|
||||
} returns false
|
||||
|
||||
every {
|
||||
themeService.findThemeById(any())
|
||||
themeService.findById(any())
|
||||
} returns mockk()
|
||||
|
||||
every {
|
||||
@ -65,8 +65,8 @@ class ReservationServiteTest : FunSpec({
|
||||
)
|
||||
|
||||
every {
|
||||
reservationTimeService.findTimeById(any())
|
||||
} returns ReservationTimeFixture.create()
|
||||
timeService.findById(any())
|
||||
} returns TimeFixture.create()
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationService.addReservation(reservationRequest, 1L)
|
||||
@ -81,8 +81,8 @@ class ReservationServiteTest : FunSpec({
|
||||
)
|
||||
|
||||
every {
|
||||
reservationTimeService.findTimeById(reservationRequest.timeId)
|
||||
} returns ReservationTimeFixture.create(
|
||||
timeService.findById(reservationRequest.timeId)
|
||||
} returns TimeFixture.create(
|
||||
startAt = LocalTime.now().minusMinutes(1)
|
||||
)
|
||||
|
||||
@ -113,7 +113,7 @@ class ReservationServiteTest : FunSpec({
|
||||
themeId = reservationRequest.themeId,
|
||||
timeId = reservationRequest.timeId
|
||||
)
|
||||
reservationService.addWaiting(waitingRequest, 1L)
|
||||
reservationService.createWaiting(waitingRequest, 1L)
|
||||
}.also {
|
||||
it.errorType shouldBe ErrorType.HAS_RESERVATION_OR_WAITING
|
||||
}
|
||||
@ -126,7 +126,7 @@ class ReservationServiteTest : FunSpec({
|
||||
val endAt = startFrom.minusDays(1)
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationService.findFilteredReservations(
|
||||
reservationService.searchReservations(
|
||||
null,
|
||||
null,
|
||||
startFrom,
|
||||
@ -147,7 +147,7 @@ class ReservationServiteTest : FunSpec({
|
||||
} returns member
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationService.approveWaiting(1L, member.id!!)
|
||||
reservationService.confirmWaiting(1L, member.id!!)
|
||||
}.also {
|
||||
it.errorType shouldBe ErrorType.PERMISSION_DOES_NOT_EXIST
|
||||
}
|
||||
@ -166,7 +166,7 @@ class ReservationServiteTest : FunSpec({
|
||||
} returns true
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationService.approveWaiting(reservationId, member.id!!)
|
||||
reservationService.confirmWaiting(reservationId, member.id!!)
|
||||
}.also {
|
||||
it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED
|
||||
}
|
||||
@ -9,12 +9,12 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import roomescape.payment.business.PaymentService
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.toReservationPaymentResponse
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.toCreateResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.web.ReservationRequest
|
||||
import roomescape.reservation.web.ReservationResponse
|
||||
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||
import roomescape.util.*
|
||||
|
||||
class ReservationWithPaymentServiceTest : FunSpec({
|
||||
@ -26,37 +26,37 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
||||
paymentService = paymentService
|
||||
)
|
||||
|
||||
val reservationRequest: ReservationRequest = ReservationFixture.createRequest()
|
||||
val reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest = ReservationFixture.createRequest()
|
||||
val paymentApproveResponse = PaymentFixture.createApproveResponse()
|
||||
val memberId = 1L
|
||||
val reservationEntity: ReservationEntity = ReservationFixture.create(
|
||||
id = 1L,
|
||||
date = reservationRequest.date,
|
||||
reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId),
|
||||
theme = ThemeFixture.create(id = reservationRequest.themeId),
|
||||
date = reservationCreateWithPaymentRequest.date,
|
||||
time = TimeFixture.create(id = reservationCreateWithPaymentRequest.timeId),
|
||||
theme = ThemeFixture.create(id = reservationCreateWithPaymentRequest.themeId),
|
||||
member = MemberFixture.create(id = memberId),
|
||||
status = ReservationStatus.CONFIRMED
|
||||
)
|
||||
val paymentEntity: PaymentEntity = PaymentFixture.create(
|
||||
id = 1L,
|
||||
orderId = reservationRequest.orderId,
|
||||
paymentKey = reservationRequest.paymentKey,
|
||||
totalAmount = reservationRequest.amount,
|
||||
orderId = reservationCreateWithPaymentRequest.orderId,
|
||||
paymentKey = reservationCreateWithPaymentRequest.paymentKey,
|
||||
totalAmount = reservationCreateWithPaymentRequest.amount,
|
||||
reservation = reservationEntity,
|
||||
)
|
||||
|
||||
context("addReservationWithPayment") {
|
||||
test("예약 및 결제 정보를 저장한다.") {
|
||||
every {
|
||||
reservationService.addReservation(reservationRequest, memberId)
|
||||
reservationService.addReservation(reservationCreateWithPaymentRequest, memberId)
|
||||
} returns reservationEntity
|
||||
|
||||
every {
|
||||
paymentService.savePayment(paymentApproveResponse, reservationEntity)
|
||||
} returns paymentEntity.toReservationPaymentResponse()
|
||||
paymentService.createPayment(paymentApproveResponse, reservationEntity)
|
||||
} returns paymentEntity.toCreateResponse()
|
||||
|
||||
val result: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
|
||||
request = reservationRequest,
|
||||
val result: ReservationRetrieveResponse = reservationWithPaymentService.createReservationAndPayment(
|
||||
request = reservationCreateWithPaymentRequest,
|
||||
paymentInfo = paymentApproveResponse,
|
||||
memberId = memberId
|
||||
)
|
||||
@ -65,7 +65,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
||||
this.id shouldBe reservationEntity.id
|
||||
this.date shouldBe reservationEntity.date
|
||||
this.member.id shouldBe reservationEntity.member.id
|
||||
this.time.id shouldBe reservationEntity.reservationTime.id
|
||||
this.time.id shouldBe reservationEntity.time.id
|
||||
this.theme.id shouldBe reservationEntity.theme.id
|
||||
this.status shouldBe ReservationStatus.CONFIRMED
|
||||
}
|
||||
@ -74,21 +74,21 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
||||
|
||||
context("removeReservationWithPayment") {
|
||||
test("예약 및 결제 정보를 삭제하고, 결제 취소 정보를 저장한다.") {
|
||||
val paymentCancelRequest: PaymentCancel.Request = PaymentFixture.createCancelRequest().copy(
|
||||
val paymentCancelRequest: PaymentCancelRequest = PaymentFixture.createCancelRequest().copy(
|
||||
paymentKey = paymentEntity.paymentKey,
|
||||
amount = paymentEntity.totalAmount,
|
||||
cancelReason = "고객 요청"
|
||||
)
|
||||
|
||||
every {
|
||||
paymentService.cancelPaymentByAdmin(reservationEntity.id!!)
|
||||
paymentService.createCanceledPaymentByReservationId(reservationEntity.id!!)
|
||||
} returns paymentCancelRequest
|
||||
|
||||
every {
|
||||
reservationService.removeReservationById(reservationEntity.id!!, reservationEntity.member.id!!)
|
||||
reservationService.deleteReservation(reservationEntity.id!!, reservationEntity.member.id!!)
|
||||
} just Runs
|
||||
|
||||
val result: PaymentCancel.Request = reservationWithPaymentService.removeReservationWithPayment(
|
||||
val result: PaymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(
|
||||
reservationId = reservationEntity.id!!,
|
||||
memberId = reservationEntity.member.id!!
|
||||
)
|
||||
@ -10,17 +10,17 @@ import org.springframework.http.HttpStatus
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.common.exception.RoomescapeException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
||||
import roomescape.reservation.web.ReservationTimeRequest
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||
import roomescape.reservation.web.TimeCreateRequest
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalTime
|
||||
|
||||
class ReservationTimeServiceTest : FunSpec({
|
||||
val reservationTimeRepository: ReservationTimeRepository = mockk()
|
||||
class TimeServiceTest : FunSpec({
|
||||
val timeRepository: TimeRepository = mockk()
|
||||
val reservationRepository: ReservationRepository = mockk()
|
||||
|
||||
val reservationTimeService = ReservationTimeService(
|
||||
reservationTimeRepository = reservationTimeRepository,
|
||||
val timeService = TimeService(
|
||||
timeRepository = timeRepository,
|
||||
reservationRepository = reservationRepository
|
||||
)
|
||||
|
||||
@ -28,13 +28,12 @@ class ReservationTimeServiceTest : FunSpec({
|
||||
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
||||
val id = 1L
|
||||
|
||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
||||
every { reservationTimeRepository.findByIdOrNull(id) } returns null
|
||||
every { timeRepository.findByIdOrNull(id) } returns null
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationTimeService.findTimeById(id)
|
||||
timeService.findById(id)
|
||||
}.apply {
|
||||
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND
|
||||
errorType shouldBe ErrorType.TIME_NOT_FOUND
|
||||
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
||||
}
|
||||
}
|
||||
@ -42,13 +41,12 @@ class ReservationTimeServiceTest : FunSpec({
|
||||
|
||||
context("addTime") {
|
||||
test("중복된 시간이 있으면 409 에러를 던진다.") {
|
||||
val request = ReservationTimeRequest(startAt = LocalTime.of(10, 0))
|
||||
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
|
||||
|
||||
// Mocking the behavior of reservationTimeRepository.findByStartAt
|
||||
every { reservationTimeRepository.existsByStartAt(request.startAt) } returns true
|
||||
every { timeRepository.existsByStartAt(request.startAt) } returns true
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationTimeService.addTime(request)
|
||||
timeService.createTime(request)
|
||||
}.apply {
|
||||
errorType shouldBe ErrorType.TIME_DUPLICATED
|
||||
httpStatus shouldBe HttpStatus.CONFLICT
|
||||
@ -60,29 +58,26 @@ class ReservationTimeServiceTest : FunSpec({
|
||||
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
||||
val id = 1L
|
||||
|
||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
||||
every { reservationTimeRepository.findByIdOrNull(id) } returns null
|
||||
every { timeRepository.findByIdOrNull(id) } returns null
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationTimeService.removeTimeById(id)
|
||||
timeService.deleteTime(id)
|
||||
}.apply {
|
||||
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND
|
||||
errorType shouldBe ErrorType.TIME_NOT_FOUND
|
||||
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 있는 시간이면 409 에러를 던진다.") {
|
||||
val id = 1L
|
||||
val reservationTime = ReservationTimeFixture.create()
|
||||
val time = TimeFixture.create()
|
||||
|
||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
||||
every { reservationTimeRepository.findByIdOrNull(id) } returns reservationTime
|
||||
every { timeRepository.findByIdOrNull(id) } returns time
|
||||
|
||||
// Mocking the behavior of reservationRepository.findByReservationTime
|
||||
every { reservationRepository.findByReservationTime(reservationTime) } returns listOf(mockk())
|
||||
every { reservationRepository.findByTime(time) } returns listOf(mockk())
|
||||
|
||||
shouldThrow<RoomescapeException> {
|
||||
reservationTimeService.removeTimeById(id)
|
||||
timeService.deleteTime(id)
|
||||
}.apply {
|
||||
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT
|
||||
httpStatus shouldBe HttpStatus.CONFLICT
|
||||
@ -8,12 +8,12 @@ import jakarta.persistence.EntityManager
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.reservation.web.MyReservationResponse
|
||||
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.util.PaymentFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
|
||||
@DataJpaTest
|
||||
class ReservationRepositoryTest(
|
||||
@ -21,13 +21,13 @@ class ReservationRepositoryTest(
|
||||
val reservationRepository: ReservationRepository,
|
||||
) : FunSpec() {
|
||||
init {
|
||||
context("findByReservationTime") {
|
||||
val time = ReservationTimeFixture.create()
|
||||
context("findByTime") {
|
||||
val time = TimeFixture.create()
|
||||
|
||||
beforeTest {
|
||||
listOf(
|
||||
ReservationFixture.create(reservationTime = time),
|
||||
ReservationFixture.create(reservationTime = ReservationTimeFixture.create(
|
||||
ReservationFixture.create(time = time),
|
||||
ReservationFixture.create(time = TimeFixture.create(
|
||||
startAt = time.startAt.plusSeconds(1)
|
||||
))
|
||||
).forEach {
|
||||
@ -39,9 +39,9 @@ class ReservationRepositoryTest(
|
||||
}
|
||||
|
||||
test("입력된 시간과 일치하는 예약을 반환한다.") {
|
||||
assertSoftly(reservationRepository.findByReservationTime(time)) {
|
||||
assertSoftly(reservationRepository.findByTime(time)) {
|
||||
it shouldHaveSize 1
|
||||
assertSoftly(it.first().reservationTime.startAt) { result ->
|
||||
assertSoftly(it.first().time.startAt) { result ->
|
||||
result.hour shouldBe time.startAt.hour
|
||||
result.minute shouldBe time.startAt.minute
|
||||
}
|
||||
@ -68,7 +68,7 @@ class ReservationRepositoryTest(
|
||||
ReservationFixture.create(date = date.plusDays(1), theme = theme1),
|
||||
ReservationFixture.create(date = date, theme = theme2),
|
||||
).forEach {
|
||||
entityManager.persist(it.reservationTime)
|
||||
entityManager.persist(it.time)
|
||||
entityManager.persist(it.member)
|
||||
entityManager.persist(it)
|
||||
}
|
||||
@ -168,7 +168,7 @@ class ReservationRepositoryTest(
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
val result: List<MyReservationResponse> = reservationRepository.findMyReservations(reservation.member.id!!)
|
||||
val result: List<MyReservationRetrieveResponse> = reservationRepository.findAllById(reservation.member.id!!)
|
||||
|
||||
result shouldHaveSize 1
|
||||
assertSoftly(result.first()) {
|
||||
@ -179,7 +179,7 @@ class ReservationRepositoryTest(
|
||||
}
|
||||
|
||||
test("결제 정보가 없다면 paymentKey와 amount는 null로 반환한다.") {
|
||||
val result: List<MyReservationResponse> = reservationRepository.findMyReservations(reservation.member.id!!)
|
||||
val result: List<MyReservationRetrieveResponse> = reservationRepository.findAllById(reservation.member.id!!)
|
||||
|
||||
result shouldHaveSize 1
|
||||
assertSoftly(result.first()) {
|
||||
@ -192,7 +192,7 @@ class ReservationRepositoryTest(
|
||||
}
|
||||
|
||||
fun persistReservation(reservation: ReservationEntity) {
|
||||
entityManager.persist(reservation.reservationTime)
|
||||
entityManager.persist(reservation.time)
|
||||
entityManager.persist(reservation.theme)
|
||||
entityManager.persist(reservation.member)
|
||||
entityManager.persist(reservation)
|
||||
@ -10,8 +10,8 @@ import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalDate
|
||||
|
||||
@DataJpaTest
|
||||
@ -25,7 +25,7 @@ class ReservationSearchSpecificationTest(
|
||||
lateinit var confirmedNotPaidYesterday: ReservationEntity
|
||||
lateinit var waitingTomorrow: ReservationEntity
|
||||
lateinit var member: MemberEntity
|
||||
lateinit var reservationTime: ReservationTimeEntity
|
||||
lateinit var time: TimeEntity
|
||||
lateinit var theme: ThemeEntity
|
||||
|
||||
"동일한 테마의 예약을 조회한다" {
|
||||
@ -56,7 +56,7 @@ class ReservationSearchSpecificationTest(
|
||||
|
||||
"동일한 예약 시간의 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.sameTimeId(reservationTime.id)
|
||||
.sameTimeId(time.id)
|
||||
.build()
|
||||
|
||||
val results: List<ReservationEntity> = reservationRepository.findAll(spec)
|
||||
@ -136,7 +136,7 @@ class ReservationSearchSpecificationTest(
|
||||
member = MemberFixture.create().also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
reservationTime = ReservationTimeFixture.create().also {
|
||||
time = TimeFixture.create().also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
theme = ThemeFixture.create().also {
|
||||
@ -144,7 +144,7 @@ class ReservationSearchSpecificationTest(
|
||||
}
|
||||
|
||||
confirmedNow = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
time = time,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now(),
|
||||
@ -154,7 +154,7 @@ class ReservationSearchSpecificationTest(
|
||||
}
|
||||
|
||||
confirmedNotPaidYesterday = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
time = time,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now().minusDays(1),
|
||||
@ -164,7 +164,7 @@ class ReservationSearchSpecificationTest(
|
||||
}
|
||||
|
||||
waitingTomorrow = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
time = time,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now().plusDays(1),
|
||||
@ -4,30 +4,30 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import jakarta.persistence.EntityManager
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalTime
|
||||
|
||||
@DataJpaTest
|
||||
class ReservationTimeRepositoryTest(
|
||||
class TimeRepositoryTest(
|
||||
val entityManager: EntityManager,
|
||||
val reservationTimeRepository: ReservationTimeRepository,
|
||||
val timeRepository: TimeRepository,
|
||||
) : FunSpec({
|
||||
|
||||
context("existsByStartAt") {
|
||||
val startAt = LocalTime.of(10, 0)
|
||||
|
||||
beforeTest {
|
||||
entityManager.persist(ReservationTimeFixture.create(startAt = startAt))
|
||||
entityManager.persist(TimeFixture.create(startAt = startAt))
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
test("동일한 시간이 있으면 true 반환") {
|
||||
reservationTimeRepository.existsByStartAt(startAt) shouldBe true
|
||||
timeRepository.existsByStartAt(startAt) shouldBe true
|
||||
}
|
||||
|
||||
test("동일한 시간이 없으면 false 반환") {
|
||||
reservationTimeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false
|
||||
timeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -28,7 +28,7 @@ import roomescape.payment.infrastructure.client.TossPaymentClient
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.util.*
|
||||
import java.time.LocalDate
|
||||
@ -70,7 +70,7 @@ class ReservationControllerTest(
|
||||
)
|
||||
|
||||
every {
|
||||
paymentClient.confirmPayment(any())
|
||||
paymentClient.confirm(any())
|
||||
} returns paymentApproveResponse
|
||||
|
||||
Given {
|
||||
@ -80,7 +80,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
post("/reservations")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(201)
|
||||
body("data.date", equalTo(reservationRequest.date.toString()))
|
||||
body("data.status", equalTo(ReservationStatus.CONFIRMED.name))
|
||||
@ -95,7 +94,7 @@ class ReservationControllerTest(
|
||||
)
|
||||
|
||||
every {
|
||||
paymentClient.confirmPayment(any())
|
||||
paymentClient.confirm(any())
|
||||
} throws paymentException
|
||||
|
||||
Given {
|
||||
@ -105,7 +104,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
post("/reservations")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(paymentException.httpStatus.value())
|
||||
body("errorType", equalTo(paymentException.errorType.name))
|
||||
}
|
||||
@ -120,7 +118,7 @@ class ReservationControllerTest(
|
||||
)
|
||||
|
||||
every {
|
||||
paymentClient.confirmPayment(any())
|
||||
paymentClient.confirm(any())
|
||||
} returns paymentApproveResponse
|
||||
|
||||
// 예약 저장 과정에서 테마가 없는 예외
|
||||
@ -128,7 +126,7 @@ class ReservationControllerTest(
|
||||
val expectedException = RoomescapeException(ErrorType.THEME_NOT_FOUND, HttpStatus.BAD_REQUEST)
|
||||
|
||||
every {
|
||||
paymentClient.cancelPayment(any())
|
||||
paymentClient.cancel(any())
|
||||
} returns PaymentFixture.createCancelResponse()
|
||||
|
||||
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
||||
@ -143,7 +141,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
post("/reservations")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(expectedException.httpStatus.value())
|
||||
body("errorType", equalTo(expectedException.errorType.name))
|
||||
}
|
||||
@ -171,7 +168,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||
}
|
||||
@ -194,7 +190,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations-mine")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(expectedReservations))
|
||||
}
|
||||
@ -216,7 +211,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
||||
}
|
||||
}
|
||||
@ -230,7 +224,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||
}
|
||||
@ -250,7 +243,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("errorType", equalTo(ErrorType.INVALID_DATE_RANGE.name))
|
||||
}
|
||||
@ -267,7 +259,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(reservations[member]?.size ?: 0))
|
||||
}
|
||||
@ -285,7 +276,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size))
|
||||
}
|
||||
@ -304,7 +294,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/search")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||
}
|
||||
@ -326,7 +315,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
delete("/reservations/${reservation.id}")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(302)
|
||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||
}
|
||||
@ -352,7 +340,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
delete("/reservations/$reservationId")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(HttpStatus.NO_CONTENT.value())
|
||||
}
|
||||
|
||||
@ -380,7 +367,7 @@ class ReservationControllerTest(
|
||||
}
|
||||
|
||||
every {
|
||||
paymentClient.cancelPayment(any())
|
||||
paymentClient.cancel(any())
|
||||
} returns PaymentFixture.createCancelResponse()
|
||||
|
||||
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
||||
@ -393,7 +380,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
delete("/reservations/${reservation.id}")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(HttpStatus.NO_CONTENT.value())
|
||||
}
|
||||
|
||||
@ -409,8 +395,8 @@ class ReservationControllerTest(
|
||||
context("POST /reservations/admin") {
|
||||
test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") {
|
||||
val member = login(MemberFixture.create(role = Role.ADMIN))
|
||||
val adminRequest: AdminReservationRequest = createRequest().let {
|
||||
AdminReservationRequest(
|
||||
val adminRequest: AdminReservationCreateRequest = createRequest().let {
|
||||
AdminReservationCreateRequest(
|
||||
date = it.date,
|
||||
themeId = it.themeId,
|
||||
timeId = it.timeId,
|
||||
@ -425,7 +411,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
post("/reservations/admin")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(201)
|
||||
body("data.status", equalTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED.name))
|
||||
}
|
||||
@ -447,7 +432,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/waiting")
|
||||
}.Then {
|
||||
log().all()
|
||||
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
||||
}
|
||||
}
|
||||
@ -463,7 +447,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
get("/reservations/waiting")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.reservations.size()", equalTo(expected))
|
||||
}
|
||||
@ -473,8 +456,8 @@ class ReservationControllerTest(
|
||||
context("POST /reservations/waiting") {
|
||||
test("회원이 대기 예약을 추가한다.") {
|
||||
val member = login(MemberFixture.create(role = Role.MEMBER))
|
||||
val waitingRequest: WaitingRequest = createRequest().let {
|
||||
WaitingRequest(
|
||||
val waitingCreateRequest: WaitingCreateRequest = createRequest().let {
|
||||
WaitingCreateRequest(
|
||||
date = it.date,
|
||||
themeId = it.themeId,
|
||||
timeId = it.timeId
|
||||
@ -484,11 +467,10 @@ class ReservationControllerTest(
|
||||
Given {
|
||||
port(port)
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
body(waitingRequest)
|
||||
body(waitingCreateRequest)
|
||||
}.When {
|
||||
post("/reservations/waiting")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(201)
|
||||
body("data.member.id", equalTo(member.id!!.toInt()))
|
||||
body("data.status", equalTo(ReservationStatus.WAITING.name))
|
||||
@ -503,7 +485,7 @@ class ReservationControllerTest(
|
||||
val reservation = ReservationFixture.create(
|
||||
date = reservationRequest.date,
|
||||
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId),
|
||||
reservationTime = entityManager.find(ReservationTimeEntity::class.java, reservationRequest.timeId),
|
||||
time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId),
|
||||
member = member,
|
||||
status = ReservationStatus.WAITING
|
||||
)
|
||||
@ -513,7 +495,7 @@ class ReservationControllerTest(
|
||||
}
|
||||
|
||||
// 이미 예약된 시간, 테마로 대기 예약 요청
|
||||
val waitingRequest = WaitingRequest(
|
||||
val waitingCreateRequest = WaitingCreateRequest(
|
||||
date = reservationRequest.date,
|
||||
themeId = reservationRequest.themeId,
|
||||
timeId = reservationRequest.timeId
|
||||
@ -522,11 +504,10 @@ class ReservationControllerTest(
|
||||
Given {
|
||||
port(port)
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
body(waitingRequest)
|
||||
body(waitingCreateRequest)
|
||||
}.When {
|
||||
post("/reservations/waiting")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("errorType", equalTo(ErrorType.HAS_RESERVATION_OR_WAITING.name))
|
||||
}
|
||||
@ -551,7 +532,6 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
delete("/reservations/waiting/${waiting.id}")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(HttpStatus.NO_CONTENT.value())
|
||||
}
|
||||
|
||||
@ -575,23 +555,21 @@ class ReservationControllerTest(
|
||||
}.When {
|
||||
delete("/reservations/waiting/{id}", reservation.id)
|
||||
}.Then {
|
||||
log().all()
|
||||
body("errorType", equalTo(ErrorType.RESERVATION_NOT_FOUND.name))
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("POST /reservations/waiting/{id}/approve") {
|
||||
context("POST /reservations/waiting/{id}/confirm") {
|
||||
test("관리자만 승인할 수 있다.") {
|
||||
login(MemberFixture.create(role = Role.MEMBER))
|
||||
|
||||
Given {
|
||||
port(port)
|
||||
}.When {
|
||||
post("/reservations/waiting/1/approve")
|
||||
post("/reservations/waiting/1/confirm")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(302)
|
||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||
}
|
||||
@ -607,9 +585,8 @@ class ReservationControllerTest(
|
||||
Given {
|
||||
port(port)
|
||||
}.When {
|
||||
post("/reservations/waiting/${reservation.id!!}/approve")
|
||||
post("/reservations/waiting/${reservation.id!!}/confirm")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
}
|
||||
|
||||
@ -624,16 +601,15 @@ class ReservationControllerTest(
|
||||
}
|
||||
}
|
||||
|
||||
context("POST /reservations/waiting/{id}/deny") {
|
||||
context("POST /reservations/waiting/{id}/reject") {
|
||||
test("관리자만 거절할 수 있다.") {
|
||||
login(MemberFixture.create(role = Role.MEMBER))
|
||||
|
||||
Given {
|
||||
port(port)
|
||||
}.When {
|
||||
post("/reservations/waiting/1/deny")
|
||||
post("/reservations/waiting/1/reject")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(302)
|
||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||
}
|
||||
@ -649,9 +625,8 @@ class ReservationControllerTest(
|
||||
Given {
|
||||
port(port)
|
||||
}.When {
|
||||
post("/reservations/waiting/${reservation.id!!}/deny")
|
||||
post("/reservations/waiting/${reservation.id!!}/reject")
|
||||
}.Then {
|
||||
log().all()
|
||||
statusCode(204)
|
||||
}
|
||||
|
||||
@ -675,7 +650,7 @@ class ReservationControllerTest(
|
||||
return ReservationFixture.create(
|
||||
date = date,
|
||||
theme = ThemeFixture.create(name = themeName),
|
||||
reservationTime = ReservationTimeFixture.create(startAt = time),
|
||||
time = TimeFixture.create(startAt = time),
|
||||
member = member,
|
||||
status = status
|
||||
).also { it ->
|
||||
@ -683,7 +658,7 @@ class ReservationControllerTest(
|
||||
if (member.id == null) {
|
||||
entityManager.persist(member)
|
||||
}
|
||||
entityManager.persist(it.reservationTime)
|
||||
entityManager.persist(it.time)
|
||||
entityManager.persist(it.theme)
|
||||
entityManager.persist(it)
|
||||
entityManager.flush()
|
||||
@ -710,14 +685,14 @@ class ReservationControllerTest(
|
||||
transactionTemplate.executeWithoutResult {
|
||||
repeat(10) { index ->
|
||||
val theme = ThemeFixture.create(name = "theme$index")
|
||||
val time = ReservationTimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
|
||||
val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
|
||||
entityManager.persist(theme)
|
||||
entityManager.persist(time)
|
||||
|
||||
val reservation = ReservationFixture.create(
|
||||
date = LocalDate.now().plusDays(index.toLong()),
|
||||
theme = theme,
|
||||
reservationTime = time,
|
||||
time = time,
|
||||
member = members[index % members.size],
|
||||
status = ReservationStatus.CONFIRMED
|
||||
)
|
||||
@ -733,15 +708,15 @@ class ReservationControllerTest(
|
||||
|
||||
fun createRequest(
|
||||
theme: ThemeEntity = ThemeFixture.create(),
|
||||
time: ReservationTimeEntity = ReservationTimeFixture.create(),
|
||||
): ReservationRequest {
|
||||
lateinit var reservationRequest: ReservationRequest
|
||||
time: TimeEntity = TimeFixture.create(),
|
||||
): ReservationCreateWithPaymentRequest {
|
||||
lateinit var reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest
|
||||
|
||||
transactionTemplate.executeWithoutResult {
|
||||
entityManager.persist(theme)
|
||||
entityManager.persist(time)
|
||||
|
||||
reservationRequest = ReservationFixture.createRequest(
|
||||
reservationCreateWithPaymentRequest = ReservationFixture.createRequest(
|
||||
themeId = theme.id!!,
|
||||
timeId = time.id!!,
|
||||
)
|
||||
@ -750,7 +725,7 @@ class ReservationControllerTest(
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
return reservationRequest
|
||||
return reservationCreateWithPaymentRequest
|
||||
}
|
||||
|
||||
fun login(member: MemberEntity): MemberEntity {
|
||||
@ -13,28 +13,28 @@ import org.springframework.http.MediaType
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import roomescape.common.config.JacksonConfig
|
||||
import roomescape.common.exception.ErrorType
|
||||
import roomescape.reservation.business.ReservationTimeService
|
||||
import roomescape.reservation.business.TimeService
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.RoomescapeApiTest
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
@WebMvcTest(ReservationTimeController::class)
|
||||
@WebMvcTest(TimeController::class)
|
||||
@Import(JacksonConfig::class)
|
||||
class ReservationTimeControllerTest(
|
||||
class TimeControllerTest(
|
||||
val mockMvc: MockMvc,
|
||||
) : RoomescapeApiTest() {
|
||||
|
||||
@SpykBean
|
||||
private lateinit var reservationTimeService: ReservationTimeService
|
||||
private lateinit var timeService: TimeService
|
||||
|
||||
@MockkBean
|
||||
private lateinit var reservationTimeRepository: ReservationTimeRepository
|
||||
private lateinit var timeRepository: TimeRepository
|
||||
|
||||
@MockkBean
|
||||
private lateinit var reservationRepository: ReservationRepository
|
||||
@ -50,16 +50,15 @@ class ReservationTimeControllerTest(
|
||||
|
||||
Then("정상 응답") {
|
||||
every {
|
||||
reservationTimeRepository.findAll()
|
||||
timeRepository.findAll()
|
||||
} returns listOf(
|
||||
ReservationTimeFixture.create(id = 1L),
|
||||
ReservationTimeFixture.create(id = 2L)
|
||||
TimeFixture.create(id = 1L),
|
||||
TimeFixture.create(id = 2L)
|
||||
)
|
||||
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
content {
|
||||
@ -78,7 +77,6 @@ class ReservationTimeControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header { string("Location", "/login") }
|
||||
@ -95,7 +93,7 @@ class ReservationTimeControllerTest(
|
||||
loginAsAdmin()
|
||||
}
|
||||
val time = LocalTime.of(10, 0)
|
||||
val request = ReservationTimeRequest(startAt = time)
|
||||
val request = TimeCreateRequest(startAt = time)
|
||||
|
||||
Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") {
|
||||
listOf(
|
||||
@ -106,7 +104,6 @@ class ReservationTimeControllerTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = it,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
}
|
||||
@ -115,14 +112,13 @@ class ReservationTimeControllerTest(
|
||||
|
||||
Then("정상 응답") {
|
||||
every {
|
||||
reservationTimeService.addTime(request)
|
||||
} returns ReservationTimeResponse(id = 1, startAt = time)
|
||||
timeService.createTime(request)
|
||||
} returns TimeCreateResponse(id = 1, startAt = time)
|
||||
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { isCreated() }
|
||||
content {
|
||||
@ -135,14 +131,13 @@ class ReservationTimeControllerTest(
|
||||
|
||||
Then("동일한 시간이 존재하면 409 응답") {
|
||||
every {
|
||||
reservationTimeRepository.existsByStartAt(time)
|
||||
timeRepository.existsByStartAt(time)
|
||||
} returns true
|
||||
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { isConflict() }
|
||||
content {
|
||||
@ -160,8 +155,7 @@ class ReservationTimeControllerTest(
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = ReservationTimeFixture.create(),
|
||||
log = true
|
||||
body = TimeFixture.create(),
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header { string("Location", "/login") }
|
||||
@ -180,13 +174,12 @@ class ReservationTimeControllerTest(
|
||||
|
||||
Then("정상 응답") {
|
||||
every {
|
||||
reservationTimeService.removeTimeById(1L)
|
||||
timeService.deleteTime(1L)
|
||||
} returns Unit
|
||||
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isNoContent() }
|
||||
}
|
||||
@ -195,18 +188,17 @@ class ReservationTimeControllerTest(
|
||||
Then("없는 시간을 조회하면 400 응답") {
|
||||
val id = 1L
|
||||
every {
|
||||
reservationTimeRepository.findByIdOrNull(id)
|
||||
timeRepository.findByIdOrNull(id)
|
||||
} returns null
|
||||
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = "/times/$id",
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
content {
|
||||
contentType(MediaType.APPLICATION_JSON)
|
||||
jsonPath("$.errorType") { value(ErrorType.RESERVATION_TIME_NOT_FOUND.name) }
|
||||
jsonPath("$.errorType") { value(ErrorType.TIME_NOT_FOUND.name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,17 +206,16 @@ class ReservationTimeControllerTest(
|
||||
Then("예약이 있는 시간을 삭제하면 409 응답") {
|
||||
val id = 1L
|
||||
every {
|
||||
reservationTimeRepository.findByIdOrNull(id)
|
||||
} returns ReservationTimeFixture.create(id = id)
|
||||
timeRepository.findByIdOrNull(id)
|
||||
} returns TimeFixture.create(id = id)
|
||||
|
||||
every {
|
||||
reservationRepository.findByReservationTime(any())
|
||||
reservationRepository.findByTime(any())
|
||||
} returns listOf(ReservationFixture.create())
|
||||
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = "/times/$id",
|
||||
log = true
|
||||
) {
|
||||
status { isConflict() }
|
||||
content {
|
||||
@ -242,7 +233,6 @@ class ReservationTimeControllerTest(
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header { string("Location", "/login") }
|
||||
@ -258,13 +248,13 @@ class ReservationTimeControllerTest(
|
||||
val themeId = 1L
|
||||
|
||||
When("저장된 예약 시간이 있으면") {
|
||||
val times: List<ReservationTimeEntity> = listOf(
|
||||
ReservationTimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
|
||||
ReservationTimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
|
||||
val times: List<TimeEntity> = listOf(
|
||||
TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
|
||||
TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
|
||||
)
|
||||
|
||||
every {
|
||||
reservationTimeRepository.findAll()
|
||||
timeRepository.findAll()
|
||||
} returns times
|
||||
|
||||
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
|
||||
@ -276,28 +266,27 @@ class ReservationTimeControllerTest(
|
||||
id = 1L,
|
||||
date = date,
|
||||
theme = ThemeFixture.create(id = themeId),
|
||||
reservationTime = times[0]
|
||||
time = times[0]
|
||||
)
|
||||
)
|
||||
|
||||
val response = runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = "/times/filter?date=$date&themeId=$themeId",
|
||||
log = true
|
||||
endpoint = "/times/search?date=$date&themeId=$themeId",
|
||||
) {
|
||||
status { isOk() }
|
||||
content {
|
||||
contentType(MediaType.APPLICATION_JSON)
|
||||
}
|
||||
}.andReturn().readValue(ReservationTimeInfosResponse::class.java)
|
||||
}.andReturn().readValue(TimeWithAvailabilityListResponse::class.java)
|
||||
|
||||
assertSoftly(response.times) {
|
||||
this shouldHaveSize times.size
|
||||
this[0].id shouldBe times[0].id
|
||||
this[0].alreadyBooked shouldBe true
|
||||
this[0].isAvailable shouldBe false
|
||||
|
||||
this[1].id shouldBe times[1].id
|
||||
this[1].alreadyBooked shouldBe false
|
||||
this[1].isAvailable shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ class ThemeServiceTest : FunSpec({
|
||||
} returns null
|
||||
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
themeService.findThemeById(themeId)
|
||||
themeService.findById(themeId)
|
||||
}
|
||||
|
||||
exception.errorType shouldBe ErrorType.THEME_NOT_FOUND
|
||||
@ -51,7 +51,7 @@ class ThemeServiceTest : FunSpec({
|
||||
themeRepository.findAll()
|
||||
} returns themes
|
||||
|
||||
assertSoftly(themeService.findAllThemes()) {
|
||||
assertSoftly(themeService.findThemes()) {
|
||||
this.themes.size shouldBe themes.size
|
||||
this.themes[0].name shouldBe "t1"
|
||||
this.themes[1].name shouldBe "t2"
|
||||
@ -68,7 +68,7 @@ class ThemeServiceTest : FunSpec({
|
||||
} returns true
|
||||
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
themeService.save(ThemeRequest(
|
||||
themeService.createTheme(ThemeRequest(
|
||||
name = name,
|
||||
description = "Description",
|
||||
thumbnail = "http://example.com/thumbnail.jpg"
|
||||
@ -91,7 +91,7 @@ class ThemeServiceTest : FunSpec({
|
||||
} returns true
|
||||
|
||||
val exception = shouldThrow<RoomescapeException> {
|
||||
themeService.deleteById(themeId)
|
||||
themeService.deleteTheme(themeId)
|
||||
}
|
||||
|
||||
assertSoftly(exception) {
|
||||
@ -28,7 +28,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
|
||||
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") {
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(1),
|
||||
5
|
||||
@ -41,7 +41,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
|
||||
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") {
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(8),
|
||||
LocalDate.now().minusDays(5),
|
||||
3
|
||||
@ -61,7 +61,7 @@ class ThemeRepositoryTest(
|
||||
date = LocalDate.now().minusDays(5),
|
||||
)
|
||||
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(6),
|
||||
LocalDate.now().minusDays(4),
|
||||
5
|
||||
@ -74,7 +74,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
|
||||
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") {
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(6),
|
||||
10
|
||||
@ -84,7 +84,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
|
||||
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") {
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(1),
|
||||
15
|
||||
@ -94,7 +94,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
|
||||
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") {
|
||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().plusDays(1),
|
||||
LocalDate.now().plusDays(10),
|
||||
5
|
||||
@ -3,12 +3,12 @@ package roomescape.theme.util
|
||||
import jakarta.persistence.EntityManager
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ReservationTimeFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
@ -23,7 +23,7 @@ object TestThemeCreateUtil {
|
||||
val member: MemberEntity = MemberFixture.create().also { entityManager.persist(it) }
|
||||
|
||||
for (i in 1..reservedCount) {
|
||||
val time: ReservationTimeEntity = ReservationTimeFixture.create(
|
||||
val time: TimeEntity = TimeFixture.create(
|
||||
startAt = LocalTime.now().plusMinutes(i.toLong())
|
||||
).also { entityManager.persist(it) }
|
||||
|
||||
@ -31,7 +31,7 @@ object TestThemeCreateUtil {
|
||||
date = date,
|
||||
theme = themeEntity,
|
||||
member = member,
|
||||
reservationTime = time,
|
||||
time = time,
|
||||
status = ReservationStatus.CONFIRMED
|
||||
).also { entityManager.persist(it) }
|
||||
}
|
||||
@ -9,22 +9,15 @@ import org.hamcrest.Matchers.equalTo
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.web.server.LocalServerPort
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import roomescape.theme.business.ThemeService
|
||||
import roomescape.theme.util.TestThemeCreateUtil
|
||||
import roomescape.util.CleanerMode
|
||||
import roomescape.util.DatabaseCleanerExtension
|
||||
import java.time.LocalDate
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* GET /themes/most-reserved-last-week API 테스트
|
||||
* 상세 테스트는 Repository 테스트에서 진행
|
||||
* 날짜 범위, 예약 수만 검증
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class MostReservedThemeAPITest(
|
||||
class MostReservedThemeApiTest(
|
||||
@LocalServerPort val port: Int,
|
||||
val themeService: ThemeService,
|
||||
val transactionTemplate: TransactionTemplate,
|
||||
val entityManager: EntityManager,
|
||||
) : FunSpec({
|
||||
@ -53,59 +46,55 @@ class MostReservedThemeAPITest(
|
||||
}
|
||||
}
|
||||
|
||||
context("가장 많이 예약된 테마를 조회할 때, ") {
|
||||
context("지난 주 가장 많이 예약된 테마 API") {
|
||||
val endpoint = "/themes/most-reserved-last-week"
|
||||
test("갯수를 입력하지 않으면 10개를 반환한다.") {
|
||||
|
||||
test("count 파라미터가 없으면 10개를 반환한다") {
|
||||
Given {
|
||||
port(port)
|
||||
} When {
|
||||
get(endpoint)
|
||||
} Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.themes.size()", equalTo(10))
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 갯수가 조회된 갯수보다 크면 조회된 갯수만큼 반환한다.") {
|
||||
test("조회된 테마가 count보다 적으면 조회된 만큼 반환한다") {
|
||||
val count = 15
|
||||
Given {
|
||||
port(port)
|
||||
} When {
|
||||
param("count", count)
|
||||
get("/themes/most-reserved-last-week")
|
||||
} When {
|
||||
get(endpoint)
|
||||
} Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.themes.size()", equalTo(10))
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 갯수가 조회된 갯수보다 작으면 입력된 갯수만큼 반환한다.") {
|
||||
test("조회된 테마가 count보다 많으면 count만큼 반환한다") {
|
||||
val count = 5
|
||||
Given {
|
||||
port(port)
|
||||
} When {
|
||||
param("count", count)
|
||||
get("/themes/most-reserved-last-week")
|
||||
} When {
|
||||
get(endpoint)
|
||||
} Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.themes.size()", equalTo(count))
|
||||
}
|
||||
}
|
||||
|
||||
test("7일 전 부터 1일 전 까지 예약된 테마를 대상으로 한다.") {
|
||||
// 현재 저장된 데이터는 지난 7일간 예약된 테마 10개와 8일 전 예약된 테마 1개
|
||||
// 8일 전 예약된 테마는 제외되어야 하므로, 10개가 조회되어야 한다.
|
||||
test("지난 7일 동안의 예약만 집계한다") {
|
||||
// 8일 전에 예약된 테마는 집계에서 제외되어야 한다.
|
||||
val count = 11
|
||||
Given {
|
||||
port(port)
|
||||
} When {
|
||||
param("count", count)
|
||||
get("/themes/most-reserved-last-week")
|
||||
} When {
|
||||
get(endpoint)
|
||||
} Then {
|
||||
log().all()
|
||||
statusCode(200)
|
||||
body("data.themes.size()", equalTo(10))
|
||||
}
|
||||
@ -36,7 +36,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -61,7 +60,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
val response: ThemesResponse = runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
content {
|
||||
@ -92,7 +90,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -109,7 +106,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
||||
@ -129,7 +125,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { isConflict() }
|
||||
jsonPath("$.errorType") { value("THEME_DUPLICATED") }
|
||||
@ -153,7 +148,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
}
|
||||
@ -201,7 +195,7 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
)
|
||||
|
||||
every {
|
||||
themeService.save(request)
|
||||
themeService.createTheme(request)
|
||||
} returns ThemeResponse(
|
||||
id = theme.id!!,
|
||||
name = theme.name,
|
||||
@ -214,7 +208,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
log = true
|
||||
) {
|
||||
status { isCreated() }
|
||||
header {
|
||||
@ -239,7 +232,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -255,7 +247,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
||||
@ -274,7 +265,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isConflict() }
|
||||
jsonPath("$.errorType") { value("THEME_IS_USED_CONFLICT") }
|
||||
@ -297,7 +287,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
log = true
|
||||
) {
|
||||
status { isNoContent() }
|
||||
}
|
||||
@ -4,20 +4,21 @@ import roomescape.auth.infrastructure.jwt.JwtHandler
|
||||
import roomescape.auth.web.LoginRequest
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||
import roomescape.payment.web.PaymentApprove
|
||||
import roomescape.payment.web.PaymentCancel
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
||||
import roomescape.reservation.web.ReservationRequest
|
||||
import roomescape.reservation.web.WaitingRequest
|
||||
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||
import roomescape.reservation.web.WaitingCreateRequest
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.random.Random
|
||||
|
||||
object MemberFixture {
|
||||
const val NOT_LOGGED_IN_USERID: Long = 0
|
||||
@ -53,11 +54,11 @@ object MemberFixture {
|
||||
)
|
||||
}
|
||||
|
||||
object ReservationTimeFixture {
|
||||
object TimeFixture {
|
||||
fun create(
|
||||
id: Long? = null,
|
||||
startAt: LocalTime = LocalTime.now().plusHours(1),
|
||||
): ReservationTimeEntity = ReservationTimeEntity(id, startAt)
|
||||
): TimeEntity = TimeEntity(id, startAt)
|
||||
}
|
||||
|
||||
object ThemeFixture {
|
||||
@ -74,10 +75,10 @@ object ReservationFixture {
|
||||
id: Long? = null,
|
||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||
theme: ThemeEntity = ThemeFixture.create(),
|
||||
reservationTime: ReservationTimeEntity = ReservationTimeFixture.create(),
|
||||
time: TimeEntity = TimeFixture.create(),
|
||||
member: MemberEntity = MemberFixture.create(),
|
||||
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||
): ReservationEntity = ReservationEntity(id, date, reservationTime, theme, member, status)
|
||||
): ReservationEntity = ReservationEntity(id, date, time, theme, member, status)
|
||||
|
||||
fun createRequest(
|
||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||
@ -87,7 +88,7 @@ object ReservationFixture {
|
||||
orderId: String = "orderId",
|
||||
amount: Long = 10000L,
|
||||
paymentType: String = "NORMAL",
|
||||
): ReservationRequest = ReservationRequest(
|
||||
): ReservationCreateWithPaymentRequest = ReservationCreateWithPaymentRequest(
|
||||
date = date,
|
||||
timeId = timeId,
|
||||
themeId = themeId,
|
||||
@ -101,7 +102,7 @@ object ReservationFixture {
|
||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||
themeId: Long = 1L,
|
||||
timeId: Long = 1L
|
||||
): WaitingRequest = WaitingRequest(
|
||||
): WaitingCreateRequest = WaitingCreateRequest(
|
||||
date = date,
|
||||
timeId = timeId,
|
||||
themeId = themeId
|
||||
@ -156,27 +157,27 @@ object PaymentFixture {
|
||||
|
||||
)
|
||||
|
||||
fun createApproveRequest(): PaymentApprove.Request = PaymentApprove.Request(
|
||||
fun createApproveRequest(): PaymentApproveRequest = PaymentApproveRequest(
|
||||
paymentKey = PAYMENT_KEY,
|
||||
orderId = ORDER_ID,
|
||||
amount = AMOUNT,
|
||||
paymentType = "CARD"
|
||||
)
|
||||
|
||||
fun createApproveResponse(): PaymentApprove.Response = PaymentApprove.Response(
|
||||
fun createApproveResponse(): PaymentApproveResponse = PaymentApproveResponse(
|
||||
paymentKey = PAYMENT_KEY,
|
||||
orderId = ORDER_ID,
|
||||
approvedAt = OffsetDateTime.now(),
|
||||
totalAmount = AMOUNT
|
||||
)
|
||||
|
||||
fun createCancelRequest(): PaymentCancel.Request = PaymentCancel.Request(
|
||||
fun createCancelRequest(): PaymentCancelRequest = PaymentCancelRequest(
|
||||
paymentKey = PAYMENT_KEY,
|
||||
amount = AMOUNT,
|
||||
cancelReason = "Test Cancel"
|
||||
)
|
||||
|
||||
fun createCancelResponse(): PaymentCancel.Response = PaymentCancel.Response(
|
||||
fun createCancelResponse(): PaymentCancelResponse = PaymentCancelResponse(
|
||||
cancelStatus = "SUCCESS",
|
||||
cancelReason = "Test Cancel",
|
||||
cancelAmount = AMOUNT,
|
||||
@ -24,7 +24,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -36,7 +35,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -48,7 +46,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -66,7 +63,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -80,7 +76,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
@ -101,7 +96,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -112,7 +106,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { isOk() }
|
||||
}
|
||||
@ -126,7 +119,6 @@ class PageControllerTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = it,
|
||||
log = true
|
||||
) {
|
||||
status { is3xxRedirection() }
|
||||
header {
|
||||
Loading…
x
Reference in New Issue
Block a user