From 9f8ee8cc02416fe9eed5afc0e9fb0eebcd29cd51 Mon Sep 17 00:00:00 2001 From: pricelees Date: Tue, 22 Jul 2025 09:05:31 +0000 Subject: [PATCH] =?UTF-8?q?[#18]=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=BC=EB=B6=80=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=20=ED=86=B5=EC=9D=BC=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #18 ## ✨ 작업 내용 - 기존 자바와의 호환성을 위해 사용하던 \@Jvm.. 어노테이션 및 팩토리 메서드 제거 - 기존에 get, find, save 등으로 산재되어 있던 메서드 컨벤션 통일 - 일부 API endpoint 수정 - 테이블 이름 단수 -> 복수 수정 추가적으로 개선이 필요한 점은 있지만, 이는 기능 개선 과정에서 수정할 예정 ## 🧪 테스트 각 작업 마다 전체 테스트 수행 및 정상 동작 확인 ## 📚 참고 자료 및 기타 Reviewed-on: https://gitea.pricelees.me/pricelees/roomescape-refactored/pulls/19 Co-authored-by: pricelees Co-committed-by: pricelees --- .../dto/response/RoomescapeApiResponse.kt | 17 --- .../dto/response/RoomescapeErrorResponse.kt | 11 -- .../java/roomescape/member/web/MemberDTO.kt | 31 ------ .../client/TossPaymentErrorResponse.kt | 7 -- .../java/roomescape/payment/web/PaymentDTO.kt | 66 ----------- .../business/ReservationTimeService.kt | 74 ------------- .../reservation/web/ReservationRequest.kt | 66 ----------- .../reservation/web/ReservationResponse.kt | 103 ------------------ .../web/ReservationTimeController.kt | 51 --------- .../reservation/web/ReservationTimeDTO.kt | 73 ------------- .../roomescape/RoomescapeApplication.kt | 0 .../roomescape/auth/docs/AuthAPI.kt | 0 .../auth/infrastructure/jwt/JwtHandler.kt | 0 .../roomescape/auth/service/AuthService.kt | 8 +- .../roomescape/auth/web/AuthController.kt | 2 +- .../roomescape/auth/web/AuthDTO.kt | 10 +- .../auth/web/support/AuthAnnotations.kt | 0 .../auth/web/support/AuthInterceptors.kt | 0 .../auth/web/support/CookieUtils.kt | 4 +- .../auth/web/support/MemberIdResolver.kt | 0 .../roomescape/common/config/JacksonConfig.kt | 0 .../roomescape/common/config/SwaggerConfig.kt | 0 .../roomescape/common/config/WebMvcConfig.kt | 0 .../common/dto/response/CommonApiResponse.kt | 0 .../roomescape/common/exception/ErrorType.kt | 22 +--- .../exception/ExceptionControllerAdvice.kt | 20 ---- .../common/exception/RoomescapeException.kt | 0 .../member/business/MemberService.kt | 10 +- .../roomescape/member/docs/MemberAPI.kt | 4 +- .../persistence/MemberEntity.kt | 2 +- .../persistence/MemberRepository.kt | 0 .../roomescape/member/web/MemberController.kt | 4 +- .../kotlin/roomescape/member/web/MemberDTO.kt | 21 ++++ .../payment/business/PaymentService.kt | 26 ++--- .../PaymentCancelResponseDeserializer.kt | 10 +- .../infrastructure/client/PaymentConfig.kt | 0 .../client/PaymentProperties.kt | 8 +- .../client/TossPaymentClient.kt | 20 ++-- .../infrastructure/client/TossPaymentDTO.kt | 24 ++++ .../persistence/CanceledPaymentEntity.kt | 2 +- .../persistence/CanceledPaymentRepository.kt | 0 .../persistence/PaymentEntity.kt | 2 +- .../persistence/PaymentRepository.kt | 0 .../roomescape/payment/web/PaymentDTO.kt | 40 +++++++ .../business/ReservationService.kt | 53 +++++---- .../business/ReservationWithPaymentService.kt | 34 +++--- .../reservation/business/TimeService.kt | 73 +++++++++++++ .../reservation/docs/ReservationAPI.kt | 38 +++---- .../roomescape/reservation/docs/TimeAPI.kt} | 24 ++-- .../persistence/ReservationEntity.kt | 11 +- .../persistence/ReservationRepository.kt | 14 +-- .../ReservationSearchSpecification.kt | 2 +- .../infrastructure/persistence/TimeEntity.kt} | 4 +- .../persistence/TimeRepository.kt} | 2 +- .../reservation/web/ReservationController.kt | 99 +++++++++-------- .../reservation/web/ReservationRequest.kt | 40 +++++++ .../reservation/web/ReservationResponse.kt | 60 ++++++++++ .../reservation/web/TimeController.kt | 51 +++++++++ .../roomescape/reservation/web/TimeDTO.kt | 55 ++++++++++ .../roomescape/theme/business/ThemeService.kt | 13 +-- .../roomescape/theme/docs/ThemeAPI.kt | 8 +- .../infrastructure/persistence/ThemeEntity.kt | 2 +- .../persistence/ThemeRepository.kt | 3 +- .../roomescape/theme/web/ThemeController.kt | 16 +-- .../roomescape/theme/web/ThemeDTO.kt | 13 +-- .../roomescape/view/PageController.kt | 0 src/main/resources/data.sql | 58 +++++----- .../resources/static/js/user-reservation.js | 12 +- src/main/resources/static/js/waiting.js | 4 +- .../auth/business/AuthServiceTest.kt | 1 - .../auth/infrastructure/jwt/JwtHandlerTest.kt | 0 .../roomescape/auth/web/AuthControllerTest.kt | 8 -- .../auth/web/support/CookieUtilsTest.kt | 6 +- .../common/config/JacksonConfigTest.kt | 0 .../member/controller/MemberControllerTest.kt | 9 +- .../payment/business/PaymentServiceTest.kt | 8 +- .../PaymentCancelResponseDeserializerTest.kt | 14 +-- .../client}/SampleTossPaymentConst.kt | 26 +---- .../client/TossPaymentClientTest.kt | 17 ++- .../CanceledPaymentRepositoryTest.kt | 0 .../persistence/PaymentRepositoryTest.kt | 2 +- .../business/ReservationServiceTest.kt} | 26 ++--- .../ReservationWithPaymentServiceTest.kt | 42 +++---- .../reservation/business/TimeServiceTest.kt} | 45 ++++---- .../persistence/ReservationRepositoryTest.kt | 24 ++-- .../ReservationSearchSpecificationTest.kt | 14 +-- .../persistence/TimeRepositoryTest.kt} | 12 +- .../web/ReservationControllerTest.kt | 83 +++++--------- .../reservation/web/TimeControllerTest.kt} | 73 ++++++------- .../theme/business/ThemeServiceTest.kt | 8 +- .../persistence/ThemeRepositoryTest.kt | 12 +- .../theme/util/TestThemeCreateUtil.kt | 8 +- .../theme/web/MostReservedThemeApiTest.kt} | 39 +++---- .../theme/web/ThemeControllerTest.kt | 13 +-- .../roomescape/util/DatabaseCleaner.kt | 0 .../roomescape/util/Fixtures.kt | 33 +++--- .../roomescape/util/KotestConfig.kt | 0 .../roomescape/util/RoomescapeApiTest.kt | 0 .../roomescape/view/PageControllerTest.kt | 8 -- 99 files changed, 829 insertions(+), 1129 deletions(-) delete mode 100644 src/main/java/roomescape/common/dto/response/RoomescapeApiResponse.kt delete mode 100644 src/main/java/roomescape/common/dto/response/RoomescapeErrorResponse.kt delete mode 100644 src/main/java/roomescape/member/web/MemberDTO.kt delete mode 100644 src/main/java/roomescape/payment/infrastructure/client/TossPaymentErrorResponse.kt delete mode 100644 src/main/java/roomescape/payment/web/PaymentDTO.kt delete mode 100644 src/main/java/roomescape/reservation/business/ReservationTimeService.kt delete mode 100644 src/main/java/roomescape/reservation/web/ReservationRequest.kt delete mode 100644 src/main/java/roomescape/reservation/web/ReservationResponse.kt delete mode 100644 src/main/java/roomescape/reservation/web/ReservationTimeController.kt delete mode 100644 src/main/java/roomescape/reservation/web/ReservationTimeDTO.kt rename src/main/{java => kotlin}/roomescape/RoomescapeApplication.kt (100%) rename src/main/{java => kotlin}/roomescape/auth/docs/AuthAPI.kt (100%) rename src/main/{java => kotlin}/roomescape/auth/infrastructure/jwt/JwtHandler.kt (100%) rename src/main/{java => kotlin}/roomescape/auth/service/AuthService.kt (78%) rename src/main/{java => kotlin}/roomescape/auth/web/AuthController.kt (96%) rename src/main/{java => kotlin}/roomescape/auth/web/AuthDTO.kt (55%) rename src/main/{java => kotlin}/roomescape/auth/web/support/AuthAnnotations.kt (100%) rename src/main/{java => kotlin}/roomescape/auth/web/support/AuthInterceptors.kt (100%) rename src/main/{java => kotlin}/roomescape/auth/web/support/CookieUtils.kt (88%) rename src/main/{java => kotlin}/roomescape/auth/web/support/MemberIdResolver.kt (100%) rename src/main/{java => kotlin}/roomescape/common/config/JacksonConfig.kt (100%) rename src/main/{java => kotlin}/roomescape/common/config/SwaggerConfig.kt (100%) rename src/main/{java => kotlin}/roomescape/common/config/WebMvcConfig.kt (100%) rename src/main/{java => kotlin}/roomescape/common/dto/response/CommonApiResponse.kt (100%) rename src/main/{java => kotlin}/roomescape/common/exception/ErrorType.kt (78%) rename src/main/{java => kotlin}/roomescape/common/exception/ExceptionControllerAdvice.kt (71%) rename src/main/{java => kotlin}/roomescape/common/exception/RoomescapeException.kt (100%) rename src/main/{java => kotlin}/roomescape/member/business/MemberService.kt (81%) rename src/main/{java => kotlin}/roomescape/member/docs/MemberAPI.kt (85%) rename src/main/{java => kotlin}/roomescape/member/infrastructure/persistence/MemberEntity.kt (94%) rename src/main/{java => kotlin}/roomescape/member/infrastructure/persistence/MemberRepository.kt (100%) rename src/main/{java => kotlin}/roomescape/member/web/MemberController.kt (75%) create mode 100644 src/main/kotlin/roomescape/member/web/MemberDTO.kt rename src/main/{java => kotlin}/roomescape/payment/business/PaymentService.kt (84%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt (81%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/client/PaymentConfig.kt (100%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/client/PaymentProperties.kt (54%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/client/TossPaymentClient.kt (87%) create mode 100644 src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentDTO.kt rename src/main/{java => kotlin}/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt (92%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/persistence/CanceledPaymentRepository.kt (100%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/persistence/PaymentEntity.kt (96%) rename src/main/{java => kotlin}/roomescape/payment/infrastructure/persistence/PaymentRepository.kt (100%) create mode 100644 src/main/kotlin/roomescape/payment/web/PaymentDTO.kt rename src/main/{java => kotlin}/roomescape/reservation/business/ReservationService.kt (80%) rename src/main/{java => kotlin}/roomescape/reservation/business/ReservationWithPaymentService.kt (54%) create mode 100644 src/main/kotlin/roomescape/reservation/business/TimeService.kt rename src/main/{java => kotlin}/roomescape/reservation/docs/ReservationAPI.kt (84%) rename src/main/{java/roomescape/reservation/docs/ReservationTimeAPI.kt => kotlin/roomescape/reservation/docs/TimeAPI.kt} (74%) rename src/main/{java => kotlin}/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt (74%) rename src/main/{java => kotlin}/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt (77%) rename src/main/{java => kotlin}/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt (96%) rename src/main/{java/roomescape/reservation/infrastructure/persistence/ReservationTimeEntity.kt => kotlin/roomescape/reservation/infrastructure/persistence/TimeEntity.kt} (80%) rename src/main/{java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepository.kt => kotlin/roomescape/reservation/infrastructure/persistence/TimeRepository.kt} (70%) rename src/main/{java => kotlin}/roomescape/reservation/web/ReservationController.kt (53%) create mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationRequest.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/TimeController.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/TimeDTO.kt rename src/main/{java => kotlin}/roomescape/theme/business/ThemeService.kt (83%) rename src/main/{java => kotlin}/roomescape/theme/docs/ThemeAPI.kt (93%) rename src/main/{java => kotlin}/roomescape/theme/infrastructure/persistence/ThemeEntity.kt (92%) rename src/main/{java => kotlin}/roomescape/theme/infrastructure/persistence/ThemeRepository.kt (85%) rename src/main/{java => kotlin}/roomescape/theme/web/ThemeController.kt (74%) rename src/main/{java => kotlin}/roomescape/theme/web/ThemeDTO.kt (88%) rename src/main/{java => kotlin}/roomescape/view/PageController.kt (100%) rename src/test/{java => kotlin}/roomescape/auth/business/AuthServiceTest.kt (99%) rename src/test/{java => kotlin}/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt (100%) rename src/test/{java => kotlin}/roomescape/auth/web/AuthControllerTest.kt (95%) rename src/test/{java => kotlin}/roomescape/auth/web/support/CookieUtilsTest.kt (93%) rename src/test/{java => kotlin}/roomescape/common/config/JacksonConfigTest.kt (100%) rename src/test/{java => kotlin}/roomescape/member/controller/MemberControllerTest.kt (90%) rename src/test/{java => kotlin}/roomescape/payment/business/PaymentServiceTest.kt (93%) rename src/test/{java/roomescape/payment/web/support => kotlin/roomescape/payment/infrastructure/client}/PaymentCancelResponseDeserializerTest.kt (73%) rename src/test/{java/roomescape/payment => kotlin/roomescape/payment/infrastructure/client}/SampleTossPaymentConst.kt (90%) rename src/test/{java => kotlin}/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt (88%) rename src/test/{java => kotlin}/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt (100%) rename src/test/{java => kotlin}/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt (98%) rename src/test/{java/roomescape/reservation/business/ReservationServiteTest.kt => kotlin/roomescape/reservation/business/ReservationServiceTest.kt} (87%) rename src/test/{java => kotlin}/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt (67%) rename src/test/{java/roomescape/reservation/business/ReservationTimeServiceTest.kt => kotlin/roomescape/reservation/business/TimeServiceTest.kt} (50%) rename src/test/{java => kotlin}/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt (88%) rename src/test/{java => kotlin}/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt (93%) rename src/test/{java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepositoryTest.kt => kotlin/roomescape/reservation/infrastructure/persistence/TimeRepositoryTest.kt} (62%) rename src/test/{java => kotlin}/roomescape/reservation/web/ReservationControllerTest.kt (91%) rename src/test/{java/roomescape/reservation/web/ReservationTimeControllerTest.kt => kotlin/roomescape/reservation/web/TimeControllerTest.kt} (77%) rename src/test/{java => kotlin}/roomescape/theme/business/ThemeServiceTest.kt (93%) rename src/test/{java => kotlin}/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt (92%) rename src/test/{java => kotlin}/roomescape/theme/util/TestThemeCreateUtil.kt (84%) rename src/test/{java/roomescape/theme/web/MostReservedThemeAPITest.kt => kotlin/roomescape/theme/web/MostReservedThemeApiTest.kt} (71%) rename src/test/{java => kotlin}/roomescape/theme/web/ThemeControllerTest.kt (95%) rename src/test/{java => kotlin}/roomescape/util/DatabaseCleaner.kt (100%) rename src/test/{java => kotlin}/roomescape/util/Fixtures.kt (83%) rename src/test/{java => kotlin}/roomescape/util/KotestConfig.kt (100%) rename src/test/{java => kotlin}/roomescape/util/RoomescapeApiTest.kt (100%) rename src/test/{java => kotlin}/roomescape/view/PageControllerTest.kt (92%) diff --git a/src/main/java/roomescape/common/dto/response/RoomescapeApiResponse.kt b/src/main/java/roomescape/common/dto/response/RoomescapeApiResponse.kt deleted file mode 100644 index a3ef0631..00000000 --- a/src/main/java/roomescape/common/dto/response/RoomescapeApiResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package roomescape.common.dto.response - -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(name = "API 성공 응답") -@JvmRecord -data class RoomescapeApiResponse( - val data: T? = null -) { - companion object { - @JvmStatic - fun success(data: T): RoomescapeApiResponse = RoomescapeApiResponse(data) - - @JvmStatic - fun success(): RoomescapeApiResponse = RoomescapeApiResponse(null) - } -} diff --git a/src/main/java/roomescape/common/dto/response/RoomescapeErrorResponse.kt b/src/main/java/roomescape/common/dto/response/RoomescapeErrorResponse.kt deleted file mode 100644 index 819eb297..00000000 --- a/src/main/java/roomescape/common/dto/response/RoomescapeErrorResponse.kt +++ /dev/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 -) diff --git a/src/main/java/roomescape/member/web/MemberDTO.kt b/src/main/java/roomescape/member/web/MemberDTO.kt deleted file mode 100644 index 8b8b4e32..00000000 --- a/src/main/java/roomescape/member/web/MemberDTO.kt +++ /dev/null @@ -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 -) diff --git a/src/main/java/roomescape/payment/infrastructure/client/TossPaymentErrorResponse.kt b/src/main/java/roomescape/payment/infrastructure/client/TossPaymentErrorResponse.kt deleted file mode 100644 index aebddc49..00000000 --- a/src/main/java/roomescape/payment/infrastructure/client/TossPaymentErrorResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package roomescape.payment.infrastructure.client - -@JvmRecord -data class TossPaymentErrorResponse( - val code: String, - val message: String -) diff --git a/src/main/java/roomescape/payment/web/PaymentDTO.kt b/src/main/java/roomescape/payment/web/PaymentDTO.kt deleted file mode 100644 index b346c20c..00000000 --- a/src/main/java/roomescape/payment/web/PaymentDTO.kt +++ /dev/null @@ -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 -) \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/business/ReservationTimeService.kt b/src/main/java/roomescape/reservation/business/ReservationTimeService.kt deleted file mode 100644 index 5f940834..00000000 --- a/src/main/java/roomescape/reservation/business/ReservationTimeService.kt +++ /dev/null @@ -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 = reservationRepository.findByDateAndThemeId(date, themeId) - - return ReservationTimeInfosResponse(allTimes.map { time -> - val alreadyBooked: Boolean = reservations.any { reservation -> reservation.reservationTime.id == time.id } - time.toInfoResponse(alreadyBooked) - }) - } -} diff --git a/src/main/java/roomescape/reservation/web/ReservationRequest.kt b/src/main/java/roomescape/reservation/web/ReservationRequest.kt deleted file mode 100644 index fa081bc9..00000000 --- a/src/main/java/roomescape/reservation/web/ReservationRequest.kt +++ /dev/null @@ -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 -) diff --git a/src/main/java/roomescape/reservation/web/ReservationResponse.kt b/src/main/java/roomescape/reservation/web/ReservationResponse.kt deleted file mode 100644 index 3776d150..00000000 --- a/src/main/java/roomescape/reservation/web/ReservationResponse.kt +++ /dev/null @@ -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 -) - -@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 -) diff --git a/src/main/java/roomescape/reservation/web/ReservationTimeController.kt b/src/main/java/roomescape/reservation/web/ReservationTimeController.kt deleted file mode 100644 index 6fb0147e..00000000 --- a/src/main/java/roomescape/reservation/web/ReservationTimeController.kt +++ /dev/null @@ -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> { - val response: ReservationTimesResponse = reservationTimeService.findAllTimes() - - return ResponseEntity.ok(CommonApiResponse(response)) - } - - @PostMapping("/times") - override fun saveTime( - @Valid @RequestBody reservationTimeRequest: ReservationTimeRequest, - ): ResponseEntity> { - 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> { - reservationTimeService.removeTimeById(id) - - return ResponseEntity.noContent().build() - } - - @GetMapping("/times/filter") - override fun findAllAvailableReservationTimes( - @RequestParam date: LocalDate, - @RequestParam themeId: Long - ): ResponseEntity> { - val response: ReservationTimeInfosResponse = reservationTimeService.findAllAvailableTimesByDateAndTheme(date, themeId) - - return ResponseEntity.ok(CommonApiResponse(response)) - } -} diff --git a/src/main/java/roomescape/reservation/web/ReservationTimeDTO.kt b/src/main/java/roomescape/reservation/web/ReservationTimeDTO.kt deleted file mode 100644 index e0b06678..00000000 --- a/src/main/java/roomescape/reservation/web/ReservationTimeDTO.kt +++ /dev/null @@ -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 -) - -fun List.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 -) diff --git a/src/main/java/roomescape/RoomescapeApplication.kt b/src/main/kotlin/roomescape/RoomescapeApplication.kt similarity index 100% rename from src/main/java/roomescape/RoomescapeApplication.kt rename to src/main/kotlin/roomescape/RoomescapeApplication.kt diff --git a/src/main/java/roomescape/auth/docs/AuthAPI.kt b/src/main/kotlin/roomescape/auth/docs/AuthAPI.kt similarity index 100% rename from src/main/java/roomescape/auth/docs/AuthAPI.kt rename to src/main/kotlin/roomescape/auth/docs/AuthAPI.kt diff --git a/src/main/java/roomescape/auth/infrastructure/jwt/JwtHandler.kt b/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt similarity index 100% rename from src/main/java/roomescape/auth/infrastructure/jwt/JwtHandler.kt rename to src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt diff --git a/src/main/java/roomescape/auth/service/AuthService.kt b/src/main/kotlin/roomescape/auth/service/AuthService.kt similarity index 78% rename from src/main/java/roomescape/auth/service/AuthService.kt rename to src/main/kotlin/roomescape/auth/service/AuthService.kt index b6ee6e00..ce3d0e81 100644 --- a/src/main/java/roomescape/auth/service/AuthService.kt +++ b/src/main/kotlin/roomescape/auth/service/AuthService.kt @@ -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 { diff --git a/src/main/java/roomescape/auth/web/AuthController.kt b/src/main/kotlin/roomescape/auth/web/AuthController.kt similarity index 96% rename from src/main/java/roomescape/auth/web/AuthController.kt rename to src/main/kotlin/roomescape/auth/web/AuthController.kt index 6866437d..65746af7 100644 --- a/src/main/java/roomescape/auth/web/AuthController.kt +++ b/src/main/kotlin/roomescape/auth/web/AuthController.kt @@ -24,7 +24,7 @@ class AuthController( override fun login( @Valid @RequestBody loginRequest: LoginRequest, ): ResponseEntity> { - val response: TokenResponse = authService.login(loginRequest) + val response: LoginResponse = authService.login(loginRequest) return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, response.toResponseCookie()) diff --git a/src/main/java/roomescape/auth/web/AuthDTO.kt b/src/main/kotlin/roomescape/auth/web/AuthDTO.kt similarity index 55% rename from src/main/java/roomescape/auth/web/AuthDTO.kt rename to src/main/kotlin/roomescape/auth/web/AuthDTO.kt index 15a87d24..8b910d01 100644 --- a/src/main/java/roomescape/auth/web/AuthDTO.kt +++ b/src/main/kotlin/roomescape/auth/web/AuthDTO.kt @@ -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 ) diff --git a/src/main/java/roomescape/auth/web/support/AuthAnnotations.kt b/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt similarity index 100% rename from src/main/java/roomescape/auth/web/support/AuthAnnotations.kt rename to src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt diff --git a/src/main/java/roomescape/auth/web/support/AuthInterceptors.kt b/src/main/kotlin/roomescape/auth/web/support/AuthInterceptors.kt similarity index 100% rename from src/main/java/roomescape/auth/web/support/AuthInterceptors.kt rename to src/main/kotlin/roomescape/auth/web/support/AuthInterceptors.kt diff --git a/src/main/java/roomescape/auth/web/support/CookieUtils.kt b/src/main/kotlin/roomescape/auth/web/support/CookieUtils.kt similarity index 88% rename from src/main/java/roomescape/auth/web/support/CookieUtils.kt rename to src/main/kotlin/roomescape/auth/web/support/CookieUtils.kt index 22eebd37..af1e606d 100644 --- a/src/main/java/roomescape/auth/web/support/CookieUtils.kt +++ b/src/main/kotlin/roomescape/auth/web/support/CookieUtils.kt @@ -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) diff --git a/src/main/java/roomescape/auth/web/support/MemberIdResolver.kt b/src/main/kotlin/roomescape/auth/web/support/MemberIdResolver.kt similarity index 100% rename from src/main/java/roomescape/auth/web/support/MemberIdResolver.kt rename to src/main/kotlin/roomescape/auth/web/support/MemberIdResolver.kt diff --git a/src/main/java/roomescape/common/config/JacksonConfig.kt b/src/main/kotlin/roomescape/common/config/JacksonConfig.kt similarity index 100% rename from src/main/java/roomescape/common/config/JacksonConfig.kt rename to src/main/kotlin/roomescape/common/config/JacksonConfig.kt diff --git a/src/main/java/roomescape/common/config/SwaggerConfig.kt b/src/main/kotlin/roomescape/common/config/SwaggerConfig.kt similarity index 100% rename from src/main/java/roomescape/common/config/SwaggerConfig.kt rename to src/main/kotlin/roomescape/common/config/SwaggerConfig.kt diff --git a/src/main/java/roomescape/common/config/WebMvcConfig.kt b/src/main/kotlin/roomescape/common/config/WebMvcConfig.kt similarity index 100% rename from src/main/java/roomescape/common/config/WebMvcConfig.kt rename to src/main/kotlin/roomescape/common/config/WebMvcConfig.kt diff --git a/src/main/java/roomescape/common/dto/response/CommonApiResponse.kt b/src/main/kotlin/roomescape/common/dto/response/CommonApiResponse.kt similarity index 100% rename from src/main/java/roomescape/common/dto/response/CommonApiResponse.kt rename to src/main/kotlin/roomescape/common/dto/response/CommonApiResponse.kt diff --git a/src/main/java/roomescape/common/exception/ErrorType.kt b/src/main/kotlin/roomescape/common/exception/ErrorType.kt similarity index 78% rename from src/main/java/roomescape/common/exception/ErrorType.kt rename to src/main/kotlin/roomescape/common/exception/ErrorType.kt index 8d19efe4..d002f14d 100644 --- a/src/main/java/roomescape/common/exception/ErrorType.kt +++ b/src/main/kotlin/roomescape/common/exception/ErrorType.kt @@ -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 - ) - } - } } diff --git a/src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt b/src/main/kotlin/roomescape/common/exception/ExceptionControllerAdvice.kt similarity index 71% rename from src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt rename to src/main/kotlin/roomescape/common/exception/ExceptionControllerAdvice.kt index fb0614d9..1f892236 100644 --- a/src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt +++ b/src/main/kotlin/roomescape/common/exception/ExceptionControllerAdvice.kt @@ -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 { - 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 { 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 { - 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 { logger.error(e) { "message: ${e.message}" } diff --git a/src/main/java/roomescape/common/exception/RoomescapeException.kt b/src/main/kotlin/roomescape/common/exception/RoomescapeException.kt similarity index 100% rename from src/main/java/roomescape/common/exception/RoomescapeException.kt rename to src/main/kotlin/roomescape/common/exception/RoomescapeException.kt diff --git a/src/main/java/roomescape/member/business/MemberService.kt b/src/main/kotlin/roomescape/member/business/MemberService.kt similarity index 81% rename from src/main/java/roomescape/member/business/MemberService.kt rename to src/main/kotlin/roomescape/member/business/MemberService.kt index 512f947d..3be407ed 100644 --- a/src/main/java/roomescape/member/business/MemberService.kt +++ b/src/main/kotlin/roomescape/member/business/MemberService.kt @@ -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, diff --git a/src/main/java/roomescape/member/docs/MemberAPI.kt b/src/main/kotlin/roomescape/member/docs/MemberAPI.kt similarity index 85% rename from src/main/java/roomescape/member/docs/MemberAPI.kt rename to src/main/kotlin/roomescape/member/docs/MemberAPI.kt index de8608ca..9428cde2 100644 --- a/src/main/java/roomescape/member/docs/MemberAPI.kt +++ b/src/main/kotlin/roomescape/member/docs/MemberAPI.kt @@ -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> + fun findMembers(): ResponseEntity> } diff --git a/src/main/java/roomescape/member/infrastructure/persistence/MemberEntity.kt b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt similarity index 94% rename from src/main/java/roomescape/member/infrastructure/persistence/MemberEntity.kt rename to src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt index f9ec13eb..ac476b00 100644 --- a/src/main/java/roomescape/member/infrastructure/persistence/MemberEntity.kt +++ b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt @@ -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) diff --git a/src/main/java/roomescape/member/infrastructure/persistence/MemberRepository.kt b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberRepository.kt similarity index 100% rename from src/main/java/roomescape/member/infrastructure/persistence/MemberRepository.kt rename to src/main/kotlin/roomescape/member/infrastructure/persistence/MemberRepository.kt diff --git a/src/main/java/roomescape/member/web/MemberController.kt b/src/main/kotlin/roomescape/member/web/MemberController.kt similarity index 75% rename from src/main/java/roomescape/member/web/MemberController.kt rename to src/main/kotlin/roomescape/member/web/MemberController.kt index 435ca9d4..b86d341e 100644 --- a/src/main/java/roomescape/member/web/MemberController.kt +++ b/src/main/kotlin/roomescape/member/web/MemberController.kt @@ -13,8 +13,8 @@ class MemberController( ) : MemberAPI { @GetMapping("/members") - override fun readAllMembers(): ResponseEntity> { - val response: MembersResponse = memberService.readAllMembers() + override fun findMembers(): ResponseEntity> { + val response: MemberRetrieveListResponse = memberService.findMembers() return ResponseEntity.ok(CommonApiResponse(response)) } diff --git a/src/main/kotlin/roomescape/member/web/MemberDTO.kt b/src/main/kotlin/roomescape/member/web/MemberDTO.kt new file mode 100644 index 00000000..f6d8a43d --- /dev/null +++ b/src/main/kotlin/roomescape/member/web/MemberDTO.kt @@ -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 +) diff --git a/src/main/java/roomescape/payment/business/PaymentService.kt b/src/main/kotlin/roomescape/payment/business/PaymentService.kt similarity index 84% rename from src/main/java/roomescape/payment/business/PaymentService.kt rename to src/main/kotlin/roomescape/payment/business/PaymentService.kt index 8a406fad..a7343ef8 100644 --- a/src/main/java/roomescape/payment/business/PaymentService.kt +++ b/src/main/kotlin/roomescape/payment/business/PaymentService.kt @@ -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( diff --git a/src/main/java/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt similarity index 81% rename from src/main/java/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt rename to src/main/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt index 075e7e5e..4ffb1e93 100644 --- a/src/main/java/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializer.kt @@ -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? = null -) : StdDeserializer(vc) { + vc: Class? = null +) : StdDeserializer(vc) { @Throws(IOException::class) override fun deserialize( jsonParser: JsonParser, deserializationContext: DeserializationContext? - ): PaymentCancel.Response { + ): PaymentCancelResponse { val cancels: JsonNode = jsonParser.codec.readTree(jsonParser) .get("cancels") .get(0) as JsonNode - return PaymentCancel.Response( + return PaymentCancelResponse( cancels.get("cancelStatus").asText(), cancels.get("cancelReason").asText(), cancels.get("cancelAmount").asLong(), diff --git a/src/main/java/roomescape/payment/infrastructure/client/PaymentConfig.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/PaymentConfig.kt similarity index 100% rename from src/main/java/roomescape/payment/infrastructure/client/PaymentConfig.kt rename to src/main/kotlin/roomescape/payment/infrastructure/client/PaymentConfig.kt diff --git a/src/main/java/roomescape/payment/infrastructure/client/PaymentProperties.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/PaymentProperties.kt similarity index 54% rename from src/main/java/roomescape/payment/infrastructure/client/PaymentProperties.kt rename to src/main/kotlin/roomescape/payment/infrastructure/client/PaymentProperties.kt index 06758eb8..a421028e 100644 --- a/src/main/java/roomescape/payment/infrastructure/client/PaymentProperties.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/client/PaymentProperties.kt @@ -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 ) diff --git a/src/main/java/roomescape/payment/infrastructure/client/TossPaymentClient.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt similarity index 87% rename from src/main/java/roomescape/payment/infrastructure/client/TossPaymentClient.kt rename to src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt index e46b95da..e8ff0abe 100644 --- a/src/main/java/roomescape/payment/infrastructure/client/TossPaymentClient.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt @@ -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("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 diff --git a/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentDTO.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentDTO.kt new file mode 100644 index 00000000..e7f7ae21 --- /dev/null +++ b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentDTO.kt @@ -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 +) diff --git a/src/main/java/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt similarity index 92% rename from src/main/java/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt rename to src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt index fb19ca0a..91a89e96 100644 --- a/src/main/java/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentEntity.kt @@ -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) diff --git a/src/main/java/roomescape/payment/infrastructure/persistence/CanceledPaymentRepository.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepository.kt similarity index 100% rename from src/main/java/roomescape/payment/infrastructure/persistence/CanceledPaymentRepository.kt rename to src/main/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepository.kt diff --git a/src/main/java/roomescape/payment/infrastructure/persistence/PaymentEntity.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt similarity index 96% rename from src/main/java/roomescape/payment/infrastructure/persistence/PaymentEntity.kt rename to src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt index 88f25dbe..45a18807 100644 --- a/src/main/java/roomescape/payment/infrastructure/persistence/PaymentEntity.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentEntity.kt @@ -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) diff --git a/src/main/java/roomescape/payment/infrastructure/persistence/PaymentRepository.kt b/src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepository.kt similarity index 100% rename from src/main/java/roomescape/payment/infrastructure/persistence/PaymentRepository.kt rename to src/main/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepository.kt diff --git a/src/main/kotlin/roomescape/payment/web/PaymentDTO.kt b/src/main/kotlin/roomescape/payment/web/PaymentDTO.kt new file mode 100644 index 00000000..85bd5ec2 --- /dev/null +++ b/src/main/kotlin/roomescape/payment/web/PaymentDTO.kt @@ -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 +) \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/business/ReservationService.kt b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt similarity index 80% rename from src/main/java/roomescape/reservation/business/ReservationService.kt rename to src/main/kotlin/roomescape/reservation/business/ReservationService.kt index 1047b9c7..95f6d8ca 100644 --- a/src/main/java/roomescape/reservation/business/ReservationService.kt +++ b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt @@ -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 = ReservationSearchSpecification() .confirmed() .build() - return ReservationsResponse(findAllReservationByStatus(spec)) + return ReservationRetrieveListResponse(findAllReservationByStatus(spec)) } @Transactional(readOnly = true) - fun findAllWaiting(): ReservationsResponse { + fun findAllWaiting(): ReservationRetrieveListResponse { val spec: Specification = ReservationSearchSpecification() .waiting() .build() - return ReservationsResponse(findAllReservationByStatus(spec)) + return ReservationRetrieveListResponse(findAllReservationByStatus(spec)) } - private fun findAllReservationByStatus(spec: Specification): List { - return reservationRepository.findAll(spec).map { it.toResponse() } + private fun findAllReservationByStatus(spec: Specification): List { + 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 = 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 = 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() diff --git a/src/main/java/roomescape/reservation/business/ReservationWithPaymentService.kt b/src/main/kotlin/roomescape/reservation/business/ReservationWithPaymentService.kt similarity index 54% rename from src/main/java/roomescape/reservation/business/ReservationWithPaymentService.kt rename to src/main/kotlin/roomescape/reservation/business/ReservationWithPaymentService.kt index a3b0c47b..3d48e276 100644 --- a/src/main/java/roomescape/reservation/business/ReservationWithPaymentService.kt +++ b/src/main/kotlin/roomescape/reservation/business/ReservationWithPaymentService.kt @@ -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 diff --git a/src/main/kotlin/roomescape/reservation/business/TimeService.kt b/src/main/kotlin/roomescape/reservation/business/TimeService.kt new file mode 100644 index 00000000..edde1923 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/business/TimeService.kt @@ -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 = 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) + }) + } +} diff --git a/src/main/java/roomescape/reservation/docs/ReservationAPI.kt b/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt similarity index 84% rename from src/main/java/roomescape/reservation/docs/ReservationAPI.kt rename to src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt index 05fe2883..0e0fe839 100644 --- a/src/main/java/roomescape/reservation/docs/ReservationAPI.kt +++ b/src/main/kotlin/roomescape/reservation/docs/ReservationAPI.kt @@ -26,33 +26,33 @@ interface ReservationAPI { @Admin @Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun getAllReservations(): ResponseEntity> + fun findReservations(): ResponseEntity> @LoginRequired @Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun getMemberReservations( + fun findReservationsByMemberId( @MemberId @Parameter(hidden = true) memberId: Long - ): ResponseEntity> + ): ResponseEntity> @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> + ): ResponseEntity> @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> @@ -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> + ): ResponseEntity> @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> + fun createReservationByAdmin( + @Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest, + ): ResponseEntity> @Admin @Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun getAllWaiting(): ResponseEntity> + fun findAllWaiting(): ResponseEntity> @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> + ): ResponseEntity> @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> @@ -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> @@ -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> diff --git a/src/main/java/roomescape/reservation/docs/ReservationTimeAPI.kt b/src/main/kotlin/roomescape/reservation/docs/TimeAPI.kt similarity index 74% rename from src/main/java/roomescape/reservation/docs/ReservationTimeAPI.kt rename to src/main/kotlin/roomescape/reservation/docs/TimeAPI.kt index 3b1535e2..067137f3 100644 --- a/src/main/java/roomescape/reservation/docs/ReservationTimeAPI.kt +++ b/src/main/kotlin/roomescape/reservation/docs/TimeAPI.kt @@ -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> + fun findTimes(): ResponseEntity> @Admin @Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true)) - fun saveTime( - @Valid @RequestBody reservationTimeRequest: ReservationTimeRequest, - ): ResponseEntity> + fun createTime( + @Valid @RequestBody timeCreateRequest: TimeCreateRequest, + ): ResponseEntity> @Admin @Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true)) - fun removeTime( + fun deleteTime( @PathVariable id: Long ): ResponseEntity> @LoginRequired @Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun findAllAvailableReservationTimes( + fun findTimesWithAvailability( @RequestParam date: LocalDate, @RequestParam themeId: Long - ): ResponseEntity> + ): ResponseEntity> } diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt similarity index 74% rename from src/main/java/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt rename to src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt index f513d5c2..b7912f5b 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationEntity.kt @@ -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 } diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt similarity index 77% rename from src/main/java/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt rename to src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt index 5d17d47a..4c563eea 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt @@ -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, JpaSpecificationExecutor { - fun findByReservationTime(reservationTime: ReservationTimeEntity): List + fun findByTime(time: TimeEntity): List fun findByDateAndThemeId(date: LocalDate, themeId: Long): List @@ -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 + fun findAllById(memberId: Long): List } diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt similarity index 96% rename from src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt rename to src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt index ca55ce2e..f42892c3 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt @@ -22,7 +22,7 @@ class ReservationSearchSpecification( fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let { Specification { root, _, cb -> - cb.equal(root.get("reservationTime").get("id"), timeId) + cb.equal(root.get("time").get("id"), timeId) } }) diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeEntity.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeEntity.kt similarity index 80% rename from src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeEntity.kt rename to src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeEntity.kt index 73479a7b..38f1f361 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeEntity.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeEntity.kt @@ -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, diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepository.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepository.kt similarity index 70% rename from src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepository.kt rename to src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepository.kt index d4a03bcd..df798889 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepository.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepository.kt @@ -3,6 +3,6 @@ package roomescape.reservation.infrastructure.persistence import org.springframework.data.jpa.repository.JpaRepository import java.time.LocalTime -interface ReservationTimeRepository : JpaRepository { +interface TimeRepository : JpaRepository { fun existsByStartAt(startAt: LocalTime): Boolean } diff --git a/src/main/java/roomescape/reservation/web/ReservationController.kt b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt similarity index 53% rename from src/main/java/roomescape/reservation/web/ReservationController.kt rename to src/main/kotlin/roomescape/reservation/web/ReservationController.kt index 4b3d7e98..47b4a720 100644 --- a/src/main/java/roomescape/reservation/web/ReservationController.kt +++ b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt @@ -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> { - val response: ReservationsResponse = reservationService.findAllReservations() + override fun findReservations(): ResponseEntity> { + 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> { - val response: MyReservationsResponse = reservationService.findMemberReservations(memberId) + ): ResponseEntity> { + 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> { - val response: ReservationsResponse = reservationService.findFilteredReservations(themeId, memberId, dateFrom, dateTo) + ): ResponseEntity> { + 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> { 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> { - val paymentRequest: PaymentApprove.Request = reservationRequest.paymentRequest - val paymentResponse: PaymentApprove.Response = paymentClient.confirmPayment(paymentRequest) + ): ResponseEntity> { + 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> { - val response: ReservationResponse = - reservationService.addReservationByAdmin(adminReservationRequest) + override fun createReservationByAdmin( + @Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest + ): ResponseEntity> { + val response: ReservationRetrieveResponse = + reservationService.createReservationByAdmin(adminReservationRequest) return ResponseEntity.created(URI.create("/reservations/${response.id}")) .body(CommonApiResponse(response)) } @GetMapping("/reservations/waiting") - override fun getAllWaiting(): ResponseEntity> { - val response: ReservationsResponse = reservationService.findAllWaiting() + override fun findAllWaiting(): ResponseEntity> { + 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> { - val response: ReservationResponse = reservationService.addWaiting( - waitingRequest, + ): ResponseEntity> { + 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> { - 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> { - 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> { - reservationService.denyWaiting(reservationId, memberId) + reservationService.rejectWaiting(reservationId, memberId) return ResponseEntity.noContent().build() } diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationRequest.kt b/src/main/kotlin/roomescape/reservation/web/ReservationRequest.kt new file mode 100644 index 00000000..bb69cb31 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/ReservationRequest.kt @@ -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 +) diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt b/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt new file mode 100644 index 00000000..69778c31 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/ReservationResponse.kt @@ -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 +) + +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 +) diff --git a/src/main/kotlin/roomescape/reservation/web/TimeController.kt b/src/main/kotlin/roomescape/reservation/web/TimeController.kt new file mode 100644 index 00000000..d697bafa --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/TimeController.kt @@ -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> { + val response: TimeRetrieveListResponse = timeService.findTimes() + + return ResponseEntity.ok(CommonApiResponse(response)) + } + + @PostMapping("/times") + override fun createTime( + @Valid @RequestBody timeCreateRequest: TimeCreateRequest, + ): ResponseEntity> { + 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> { + timeService.deleteTime(id) + + return ResponseEntity.noContent().build() + } + + @GetMapping("/times/search") + override fun findTimesWithAvailability( + @RequestParam date: LocalDate, + @RequestParam themeId: Long + ): ResponseEntity> { + val response: TimeWithAvailabilityListResponse = timeService.findTimesWithAvailability(date, themeId) + + return ResponseEntity.ok(CommonApiResponse(response)) + } +} diff --git a/src/main/kotlin/roomescape/reservation/web/TimeDTO.kt b/src/main/kotlin/roomescape/reservation/web/TimeDTO.kt new file mode 100644 index 00000000..847fd855 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/TimeDTO.kt @@ -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 +) + +fun List.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 +) diff --git a/src/main/java/roomescape/theme/business/ThemeService.kt b/src/main/kotlin/roomescape/theme/business/ThemeService.kt similarity index 83% rename from src/main/java/roomescape/theme/business/ThemeService.kt rename to src/main/kotlin/roomescape/theme/business/ThemeService.kt index 1bb1199e..91708c13 100644 --- a/src/main/java/roomescape/theme/business/ThemeService.kt +++ b/src/main/kotlin/roomescape/theme/business/ThemeService.kt @@ -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, diff --git a/src/main/java/roomescape/theme/docs/ThemeAPI.kt b/src/main/kotlin/roomescape/theme/docs/ThemeAPI.kt similarity index 93% rename from src/main/java/roomescape/theme/docs/ThemeAPI.kt rename to src/main/kotlin/roomescape/theme/docs/ThemeAPI.kt index 624d830a..d971884b 100644 --- a/src/main/java/roomescape/theme/docs/ThemeAPI.kt +++ b/src/main/kotlin/roomescape/theme/docs/ThemeAPI.kt @@ -23,11 +23,11 @@ interface ThemeAPI { @LoginRequired @Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun getAllThemes(): ResponseEntity> + fun findThemes(): ResponseEntity> @Operation(summary = "가장 많이 예약된 테마 조회") @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun getMostReservedThemes( + fun findMostReservedThemes( @RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int ): ResponseEntity> @@ -36,7 +36,7 @@ interface ThemeAPI { @ApiResponses( ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true), ) - fun saveTheme( + fun createTheme( @Valid @RequestBody request: ThemeRequest, ): ResponseEntity> @@ -45,7 +45,7 @@ interface ThemeAPI { @ApiResponses( ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true), ) - fun removeTheme( + fun deleteTheme( @PathVariable id: Long ): ResponseEntity> } diff --git a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeEntity.kt b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt similarity index 92% rename from src/main/java/roomescape/theme/infrastructure/persistence/ThemeEntity.kt rename to src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt index 6d8e3d79..04a13cf0 100644 --- a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeEntity.kt +++ b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeEntity.kt @@ -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) diff --git a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt similarity index 85% rename from src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt rename to src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt index 83b02cfe..baf1bd62 100644 --- a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt +++ b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt @@ -14,10 +14,9 @@ interface ThemeRepository : JpaRepository { 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 + fun findPopularThemes(startDate: LocalDate, endDate: LocalDate, limit: Int): List fun existsByName(name: String): Boolean diff --git a/src/main/java/roomescape/theme/web/ThemeController.kt b/src/main/kotlin/roomescape/theme/web/ThemeController.kt similarity index 74% rename from src/main/java/roomescape/theme/web/ThemeController.kt rename to src/main/kotlin/roomescape/theme/web/ThemeController.kt index 1a7a7ec1..05820cb2 100644 --- a/src/main/java/roomescape/theme/web/ThemeController.kt +++ b/src/main/kotlin/roomescape/theme/web/ThemeController.kt @@ -15,36 +15,36 @@ class ThemeController( ) : ThemeAPI { @GetMapping("/themes") - override fun getAllThemes(): ResponseEntity> { - val response: ThemesResponse = themeService.findAllThemes() + override fun findThemes(): ResponseEntity> { + 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> { - 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> { - 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> { - themeService.deleteById(id) + themeService.deleteTheme(id) return ResponseEntity.noContent().build() } diff --git a/src/main/java/roomescape/theme/web/ThemeDTO.kt b/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt similarity index 88% rename from src/main/java/roomescape/theme/web/ThemeDTO.kt rename to src/main/kotlin/roomescape/theme/web/ThemeDTO.kt index b814a43a..5fa35711 100644 --- a/src/main/java/roomescape/theme/web/ThemeDTO.kt +++ b/src/main/kotlin/roomescape/theme/web/ThemeDTO.kt @@ -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 diff --git a/src/main/java/roomescape/view/PageController.kt b/src/main/kotlin/roomescape/view/PageController.kt similarity index 100% rename from src/main/java/roomescape/view/PageController.kt rename to src/main/kotlin/roomescape/view/PageController.kt diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 01d159e6..8c38d67d 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -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); \ No newline at end of file diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js index 1bfd937b..500c016a 100644 --- a/src/main/resources/static/js/user-reservation.js +++ b/src/main/resources/static/js/user-reservation.js @@ -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 = '
선택할 수 있는 시간이 없습니다.
'; 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"); // 예약 대기 버튼 활성화 diff --git a/src/main/resources/static/js/waiting.js b/src/main/resources/static/js/waiting.js index cea6f6f1..eaaf963e 100644 --- a/src/main/resources/static/js/waiting.js +++ b/src/main/resources/static/js/waiting.js @@ -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 => { diff --git a/src/test/java/roomescape/auth/business/AuthServiceTest.kt b/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt similarity index 99% rename from src/test/java/roomescape/auth/business/AuthServiceTest.kt rename to src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt index 90fb95cb..5f37c79e 100644 --- a/src/test/java/roomescape/auth/business/AuthServiceTest.kt +++ b/src/test/kotlin/roomescape/auth/business/AuthServiceTest.kt @@ -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) diff --git a/src/test/java/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt b/src/test/kotlin/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt similarity index 100% rename from src/test/java/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt rename to src/test/kotlin/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt diff --git a/src/test/java/roomescape/auth/web/AuthControllerTest.kt b/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt similarity index 95% rename from src/test/java/roomescape/auth/web/AuthControllerTest.kt rename to src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt index a4f575d1..8fa954f9 100644 --- a/src/test/java/roomescape/auth/web/AuthControllerTest.kt +++ b/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt @@ -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 { diff --git a/src/test/java/roomescape/auth/web/support/CookieUtilsTest.kt b/src/test/kotlin/roomescape/auth/web/support/CookieUtilsTest.kt similarity index 93% rename from src/test/java/roomescape/auth/web/support/CookieUtilsTest.kt rename to src/test/kotlin/roomescape/auth/web/support/CookieUtilsTest.kt index daa3a066..486885ee 100644 --- a/src/test/java/roomescape/auth/web/support/CookieUtilsTest.kt +++ b/src/test/kotlin/roomescape/auth/web/support/CookieUtilsTest.kt @@ -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", diff --git a/src/test/java/roomescape/common/config/JacksonConfigTest.kt b/src/test/kotlin/roomescape/common/config/JacksonConfigTest.kt similarity index 100% rename from src/test/java/roomescape/common/config/JacksonConfigTest.kt rename to src/test/kotlin/roomescape/common/config/JacksonConfigTest.kt diff --git a/src/test/java/roomescape/member/controller/MemberControllerTest.kt b/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt similarity index 90% rename from src/test/java/roomescape/member/controller/MemberControllerTest.kt rename to src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt index 7eddc94e..b34e2555 100644 --- a/src/test/java/roomescape/member/controller/MemberControllerTest.kt +++ b/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt @@ -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 { diff --git a/src/test/java/roomescape/payment/business/PaymentServiceTest.kt b/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt similarity index 93% rename from src/test/java/roomescape/payment/business/PaymentServiceTest.kt rename to src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt index be9c05fa..a6ceaab2 100644 --- a/src/test/java/roomescape/payment/business/PaymentServiceTest.kt +++ b/src/test/kotlin/roomescape/payment/business/PaymentServiceTest.kt @@ -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 { - paymentService.cancelPaymentByAdmin(reservationId) + paymentService.createCanceledPaymentByReservationId(reservationId) } assertSoftly(exception) { @@ -51,7 +51,7 @@ class PaymentServiceTest : FunSpec({ } returns null val exception = shouldThrow { - 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 diff --git a/src/test/java/roomescape/payment/web/support/PaymentCancelResponseDeserializerTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializerTest.kt similarity index 73% rename from src/test/java/roomescape/payment/web/support/PaymentCancelResponseDeserializerTest.kt rename to src/test/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializerTest.kt index 97908a0a..da2d10c0 100644 --- a/src/test/java/roomescape/payment/web/support/PaymentCancelResponseDeserializerTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/client/PaymentCancelResponseDeserializerTest.kt @@ -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) { @@ -33,4 +31,4 @@ class PaymentCancelResponseDeserializerTest : StringSpec({ cancelResponse.canceledAt.toString() shouldBe "2024-02-13T12:20:23+09:00" } } -}) +}) \ No newline at end of file diff --git a/src/test/java/roomescape/payment/SampleTossPaymentConst.kt b/src/test/kotlin/roomescape/payment/infrastructure/client/SampleTossPaymentConst.kt similarity index 90% rename from src/test/java/roomescape/payment/SampleTossPaymentConst.kt rename to src/test/kotlin/roomescape/payment/infrastructure/client/SampleTossPaymentConst.kt index ba1db53e..5f84aa4a 100644 --- a/src/test/java/roomescape/payment/SampleTossPaymentConst.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/client/SampleTossPaymentConst.kt @@ -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()) -} \ No newline at end of file diff --git a/src/test/java/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt similarity index 88% rename from src/test/java/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt rename to src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt index a36e409e..b4f6e5c4 100644 --- a/src/test/java/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/client/TossPaymentClientTest.kt @@ -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 { - 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 { - client.cancelPayment(cancelRequest) + client.cancel(cancelRequest) } assertSoftly(exception) { diff --git a/src/test/java/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt similarity index 100% rename from src/test/java/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt rename to src/test/kotlin/roomescape/payment/infrastructure/persistence/CanceledPaymentRepositoryTest.kt diff --git a/src/test/java/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt b/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt similarity index 98% rename from src/test/java/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt rename to src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt index 4b202077..34e64291 100644 --- a/src/test/java/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt +++ b/src/test/kotlin/roomescape/payment/infrastructure/persistence/PaymentRepositoryTest.kt @@ -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() diff --git a/src/test/java/roomescape/reservation/business/ReservationServiteTest.kt b/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt similarity index 87% rename from src/test/java/roomescape/reservation/business/ReservationServiteTest.kt rename to src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt index c9b2fa6a..745cc89d 100644 --- a/src/test/java/roomescape/reservation/business/ReservationServiteTest.kt +++ b/src/test/kotlin/roomescape/reservation/business/ReservationServiceTest.kt @@ -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 { 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 { - reservationService.findFilteredReservations( + reservationService.searchReservations( null, null, startFrom, @@ -147,7 +147,7 @@ class ReservationServiteTest : FunSpec({ } returns member shouldThrow { - 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 { - reservationService.approveWaiting(reservationId, member.id!!) + reservationService.confirmWaiting(reservationId, member.id!!) }.also { it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED } diff --git a/src/test/java/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt b/src/test/kotlin/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt similarity index 67% rename from src/test/java/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt rename to src/test/kotlin/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt index d4e1e804..dcbf069b 100644 --- a/src/test/java/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt +++ b/src/test/kotlin/roomescape/reservation/business/ReservationWithPaymentServiceTest.kt @@ -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!! ) diff --git a/src/test/java/roomescape/reservation/business/ReservationTimeServiceTest.kt b/src/test/kotlin/roomescape/reservation/business/TimeServiceTest.kt similarity index 50% rename from src/test/java/roomescape/reservation/business/ReservationTimeServiceTest.kt rename to src/test/kotlin/roomescape/reservation/business/TimeServiceTest.kt index a1bedcfe..83ed7417 100644 --- a/src/test/java/roomescape/reservation/business/ReservationTimeServiceTest.kt +++ b/src/test/kotlin/roomescape/reservation/business/TimeServiceTest.kt @@ -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 { - 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 { - 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 { - 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 { - reservationTimeService.removeTimeById(id) + timeService.deleteTime(id) }.apply { errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT httpStatus shouldBe HttpStatus.CONFLICT diff --git a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt similarity index 88% rename from src/test/java/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt rename to src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt index 40a40923..f911261b 100644 --- a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationRepositoryTest.kt @@ -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 = reservationRepository.findMyReservations(reservation.member.id!!) + val result: List = reservationRepository.findAllById(reservation.member.id!!) result shouldHaveSize 1 assertSoftly(result.first()) { @@ -179,7 +179,7 @@ class ReservationRepositoryTest( } test("결제 정보가 없다면 paymentKey와 amount는 null로 반환한다.") { - val result: List = reservationRepository.findMyReservations(reservation.member.id!!) + val result: List = 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) diff --git a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt similarity index 93% rename from src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt rename to src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt index 34f96d9f..6d764f42 100644 --- a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt @@ -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 = 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), diff --git a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepositoryTest.kt b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepositoryTest.kt similarity index 62% rename from src/test/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepositoryTest.kt rename to src/test/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepositoryTest.kt index ba7045a3..d3ef18c5 100644 --- a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationTimeRepositoryTest.kt +++ b/src/test/kotlin/roomescape/reservation/infrastructure/persistence/TimeRepositoryTest.kt @@ -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 } } }) diff --git a/src/test/java/roomescape/reservation/web/ReservationControllerTest.kt b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt similarity index 91% rename from src/test/java/roomescape/reservation/web/ReservationControllerTest.kt rename to src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt index f4fbfffe..cf9afdef 100644 --- a/src/test/java/roomescape/reservation/web/ReservationControllerTest.kt +++ b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt @@ -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 { diff --git a/src/test/java/roomescape/reservation/web/ReservationTimeControllerTest.kt b/src/test/kotlin/roomescape/reservation/web/TimeControllerTest.kt similarity index 77% rename from src/test/java/roomescape/reservation/web/ReservationTimeControllerTest.kt rename to src/test/kotlin/roomescape/reservation/web/TimeControllerTest.kt index 71b4040a..1161b705 100644 --- a/src/test/java/roomescape/reservation/web/ReservationTimeControllerTest.kt +++ b/src/test/kotlin/roomescape/reservation/web/TimeControllerTest.kt @@ -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 = listOf( - ReservationTimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)), - ReservationTimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0)) + val times: List = 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 } } } diff --git a/src/test/java/roomescape/theme/business/ThemeServiceTest.kt b/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt similarity index 93% rename from src/test/java/roomescape/theme/business/ThemeServiceTest.kt rename to src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt index a244273c..c823c351 100644 --- a/src/test/java/roomescape/theme/business/ThemeServiceTest.kt +++ b/src/test/kotlin/roomescape/theme/business/ThemeServiceTest.kt @@ -37,7 +37,7 @@ class ThemeServiceTest : FunSpec({ } returns null val exception = shouldThrow { - 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 { - 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 { - themeService.deleteById(themeId) + themeService.deleteTheme(themeId) } assertSoftly(exception) { diff --git a/src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt b/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt similarity index 92% rename from src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt rename to src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt index 73df4b11..df2ba71b 100644 --- a/src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt +++ b/src/test/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt @@ -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 diff --git a/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt b/src/test/kotlin/roomescape/theme/util/TestThemeCreateUtil.kt similarity index 84% rename from src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt rename to src/test/kotlin/roomescape/theme/util/TestThemeCreateUtil.kt index 0f05cdfa..0dacd49b 100644 --- a/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt +++ b/src/test/kotlin/roomescape/theme/util/TestThemeCreateUtil.kt @@ -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) } } diff --git a/src/test/java/roomescape/theme/web/MostReservedThemeAPITest.kt b/src/test/kotlin/roomescape/theme/web/MostReservedThemeApiTest.kt similarity index 71% rename from src/test/java/roomescape/theme/web/MostReservedThemeAPITest.kt rename to src/test/kotlin/roomescape/theme/web/MostReservedThemeApiTest.kt index 73b62fdf..5ebf0851 100644 --- a/src/test/java/roomescape/theme/web/MostReservedThemeAPITest.kt +++ b/src/test/kotlin/roomescape/theme/web/MostReservedThemeApiTest.kt @@ -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)) } diff --git a/src/test/java/roomescape/theme/web/ThemeControllerTest.kt b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt similarity index 95% rename from src/test/java/roomescape/theme/web/ThemeControllerTest.kt rename to src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt index 46893438..817f5077 100644 --- a/src/test/java/roomescape/theme/web/ThemeControllerTest.kt +++ b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt @@ -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() } } diff --git a/src/test/java/roomescape/util/DatabaseCleaner.kt b/src/test/kotlin/roomescape/util/DatabaseCleaner.kt similarity index 100% rename from src/test/java/roomescape/util/DatabaseCleaner.kt rename to src/test/kotlin/roomescape/util/DatabaseCleaner.kt diff --git a/src/test/java/roomescape/util/Fixtures.kt b/src/test/kotlin/roomescape/util/Fixtures.kt similarity index 83% rename from src/test/java/roomescape/util/Fixtures.kt rename to src/test/kotlin/roomescape/util/Fixtures.kt index 99bb0ec4..21316345 100644 --- a/src/test/java/roomescape/util/Fixtures.kt +++ b/src/test/kotlin/roomescape/util/Fixtures.kt @@ -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, diff --git a/src/test/java/roomescape/util/KotestConfig.kt b/src/test/kotlin/roomescape/util/KotestConfig.kt similarity index 100% rename from src/test/java/roomescape/util/KotestConfig.kt rename to src/test/kotlin/roomescape/util/KotestConfig.kt diff --git a/src/test/java/roomescape/util/RoomescapeApiTest.kt b/src/test/kotlin/roomescape/util/RoomescapeApiTest.kt similarity index 100% rename from src/test/java/roomescape/util/RoomescapeApiTest.kt rename to src/test/kotlin/roomescape/util/RoomescapeApiTest.kt diff --git a/src/test/java/roomescape/view/PageControllerTest.kt b/src/test/kotlin/roomescape/view/PageControllerTest.kt similarity index 92% rename from src/test/java/roomescape/view/PageControllerTest.kt rename to src/test/kotlin/roomescape/view/PageControllerTest.kt index a9f10075..0b223c7b 100644 --- a/src/test/java/roomescape/view/PageControllerTest.kt +++ b/src/test/kotlin/roomescape/view/PageControllerTest.kt @@ -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 {