generated from pricelees/issue-pr-template
[#41] 예약 스키마 재정의 #42
@ -2,150 +2,59 @@ package roomescape.reservation.docs
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
import io.swagger.v3.oas.annotations.headers.Header
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses
|
import io.swagger.v3.oas.annotations.responses.ApiResponses
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag
|
|
||||||
import jakarta.validation.Valid
|
import jakarta.validation.Valid
|
||||||
import org.springframework.http.HttpHeaders
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
|
||||||
import roomescape.auth.web.support.Admin
|
|
||||||
import roomescape.auth.web.support.LoginRequired
|
import roomescape.auth.web.support.LoginRequired
|
||||||
import roomescape.auth.web.support.MemberId
|
import roomescape.auth.web.support.MemberId
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.reservation.web.*
|
import roomescape.reservation.web.PendingReservationCreateRequest
|
||||||
import java.time.LocalDate
|
import roomescape.reservation.web.PendingReservationCreateResponse
|
||||||
|
import roomescape.reservation.web.ReservationCancelRequest
|
||||||
|
import roomescape.reservation.web.ReservationDetailRetrieveResponse
|
||||||
|
import roomescape.reservation.web.ReservationSummaryRetrieveListResponse
|
||||||
|
|
||||||
@Tag(name = "3. 예약 API", description = "예약 및 대기 정보를 추가 / 조회 / 삭제할 때 사용합니다.")
|
|
||||||
interface ReservationAPI {
|
interface ReservationAPI {
|
||||||
|
|
||||||
@Admin
|
@LoginRequired
|
||||||
@Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "결제 대기 예약 저장", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
fun createPendingReservation(
|
||||||
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
|
@Valid @RequestBody request: PendingReservationCreateRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<PendingReservationCreateResponse>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 확정", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findReservationsByMemberId(
|
fun confirmReservation(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@PathVariable("id") id: Long
|
||||||
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>>
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@Operation(summary = "관리자의 예약 검색", description = "특정 조건에 해당되는 예약 검색", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
|
|
||||||
)
|
|
||||||
fun searchReservations(
|
|
||||||
@RequestParam(required = false) themeId: Long?,
|
|
||||||
@RequestParam(required = false) memberId: Long?,
|
|
||||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
|
||||||
@RequestParam(required = false) dateTo: LocalDate?
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@Operation(summary = "관리자의 예약 취소", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(responseCode = "204", description = "성공"),
|
|
||||||
)
|
|
||||||
fun cancelReservationByAdmin(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 추가", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 취소", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "성공",
|
|
||||||
useReturnTypeSchema = true,
|
|
||||||
headers = [Header(
|
|
||||||
name = HttpHeaders.LOCATION,
|
|
||||||
description = "생성된 예약 정보 URL",
|
|
||||||
schema = Schema(example = "/reservations/1")
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fun createReservationWithPayment(
|
|
||||||
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@Operation(summary = "관리자 예약 추가", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "성공",
|
|
||||||
useReturnTypeSchema = true,
|
|
||||||
headers = [Header(
|
|
||||||
name = HttpHeaders.LOCATION,
|
|
||||||
description = "생성된 예약 정보 URL",
|
|
||||||
schema = Schema(example = "/reservations/1")
|
|
||||||
)],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fun createReservationByAdmin(
|
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
fun cancelReservation(
|
||||||
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
|
@PathVariable reservationId: Long,
|
||||||
|
@Valid @RequestBody request: ReservationCancelRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 대기 신청", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "회원별 예약 요약 목록 조회", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
ApiResponse(
|
fun findSummaryByMemberId(
|
||||||
responseCode = "201",
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
description = "성공",
|
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>>
|
||||||
useReturnTypeSchema = true,
|
|
||||||
headers = [Header(
|
|
||||||
name = HttpHeaders.LOCATION,
|
|
||||||
description = "생성된 예약 정보 URL",
|
|
||||||
schema = Schema(example = "/reservations/1")
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fun createWaiting(
|
|
||||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
|
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "특정 예약에 대한 상세 조회", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
ApiResponse(responseCode = "204", description = "성공"),
|
fun findDetailById(
|
||||||
)
|
@PathVariable("id") id: Long
|
||||||
fun cancelWaitingByMember(
|
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>>
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
|
||||||
|
|
||||||
@Admin
|
}
|
||||||
@Operation(summary = "대기 중인 예약 승인", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(responseCode = "200", description = "성공"),
|
|
||||||
)
|
|
||||||
fun confirmWaiting(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@Operation(summary = "대기 중인 예약 거절", tags = ["관리자 로그인이 필요한 API"])
|
|
||||||
@ApiResponses(
|
|
||||||
ApiResponse(responseCode = "204", description = "대기 중인 예약 거절 성공"),
|
|
||||||
)
|
|
||||||
fun rejectWaiting(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
|
||||||
}
|
|
||||||
@ -6,170 +6,59 @@ import org.springframework.http.ResponseEntity
|
|||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import roomescape.auth.web.support.MemberId
|
import roomescape.auth.web.support.MemberId
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
import roomescape.reservation.business.ReservationService
|
||||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
|
||||||
import roomescape.payment.infrastructure.client.TossPaymentClient
|
|
||||||
import roomescape.payment.web.PaymentCancelRequest
|
|
||||||
import roomescape.reservation.business.ReservationWriteService
|
|
||||||
import roomescape.reservation.business.ReservationFindService
|
|
||||||
import roomescape.reservation.business.ReservationWithPaymentService
|
|
||||||
import roomescape.reservation.docs.ReservationAPI
|
import roomescape.reservation.docs.ReservationAPI
|
||||||
import java.net.URI
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class ReservationController(
|
class ReservationController(
|
||||||
private val reservationWithPaymentService: ReservationWithPaymentService,
|
private val reservationService: ReservationService
|
||||||
private val reservationFindService: ReservationFindService,
|
|
||||||
private val reservationWriteService: ReservationWriteService,
|
|
||||||
private val paymentClient: TossPaymentClient
|
|
||||||
) : ReservationAPI {
|
) : ReservationAPI {
|
||||||
@GetMapping("/reservations")
|
|
||||||
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
@PostMapping("/reservations/pending")
|
||||||
val response: ReservationRetrieveListResponse = reservationFindService.findReservations()
|
override fun createPendingReservation(
|
||||||
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
|
@Valid @RequestBody request: PendingReservationCreateRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<PendingReservationCreateResponse>> {
|
||||||
|
val response = reservationService.createPendingReservation(memberId, request)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/reservations-mine")
|
@PatchMapping("/reservations/{id}/confirm")
|
||||||
override fun findReservationsByMemberId(
|
override fun confirmReservation(
|
||||||
|
@PathVariable("id") id: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
|
reservationService.confirmReservation(id)
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(CommonApiResponse())
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/reservations/{reservationId}/cancel")
|
||||||
|
override fun cancelReservation(
|
||||||
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
|
@PathVariable reservationId: Long,
|
||||||
|
@Valid @RequestBody request: ReservationCancelRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
|
reservationService.cancelReservation(memberId, reservationId, request)
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/reservations/summary")
|
||||||
|
override fun findSummaryByMemberId(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>> {
|
||||||
val response: MyReservationRetrieveListResponse = reservationFindService.findReservationsByMemberId(memberId)
|
val response = reservationService.findSummaryByMemberId(memberId)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/reservations/search")
|
@GetMapping("/reservations/{id}/detail")
|
||||||
override fun searchReservations(
|
override fun findDetailById(
|
||||||
@RequestParam(required = false) themeId: Long?,
|
@PathVariable("id") id: Long
|
||||||
@RequestParam(required = false) memberId: Long?,
|
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>> {
|
||||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
val response = reservationService.findDetailById(id)
|
||||||
@RequestParam(required = false) dateTo: LocalDate?
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
|
||||||
val response: ReservationRetrieveListResponse = reservationFindService.searchReservations(
|
|
||||||
themeId,
|
|
||||||
memberId,
|
|
||||||
dateFrom,
|
|
||||||
dateTo
|
|
||||||
)
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/reservations/{id}")
|
|
||||||
override fun cancelReservationByAdmin(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
|
||||||
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
|
||||||
reservationWriteService.deleteReservation(reservationId, memberId)
|
|
||||||
return ResponseEntity.noContent().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val paymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(reservationId, memberId)
|
|
||||||
val paymentCancelResponse = paymentClient.cancel(paymentCancelRequest)
|
|
||||||
reservationWithPaymentService.updateCanceledTime(
|
|
||||||
paymentCancelRequest.paymentKey,
|
|
||||||
paymentCancelResponse.canceledAt
|
|
||||||
)
|
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/reservations")
|
|
||||||
override fun createReservationWithPayment(
|
|
||||||
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
|
|
||||||
val paymentRequest: PaymentApproveRequest = reservationCreateWithPaymentRequest.toPaymentApproveRequest()
|
|
||||||
val paymentResponse: PaymentApproveResponse = paymentClient.confirm(paymentRequest)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val response: ReservationCreateResponse =
|
|
||||||
reservationWithPaymentService.createReservationAndPayment(
|
|
||||||
reservationCreateWithPaymentRequest,
|
|
||||||
paymentResponse,
|
|
||||||
memberId
|
|
||||||
)
|
|
||||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
|
||||||
.body(CommonApiResponse(response))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
val cancelRequest = PaymentCancelRequest(
|
|
||||||
paymentRequest.paymentKey,
|
|
||||||
paymentRequest.amount,
|
|
||||||
e.message!!
|
|
||||||
)
|
|
||||||
val paymentCancelResponse = paymentClient.cancel(cancelRequest)
|
|
||||||
reservationWithPaymentService.createCanceledPayment(
|
|
||||||
paymentCancelResponse,
|
|
||||||
paymentResponse.approvedAt,
|
|
||||||
paymentRequest.paymentKey
|
|
||||||
)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/reservations/admin")
|
|
||||||
override fun createReservationByAdmin(
|
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
|
|
||||||
val response: ReservationCreateResponse =
|
|
||||||
reservationWriteService.createReservationByAdmin(adminReservationRequest, memberId)
|
|
||||||
|
|
||||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
|
||||||
.body(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/reservations/waiting")
|
|
||||||
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
|
||||||
val response: ReservationRetrieveListResponse = reservationFindService.findAllWaiting()
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting")
|
|
||||||
override fun createWaiting(
|
|
||||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
|
|
||||||
val response: ReservationCreateResponse = reservationWriteService.createWaiting(
|
|
||||||
waitingCreateRequest,
|
|
||||||
memberId
|
|
||||||
)
|
|
||||||
|
|
||||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
|
||||||
.body(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/reservations/waiting/{id}")
|
|
||||||
override fun cancelWaitingByMember(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
|
||||||
reservationWriteService.deleteWaiting(reservationId, memberId)
|
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting/{id}/confirm")
|
|
||||||
override fun confirmWaiting(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
|
||||||
reservationWriteService.confirmWaiting(reservationId, memberId)
|
|
||||||
|
|
||||||
return ResponseEntity.ok().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting/{id}/reject")
|
|
||||||
override fun rejectWaiting(
|
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
|
||||||
@PathVariable("id") reservationId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
|
||||||
reservationWriteService.deleteWaiting(reservationId, memberId)
|
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user