From 6d96ae3c93ed861c0a96a8aea51802d3efe14cee Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 13:14:29 +0900 Subject: [PATCH 01/12] Profile Exception --- .../profile/ProfileException.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt new file mode 100644 index 0000000..170a670 --- /dev/null +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt @@ -0,0 +1,24 @@ +package com.example.toyTeam6Airbnb.profile + +import com.example.toyTeam6Airbnb.DomainException +import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode + +sealed class ProfileException( + errorCode: Int, + httpStatusCode: HttpStatusCode, + msg: String, + cause: Throwable? = null +) : DomainException(errorCode, httpStatusCode, msg, cause) + +class ProfileAlreadyExistException : ProfileException( + errorCode = 5001, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "Profile already exists" +) + +class ProfileNotFoundException : ProfileException( + errorCode = 5002, + httpStatusCode = HttpStatus.NOT_FOUND, + msg = "Profile not found" +) From 2e71246549e2f5ee002671e2c99dad8333af3a5e Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 13:15:25 +0900 Subject: [PATCH 02/12] =?UTF-8?q?Profile=20bio=20field=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20superhost=20=ED=8C=90=EB=B3=84=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80,=20=EC=9E=98=EB=AA=BB=EB=90=9C?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/controller/Profile.kt | 2 + .../profile/controller/ProfileController.kt | 29 ++++++++----- .../profile/persistence/ProfileEntity.kt | 3 ++ .../profile/persistence/ProfileRepository.kt | 2 + .../profile/service/ProfileService.kt | 20 +++++++-- .../profile/service/ProfileServiceImpl.kt | 43 ++++++++++++++----- 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt index f5ab5fc..3d52e85 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt @@ -6,6 +6,7 @@ data class Profile( val id: Long, val userId: Long, val nickname: String, + val bio: String, val isSuperhost: Boolean ) { companion object { @@ -14,6 +15,7 @@ data class Profile( id = profileEntity.id, userId = profileEntity.user.id!!, nickname = profileEntity.nickname, + bio = profileEntity.bio, isSuperhost = profileEntity.isSuperhost ) } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt index 7b51c3b..9a2c7c3 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt @@ -4,6 +4,7 @@ import com.example.toyTeam6Airbnb.profile.service.ProfileService import com.example.toyTeam6Airbnb.user.controller.PrincipalDetails import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping @@ -22,34 +23,40 @@ class ProfileController( @GetMapping @Operation(summary = "Get User Profile", description = "Get the profile of the current user") - fun getCurrentUserProfile(@AuthenticationPrincipal principalDetails: PrincipalDetails): ResponseEntity { + fun getCurrentUserProfile( + @AuthenticationPrincipal principalDetails: PrincipalDetails + ): ResponseEntity { val profile = profileService.getCurrentUserProfile(principalDetails.getUser()) - return if (profile != null) { - ResponseEntity.ok(Profile.fromEntity(profile)) - } else { - ResponseEntity.notFound().build() - } + return ResponseEntity.ok(Profile.fromEntity(profile)) } @PutMapping @Operation(summary = "Update User Profile", description = "Update the profile of the current user") - fun updateCurrentUserProfile(@AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: UpdateProfileRequest): ResponseEntity { + fun updateCurrentUserProfile( + @AuthenticationPrincipal principalDetails: PrincipalDetails, + @RequestBody request: UpdateProfileRequest + ): ResponseEntity { val updatedProfile = profileService.updateCurrentUserProfile(principalDetails.getUser(), request) return ResponseEntity.ok(Profile.fromEntity(updatedProfile)) } @PostMapping @Operation(summary = "Add Profile to User", description = "Add a profile to the current user, only for users logged in with social login") - fun addProfileToCurrentUser(@AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: CreateProfileRequest): ResponseEntity { + fun addProfileToCurrentUser( + @AuthenticationPrincipal principalDetails: PrincipalDetails, + @RequestBody request: CreateProfileRequest + ): ResponseEntity { val newProfile = profileService.addProfileToCurrentUser(principalDetails.getUser(), request) - return ResponseEntity.status(201).body(Profile.fromEntity(newProfile)) + return ResponseEntity.status(HttpStatus.CREATED).body(Profile.fromEntity(newProfile)) } } data class UpdateProfileRequest( - val nickname: String + val nickname: String, + val bio: String ) data class CreateProfileRequest( - val nickname: String + val nickname: String, + val bio: String ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt index c5b6fc9..1fc7005 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt @@ -25,6 +25,9 @@ class ProfileEntity( @Column(nullable = false) var nickname: String, + @Column(nullable = false) + var bio: String, + @Column(nullable = false) var isSuperhost: Boolean = false ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileRepository.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileRepository.kt index 1b156be..8a15b7f 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileRepository.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileRepository.kt @@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository interface ProfileRepository : JpaRepository { fun findByUser(user: UserEntity): ProfileEntity? + + fun existsByUser(user: UserEntity): Boolean } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt index ca0cbfc..cf1ed8a 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt @@ -6,7 +6,21 @@ import com.example.toyTeam6Airbnb.profile.persistence.ProfileEntity import com.example.toyTeam6Airbnb.user.persistence.UserEntity interface ProfileService { - fun getCurrentUserProfile(user: UserEntity): ProfileEntity? - fun updateCurrentUserProfile(user: UserEntity, request: UpdateProfileRequest): ProfileEntity - fun addProfileToCurrentUser(user: UserEntity, request: CreateProfileRequest): ProfileEntity + fun getCurrentUserProfile( + user: UserEntity + ): ProfileEntity + + fun updateCurrentUserProfile( + user: UserEntity, + request: UpdateProfileRequest + ): ProfileEntity + + fun addProfileToCurrentUser( + user: UserEntity, + request: CreateProfileRequest + ): ProfileEntity + + fun updateSuperhostStatus( + profile: ProfileEntity + ) } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt index ebbfed7..bc5de4e 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt @@ -1,38 +1,59 @@ package com.example.toyTeam6Airbnb.profile.service +import com.example.toyTeam6Airbnb.profile.ProfileAlreadyExistException +import com.example.toyTeam6Airbnb.profile.ProfileNotFoundException import com.example.toyTeam6Airbnb.profile.controller.CreateProfileRequest import com.example.toyTeam6Airbnb.profile.controller.UpdateProfileRequest import com.example.toyTeam6Airbnb.profile.persistence.ProfileEntity import com.example.toyTeam6Airbnb.profile.persistence.ProfileRepository +import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.persistence.UserEntity import com.example.toyTeam6Airbnb.user.persistence.UserRepository -import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class ProfileServiceImpl( private val profileRepository: ProfileRepository, - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val roomRepository: RoomRepository ) : ProfileService { - override fun getCurrentUserProfile(@AuthenticationPrincipal user: UserEntity): ProfileEntity? { - return profileRepository.findByUser(user) + override fun getCurrentUserProfile( + user: UserEntity + ): ProfileEntity { + return profileRepository.findByUser(user) ?: throw ProfileNotFoundException() } @Transactional - override fun updateCurrentUserProfile(user: UserEntity, request: UpdateProfileRequest): ProfileEntity { - val profile = profileRepository.findByUser(user) ?: throw IllegalArgumentException("Profile not found") + override fun updateCurrentUserProfile( + user: UserEntity, + request: UpdateProfileRequest + ): ProfileEntity { + val profile = profileRepository.findByUser(user) ?: throw ProfileNotFoundException() + profile.nickname = request.nickname + profile.bio = request.bio + updateSuperhostStatus(profile) + return profileRepository.save(profile) } @Transactional - override fun addProfileToCurrentUser(user: UserEntity, request: CreateProfileRequest): ProfileEntity { - if (profileRepository.findByUser(user) != null) { - throw IllegalArgumentException("Profile already exists") - } - val profile = ProfileEntity(user = user, nickname = request.nickname) + override fun addProfileToCurrentUser( + user: UserEntity, + request: CreateProfileRequest + ): ProfileEntity { + if (profileRepository.existsByUser(user)) throw ProfileAlreadyExistException() + + val profile = ProfileEntity(user = user, nickname = request.nickname, bio = request.bio) + updateSuperhostStatus(profile) return profileRepository.save(profile) } + + @Transactional + override fun updateSuperhostStatus(profile: ProfileEntity) { + val roomCount = roomRepository.countByHost(profile.user) + profile.isSuperhost = roomCount >= 5 + } } From 477c69428289b9b60cdfdf51a7ac5f86c5749f6a Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 13:15:51 +0900 Subject: [PATCH 03/12] =?UTF-8?q?review=20=EA=B4=80=EB=A0=A8=20=EC=A4=91?= =?UTF-8?q?=EA=B0=84=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.kt | 19 ++++++++++++++++--- .../review/service/ReviewService.kt | 15 ++++++++++++--- .../review/service/ReviewServiceImpl.kt | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt index 1de7191..f705e8f 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt @@ -16,6 +16,9 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable @RestController @RequestMapping("/api/v1/reviews") @@ -43,9 +46,10 @@ class ReviewController( @GetMapping("/room/{roomId}") @Operation(summary = "Get Reviews", description = "Get all reviews for a room") fun getReviews( - @PathVariable roomId: Long - ): ResponseEntity> { - val reviews = reviewService.getReviews(roomId).map { it.toDTO() } + @PathVariable roomId: Long, + @RequestParam pageable: Pageable + ): ResponseEntity> { + val reviews = reviewService.getReviewsByRoom(roomId, pageable).map { it.toDTO() } return ResponseEntity.ok(reviews) } @@ -58,6 +62,15 @@ class ReviewController( return ResponseEntity.ok(review.toDTO()) } + @GetMapping("/user/{userId}") + @Operation(summary = "Get Reviews by User", description = "Get all reviews by a user") + fun getReviewsByUser( + @PathVariable userId: Long + ): ResponseEntity> { + val reviews = reviewService.getReviewsByUser(userId).map { it.toDTO() } + return ResponseEntity.ok(reviews) + } + // Review에 수정 사항이 추가되면 파라미터 수정 필요 @PutMapping("/{reviewId}") @Operation(summary = "Update Review", description = "Update an existing review") diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt index 986b6ce..5fa8712 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt @@ -2,6 +2,9 @@ package com.example.toyTeam6Airbnb.review.service import com.example.toyTeam6Airbnb.review.controller.Review import com.example.toyTeam6Airbnb.user.controller.User +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable + interface ReviewService { fun createReview( @@ -12,9 +15,15 @@ interface ReviewService { rating: Int ): Review - fun getReviews( - roomId: Long - ): List + fun getReviewsByRoom( + roomId: Long, + pageable: Pageable + ): Page + + fun getReviewsByUser( + userId: Long, + pageable: Pageable + ): Page fun getReviewDetails( reviewId: Long diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt index 594ea41..33761b2 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt @@ -7,7 +7,7 @@ import com.example.toyTeam6Airbnb.review.ReviewPermissionDeniedException import com.example.toyTeam6Airbnb.review.controller.Review import com.example.toyTeam6Airbnb.review.persistence.ReviewEntity import com.example.toyTeam6Airbnb.review.persistence.ReviewRepository -import com.example.toyTeam6Airbnb.room.RoomNotFoundException +import com.example.toyTeam6Airbnb.profile.RoomNotFoundException import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.AuthenticateException import com.example.toyTeam6Airbnb.user.controller.User From 06a50fc2bd757722c7de3c119f13043dd4fbdd2c Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 13:16:50 +0900 Subject: [PATCH 04/12] =?UTF-8?q?=EB=82=98=EB=A8=B8=EC=A7=80=20=EC=A4=91?= =?UTF-8?q?=EA=B0=84=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/service/ReservationServiceImpl.kt | 2 +- .../example/toyTeam6Airbnb/room/persistence/RoomRepository.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt index ce745d6..23b03fb 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt @@ -9,7 +9,7 @@ import com.example.toyTeam6Airbnb.reservation.controller.Reservation import com.example.toyTeam6Airbnb.reservation.controller.RoomAvailabilityResponse import com.example.toyTeam6Airbnb.reservation.persistence.ReservationEntity import com.example.toyTeam6Airbnb.reservation.persistence.ReservationRepository -import com.example.toyTeam6Airbnb.room.RoomNotFoundException +import com.example.toyTeam6Airbnb.profile.RoomNotFoundException import com.example.toyTeam6Airbnb.room.persistence.RoomEntity import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.AuthenticateException diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomRepository.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomRepository.kt index 455ea5f..1a006f8 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomRepository.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomRepository.kt @@ -1,5 +1,6 @@ package com.example.toyTeam6Airbnb.room.persistence +import com.example.toyTeam6Airbnb.user.persistence.UserEntity import jakarta.persistence.LockModeType import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -65,4 +66,6 @@ interface RoomRepository : JpaRepository, JpaSpecificationExec @Param("detail") detail: String?, pageable: Pageable ): Page + + fun countByHost(userEntity: UserEntity): Int } From 32200f9838afa189de6aaa53f067346b26e30fc5 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:40:47 +0900 Subject: [PATCH 05/12] =?UTF-8?q?user=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toyTeam6Airbnb/Image/ImageController.kt | 92 ------------------- .../toyTeam6Airbnb/Image/ImageService.kt | 92 ------------------- .../{room => }/ValidatePageable.kt | 0 .../toyTeam6Airbnb/user/UserException.kt | 6 ++ .../user/controller/PrincipalDetails.kt | 6 +- .../user/persistence/UserEntity.kt | 10 +- 6 files changed, 19 insertions(+), 187 deletions(-) delete mode 100644 src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageController.kt delete mode 100644 src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageService.kt rename src/main/kotlin/com/example/toyTeam6Airbnb/{room => }/ValidatePageable.kt (100%) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageController.kt deleted file mode 100644 index 2ddb38f..0000000 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageController.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.example.toyTeam6Airbnb.Image - -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.multipart.MultipartFile -import java.nio.file.Paths -import java.util.Date - -@RestController -@RequestMapping("/api/images") -class ImageController( - private val imageService: ImageService -) { - - // 이미지 업로드 엔드포인트 - @PostMapping("/upload") - fun uploadImage( - @RequestParam("file") file: MultipartFile, - @RequestParam("key") key: String - ): ResponseEntity { - return try { - val tempFile = Paths.get(System.getProperty("java.io.tmpdir"), file.originalFilename).toFile() - file.transferTo(tempFile) // MultipartFile을 임시 파일로 저장 - - imageService.uploadFile(tempFile.absolutePath, key) // S3에 업로드 - tempFile.delete() // 임시 파일 삭제 - - ResponseEntity("Image uploaded successfully with key: $key", HttpStatus.OK) - } catch (e: Exception) { - ResponseEntity("Failed to upload image: ${e.message}", HttpStatus.INTERNAL_SERVER_ERROR) - } - } - - @GetMapping("/{key}") - fun getImageSignedUrl( - @PathVariable("key") key: String - ): ResponseEntity { - return try { - val expirationDate = Date(System.currentTimeMillis() + 3600_000) // 1시간 유효 - - val signedUrl = imageService.generateSignedUrl( - "https://d3m9s5wmwvsq01.cloudfront.net", - key, - expirationDate - ) - - ResponseEntity.ok(signedUrl) - } catch (e: Exception) { - ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Failed to generate signed URL: ${e.message}") - } - } -} - -// // 이미지 다운로드 엔드포인트 -// @GetMapping("/download") -// fun downloadImage( -// @RequestParam("key") key: String, -// @RequestParam("destination") destination: String -// ): ResponseEntity { -// return try { -// imageService.downloadFile(key, destination) // S3에서 다운로드 -// ResponseEntity("Image downloaded successfully to: $destination", HttpStatus.OK) -// } catch (e: Exception) { -// ResponseEntity("Failed to download image: ${e.message}", HttpStatus.INTERNAL_SERVER_ERROR) -// } -// } - -// // 이미지를 화면에 표시, 보안 취약 -// @GetMapping("/{key}") -// fun getImage( -// @PathVariable("key") key: String -// ): ResponseEntity { -// return try { -// // CloudFront 배포 URL -// val cloudFrontUrl = "https://d3m9s5wmwvsq01.cloudfront.net/$key" -// -// // 302 Redirect -// ResponseEntity.status(HttpStatus.FOUND) -// .header("Location", cloudFrontUrl) -// .build() -// } catch (e: Exception) { -// ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(null) -// } -// } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageService.kt deleted file mode 100644 index eda7c22..0000000 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/Image/ImageService.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.example.toyTeam6Airbnb.Image - -import com.amazonaws.services.cloudfront.CloudFrontUrlSigner -import com.amazonaws.services.cloudfront.util.SignerUtils -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Service -import software.amazon.awssdk.core.sync.RequestBody -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.PutObjectRequest -import java.io.File -import java.nio.file.Paths -import java.security.PrivateKey -import java.util.Date - -@Service -class ImageService() { - - @Value("\${cloudfront.private-key}") - private lateinit var privateKey: String - - @Value("\${cloudfront.key-pair-id}") - private lateinit var keyPairId: String - private val s3Client: S3Client = S3Client.builder() - .region(Region.AP_NORTHEAST_2) // 원하는 리전 설정 - .build() - private val bucketName: String = "waffle-team6-storage" - private val cloudFrontUrl: String = "https://d3m9s5wmwvsq01.cloudfront.net" - - // 파일 업로드 메서드 - fun uploadFile(filePath: String, key: String) { - s3Client.putObject( - PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(), - RequestBody.fromFile(Paths.get(filePath)) - ) - } - -// // 파일 다운로드 메서드 -// @Transactional -// fun downloadFile(key: String, destination: String) { -// s3Client.getObject( -// GetObjectRequest.builder() -// .bucket(bucketName) -// .key(key) -// .build(), -// Paths.get(destination) -// ) -// } - - // CloudFront signed URL 생성 메서드 - fun generateSignedUrl( - domain: String, - key: String, - expiration: Date - ): String { - // privateKey와 keyPairId는 GitHub Secrets에서 가져온 값이 자동으로 주입됨 - return createSignedUrl(domain, key, privateKey, keyPairId, expiration) - } - - // CloudFront 서명 URL 생성 메서드 - // createSignedUrl 메서드 구현 - private fun createSignedUrl( - domain: String, - key: String, - privateKey: String, - keyPairId: String, - expiration: Date - ): String { - try { - // 1. 임시 파일 생성 및 비공개 키 쓰기 - val tempFile = File.createTempFile("privateKey", ".pem") - tempFile.deleteOnExit() // 애플리케이션 종료 시 자동 삭제 - tempFile.writeText(privateKey) - - // 2. Private Key 객체 로드 - val privateKeyObj: PrivateKey = SignerUtils.loadPrivateKey(tempFile) - - // 3. CloudFront 서명된 URL 생성 - return CloudFrontUrlSigner.getSignedURLWithCannedPolicy( - "$domain/$key", // 리소스 URL - keyPairId, // Key Pair ID - privateKeyObj, // PrivateKey 객체 - expiration // 만료 시간 - ) - } catch (e: Exception) { - throw RuntimeException("Failed to create signed URL: ${e.message}", e) - } - } -} diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/ValidatePageable.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt similarity index 100% rename from src/main/kotlin/com/example/toyTeam6Airbnb/room/ValidatePageable.kt rename to src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/user/UserException.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/user/UserException.kt index 50bb0b9..f98a327 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/user/UserException.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/user/UserException.kt @@ -52,3 +52,9 @@ class OAuthException : UserException( httpStatusCode = HttpStatus.BAD_REQUEST, msg = "OAuth Exception" ) + +class UserNotFoundException : UserException( + errorCode = 1008, + httpStatusCode = HttpStatus.NOT_FOUND, + msg = "User not found" +) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/user/controller/PrincipalDetails.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/user/controller/PrincipalDetails.kt index 2f78ee2..1618fb9 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/user/controller/PrincipalDetails.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/user/controller/PrincipalDetails.kt @@ -14,6 +14,10 @@ class PrincipalDetails( return user } + fun getId(): Long { + return user.id!! + } + override fun getAttributes(): MutableMap { return attributes } @@ -26,7 +30,7 @@ class PrincipalDetails( return listOf(GrantedAuthority { user.username }) } - override fun getPassword(): String? { + override fun getPassword(): String { return user.password } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/user/persistence/UserEntity.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/user/persistence/UserEntity.kt index 5663c3c..801a865 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/user/persistence/UserEntity.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/user/persistence/UserEntity.kt @@ -43,11 +43,17 @@ class UserEntity( val reviews: List = mutableListOf(), @OneToOne(mappedBy = "user", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true) - val profile: ProfileEntity? = null + val profile: ProfileEntity? = null, + + @Column + var imageUploadUrl: String? = null, + + @Column + var imageDownloadUrl: String? = null ) { fun isSuperhost(): Boolean { - return profile?.isSuperhost == true + return profile?.isSuperHost == true } } From f3c9fecd452cfed26345984ae3391825b4f5adee Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:41:08 +0900 Subject: [PATCH 06/12] =?UTF-8?q?room=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toyTeam6Airbnb/room/controller/Room.kt | 90 +++++++------------ .../room/controller/RoomController.kt | 63 +++---------- .../room/persistence/RoomEntity.kt | 6 +- .../room/service/RoomService.kt | 10 +-- .../room/service/RoomServiceImpl.kt | 24 +++-- 5 files changed, 62 insertions(+), 131 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/Room.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/Room.kt index 63941a1..47dc23b 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/Room.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/Room.kt @@ -3,6 +3,7 @@ package com.example.toyTeam6Airbnb.room.controller import com.example.toyTeam6Airbnb.room.persistence.Address import com.example.toyTeam6Airbnb.room.persistence.Price import com.example.toyTeam6Airbnb.room.persistence.RoomDetails +import com.example.toyTeam6Airbnb.room.persistence.RoomEntity import com.example.toyTeam6Airbnb.room.persistence.RoomType import java.time.Instant @@ -13,17 +14,12 @@ data class Room( val description: String, val type: RoomType, val address: Address, - val roomDetails: RoomDetails, val price: Price, val maxOccupancy: Int, - val rating: Double, - val reviewCount: Int, - val isSuperhost: Boolean, - val createdAt: Instant, - val updatedAt: Instant + val rating: Double ) { companion object { - fun fromEntity(entity: com.example.toyTeam6Airbnb.room.persistence.RoomEntity): Room { + fun fromEntity(entity: RoomEntity): Room { var averageRating = entity.reviews.map { it.rating }.average() if (averageRating.isNaN()) averageRating = 0.0 @@ -34,64 +30,14 @@ data class Room( description = entity.description, type = entity.type, address = entity.address, - roomDetails = entity.roomDetails, price = entity.price, maxOccupancy = entity.maxOccupancy, - rating = averageRating, - reviewCount = entity.reviews.size, - isSuperhost = entity.host.isSuperhost(), - createdAt = entity.createdAt, - updatedAt = entity.updatedAt + rating = averageRating ) } } - - fun toDTO(): RoomDTO { - return RoomDTO( - id = this.id, - hostId = this.hostId, - name = this.name, - description = this.description, - type = this.type, - address = this.address, - price = this.price, - maxOccupancy = this.maxOccupancy, - rating = this.rating - ) - } - - fun toDetailsDTO(): RoomDetailsDTO { - return RoomDetailsDTO( - id = this.id, - hostId = this.hostId, - name = this.name, - description = this.description, - type = this.type, - address = this.address, - roomDetails = this.roomDetails, - price = this.price, - maxOccupancy = this.maxOccupancy, - rating = this.rating, - reviewCount = this.reviewCount, - isSuperhost = this.isSuperhost, - createdAt = this.createdAt, - updatedAt = this.updatedAt - ) - } } -data class RoomDTO( - val id: Long, - val hostId: Long, - val name: String, - val description: String, - val type: RoomType, - val address: Address, - val price: Price, - val maxOccupancy: Int, - val rating: Double -) - data class RoomDetailsDTO( val id: Long, val hostId: Long, @@ -104,7 +50,31 @@ data class RoomDetailsDTO( val maxOccupancy: Int, val rating: Double, val reviewCount: Int, - val isSuperhost: Boolean, + val isSuperHost: Boolean, val createdAt: Instant, val updatedAt: Instant -) +) { + companion object { + fun fromEntity(entity: RoomEntity): RoomDetailsDTO { + var averageRating = entity.reviews.map { it.rating }.average() + if (averageRating.isNaN()) averageRating = 0.0 + + return RoomDetailsDTO( + id = entity.id!!, + hostId = entity.host.id!!, + name = entity.name, + description = entity.description, + type = entity.type, + address = entity.address, + roomDetails = entity.roomDetails, + price = entity.price, + maxOccupancy = entity.maxOccupancy, + rating = averageRating, + reviewCount = entity.reviews.size, + isSuperHost = entity.host.isSuperhost(), + createdAt = entity.createdAt, + updatedAt = entity.updatedAt + ) + } + } +} diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt index 71b1f4e..810c538 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt @@ -1,14 +1,13 @@ package com.example.toyTeam6Airbnb.room.controller -import com.example.toyTeam6Airbnb.review.persistence.ReviewEntity import com.example.toyTeam6Airbnb.room.persistence.Address import com.example.toyTeam6Airbnb.room.persistence.Price import com.example.toyTeam6Airbnb.room.persistence.RoomDetails import com.example.toyTeam6Airbnb.room.persistence.RoomType import com.example.toyTeam6Airbnb.room.service.RoomService -import com.example.toyTeam6Airbnb.room.validatePageable import com.example.toyTeam6Airbnb.user.controller.PrincipalDetails import com.example.toyTeam6Airbnb.user.controller.User +import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -24,7 +23,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import java.time.Instant import java.time.LocalDate @RestController @@ -34,6 +32,7 @@ class RoomController( private val roomService: RoomService ) { @PostMapping("/rooms") + @Operation(summary = "방 생성", description = "방을 생성합니다") fun createRoom( @RequestBody request: CreateRoomRequest, @AuthenticationPrincipal principalDetails: PrincipalDetails @@ -49,38 +48,29 @@ class RoomController( maxOccupancy = request.maxOccupancy ) - return ResponseEntity.status(HttpStatus.CREATED).body(room.toDetailsDTO()) + return ResponseEntity.status(HttpStatus.CREATED).body(room) } @GetMapping("/rooms/main") + @Operation(summary = "메인 페이지 방 조회", description = "메인 페이지 용 방 목록을 조회합니다(페이지네이션 적용)") fun getRooms( pageable: Pageable - ): ResponseEntity> { - // 정렬 기준 검증 및 기본값 처리 - val validatedPageable = validatePageable(pageable) - val rooms = roomService.getRooms(validatedPageable).map { it.toDTO() } + ): ResponseEntity> { + val rooms = roomService.getRooms(pageable) return ResponseEntity.ok(rooms) } @GetMapping("/rooms/main/{roomId}") + @Operation(summary = "방 상세 조회", description = "특정 방의 상세 정보를 조회합니다") fun getRoomDetails( @PathVariable roomId: Long ): ResponseEntity { val room = roomService.getRoomDetails(roomId) - return ResponseEntity.ok(room.toDetailsDTO()) - } - - @GetMapping("/rooms/main/{roomId}/reviews") - fun getRoomReviews( - @PathVariable roomId: Long, - pageable: Pageable - ): ResponseEntity> { - val validatedPageable = validatePageable(pageable) - val reviews = roomService.getRoomReviews(roomId, validatedPageable) - return ResponseEntity.ok(reviews) + return ResponseEntity.ok(room) } @PutMapping("/rooms/{roomId}") + @Operation(summary = "방 정보 수정", description = "방의 정보를 수정합니다") fun updateRoom( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable roomId: Long, @@ -98,10 +88,11 @@ class RoomController( request.maxOccupancy ) - return ResponseEntity.ok(updatedRoom.toDetailsDTO()) + return ResponseEntity.ok(updatedRoom) } @DeleteMapping("/rooms/{roomId}") + @Operation(summary = "방 삭제", description = "방을 삭제합니다") fun deleteRoom( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable roomId: Long @@ -128,11 +119,9 @@ class RoomController( @RequestParam(required = false) startDate: LocalDate?, @RequestParam(required = false) endDate: LocalDate?, pageable: Pageable - ): ResponseEntity> { + ): ResponseEntity> { val address = AddressSearchDTO(sido, sigungu, street, detail) - val validatedPage = validatePageable(pageable) - val rooms = roomService.searchRooms(name, type, minPrice, maxPrice, address, maxOccupancy, rating, startDate, endDate, validatedPage) - .map { it.toDTO() } + val rooms = roomService.searchRooms(name, type, minPrice, maxPrice, address, maxOccupancy, rating, startDate, endDate, pageable) return ResponseEntity.ok(rooms) } } @@ -163,29 +152,3 @@ data class UpdateRoomRequest( val price: Price, val maxOccupancy: Int ) - -data class RoomReviewDTO( - val id: Long, - val userId: Long, - val rating: Int, - val content: String, - val reservationStartDate: LocalDate, - val reservationEndDate: LocalDate, - val createdAt: Instant, - val updatedAt: Instant -) { - companion object { - fun fromEntity(entity: ReviewEntity): RoomReviewDTO { - return RoomReviewDTO( - id = entity.id!!, - userId = entity.user.id!!, - rating = entity.rating, - content = entity.content, - reservationStartDate = entity.reservation.startDate, - reservationEndDate = entity.reservation.endDate, - createdAt = entity.createdAt, - updatedAt = entity.updatedAt - ) - } - } -} diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomEntity.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomEntity.kt index 948ad29..8befff9 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomEntity.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/persistence/RoomEntity.kt @@ -56,7 +56,11 @@ class RoomEntity( @Column(nullable = false) var createdAt: Instant = Instant.now(), @Column(nullable = false) - var updatedAt: Instant = Instant.now() + var updatedAt: Instant = Instant.now(), + @Column + var imageUploadUrl: String? = null, + @Column + var imageDownloadUrl: String? = null ) { @PrePersist fun onPrePersist() { diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomService.kt index ceff182..ad608ed 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomService.kt @@ -2,7 +2,7 @@ package com.example.toyTeam6Airbnb.room.service import com.example.toyTeam6Airbnb.room.controller.AddressSearchDTO import com.example.toyTeam6Airbnb.room.controller.Room -import com.example.toyTeam6Airbnb.room.controller.RoomReviewDTO +import com.example.toyTeam6Airbnb.room.controller.RoomDetailsDTO import com.example.toyTeam6Airbnb.room.persistence.Address import com.example.toyTeam6Airbnb.room.persistence.Price import com.example.toyTeam6Airbnb.room.persistence.RoomDetails @@ -21,13 +21,11 @@ interface RoomService { roomDetails: RoomDetails, price: Price, maxOccupancy: Int - ): Room + ): RoomDetailsDTO fun getRooms(pageable: Pageable): Page - fun getRoomDetails(roomId: Long): Room - - fun getRoomReviews(roomId: Long, pageable: Pageable): Page + fun getRoomDetails(roomId: Long): RoomDetailsDTO fun updateRoom( hostId: Long, @@ -39,7 +37,7 @@ interface RoomService { roomDetails: RoomDetails, price: Price, maxOccupancy: Int - ): Room + ): RoomDetailsDTO fun deleteRoom( userId: Long, diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomServiceImpl.kt index 2c853c0..983a580 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/service/RoomServiceImpl.kt @@ -12,7 +12,7 @@ import com.example.toyTeam6Airbnb.room.RoomNotFoundException import com.example.toyTeam6Airbnb.room.RoomPermissionDeniedException import com.example.toyTeam6Airbnb.room.controller.AddressSearchDTO import com.example.toyTeam6Airbnb.room.controller.Room -import com.example.toyTeam6Airbnb.room.controller.RoomReviewDTO +import com.example.toyTeam6Airbnb.room.controller.RoomDetailsDTO import com.example.toyTeam6Airbnb.room.persistence.Address import com.example.toyTeam6Airbnb.room.persistence.Price import com.example.toyTeam6Airbnb.room.persistence.RoomDetails @@ -21,6 +21,7 @@ import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.room.persistence.RoomType import com.example.toyTeam6Airbnb.user.AuthenticateException import com.example.toyTeam6Airbnb.user.persistence.UserRepository +import com.example.toyTeam6Airbnb.validatePageable import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -46,7 +47,7 @@ class RoomServiceImpl( roomDetails: RoomDetails, price: Price, maxOccupancy: Int - ): Room { + ): RoomDetailsDTO { val hostEntity = userRepository.findByIdOrNull(hostId) ?: throw AuthenticateException() validateRoomInfo(name, description, type, address, price, maxOccupancy) @@ -69,7 +70,7 @@ class RoomServiceImpl( roomRepository.save(it) } - return Room.fromEntity(roomEntity) + return RoomDetailsDTO.fromEntity(roomEntity) } catch (e: DataIntegrityViolationException) { throw DuplicateRoomException() } @@ -77,18 +78,13 @@ class RoomServiceImpl( @Transactional override fun getRooms(pageable: Pageable): Page { - return roomRepository.findAll(pageable).map { Room.fromEntity(it) } + return roomRepository.findAll(validatePageable(pageable)).map { Room.fromEntity(it) } } @Transactional - override fun getRoomDetails(roomId: Long): Room { + override fun getRoomDetails(roomId: Long): RoomDetailsDTO { val roomEntity = roomRepository.findByIdOrNull(roomId) ?: throw RoomNotFoundException() - return Room.fromEntity(roomEntity) - } - - @Transactional - override fun getRoomReviews(roomId: Long, pageable: Pageable): Page { - return reviewRepository.findAllByRoomId(roomId, pageable).map { RoomReviewDTO.fromEntity(it) } + return RoomDetailsDTO.fromEntity(roomEntity) } @Transactional @@ -102,7 +98,7 @@ class RoomServiceImpl( roomDetails: RoomDetails, price: Price, maxOccupancy: Int - ): Room { + ): RoomDetailsDTO { val hostEntity = userRepository.findByIdOrNull(hostId) ?: throw AuthenticateException() val roomEntity = roomRepository.findByIdOrNullForUpdate(roomId) ?: throw RoomNotFoundException() @@ -124,7 +120,7 @@ class RoomServiceImpl( roomEntity.maxOccupancy = maxOccupancy roomRepository.save(roomEntity) - return Room.fromEntity(roomEntity) + return RoomDetailsDTO.fromEntity(roomEntity) } @Transactional @@ -166,7 +162,7 @@ class RoomServiceImpl( sigungu = address?.sigungu, street = address?.street, detail = address?.detail, - pageable = pageable + pageable = validatePageable(pageable) ) return rooms.map { Room.fromEntity(it) } } From 3965a20dbad5409eed6578978e96eba5e629bfe1 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:41:28 +0900 Subject: [PATCH 07/12] =?UTF-8?q?review=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toyTeam6Airbnb/review/ReviewException.kt | 4 +- .../review/controller/Review.kt | 39 ++++++++++++------ .../review/controller/ReviewController.kt | 40 +++++++++---------- .../review/persistence/ReviewRepository.kt | 2 +- .../review/service/ReviewService.kt | 13 +++--- .../review/service/ReviewServiceImpl.kt | 28 ++++++++++--- 6 files changed, 78 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/ReviewException.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/ReviewException.kt index 8cca374..1754be8 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/ReviewException.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/ReviewException.kt @@ -13,7 +13,7 @@ sealed class ReviewException( class ReviewNotFoundException : ReviewException( errorCode = 3001, - httpStatusCode = HttpStatus.CONFLICT, + httpStatusCode = HttpStatus.NOT_FOUND, msg = "Review doesn't exist" ) @@ -25,6 +25,6 @@ class ReviewPermissionDeniedException : ReviewException( class DuplicateReviewException : ReviewException( errorCode = 3003, - httpStatusCode = HttpStatus.BAD_REQUEST, + httpStatusCode = HttpStatus.CONFLICT, msg = "Review Already Exists" ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/Review.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/Review.kt index fe7ec80..634f1a0 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/Review.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/Review.kt @@ -2,6 +2,7 @@ package com.example.toyTeam6Airbnb.review.controller import com.example.toyTeam6Airbnb.review.persistence.ReviewEntity import java.time.Instant +import java.time.LocalDate data class Review( val id: Long, @@ -27,17 +28,6 @@ data class Review( ) } } - - fun toDTO(): ReviewDTO { - return ReviewDTO( - id = this.id, - userId = this.userId, - reservationId = this.reservationId, - roomId = this.roomId, - content = this.content, - rating = this.rating - ) - } } data class ReviewDTO( @@ -46,5 +36,28 @@ data class ReviewDTO( val reservationId: Long, val roomId: Long, val content: String, - val rating: Int -) + val rating: Int, + val place: String, + val startDate: LocalDate, + val endDate: LocalDate, + val createdAt: Instant, + val updatedAt: Instant +) { + companion object { + fun fromEntity(entity: ReviewEntity): ReviewDTO { + return ReviewDTO( + id = entity.id!!, + userId = entity.user.id!!, + reservationId = entity.reservation.id!!, + roomId = entity.room.id!!, + content = entity.content, + rating = entity.rating, + place = entity.room.address.sido, + startDate = entity.reservation.startDate, + endDate = entity.reservation.endDate, + createdAt = entity.createdAt, + updatedAt = entity.updatedAt + ) + } + } +} diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt index f705e8f..3215919 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/controller/ReviewController.kt @@ -5,6 +5,8 @@ import com.example.toyTeam6Airbnb.user.controller.PrincipalDetails import com.example.toyTeam6Airbnb.user.controller.User import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -15,24 +17,22 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RequestParam -import org.springframework.data.domain.Page -import org.springframework.data.domain.Pageable +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/reviews") -@Tag(name = "Review Controller", description = "Review Controller API") +@Tag(name = "Review Controller", description = "Review Controller API - 리뷰는 예약 당 1개입니다") class ReviewController( private val reviewService: ReviewService ) { @PostMapping - @Operation(summary = "Create Review", description = "Create a new review") + @Operation(summary = "리뷰 생성", description = "새로운 리뷰를 생성합니다") fun createReview( @AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: CreateReviewRequest - ): ResponseEntity { + ): ResponseEntity { val review = reviewService.createReview( request.roomId, User.fromEntity(principalDetails.getUser()), @@ -40,56 +40,56 @@ class ReviewController( request.content, request.rating ) - return ResponseEntity.status(HttpStatus.CREATED).body(review.toDTO()) + return ResponseEntity.status(HttpStatus.CREATED).body(review) } @GetMapping("/room/{roomId}") - @Operation(summary = "Get Reviews", description = "Get all reviews for a room") - fun getReviews( + @Operation(summary = "특정 방의 리뷰 조회", description = "특정 방의 모든 리뷰를 조회합니다") + fun getReviewsByRoom( @PathVariable roomId: Long, @RequestParam pageable: Pageable ): ResponseEntity> { - val reviews = reviewService.getReviewsByRoom(roomId, pageable).map { it.toDTO() } + val reviews = reviewService.getReviewsByRoom(roomId, pageable) return ResponseEntity.ok(reviews) } @GetMapping("/{reviewId}") - @Operation(summary = "Get Review Details", description = "Get details of a specific review") + @Operation(summary = "특정 리뷰 상세 조회", description = "특정 리뷰의 상세정보를 조회합니다") fun getReviewDetails( @PathVariable reviewId: Long ): ResponseEntity { val review = reviewService.getReviewDetails(reviewId) - return ResponseEntity.ok(review.toDTO()) + return ResponseEntity.ok(review) } @GetMapping("/user/{userId}") - @Operation(summary = "Get Reviews by User", description = "Get all reviews by a user") + @Operation(summary = "특정 유저의 리뷰 조회", description = "특정 유저의 모든 리뷰를 조회합니다") fun getReviewsByUser( - @PathVariable userId: Long + @PathVariable userId: Long, + @RequestParam pageable: Pageable ): ResponseEntity> { - val reviews = reviewService.getReviewsByUser(userId).map { it.toDTO() } + val reviews = reviewService.getReviewsByUser(userId, pageable) return ResponseEntity.ok(reviews) } - // Review에 수정 사항이 추가되면 파라미터 수정 필요 @PutMapping("/{reviewId}") - @Operation(summary = "Update Review", description = "Update an existing review") + @Operation(summary = "리뷰 수정", description = "존재하는 리뷰를 수정합니다") fun updateReview( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable reviewId: Long, @RequestBody request: UpdateReviewRequest - ): ResponseEntity { + ): ResponseEntity { val review = reviewService.updateReview( User.fromEntity(principalDetails.getUser()), reviewId, request.content, request.rating ) - return ResponseEntity.ok(review.toDTO()) + return ResponseEntity.ok(review) } @DeleteMapping("/{reviewId}") - @Operation(summary = "Delete Review", description = "Delete a review") + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제합니다") fun deleteReview( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable reviewId: Long diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/persistence/ReviewRepository.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/persistence/ReviewRepository.kt index 716a3ea..1337f16 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/persistence/ReviewRepository.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/persistence/ReviewRepository.kt @@ -5,6 +5,6 @@ import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository interface ReviewRepository : JpaRepository { - fun findAllByRoomId(roomId: Long): List fun findAllByRoomId(roomId: Long, pageable: Pageable): Page + fun findAllByUserId(userId: Long, pageable: Pageable): Page } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt index 5fa8712..538f977 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewService.kt @@ -1,6 +1,7 @@ package com.example.toyTeam6Airbnb.review.service import com.example.toyTeam6Airbnb.review.controller.Review +import com.example.toyTeam6Airbnb.review.controller.ReviewDTO import com.example.toyTeam6Airbnb.user.controller.User import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -18,16 +19,16 @@ interface ReviewService { fun getReviewsByRoom( roomId: Long, pageable: Pageable - ): Page + ): Page + + fun getReviewDetails( + reviewId: Long + ): ReviewDTO fun getReviewsByUser( userId: Long, pageable: Pageable - ): Page - - fun getReviewDetails( - reviewId: Long - ): Review + ): Page fun updateReview( user: User, diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt index 33761b2..c4e23c9 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/review/service/ReviewServiceImpl.kt @@ -1,18 +1,24 @@ package com.example.toyTeam6Airbnb.review.service + import com.example.toyTeam6Airbnb.reservation.ReservationNotFound import com.example.toyTeam6Airbnb.reservation.persistence.ReservationRepository import com.example.toyTeam6Airbnb.review.DuplicateReviewException import com.example.toyTeam6Airbnb.review.ReviewNotFoundException import com.example.toyTeam6Airbnb.review.ReviewPermissionDeniedException import com.example.toyTeam6Airbnb.review.controller.Review +import com.example.toyTeam6Airbnb.review.controller.ReviewDTO import com.example.toyTeam6Airbnb.review.persistence.ReviewEntity import com.example.toyTeam6Airbnb.review.persistence.ReviewRepository -import com.example.toyTeam6Airbnb.profile.RoomNotFoundException +import com.example.toyTeam6Airbnb.room.RoomNotFoundException import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.AuthenticateException +import com.example.toyTeam6Airbnb.user.UserNotFoundException import com.example.toyTeam6Airbnb.user.controller.User import com.example.toyTeam6Airbnb.user.persistence.UserRepository +import com.example.toyTeam6Airbnb.validatePageable import org.springframework.dao.DataIntegrityViolationException +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -62,19 +68,29 @@ class ReviewServiceImpl( } @Transactional - override fun getReviews(roomId: Long): List { + override fun getReviewsByRoom(roomId: Long, pageable: Pageable): Page { roomRepository.findByIdOrNull(roomId) ?: throw RoomNotFoundException() - val reviewEntities = reviewRepository.findAllByRoomId(roomId) + val reviewEntities = reviewRepository.findAllByRoomId(roomId, validatePageable(pageable)) + + val reviews = reviewEntities.map { ReviewDTO.fromEntity(it) } + return reviews + } + + @Transactional + override fun getReviewsByUser(userId: Long, pageable: Pageable): Page { + userRepository.findByIdOrNull(userId) ?: throw UserNotFoundException() + + val reviewEntities = reviewRepository.findAllByUserId(userId, validatePageable(pageable)) - val reviews = reviewEntities.map { Review.fromEntity(it) } + val reviews = reviewEntities.map { ReviewDTO.fromEntity(it) } return reviews } @Transactional - override fun getReviewDetails(reviewId: Long): Review { + override fun getReviewDetails(reviewId: Long): ReviewDTO { val reviewEntity = reviewRepository.findByIdOrNull(reviewId) ?: throw ReviewNotFoundException() - return Review.fromEntity(reviewEntity) + return ReviewDTO.fromEntity(reviewEntity) } @Transactional From 3fdf6a5b44751a6d4634927645a7e764d8a793f3 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:41:46 +0900 Subject: [PATCH 08/12] =?UTF-8?q?reservation=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/ReservationException.kt | 2 +- .../reservation/controller/Reservation.kt | 34 ++++++++++-------- .../controller/ReservationController.kt | 35 +++++++++---------- .../reservation/service/ReservationService.kt | 8 +++-- .../service/ReservationServiceImpl.kt | 13 ++++--- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/ReservationException.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/ReservationException.kt index 03a9aac..73d06f3 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/ReservationException.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/ReservationException.kt @@ -31,7 +31,7 @@ class ReservationPermissionDenied : ReservationException( class MaxOccupancyExceeded : ReservationException( errorCode = 4004, - httpStatusCode = HttpStatus.CONFLICT, + httpStatusCode = HttpStatus.BAD_REQUEST, msg = "Max Occupancy Exceeded" ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/Reservation.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/Reservation.kt index 961e39a..a3f9fb5 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/Reservation.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/Reservation.kt @@ -12,7 +12,7 @@ data class Reservation( val startDate: LocalDate, val endDate: LocalDate, val totalPrice: Double, - val numberofGuests: Int, + val numberOfGuests: Int, val createdAt: Instant, val updatedAt: Instant ) { @@ -28,28 +28,32 @@ data class Reservation( totalPrice = entity.totalPrice, createdAt = entity.createdAt, updatedAt = entity.updatedAt, - numberofGuests = entity.numberOfGuests + numberOfGuests = entity.numberOfGuests ) } } - - fun toDTO(): ReservationDTO { - return ReservationDTO( - id = this.id, - userId = this.userId, - roomId = this.roomId, - startDate = this.startDate, - endDate = this.endDate, - numberOfGuests = this.numberofGuests - ) - } } data class ReservationDTO( val id: Long, - val roomId: Long, val userId: Long, + val roomId: Long, val startDate: LocalDate, val endDate: LocalDate, + val place: String, val numberOfGuests: Int -) +) { + companion object { + fun fromEntity(entity: ReservationEntity): ReservationDTO { + return ReservationDTO( + id = entity.id!!, + userId = entity.user.id!!, + roomId = entity.room.id!!, + startDate = entity.startDate, + endDate = entity.endDate, + place = entity.room.address.sido, + numberOfGuests = entity.numberOfGuests + ) + } + } +} diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/ReservationController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/ReservationController.kt index 9024f27..1871080 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/ReservationController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/controller/ReservationController.kt @@ -5,6 +5,7 @@ import com.example.toyTeam6Airbnb.user.controller.PrincipalDetails import com.example.toyTeam6Airbnb.user.controller.User import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.data.domain.Pageable import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -28,11 +29,11 @@ class ReservationController( ) { @PostMapping - @Operation(summary = "create Reservation", description = "create Reservation") + @Operation(summary = "예약 생성", description = "예약을 생성합니다") fun createReservation( @AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: CreateReservationRequest - ): ResponseEntity { + ): ResponseEntity { val reservation = reservationService.createReservation( User.fromEntity(principalDetails.getUser()), request.roomId, @@ -41,11 +42,11 @@ class ReservationController( request.numberOfGuests ) - return ResponseEntity.status(HttpStatus.CREATED).body(reservation.toDTO()) + return ResponseEntity.status(HttpStatus.CREATED).body(reservation) } @DeleteMapping("/{reservationId}") - @Operation(summary = "delete Reservation", description = "delete Reservation") + @Operation(summary = "예약 삭제", description = "예약을 삭제합니다") fun deleteReservation( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable reservationId: Long @@ -59,12 +60,12 @@ class ReservationController( } @PutMapping("/{reservationId}") - @Operation(summary = "update Reservation", description = "update Reservation") + @Operation(summary = "예약 수정", description = "예약을 수정합니다") fun updateReservation( @AuthenticationPrincipal principalDetails: PrincipalDetails, @PathVariable reservationId: Long, @RequestBody request: UpdateReservationRequest - ): ResponseEntity { + ): ResponseEntity { val reservation = reservationService.updateReservation( User.fromEntity(principalDetails.getUser()), reservationId, @@ -73,28 +74,26 @@ class ReservationController( request.numberOfGuests ) - return ResponseEntity.ok().body(reservation.toDTO()) + return ResponseEntity.ok().body(reservation) } - // 특정 reservation을 가져오는 API @GetMapping("/{reservationId}") + @Operation(summary = "예약 상세 조회", description = "예약 상세 정보를 조회합니다") fun getReservation( @PathVariable reservationId: Long - ): ResponseEntity { + ): ResponseEntity { val reservation = reservationService.getReservation(reservationId) - return ResponseEntity.ok(reservation.toDTO()) + return ResponseEntity.ok(reservation) } - // 특정 user의 reservation을 모두 가져오는 API - @GetMapping + @GetMapping("/user/{userId}") + @Operation(summary = "유저별 예약 조회", description = "특정 유저의 모든 예약 정보를 조회합니다") fun getReservationsByUser( - @AuthenticationPrincipal principalDetails: PrincipalDetails + @PathVariable userId: Long, + @RequestParam pageable: Pageable ): ResponseEntity> { - val reservations = reservationService.getReservationsByUser( - User.fromEntity(principalDetails.getUser()) - ).map { it.toDTO() } - + val reservations = reservationService.getReservationsByUser(userId, pageable) return ResponseEntity.ok(reservations) } @@ -124,7 +123,7 @@ class ReservationController( // 특정 room의 특정 month의 available/unavailable date를 가져오는 API @GetMapping("/availability/{roomId}") - @Operation(summary = "get Date Available by month", description = "get Date Available by month") + @Operation(summary = "해당 월의 예약 가능 날짜", description = "특정 방의 특정 월에 예약 가능/불가능한 모든 날짜 조회") fun getRoomAvailabilityByMonth( @PathVariable roomId: Long, @RequestParam year: Int, diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationService.kt index 5ca9041..6bd8145 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationService.kt @@ -1,8 +1,10 @@ package com.example.toyTeam6Airbnb.reservation.service import com.example.toyTeam6Airbnb.reservation.controller.Reservation +import com.example.toyTeam6Airbnb.reservation.controller.ReservationDTO import com.example.toyTeam6Airbnb.reservation.controller.RoomAvailabilityResponse import com.example.toyTeam6Airbnb.user.controller.User +import org.springframework.data.domain.Pageable import java.time.LocalDate import java.time.YearMonth @@ -27,7 +29,6 @@ interface ReservationService { startDate: LocalDate, endDate: LocalDate, numberOfGuests: Int - ): Reservation fun getReservation( @@ -35,8 +36,9 @@ interface ReservationService { ): Reservation fun getReservationsByUser( - user: User - ): List + userId: Long, + pageable: Pageable + ): List // fun getReservationsByRoom( // roomId: Long diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt index 23b03fb..63193ea 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/reservation/service/ReservationServiceImpl.kt @@ -6,17 +6,20 @@ 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.ReservationDTO import com.example.toyTeam6Airbnb.reservation.controller.RoomAvailabilityResponse import com.example.toyTeam6Airbnb.reservation.persistence.ReservationEntity import com.example.toyTeam6Airbnb.reservation.persistence.ReservationRepository -import com.example.toyTeam6Airbnb.profile.RoomNotFoundException +import com.example.toyTeam6Airbnb.room.RoomNotFoundException import com.example.toyTeam6Airbnb.room.persistence.RoomEntity import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.AuthenticateException +import com.example.toyTeam6Airbnb.user.UserNotFoundException import com.example.toyTeam6Airbnb.user.controller.User import com.example.toyTeam6Airbnb.user.persistence.UserRepository import jakarta.persistence.EntityManager import jakarta.persistence.LockModeType +import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Isolation @@ -65,7 +68,7 @@ class ReservationServiceImpl( ).let { reservationRepository.save(it) } - println("${roomEntity.id}") + return Reservation.fromEntity(reservationEntity) } @@ -129,10 +132,10 @@ class ReservationServiceImpl( } @Transactional - override fun getReservationsByUser(user: User): List { - val userEntity = userRepository.findByIdOrNull(user.id) ?: throw AuthenticateException() + override fun getReservationsByUser(userId: Long, pageable: Pageable): List { + val userEntity = userRepository.findByIdOrNull(userId) ?: throw UserNotFoundException() - return reservationRepository.findAllByUser(userEntity).map(Reservation::fromEntity) + return reservationRepository.findAllByUser(userEntity).map(ReservationDTO::fromEntity) } // @Transactional From 3fa209f20de502848abd57429a8e5889efa702a1 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:42:03 +0900 Subject: [PATCH 09/12] =?UTF-8?q?profile=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/ProfileException.kt | 2 +- .../profile/controller/Profile.kt | 6 ++-- .../profile/controller/ProfileController.kt | 14 ++++---- .../profile/persistence/ProfileEntity.kt | 4 +-- .../profile/service/ProfileService.kt | 9 +++--- .../profile/service/ProfileServiceImpl.kt | 32 +++++++++++-------- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt index 170a670..137e141 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/ProfileException.kt @@ -13,7 +13,7 @@ sealed class ProfileException( class ProfileAlreadyExistException : ProfileException( errorCode = 5001, - httpStatusCode = HttpStatus.BAD_REQUEST, + httpStatusCode = HttpStatus.CONFLICT, msg = "Profile already exists" ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt index 3d52e85..8d6c576 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/Profile.kt @@ -7,16 +7,16 @@ data class Profile( val userId: Long, val nickname: String, val bio: String, - val isSuperhost: Boolean + val isSuperHost: Boolean ) { companion object { fun fromEntity(profileEntity: ProfileEntity): Profile { return Profile( - id = profileEntity.id, + id = profileEntity.id!!, userId = profileEntity.user.id!!, nickname = profileEntity.nickname, bio = profileEntity.bio, - isSuperhost = profileEntity.isSuperhost + isSuperHost = profileEntity.isSuperHost ) } } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt index 9a2c7c3..bbb7d8d 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/controller/ProfileController.kt @@ -22,32 +22,32 @@ class ProfileController( ) { @GetMapping - @Operation(summary = "Get User Profile", description = "Get the profile of the current user") + @Operation(summary = "유저 프로필 가져오기", description = "현재 로그인 되어 있는 user의 프로필을 가져옵니다.") fun getCurrentUserProfile( @AuthenticationPrincipal principalDetails: PrincipalDetails ): ResponseEntity { val profile = profileService.getCurrentUserProfile(principalDetails.getUser()) - return ResponseEntity.ok(Profile.fromEntity(profile)) + return ResponseEntity.ok(profile) } @PutMapping - @Operation(summary = "Update User Profile", description = "Update the profile of the current user") + @Operation(summary = "유저 프로필 업데이트하기", description = "현재 로그인 되어 있는 user의 프로필을 업데이트합니다.") fun updateCurrentUserProfile( @AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: UpdateProfileRequest ): ResponseEntity { val updatedProfile = profileService.updateCurrentUserProfile(principalDetails.getUser(), request) - return ResponseEntity.ok(Profile.fromEntity(updatedProfile)) + return ResponseEntity.ok(updatedProfile) } @PostMapping - @Operation(summary = "Add Profile to User", description = "Add a profile to the current user, only for users logged in with social login") + @Operation(summary = "유저 프로필 추가", description = "현재 로그인 되어 있는 user에게 프로필을 추가합니다. (소셜 로그인 전용)") fun addProfileToCurrentUser( @AuthenticationPrincipal principalDetails: PrincipalDetails, @RequestBody request: CreateProfileRequest ): ResponseEntity { - val newProfile = profileService.addProfileToCurrentUser(principalDetails.getUser(), request) - return ResponseEntity.status(HttpStatus.CREATED).body(Profile.fromEntity(newProfile)) + val profile = profileService.addProfileToCurrentUser(principalDetails.getUser(), request) + return ResponseEntity.status(HttpStatus.CREATED).body(profile) } } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt index 1fc7005..dbc3db2 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/persistence/ProfileEntity.kt @@ -16,7 +16,7 @@ import jakarta.persistence.Table class ProfileEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0, + val id: Long? = null, @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false, unique = true) @@ -29,5 +29,5 @@ class ProfileEntity( var bio: String, @Column(nullable = false) - var isSuperhost: Boolean = false + var isSuperHost: Boolean = false ) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt index cf1ed8a..76a48be 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileService.kt @@ -1,6 +1,7 @@ package com.example.toyTeam6Airbnb.profile.service import com.example.toyTeam6Airbnb.profile.controller.CreateProfileRequest +import com.example.toyTeam6Airbnb.profile.controller.Profile import com.example.toyTeam6Airbnb.profile.controller.UpdateProfileRequest import com.example.toyTeam6Airbnb.profile.persistence.ProfileEntity import com.example.toyTeam6Airbnb.user.persistence.UserEntity @@ -8,19 +9,19 @@ import com.example.toyTeam6Airbnb.user.persistence.UserEntity interface ProfileService { fun getCurrentUserProfile( user: UserEntity - ): ProfileEntity + ): Profile fun updateCurrentUserProfile( user: UserEntity, request: UpdateProfileRequest - ): ProfileEntity + ): Profile fun addProfileToCurrentUser( user: UserEntity, request: CreateProfileRequest - ): ProfileEntity + ): Profile - fun updateSuperhostStatus( + fun updateSuperHostStatus( profile: ProfileEntity ) } diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt index bc5de4e..55337b1 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/profile/service/ProfileServiceImpl.kt @@ -3,57 +3,63 @@ package com.example.toyTeam6Airbnb.profile.service import com.example.toyTeam6Airbnb.profile.ProfileAlreadyExistException import com.example.toyTeam6Airbnb.profile.ProfileNotFoundException import com.example.toyTeam6Airbnb.profile.controller.CreateProfileRequest +import com.example.toyTeam6Airbnb.profile.controller.Profile import com.example.toyTeam6Airbnb.profile.controller.UpdateProfileRequest import com.example.toyTeam6Airbnb.profile.persistence.ProfileEntity import com.example.toyTeam6Airbnb.profile.persistence.ProfileRepository import com.example.toyTeam6Airbnb.room.persistence.RoomRepository import com.example.toyTeam6Airbnb.user.persistence.UserEntity -import com.example.toyTeam6Airbnb.user.persistence.UserRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class ProfileServiceImpl( private val profileRepository: ProfileRepository, - private val userRepository: UserRepository, private val roomRepository: RoomRepository ) : ProfileService { override fun getCurrentUserProfile( user: UserEntity - ): ProfileEntity { - return profileRepository.findByUser(user) ?: throw ProfileNotFoundException() + ): Profile { + val profile = profileRepository.findByUser(user) ?: throw ProfileNotFoundException() + return Profile.fromEntity(profile) } @Transactional override fun updateCurrentUserProfile( user: UserEntity, request: UpdateProfileRequest - ): ProfileEntity { + ): Profile { val profile = profileRepository.findByUser(user) ?: throw ProfileNotFoundException() profile.nickname = request.nickname profile.bio = request.bio - updateSuperhostStatus(profile) + updateSuperHostStatus(profile) + profileRepository.save(profile) - return profileRepository.save(profile) + return Profile.fromEntity(profile) } @Transactional override fun addProfileToCurrentUser( user: UserEntity, request: CreateProfileRequest - ): ProfileEntity { + ): Profile { if (profileRepository.existsByUser(user)) throw ProfileAlreadyExistException() - val profile = ProfileEntity(user = user, nickname = request.nickname, bio = request.bio) - updateSuperhostStatus(profile) - return profileRepository.save(profile) + val profile = ProfileEntity( + user = user, + nickname = request.nickname, + bio = request.bio + ) + updateSuperHostStatus(profile) + + return Profile.fromEntity(profileRepository.save(profile)) } @Transactional - override fun updateSuperhostStatus(profile: ProfileEntity) { + override fun updateSuperHostStatus(profile: ProfileEntity) { val roomCount = roomRepository.countByHost(profile.user) - profile.isSuperhost = roomCount >= 5 + profile.isSuperHost = roomCount >= 5 } } From 9e8d93a03087ed09da138886b41b9d9195412831 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:42:25 +0900 Subject: [PATCH 10/12] =?UTF-8?q?pageable=20=EC=84=A4=EC=A0=95=20=EC=8B=9C?= =?UTF-8?q?=20validate=20=ED=95=A8=EC=88=98=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20directory=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt index f6c99d1..0b7112f 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/ValidatePageable.kt @@ -1,4 +1,4 @@ -package com.example.toyTeam6Airbnb.room +package com.example.toyTeam6Airbnb import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable From c63e0be2a9cdbfe1346ecb75c854574f6b83a244 Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:46:00 +0900 Subject: [PATCH 11/12] =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/toyTeam6Airbnb/room/controller/RoomController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt index 810c538..0b865fe 100644 --- a/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt +++ b/src/main/kotlin/com/example/toyTeam6Airbnb/room/controller/RoomController.kt @@ -105,6 +105,7 @@ class RoomController( } @GetMapping("/rooms/main/search") + @Operation(summary = "방 검색", description = "방을 검색합니다(페이지네이션 적용)") fun searchRooms( @RequestParam(required = false) name: String?, @RequestParam(required = false) type: RoomType?, From 4e81281434d2a3d005e1275dd4c140ad0154fe6d Mon Sep 17 00:00:00 2001 From: dennis0405 Date: Tue, 14 Jan 2025 22:53:36 +0900 Subject: [PATCH 12/12] =?UTF-8?q?test=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationControllerTest.kt | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/test/kotlin/com/example/toyTeam6Airbnb/ReservationControllerTest.kt b/src/test/kotlin/com/example/toyTeam6Airbnb/ReservationControllerTest.kt index bf69323..ab79f35 100644 --- a/src/test/kotlin/com/example/toyTeam6Airbnb/ReservationControllerTest.kt +++ b/src/test/kotlin/com/example/toyTeam6Airbnb/ReservationControllerTest.kt @@ -162,27 +162,6 @@ class ReservationControllerTest { println(result2.response.contentAsString) } - @Test - fun `전체 예약 조회시 200 응답을 반환한다`() { - val (user, token) = dataGenerator.generateUserAndToken() - val room = dataGenerator.generateRoom(maxOccupancy = 10) - val roomId = room.id - dataGenerator.generateReservation(user, room, startDate = LocalDate.of(2023, 12, 1), endDate = LocalDate.of(2023, 12, 10), numberOfGuests = 2) - dataGenerator.generateReservation(user, room, startDate = LocalDate.of(2024, 12, 1), endDate = LocalDate.of(2024, 12, 10), numberOfGuests = 2) - - val result = mockMvc.perform( - MockMvcRequestBuilders.get("/api/v1/reservations") - .accept(MediaType.APPLICATION_JSON) - .header("Authorization", "Bearer $token") - ) - .andExpect(MockMvcResultMatchers.status().isOk) // expect 200 status - .andReturn() - - val responseContent = result.response.contentAsString - // Add assertions to verify the response content if needed - println(responseContent) - } - @Test fun `예약에대한 Availablity를 200을 반환한다`() { val (user, token) = dataGenerator.generateUserAndToken()