Skip to content

Commit

Permalink
Merge pull request #53 from wafflestudio/feat/reservation
Browse files Browse the repository at this point in the history
Reservation API (인원 고려) 및 순환참조 에러 해결
  • Loading branch information
gs18113 authored Jan 8, 2025
2 parents 9863029 + 5c1fa87 commit 0b5db0e
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,31 @@ sealed class ReservationException(
) : DomainException(errorCode, httpStatusCode, msg, cause)

class ReservationUnavailable : ReservationException(
errorCode = 1009,
errorCode = 4001,
httpStatusCode = HttpStatus.CONFLICT,
msg = "Reservation is not Available"
)

class ReservationNotFound : ReservationException(
errorCode = 1010,
errorCode = 4002,
httpStatusCode = HttpStatus.NOT_FOUND,
msg = "Reservation doesn't exist"
)

class ReservationPermissionDenied : ReservationException(
errorCode = 1011,
errorCode = 4003,
httpStatusCode = HttpStatus.FORBIDDEN,
msg = "Permission Denied"
)

class MaxOccupancyExceeded : ReservationException(
errorCode = 4004,
httpStatusCode = HttpStatus.CONFLICT,
msg = "Max Occupancy Exceeded"
)

class ZeroGuests : ReservationException(
errorCode = 4005,
httpStatusCode = HttpStatus.BAD_REQUEST,
msg = "Number of Guests should be more than 0"
)
Original file line number Diff line number Diff line change
@@ -1,56 +1,55 @@
package com.example.toyTeam6Airbnb.reservation.controller

import com.example.toyTeam6Airbnb.reservation.persistence.ReservationEntity
import com.example.toyTeam6Airbnb.review.controller.Review
import com.example.toyTeam6Airbnb.room.controller.Room
import com.example.toyTeam6Airbnb.user.controller.User
import java.time.Instant
import java.time.LocalDate

data class Reservation(
val id: Long,
val user: User,
val room: Room,
val review: Review?,
val userId: Long,
val roomId: Long,
val reviewId: Long?,
val startDate: LocalDate,
val endDate: LocalDate,
val totalPrice: Double,
val numberofGuests: Int,
val createdAt: Instant,
val updatedAt: Instant
) {
companion object {
fun fromEntity(entity: ReservationEntity): Reservation {
return Reservation(
id = entity.id!!,
user = User.fromEntity(entity.user),
room = Room.fromEntity(entity.room),
review = entity.review?.let { Review.fromEntity(it) },
userId = entity.user.id!!,
roomId = entity.room.id!!,
reviewId = entity.review?.id,
startDate = entity.startDate,
endDate = entity.endDate,
totalPrice = entity.totalPrice,
createdAt = entity.createdAt,
updatedAt = entity.updatedAt
updatedAt = entity.updatedAt,
numberofGuests = entity.numberOfGuests
)
}
}

fun toDTO(): ReservationDTO {
return ReservationDTO(
id = this.id,
userId = this.user.id,
roomId = this.room.id,
userId = this.userId,
roomId = this.roomId,
startDate = this.startDate,
endDate = this.endDate
endDate = this.endDate,
numberOfGuests = this.numberofGuests
)
}
}

// Reservation DTO
// 추후, 가격이나 특정 프로퍼티 추가할 수 있음.
data class ReservationDTO(
val id: Long,
val roomId: Long,
val userId: Long,
val startDate: LocalDate,
val endDate: LocalDate
val endDate: LocalDate,
val numberOfGuests: Int
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class ReservationController(
User.fromEntity(principalDetails.getUser()),
request.roomId,
request.startDate,
request.endDate
request.endDate,
request.numberOfGuests
)

return ResponseEntity.status(HttpStatus.CREATED).body(reservation.toDTO())
Expand Down Expand Up @@ -68,7 +69,8 @@ class ReservationController(
User.fromEntity(principalDetails.getUser()),
reservationId,
request.startDate,
request.endDate
request.endDate,
request.numberOfGuests
)

return ResponseEntity.ok().body(reservation.toDTO())
Expand Down Expand Up @@ -96,29 +98,29 @@ class ReservationController(
return ResponseEntity.ok(reservations)
}

// 특정 room의 reservation을 모두 가져오는 API
@GetMapping("/room/{roomId}")
fun getReservationsByRoom(
@PathVariable roomId: Long
): ResponseEntity<List<ReservationDTO>> {
val reservations = reservationService.getReservationsByRoom(roomId).map { it.toDTO() }

return ResponseEntity.ok().body(reservations)
}

// 특정 date range의 reservation을 모두 가져오는 API
@GetMapping("/date")
fun getReservationsByDate(
@RequestParam startDate: String,
@RequestParam endDate: String
): ResponseEntity<List<ReservationDTO>> {
val reservations = reservationService.getReservationsByDate(
LocalDate.parse(startDate),
LocalDate.parse(endDate)
).map { it.toDTO() }

return ResponseEntity.ok().body(reservations)
}
// // 특정 room의 reservation을 모두 가져오는 API
// @GetMapping("/room/{roomId}")
// fun getReservationsByRoom(
// @PathVariable roomId: Long
// ): ResponseEntity<List<ReservationDTO>> {
// val reservations = reservationService.getReservationsByRoom(roomId).map { it.toDTO() }
//
// return ResponseEntity.ok().body(reservations)
// }

// // 특정 date range의 reservation을 모두 가져오는 API
// @GetMapping("/date")
// fun getReservationsByDate(
// @RequestParam startDate: String,
// @RequestParam endDate: String
// ): ResponseEntity<List<ReservationDTO>> {
// val reservations = reservationService.getReservationsByDate(
// LocalDate.parse(startDate),
// LocalDate.parse(endDate)
// ).map { it.toDTO() }
//
// return ResponseEntity.ok().body(reservations)
// }

// 특정 room의 특정 month의 available/unavailable date를 가져오는 API
@GetMapping("/availability/{roomId}")
Expand All @@ -140,12 +142,14 @@ class ReservationController(
class CreateReservationRequest(
val roomId: Long,
val startDate: LocalDate,
val endDate: LocalDate
val endDate: LocalDate,
val numberOfGuests: Int
)

class UpdateReservationRequest(
val startDate: LocalDate,
val endDate: LocalDate
val endDate: LocalDate,
val numberOfGuests: Int
)

// 특정 방의 예약 가능날짜와 불가능한 날짜 반환 DTO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ReservationEntity(
@JoinColumn(name = "room_id", nullable = false)
val room: RoomEntity,

@OneToOne(mappedBy = "reservation", cascade = [CascadeType.ALL], orphanRemoval = true)
@OneToOne(mappedBy = "reservation", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
val review: ReviewEntity?,

@Column(nullable = false)
Expand All @@ -50,7 +50,11 @@ class ReservationEntity(
var createdAt: Instant = Instant.now(),

@Column(nullable = false)
var updatedAt: Instant = Instant.now()
var updatedAt: Instant = Instant.now(),

@Column(nullable = false)
var numberOfGuests: Int

) {
@PrePersist
fun onPrePersist() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ interface ReservationService {
user: User,
roomId: Long,
startDate: LocalDate,
endDate: LocalDate
endDate: LocalDate,
numberOfGuests: Int
): Reservation

fun deleteReservation(
Expand All @@ -24,7 +25,9 @@ interface ReservationService {
user: User,
reservationId: Long,
startDate: LocalDate,
endDate: LocalDate
endDate: LocalDate,
numberOfGuests: Int

): Reservation

fun getReservation(
Expand All @@ -35,14 +38,14 @@ interface ReservationService {
user: User
): List<Reservation>

fun getReservationsByRoom(
roomId: Long
): List<Reservation>

fun getReservationsByDate(
startDate: LocalDate,
endDate: LocalDate
): List<Reservation>
// fun getReservationsByRoom(
// roomId: Long
// ): List<Reservation>
//
// fun getReservationsByDate(
// startDate: LocalDate,
// endDate: LocalDate
// ): List<Reservation>

fun getAvailabilityByMonth(roomId: Long, yearMonth: YearMonth): RoomAvailabilityResponse
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.example.toyTeam6Airbnb.reservation.service

import com.example.toyTeam6Airbnb.reservation.MaxOccupancyExceeded
import com.example.toyTeam6Airbnb.reservation.ReservationNotFound
import com.example.toyTeam6Airbnb.reservation.ReservationPermissionDenied
import com.example.toyTeam6Airbnb.reservation.ReservationUnavailable
import com.example.toyTeam6Airbnb.reservation.ZeroGuests
import com.example.toyTeam6Airbnb.reservation.controller.Reservation
import com.example.toyTeam6Airbnb.reservation.controller.RoomAvailabilityResponse
import com.example.toyTeam6Airbnb.reservation.persistence.ReservationEntity
Expand Down Expand Up @@ -32,20 +34,28 @@ class ReservationServiceImpl(
user: User,
roomId: Long,
startDate: LocalDate,
endDate: LocalDate
endDate: LocalDate,
numberOfGuests: Int
): Reservation {
val userEntity = userRepository.findByIdOrNull(user.id) ?: throw AuthenticateException()
val roomEntity = roomRepository.findByIdOrNull(roomId) ?: throw RoomNotFoundException()

if (!isAvailable(roomEntity, startDate, endDate)) throw ReservationUnavailable()

// 예약 인원수가 초과할 경우 예외 발생
if (numberOfGuests > roomEntity.maxOccupancy) throw MaxOccupancyExceeded()

// 예약 인원이 0명인 경우 예외 발생
if (numberOfGuests == 0) throw ZeroGuests()

val reservationEntity = ReservationEntity(
user = userEntity,
room = roomEntity,
review = null,
startDate = startDate,
endDate = endDate,
totalPrice = roomEntity.price * ChronoUnit.DAYS.between(startDate, endDate)
totalPrice = roomEntity.price * ChronoUnit.DAYS.between(startDate, endDate),
numberOfGuests = numberOfGuests
).let {
reservationRepository.save(it)
}
Expand Down Expand Up @@ -79,7 +89,8 @@ class ReservationServiceImpl(
user: User,
reservationId: Long,
startDate: LocalDate,
endDate: LocalDate
endDate: LocalDate,
numberOfGuests: Int
): Reservation {
val userEntity = userRepository.findByIdOrNull(user.id) ?: throw AuthenticateException()
val reservationEntity = reservationRepository.findByIdOrNull(reservationId) ?: throw ReservationNotFound()
Expand All @@ -89,9 +100,13 @@ class ReservationServiceImpl(

if (!isAvailable(roomEntity, startDate, endDate, reservationEntity.id)) throw ReservationUnavailable()

if (numberOfGuests > roomEntity.maxOccupancy) throw MaxOccupancyExceeded()
if (numberOfGuests == 0) throw ZeroGuests()

reservationEntity.startDate = startDate
reservationEntity.endDate = endDate
reservationEntity.totalPrice = roomEntity.price * ChronoUnit.DAYS.between(startDate, endDate)
reservationEntity.numberOfGuests = numberOfGuests
reservationRepository.save(reservationEntity)

return Reservation.fromEntity(reservationEntity)
Expand All @@ -111,21 +126,21 @@ class ReservationServiceImpl(
return reservationRepository.findAllByUser(userEntity).map(Reservation::fromEntity)
}

@Transactional
override fun getReservationsByRoom(roomId: Long): List<Reservation> {
val roomEntity = roomRepository.findByIdOrNull(roomId) ?: throw RoomNotFoundException()

return reservationRepository.findAllByRoom(roomEntity).map(Reservation::fromEntity)
}

@Transactional
override fun getReservationsByDate(startDate: LocalDate, endDate: LocalDate): List<Reservation> {
val reservations = reservationRepository.findAll().filter { reservation ->
startDate < reservation.endDate && endDate > reservation.startDate
}

return reservations.map(Reservation::fromEntity)
}
// @Transactional
// override fun getReservationsByRoom(roomId: Long): List<Reservation> {
// val roomEntity = roomRepository.findByIdOrNull(roomId) ?: throw RoomNotFoundException()
//
// return reservationRepository.findAllByRoom(roomEntity).map(Reservation::fromEntity)
// }

// @Transactional
// override fun getReservationsByDate(startDate: LocalDate, endDate: LocalDate): List<Reservation> {
// val reservations = reservationRepository.findAll().filter { reservation ->
// startDate < reservation.endDate && endDate > reservation.startDate
// }
//
// return reservations.map(Reservation::fromEntity)
// }

// 특정 방의 해당 월의 예약 가능한 날짜와 예약 불가능한 날짜를 가져오는 API를 위한 서비스 로직
@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ sealed class ReviewException(
) : DomainException(errorCode, httpStatusCode, msg, cause)

class ReviewNotFound : ReviewException(
errorCode = 1012,
errorCode = 3001,
httpStatusCode = HttpStatus.CONFLICT,
msg = "Review doesn't exist"
)

class ReviewPermissionDenied : ReviewException(
errorCode = 1013,
errorCode = 3002,
httpStatusCode = HttpStatus.FORBIDDEN,
msg = "Permission Denied"
)
Loading

0 comments on commit 0b5db0e

Please sign in to comment.