[#18] 코드 정리 및 일부 컨벤션 통일 #19

Merged
pricelees merged 24 commits from refactor/#18 into main 2025-07-22 09:05:31 +00:00
29 changed files with 321 additions and 335 deletions
Showing only changes of commit 8befd45741 - Show all commits

View File

@ -26,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) 정보가 존재하지 않습니다."),

View File

@ -5,7 +5,7 @@ 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 roomescape.reservation.web.toCreateResponse
import java.time.OffsetDateTime
data class PaymentApproveRequest(
@ -51,6 +51,6 @@ fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = R
orderId = this.orderId,
paymentKey = this.paymentKey,
totalAmount = this.totalAmount,
reservation = this.reservation.toResponse(),
reservation = this.reservation.toCreateResponse(),
approvedAt = this.approvedAt
)

View File

@ -18,7 +18,7 @@ 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,
) {
@ -43,7 +43,7 @@ class ReservationService(
}
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> {
return reservationRepository.findAll(spec).map { it.toResponse() }
return reservationRepository.findAll(spec).map { it.toCreateResponse() }
}
fun removeReservationById(reservationId: Long, memberId: Long) {
@ -96,7 +96,7 @@ class ReservationService(
): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status)
.also {
reservationRepository.save(it)
}.toResponse()
}.toCreateResponse()
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
@ -127,10 +127,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,7 +148,7 @@ class ReservationService(
memberId: Long,
status: ReservationStatus
): ReservationEntity {
val time = reservationTimeService.findTimeById(timeId)
val time = timeService.findById(timeId)
val theme = themeService.findById(themeId)
val member = memberService.findById(memberId)
@ -156,7 +156,7 @@ class ReservationService(
return ReservationEntity(
date = date,
reservationTime = time,
time = time,
theme = theme,
member = member,
reservationStatus = status

View File

@ -1,74 +0,0 @@
package roomescape.reservation.business
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
import roomescape.reservation.web.*
import java.time.LocalDate
import java.time.LocalTime
@Service
class ReservationTimeService(
private val reservationTimeRepository: ReservationTimeRepository,
private val reservationRepository: ReservationRepository
) {
@Transactional(readOnly = true)
fun findTimeById(id: Long): ReservationTimeEntity = reservationTimeRepository.findByIdOrNull(id)
?: throw RoomescapeException(
ErrorType.RESERVATION_TIME_NOT_FOUND,
"[reservationTimeId: $id]",
HttpStatus.BAD_REQUEST
)
@Transactional(readOnly = true)
fun findAllTimes(): ReservationTimesResponse = reservationTimeRepository.findAll()
.toResponses()
@Transactional
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
val startAt: LocalTime = reservationTimeRequest.startAt
if (reservationTimeRepository.existsByStartAt(startAt)) {
throw RoomescapeException(
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
)
}
return ReservationTimeEntity(startAt = startAt)
.also { reservationTimeRepository.save(it) }
.toResponse()
}
@Transactional
fun removeTimeById(id: Long) {
val reservationTime: ReservationTimeEntity = findTimeById(id)
reservationRepository.findByReservationTime(reservationTime)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
)
}
reservationTimeRepository.deleteById(id)
}
}
@Transactional(readOnly = true)
fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
val allTimes = reservationTimeRepository.findAll()
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
return ReservationTimeInfosResponse(allTimes.map { time ->
val alreadyBooked: Boolean = reservations.any { reservation -> reservation.reservationTime.id == time.id }
time.toInfoResponse(alreadyBooked)
})
}
}

View File

@ -0,0 +1,74 @@
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 findAll(): TimeRetrieveListResponse = timeRepository.findAll().toRetrieveListResponse()
@Transactional
fun create(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 deleteById(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 findAllWithAvailability(date: LocalDate, themeId: Long): TimeWithAvailabilityListResponse {
val allTimes = timeRepository.findAll()
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
return TimeWithAvailabilityListResponse(allTimes.map { time ->
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
})
}
}

View File

@ -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.TimeWithAvailabilityListResponse
import roomescape.reservation.web.TimeCreateRequest
import roomescape.reservation.web.TimeCreateResponse
import roomescape.reservation.web.TimeRetrieveListResponse
import java.time.LocalDate
@Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.")
interface ReservationTimeAPI {
interface TimeAPI {
@Admin
@Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>>
fun findAll(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>>
@Admin
@Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true))
fun saveTime(
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>>
fun create(
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
): ResponseEntity<CommonApiResponse<TimeCreateResponse>>
@Admin
@Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true))
fun removeTime(
fun deleteById(
@PathVariable id: Long
): ResponseEntity<CommonApiResponse<Unit>>
@LoginRequired
@Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findAllAvailableReservationTimes(
fun findAllWithAvailability(
@RequestParam date: LocalDate,
@RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>>
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>>
}

View File

@ -18,7 +18,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 +40,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
}

View File

@ -10,7 +10,7 @@ import java.time.LocalDate
interface ReservationRepository
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
fun findByReservationTime(reservationTime: ReservationTimeEntity): List<ReservationEntity>
fun findByTime(time: TimeEntity): List<ReservationEntity>
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
@ -33,7 +33,7 @@ interface ReservationRepository
AND EXISTS (
SELECT 1 FROM ReservationEntity r
WHERE r.theme.id = r2.theme.id
AND r.reservationTime.id = r2.reservationTime.id
AND r.time.id = r2.time.id
AND r.date = r2.date
AND r.reservationStatus != 'WAITING'
)
@ -46,9 +46,9 @@ interface ReservationRepository
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
)

View File

@ -22,7 +22,7 @@ class ReservationSearchSpecification(
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
Specification { root, _, cb ->
cb.equal(root.get<ReservationTimeEntity>("reservationTime").get<Long>("id"), timeId)
cb.equal(root.get<TimeEntity>("time").get<Long>("id"), timeId)
}
})

View File

@ -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,

View File

@ -3,6 +3,6 @@ package roomescape.reservation.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalTime
interface ReservationTimeRepository : JpaRepository<ReservationTimeEntity, Long> {
interface TimeRepository : JpaRepository<TimeEntity, Long> {
fun existsByStartAt(startAt: LocalTime): Boolean
}

View File

@ -46,7 +46,7 @@ data class MyReservationsResponse(
@Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.")
data class ReservationResponse(
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
val id: Long,
@ -59,7 +59,7 @@ data class ReservationResponse(
@field:Schema(description = "예약 시간 정보")
@field:JsonProperty("time")
val time: ReservationTimeResponse,
val time: TimeCreateResponse,
@field:Schema(description = "예약한 테마 정보")
@field:JsonProperty("theme")
@ -69,11 +69,11 @@ data class ReservationResponse(
val status: ReservationStatus
)
fun ReservationEntity.toResponse(): ReservationResponse = ReservationResponse(
fun ReservationEntity.toCreateResponse(): ReservationResponse = ReservationResponse(
id = this.id!!,
date = this.date,
member = this.member.toResponse(),
time = this.reservationTime.toResponse(),
time = this.time.toCreateResponse(),
theme = this.theme.toResponse(),
status = this.reservationStatus
)

View File

@ -1,51 +0,0 @@
package roomescape.reservation.web
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.business.ReservationTimeService
import roomescape.reservation.docs.ReservationTimeAPI
import java.net.URI
import java.time.LocalDate
@RestController
class ReservationTimeController(
private val reservationTimeService: ReservationTimeService
) : ReservationTimeAPI {
@GetMapping("/times")
override fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>> {
val response: ReservationTimesResponse = reservationTimeService.findAllTimes()
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/times")
override fun saveTime(
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>> {
val response: ReservationTimeResponse = reservationTimeService.addTime(reservationTimeRequest)
return ResponseEntity
.created(URI.create("/times/${response.id}"))
.body(CommonApiResponse(response))
}
@DeleteMapping("/times/{id}")
override fun removeTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
reservationTimeService.removeTimeById(id)
return ResponseEntity.noContent().build()
}
@GetMapping("/times/filter")
override fun findAllAvailableReservationTimes(
@RequestParam date: LocalDate,
@RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>> {
val response: ReservationTimeInfosResponse = reservationTimeService.findAllAvailableTimesByDateAndTheme(date, themeId)
return ResponseEntity.ok(CommonApiResponse(response))
}
}

View File

@ -1,58 +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 = "예약 시간 저장 요청시 사용됩니다.")
data class ReservationTimeRequest(
@field:Schema(description = "예약 시간. HH:mm 형식으로 입력해야 합니다.", type = "string", example = "09:00")
val startAt: LocalTime
)
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
data class ReservationTimeResponse(
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
val id: Long,
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
val startAt: LocalTime
)
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
this.id!!, this.startAt
)
@Schema(name = "예약 시간 정보 목록 응답", description = "모든 예약 시간 조회 응답시 사용됩니다.")
data class ReservationTimesResponse(
@field:Schema(description = "모든 시간 목록")
val times: List<ReservationTimeResponse>
)
fun List<ReservationTimeEntity>.toResponses(): ReservationTimesResponse = ReservationTimesResponse(
this.map { it.toResponse() }
)
@Schema(name = "특정 테마, 날짜에 대한 시간 정보 응답", description = "특정 날짜와 테마에 대해, 예약 가능 여부를 포함한 시간 정보를 저장합니다.")
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 = "특정 테마, 날짜에 대한 모든 예약 가능 시간 정보를 저장합니다.")
data class ReservationTimeInfosResponse(
@field:Schema(description = "특정 테마, 날짜에 대한 예약 가능 여부를 포함한 시간 목록")
val times: List<ReservationTimeInfoResponse>
)

View File

@ -0,0 +1,51 @@
package roomescape.reservation.web
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.business.TimeService
import roomescape.reservation.docs.TimeAPI
import java.net.URI
import java.time.LocalDate
@RestController
class TimeController(
private val timeService: TimeService
) : TimeAPI {
@GetMapping("/times")
override fun findAll(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>> {
val response: TimeRetrieveListResponse = timeService.findAll()
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/times")
override fun create(
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
): ResponseEntity<CommonApiResponse<TimeCreateResponse>> {
val response: TimeCreateResponse = timeService.create(timeCreateRequest)
return ResponseEntity
.created(URI.create("/times/${response.id}"))
.body(CommonApiResponse(response))
}
@DeleteMapping("/times/{id}")
override fun deleteById(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
timeService.deleteById(id)
return ResponseEntity.noContent().build()
}
@GetMapping("/times/filter")
override fun findAllWithAvailability(
@RequestParam date: LocalDate,
@RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>> {
val response: TimeWithAvailabilityListResponse = timeService.findAllWithAvailability(date, themeId)
return ResponseEntity.ok(CommonApiResponse(response))
}
}

View File

@ -0,0 +1,55 @@
package roomescape.reservation.web
import io.swagger.v3.oas.annotations.media.Schema
import roomescape.reservation.infrastructure.persistence.TimeEntity
import java.time.LocalTime
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
data class TimeCreateRequest(
@field:Schema(description = "시간", type = "string", example = "09:00")
val startAt: LocalTime
)
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
data class TimeCreateResponse(
@field:Schema(description = "시간 식별자")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime
)
fun TimeEntity.toCreateResponse(): TimeCreateResponse = TimeCreateResponse(this.id!!, this.startAt)
data class TimeRetrieveResponse(
@field:Schema(description = "시간 식별자.")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime
)
fun TimeEntity.toRetrieveResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt)
data class TimeRetrieveListResponse(
val times: List<TimeRetrieveResponse>
)
fun List<TimeEntity>.toRetrieveListResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse(
this.map { it.toRetrieveResponse() }
)
data class TimeWithAvailabilityResponse(
@field:Schema(description = "시간 식별자")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime,
@field:Schema(description = "예약 가능 여부")
val isAvailable: Boolean
)
data class TimeWithAvailabilityListResponse(
val times: List<TimeWithAvailabilityResponse>
)

View File

@ -1,10 +1,10 @@
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)

View File

@ -124,9 +124,9 @@ function renderAvailableTimes(times) {
times.data.times.forEach(time => {
const startAt = time.startAt;
const timeId = time.id;
const alreadyBooked = time.alreadyBooked;
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"); // 예약 대기 버튼 활성화

View File

@ -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()

View File

@ -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({
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
)
@ -65,8 +65,8 @@ class ReservationServiteTest : FunSpec({
)
every {
reservationTimeService.findTimeById(any())
} returns ReservationTimeFixture.create()
timeService.findById(any())
} returns TimeFixture.create()
shouldThrow<RoomescapeException> {
reservationService.addReservation(reservationRequest, 1L)
@ -81,8 +81,8 @@ class ReservationServiteTest : FunSpec({
)
every {
reservationTimeService.findTimeById(reservationRequest.timeId)
} returns ReservationTimeFixture.create(
timeService.findById(reservationRequest.timeId)
} returns TimeFixture.create(
startAt = LocalTime.now().minusMinutes(1)
)

View File

@ -32,7 +32,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
val reservationEntity: ReservationEntity = ReservationFixture.create(
id = 1L,
date = reservationRequest.date,
reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId),
time = TimeFixture.create(id = reservationRequest.timeId),
theme = ThemeFixture.create(id = reservationRequest.themeId),
member = MemberFixture.create(id = memberId),
status = ReservationStatus.CONFIRMED
@ -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
}

View File

@ -10,17 +10,17 @@ import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
import roomescape.reservation.web.ReservationTimeRequest
import roomescape.util.ReservationTimeFixture
import roomescape.reservation.infrastructure.persistence.TimeRepository
import roomescape.reservation.web.TimeCreateRequest
import roomescape.util.TimeFixture
import java.time.LocalTime
class ReservationTimeServiceTest : FunSpec({
val reservationTimeRepository: ReservationTimeRepository = mockk()
class TimeServiceTest : FunSpec({
val timeRepository: TimeRepository = mockk()
val reservationRepository: ReservationRepository = mockk()
val reservationTimeService = ReservationTimeService(
reservationTimeRepository = reservationTimeRepository,
val timeService = TimeService(
timeRepository = timeRepository,
reservationRepository = reservationRepository
)
@ -28,13 +28,12 @@ class ReservationTimeServiceTest : FunSpec({
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
val id = 1L
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
every { reservationTimeRepository.findByIdOrNull(id) } returns null
every { timeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> {
reservationTimeService.findTimeById(id)
timeService.findById(id)
}.apply {
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND
errorType shouldBe ErrorType.TIME_NOT_FOUND
httpStatus shouldBe HttpStatus.BAD_REQUEST
}
}
@ -42,13 +41,12 @@ class ReservationTimeServiceTest : FunSpec({
context("addTime") {
test("중복된 시간이 있으면 409 에러를 던진다.") {
val request = ReservationTimeRequest(startAt = LocalTime.of(10, 0))
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
// Mocking the behavior of reservationTimeRepository.findByStartAt
every { reservationTimeRepository.existsByStartAt(request.startAt) } returns true
every { timeRepository.existsByStartAt(request.startAt) } returns true
shouldThrow<RoomescapeException> {
reservationTimeService.addTime(request)
timeService.create(request)
}.apply {
errorType shouldBe ErrorType.TIME_DUPLICATED
httpStatus shouldBe HttpStatus.CONFLICT
@ -60,29 +58,26 @@ class ReservationTimeServiceTest : FunSpec({
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
val id = 1L
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
every { reservationTimeRepository.findByIdOrNull(id) } returns null
every { timeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> {
reservationTimeService.removeTimeById(id)
timeService.deleteById(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
// Mocking the behavior of reservationRepository.findByReservationTime
every { reservationRepository.findByReservationTime(reservationTime) } returns listOf(mockk())
every { timeRepository.findByIdOrNull(id) } returns time
every { reservationRepository.findByTime(time) } returns listOf(mockk())
shouldThrow<RoomescapeException> {
reservationTimeService.removeTimeById(id)
timeService.deleteById(id)
}.apply {
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT
httpStatus shouldBe HttpStatus.CONFLICT

View File

@ -12,7 +12,7 @@ import roomescape.reservation.web.MyReservationResponse
import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture
import roomescape.util.TimeFixture
import roomescape.util.ThemeFixture
@DataJpaTest
@ -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)
}
@ -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)

View File

@ -10,7 +10,7 @@ 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.TimeFixture
import roomescape.util.ThemeFixture
import java.time.LocalDate
@ -25,7 +25,7 @@ class ReservationSearchSpecificationTest(
lateinit var confirmedNotPaidYesterday: ReservationEntity
lateinit var waitingTomorrow: ReservationEntity
lateinit var member: MemberEntity
lateinit var reservationTime: ReservationTimeEntity
lateinit var time: TimeEntity
lateinit var theme: ThemeEntity
"동일한 테마의 예약을 조회한다" {
@ -56,7 +56,7 @@ class ReservationSearchSpecificationTest(
"동일한 예약 시간의 예약을 조회한다" {
val spec = ReservationSearchSpecification()
.sameTimeId(reservationTime.id)
.sameTimeId(time.id)
.build()
val results: List<ReservationEntity> = reservationRepository.findAll(spec)
@ -136,7 +136,7 @@ class ReservationSearchSpecificationTest(
member = MemberFixture.create().also {
entityManager.persist(it)
}
reservationTime = ReservationTimeFixture.create().also {
time = TimeFixture.create().also {
entityManager.persist(it)
}
theme = ThemeFixture.create().also {
@ -144,7 +144,7 @@ class ReservationSearchSpecificationTest(
}
confirmedNow = ReservationFixture.create(
reservationTime = reservationTime,
time = time,
member = member,
theme = theme,
date = LocalDate.now(),
@ -154,7 +154,7 @@ class ReservationSearchSpecificationTest(
}
confirmedNotPaidYesterday = ReservationFixture.create(
reservationTime = reservationTime,
time = time,
member = member,
theme = theme,
date = LocalDate.now().minusDays(1),
@ -164,7 +164,7 @@ class ReservationSearchSpecificationTest(
}
waitingTomorrow = ReservationFixture.create(
reservationTime = reservationTime,
time = time,
member = member,
theme = theme,
date = LocalDate.now().plusDays(1),

View File

@ -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
}
}
})

View File

@ -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
@ -503,7 +503,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
)
@ -675,7 +675,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 +683,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 +710,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,7 +733,7 @@ class ReservationControllerTest(
fun createRequest(
theme: ThemeEntity = ThemeFixture.create(),
time: ReservationTimeEntity = ReservationTimeFixture.create(),
time: TimeEntity = TimeFixture.create(),
): ReservationRequest {
lateinit var reservationRequest: ReservationRequest

View File

@ -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.TimeFixture
import roomescape.util.RoomescapeApiTest
import roomescape.util.ThemeFixture
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,10 +50,10 @@ 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(
@ -95,7 +95,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(
@ -115,8 +115,8 @@ class ReservationTimeControllerTest(
Then("정상 응답") {
every {
reservationTimeService.addTime(request)
} returns ReservationTimeResponse(id = 1, startAt = time)
timeService.create(request)
} returns TimeCreateResponse(id = 1, startAt = time)
runPostTest(
mockMvc = mockMvc,
@ -135,7 +135,7 @@ class ReservationTimeControllerTest(
Then("동일한 시간이 존재하면 409 응답") {
every {
reservationTimeRepository.existsByStartAt(time)
timeRepository.existsByStartAt(time)
} returns true
runPostTest(
@ -160,7 +160,7 @@ class ReservationTimeControllerTest(
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = ReservationTimeFixture.create(),
body = TimeFixture.create(),
log = true
) {
status { is3xxRedirection() }
@ -180,7 +180,7 @@ class ReservationTimeControllerTest(
Then("정상 응답") {
every {
reservationTimeService.removeTimeById(1L)
timeService.deleteById(1L)
} returns Unit
runDeleteTest(
@ -195,7 +195,7 @@ class ReservationTimeControllerTest(
Then("없는 시간을 조회하면 400 응답") {
val id = 1L
every {
reservationTimeRepository.findByIdOrNull(id)
timeRepository.findByIdOrNull(id)
} returns null
runDeleteTest(
@ -206,7 +206,7 @@ class ReservationTimeControllerTest(
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,11 +214,11 @@ 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(
@ -258,13 +258,13 @@ class ReservationTimeControllerTest(
val themeId = 1L
When("저장된 예약 시간이 있으면") {
val times: List<ReservationTimeEntity> = listOf(
ReservationTimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
ReservationTimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
val times: List<TimeEntity> = listOf(
TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
)
every {
reservationTimeRepository.findAll()
timeRepository.findAll()
} returns times
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
@ -276,7 +276,7 @@ class ReservationTimeControllerTest(
id = 1L,
date = date,
theme = ThemeFixture.create(id = themeId),
reservationTime = times[0]
time = times[0]
)
)
@ -289,15 +289,15 @@ class ReservationTimeControllerTest(
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
}
}
}

View File

@ -3,11 +3,11 @@ 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.TimeFixture
import roomescape.util.ThemeFixture
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) }
}

View File

@ -12,7 +12,7 @@ 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.infrastructure.persistence.TimeEntity
import roomescape.reservation.web.ReservationRequest
import roomescape.reservation.web.WaitingRequest
import roomescape.theme.infrastructure.persistence.ThemeEntity
@ -54,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 {
@ -75,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),