diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index dc05090..00a8707 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -95,6 +95,7 @@ jobs: sudo docker rm $(sudo docker ps -aq) sudo docker pull ${{ secrets.DOCKER_USERNAME }}/karrot sudo docker run -d -p 8080:8080 \ + --memory="768m" --cpus="0.8" \ -e JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} \ -e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ -e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ @@ -107,4 +108,4 @@ jobs: -e KAKAO_CLI_ID=${{ secrets.KAKAO_CLI_ID }} \ -e KAKAO_CLI_SECRET=${{ secrets.KAKAO_CLI_SECRET }} \ ${{ secrets.DOCKER_USERNAME }}/karrot - sudo docker image prune -f \ No newline at end of file + sudo docker system prune -a -f --volumes \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index cd4fbc5..50c620c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ -FROM openjdk:17 +# FROM openjdk:17 +# ARG JAR_FILE=build/libs/karrot-0.0.1-SNAPSHOT.jar +# COPY ${JAR_FILE} /app.jar +# EXPOSE 8080 +# ENTRYPOINT ["java", "-jar", "/app.jar"] + +FROM eclipse-temurin:17-jre-alpine +WORKDIR /app ARG JAR_FILE=build/libs/karrot-0.0.1-SNAPSHOT.jar -COPY ${JAR_FILE} /app.jar -EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Xmx512m", "-Xms256m", "-jar", "app.jar"] +EXPOSE 8080 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d8a7a6 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# 우리 프로젝트 이름 + +이 프로젝트는 온라인 중고거래 및 커뮤니티 플랫폼인 "당근 마켓"을 클론한 프로젝트입니다. 기존의 중고거래 기능에 더해 **경매 기능**을 추가하여 사용자들이 물품을 경매 형식으로 거래할 수 있도록 확장했습니다. 이 프로젝트는 필수 스펙과 권장 스펙을 충족하며, 새로운 기능인 경매를 통해 사용자 경험을 향상시켰습니다. [서비스 바로가기](https://toykarrot.shop/) + +--- + +## 팀원 소개 + +- **김정훈**: 소셜 로그인, 환경 설정, 보안 설정 +- **박원석**: +- **이준용**: + +--- + +## 클론 코딩 필수 스펙 + +### **필수 스펙** + +- **회원가입 / 로그인 / 소셜 로그인** + - 닉네임, 아이디, 비밀번호, 이메일을 통한 회원가입 + - 소셜 로그인 (Google, Kakao, Naver) 지원 +- **유저 계정 페이지** + - 프로필 수정 (사진, 닉네임, 동네) + - 매너온도 확인 + - 판매내역 조회 + - 내 매너 평가 및 거래 후기 조회 +- **글 작성 / 댓글 작성** + - 중고거래 및 동네생활 게시글 작성 + - 댓글 및 댓글 좋아요 기능 +- **페이지네이션** + - 게시글 목록 및 댓글 목록에 페이지네이션 적용 +- **AWS 배포** + - EC2와 S3를 통한 프로젝트 배포 + +### **권장 스펙** + +- **HTTPS 설정** + - 보안 강화를 위한 HTTPS 적용 +- **Github Actions CI/CD** + - 자동화된 배포 및 테스트를 위한 CI/CD 파이프라인 구축 + +--- + +## 새로운 기능: **경매** + +- **경매 물품 올리기** + - 판매자가 경매 물품을 등록할 수 있습니다. + - 시작가와 경매 종료 시간을 설정할 수 있습니다. +- **경매 참여** + - 구매자는 경매에 참여하여 입찰할 수 있습니다. + - 입찰 가격은 시작가의 5% 배수로 인상 가능합니다. +- **경매 종료** + - 제한 시간 내에 가장 높은 가격을 부른 구매자에게 물품이 판매됩니다. + +--- + +## 전체 기능 + +### 회원가입 +- 닉네임, 아이디, 비밀번호, 이메일을 받아 회원가입을 진행합니다. + +### 로그인 +- 일반 로그인 및 소셜 로그인을 지원합니다. + +### 동네 설정 +- 사용자의 동네를 설정합니다. 아직 지역 기반 서비스는 구현하지 못했습니다. + +### 홈 (중고거래) +- **물품 올리기**: 판매자가 물품을 등록할 수 있습니다. +- **물품 조회**: 현재 판매중인 모든 물품을 최신순으로 보여줍니다. +- **물품 관심 기능**: 관심 있는 물품을 저장할 수 있습니다. +- **판매자와 채팅**: 구매자와 판매자가 실시간으로 채팅할 수 있습니다. + +### 동네 생활 +- **글쓰기**: 사용자가 동네 생활 게시글을 작성할 수 있습니다. +- **글 조회**: 게시글을 조회하고 댓글을 작성할 수 있습니다. +- **댓글 좋아요**: 댓글에 좋아요를 누를 수 있습니다. +- **공감**: 게시글에 공감을 표시할 수 있습니다. + +### 경매 (새로운 기능) +- **경매 물품 올리기**: 판매자가 경매 물품을 등록합니다. +- **경매 참여**: 구매자가 경매에 참여하여 입찰합니다. +- **경매 종료**: 가장 높은 가격을 부른 구매자에게 물품이 판매됩니다. + +### 채팅 +- **채팅 기록 저장**: 물건을 구매할 때 채팅했던 기록이 저장됩니다. +- **실시간 채팅**: WebSocket을 이용하여 실시간 채팅을 구현하였습니다. + +### 나의 당근 +- **프로필 수정**: 사진, 닉네임, 동네를 수정할 수 있습니다. +- **매너온도 확인**: 사용자의 매너온도를 확인할 수 있습니다. +- **판매내역**: 판매한 물품의 내역을 조회할 수 있습니다. +- **내 매너 평가 조회**: 다른 사용자로부터 받은 매너 평가를 확인할 수 있습니다. +- **거래 후기 조회**: 거래 후기를 조회할 수 있습니다. + +### 타사용자 프로필 +- **매너 칭찬**: 다른 사용자의 매너를 칭찬할 수 있습니다. +- **판매물품 목록**: 해당 사용자가 판매 중인 물품을 조회할 수 있습니다. +- **받은 매너 평가**: 해당 사용자가 받은 매너 평가를 확인할 수 있습니다. +- **받은 거래 후기 조회**: 해당 사용자가 받은 거래 후기를 조회할 수 있습니다. diff --git a/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt b/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt index 48aed6e..e676376 100644 --- a/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt +++ b/src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt @@ -35,7 +35,6 @@ class SecurityConfig( registry .requestMatchers( *SecurityConstants.PUBLIC_PATHS, - "/ws/**", ).permitAll() .anyRequest().authenticated() } diff --git a/src/main/kotlin/com/toyProject7/karrot/auction/AuctionException.kt b/src/main/kotlin/com/toyProject7/karrot/auction/AuctionException.kt index 46e0d2a..ece7a16 100644 --- a/src/main/kotlin/com/toyProject7/karrot/auction/AuctionException.kt +++ b/src/main/kotlin/com/toyProject7/karrot/auction/AuctionException.kt @@ -23,8 +23,20 @@ class AuctionPermissionDeniedException : AuctionException( msg = "Permission denied", ) -class AuctionBadPriceException : AuctionException( +class AuctionTooLowPriceException : AuctionException( errorCode = 0, httpStatusCode = HttpStatus.BAD_REQUEST, msg = "Your price is lower than current price", ) + +class AuctionTooFineUnitExceptions : AuctionException( + errorCode = 0, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "Minimum unit is 5% of the start price", +) + +class AuctionOverException : AuctionException( + errorCode = 0, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "Over auction time", +) diff --git a/src/main/kotlin/com/toyProject7/karrot/auction/service/AuctionService.kt b/src/main/kotlin/com/toyProject7/karrot/auction/service/AuctionService.kt index 37928ea..e1506a2 100644 --- a/src/main/kotlin/com/toyProject7/karrot/auction/service/AuctionService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/auction/service/AuctionService.kt @@ -2,9 +2,11 @@ package com.toyProject7.karrot.auction.service import com.toyProject7.karrot.article.ArticlePermissionDeniedException import com.toyProject7.karrot.article.controller.UpdateStatusRequest -import com.toyProject7.karrot.auction.AuctionBadPriceException import com.toyProject7.karrot.auction.AuctionNotFoundException +import com.toyProject7.karrot.auction.AuctionOverException import com.toyProject7.karrot.auction.AuctionPermissionDeniedException +import com.toyProject7.karrot.auction.AuctionTooFineUnitExceptions +import com.toyProject7.karrot.auction.AuctionTooLowPriceException import com.toyProject7.karrot.auction.controller.Auction import com.toyProject7.karrot.auction.controller.AuctionMessage import com.toyProject7.karrot.auction.controller.PostAuctionRequest @@ -32,15 +34,21 @@ class AuctionService( @Transactional fun updatePrice(auctionMessage: AuctionMessage): AuctionMessage { val auctionEntity = auctionRepository.findByIdOrNull(auctionMessage.auctionId) ?: throw AuctionNotFoundException() + if (Instant.now().isAfter(auctionEntity.endTime)) { + throw AuctionOverException() + } if (auctionEntity.currentPrice >= auctionMessage.price) { - throw AuctionBadPriceException() + throw AuctionTooLowPriceException() + } + if ((auctionMessage.price - auctionEntity.currentPrice) % (auctionEntity.startingPrice * 0.05).toInt() != 0) { + throw AuctionTooFineUnitExceptions() } val bidder = userService.getUserEntityByNickname(auctionMessage.senderNickname) auctionEntity.currentPrice = auctionMessage.price auctionEntity.bidder = bidder - if (ChronoUnit.MINUTES.between(auctionEntity.endTime, Instant.now()) <= 1) { - auctionEntity.endTime.plus(1, ChronoUnit.MINUTES) + if (ChronoUnit.SECONDS.between(Instant.now(), auctionEntity.endTime) <= 60) { + auctionEntity.endTime = Instant.now().plus(1, ChronoUnit.MINUTES) } return auctionMessage diff --git a/src/main/kotlin/com/toyProject7/karrot/chatRoom/controller/ChatRoomController.kt b/src/main/kotlin/com/toyProject7/karrot/chatRoom/controller/ChatRoomController.kt index 1d2a620..6f7e4e3 100644 --- a/src/main/kotlin/com/toyProject7/karrot/chatRoom/controller/ChatRoomController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/chatRoom/controller/ChatRoomController.kt @@ -1,6 +1,5 @@ package com.toyProject7.karrot.chatRoom.controller -import com.toyProject7.karrot.article.controller.Article import com.toyProject7.karrot.chatRoom.service.ChatRoomService import com.toyProject7.karrot.user.AuthUser import com.toyProject7.karrot.user.controller.User @@ -55,6 +54,6 @@ data class CreateChatRoomRequest( ) data class ChatRoomResponse( - val article: Article, + val chatRoom: ChatRoom, val messages: List, ) diff --git a/src/main/kotlin/com/toyProject7/karrot/chatRoom/service/ChatRoomService.kt b/src/main/kotlin/com/toyProject7/karrot/chatRoom/service/ChatRoomService.kt index 355c42d..f2136c6 100644 --- a/src/main/kotlin/com/toyProject7/karrot/chatRoom/service/ChatRoomService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/chatRoom/service/ChatRoomService.kt @@ -75,7 +75,7 @@ class ChatRoomService( ) val messages = chatMessageEntities.map { chatMessageEntity -> ChatMessage.fromEntity(chatMessageEntity) } return ChatRoomResponse( - article = chatRoom.article, + chatRoom = chatRoom, messages = messages, ) } diff --git a/src/main/kotlin/com/toyProject7/karrot/comment/controller/CommentController.kt b/src/main/kotlin/com/toyProject7/karrot/comment/controller/CommentController.kt index 326a788..2e33d55 100644 --- a/src/main/kotlin/com/toyProject7/karrot/comment/controller/CommentController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/comment/controller/CommentController.kt @@ -1,27 +1,21 @@ package com.toyProject7.karrot.comment.controller -import com.toyProject7.karrot.comment.persistence.CommentRepository import com.toyProject7.karrot.comment.service.CommentService -import com.toyProject7.karrot.feed.controller.FeedPreview -import com.toyProject7.karrot.feed.persistence.FeedEntity import com.toyProject7.karrot.user.AuthUser import com.toyProject7.karrot.user.controller.User import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping -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.PutMapping 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 @RestController @RequestMapping("/api") class CommentController( private val commentService: CommentService, - private val commentRepository: CommentRepository, ) { @PostMapping("/comment/post/{feedId}") fun postComment( @@ -69,19 +63,6 @@ class CommentController( commentService.unlikeComment(commentId, user.id) return ResponseEntity.ok("Unliked Successfully") } - - @GetMapping("/myfeed/comment") - fun getFeedsByUserComments( - @RequestParam("feedId") feedId: Long, - @AuthUser user: User, - ): ResponseEntity> { - val feeds: List = commentService.getFeedsByUserComments(feedId, user.id) - val response = - feeds.map { feed -> - FeedPreview.fromEntity(feed) - } - return ResponseEntity.ok(response) - } } data class CommentRequest( diff --git a/src/main/kotlin/com/toyProject7/karrot/comment/persistence/CommentRepository.kt b/src/main/kotlin/com/toyProject7/karrot/comment/persistence/CommentRepository.kt index 7a911c6..421ca6d 100644 --- a/src/main/kotlin/com/toyProject7/karrot/comment/persistence/CommentRepository.kt +++ b/src/main/kotlin/com/toyProject7/karrot/comment/persistence/CommentRepository.kt @@ -1,22 +1,7 @@ package com.toyProject7.karrot.comment.persistence -import com.toyProject7.karrot.feed.persistence.FeedEntity -import com.toyProject7.karrot.user.persistence.UserEntity import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.data.jpa.repository.Query -import org.springframework.data.repository.query.Param interface CommentRepository : JpaRepository { - @Query( - """ - SELECT DISTINCT c.feed - FROM comments c - WHERE c.user = :user AND c.feed.id < :feedId - ORDER BY c.feed.id DESC - """, - ) - fun findFeedsByUserComments( - @Param("user") user: UserEntity, - @Param("feedId") feedId: Long, - ): List + fun findByUserId(userId: String): List } diff --git a/src/main/kotlin/com/toyProject7/karrot/comment/service/CommentService.kt b/src/main/kotlin/com/toyProject7/karrot/comment/service/CommentService.kt index 245830a..f473857 100644 --- a/src/main/kotlin/com/toyProject7/karrot/comment/service/CommentService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/comment/service/CommentService.kt @@ -8,7 +8,6 @@ import com.toyProject7.karrot.comment.persistence.CommentEntity import com.toyProject7.karrot.comment.persistence.CommentLikesEntity import com.toyProject7.karrot.comment.persistence.CommentLikesRepository import com.toyProject7.karrot.comment.persistence.CommentRepository -import com.toyProject7.karrot.feed.persistence.FeedEntity import com.toyProject7.karrot.feed.service.FeedService import com.toyProject7.karrot.user.service.UserService import org.springframework.data.repository.findByIdOrNull @@ -106,13 +105,8 @@ class CommentService( } @Transactional - fun getFeedsByUserComments( - feedId: Long, - id: String, - ): List { - val user = userService.getUserEntityById(id) - val feeds = commentRepository.findFeedsByUserComments(user, feedId) - return feeds + fun getCommentsByUser(id: String): List { + return commentRepository.findByUserId(id) } @Transactional diff --git a/src/main/kotlin/com/toyProject7/karrot/feed/controller/FeedController.kt b/src/main/kotlin/com/toyProject7/karrot/feed/controller/FeedController.kt index e0564ae..a882c76 100644 --- a/src/main/kotlin/com/toyProject7/karrot/feed/controller/FeedController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/feed/controller/FeedController.kt @@ -111,6 +111,19 @@ class FeedController( } return ResponseEntity.ok(response) } + + @GetMapping("/myfeed/comment") + fun getFeedsThatUserComments( + @RequestParam("feedId") feedId: Long, + @AuthUser user: User, + ): ResponseEntity> { + val feeds: List = feedService.getFeedsThatUserComments(user.id, feedId) + val response = + feeds.map { feed -> + FeedPreview.fromEntity(feed) + } + return ResponseEntity.ok(response) + } } data class PostFeedRequest( diff --git a/src/main/kotlin/com/toyProject7/karrot/feed/service/FeedService.kt b/src/main/kotlin/com/toyProject7/karrot/feed/service/FeedService.kt index 32ea054..2250cee 100644 --- a/src/main/kotlin/com/toyProject7/karrot/feed/service/FeedService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/feed/service/FeedService.kt @@ -213,6 +213,22 @@ class FeedService( return feeds } + @Transactional + fun getFeedsThatUserComments( + id: String, + feedId: Long, + ): List { + val comments: List = commentService.getCommentsByUser(id) + val feeds: List = + comments + .map { it.feed } + .filter { it.id!! < feedId } + .distinctBy { it.id } + .sortedByDescending { it.id } + if (feeds.size < 10) return feeds + return feeds.subList(0, 10) + } + @Transactional fun getFeedEntityById(feedId: Long): FeedEntity { return feedRepository.findByIdOrNull(feedId) ?: throw FeedNotFoundException() diff --git a/src/main/kotlin/com/toyProject7/karrot/profile/ProfileException.kt b/src/main/kotlin/com/toyProject7/karrot/profile/ProfileException.kt index c90a8de..03aa70a 100644 --- a/src/main/kotlin/com/toyProject7/karrot/profile/ProfileException.kt +++ b/src/main/kotlin/com/toyProject7/karrot/profile/ProfileException.kt @@ -16,3 +16,9 @@ class ProfileNotFoundException : ProfileException( httpStatusCode = HttpStatus.NOT_FOUND, msg = "Profile not found", ) + +class ProfileEditNicknameConflictException : ProfileException( + errorCode = 0, + httpStatusCode = HttpStatus.CONFLICT, + msg = "Nickname conflict", +) diff --git a/src/main/kotlin/com/toyProject7/karrot/profile/service/ProfileService.kt b/src/main/kotlin/com/toyProject7/karrot/profile/service/ProfileService.kt index ade0f9b..4af7b8d 100644 --- a/src/main/kotlin/com/toyProject7/karrot/profile/service/ProfileService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/profile/service/ProfileService.kt @@ -6,17 +6,16 @@ import com.toyProject7.karrot.article.service.ArticleService import com.toyProject7.karrot.image.persistence.ImageUrlEntity import com.toyProject7.karrot.image.service.ImageService import com.toyProject7.karrot.manner.controller.Manner +import com.toyProject7.karrot.profile.ProfileEditNicknameConflictException import com.toyProject7.karrot.profile.ProfileNotFoundException import com.toyProject7.karrot.profile.controller.EditProfileRequest import com.toyProject7.karrot.profile.controller.Profile import com.toyProject7.karrot.profile.persistence.ProfileEntity import com.toyProject7.karrot.profile.persistence.ProfileRepository import com.toyProject7.karrot.review.controller.Review -import com.toyProject7.karrot.review.service.ReviewService import com.toyProject7.karrot.user.controller.User import com.toyProject7.karrot.user.persistence.UserEntity import com.toyProject7.karrot.user.service.UserService -import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.Instant @@ -28,7 +27,6 @@ class ProfileService( private val userService: UserService, private val articleService: ArticleService, private val imageService: ImageService, - @Lazy private val reviewService: ReviewService, ) { @Transactional fun getMyProfile(user: User): Profile { @@ -71,6 +69,10 @@ class ProfileService( user: User, request: EditProfileRequest, ): Profile { + if (user.nickname != request.nickname && userService.existUserEntityByNickname(request.nickname)) { + throw ProfileEditNicknameConflictException() + } + val userEntity = userService.getUserEntityById(user.id) val profileEntity = profileRepository.findByUserId(user.id) ?: throw ProfileNotFoundException() val itemCount = getItemCount(user.id) @@ -122,7 +124,14 @@ class ProfileService( nickname: String, reviewId: Long, ): List { - return reviewService.getPreviousReviews(nickname, reviewId) + val userEntity = userService.getUserEntityByNickname(nickname) + val profileEntity = profileRepository.findByUserId(userEntity.id!!) ?: throw ProfileNotFoundException() + + return profileEntity.reviews + .filter { it.id!! < reviewId } + .sortedByDescending { it.createdAt } + .take(10) + .map { Review.fromEntity(it) } } fun getItemCount(id: String): Int { diff --git a/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt b/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt index a5c1aec..1097603 100644 --- a/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt @@ -1,10 +1,7 @@ package com.toyProject7.karrot.review.controller import com.toyProject7.karrot.review.service.ReviewService -import com.toyProject7.karrot.user.AuthUser -import com.toyProject7.karrot.user.controller.User import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @@ -13,13 +10,11 @@ import org.springframework.web.bind.annotation.RestController class ReviewController( private val reviewService: ReviewService, ) { - @PostMapping("/api/{sellerNickname}/review") + @PostMapping("/api/review/post") fun createReview( @RequestBody request: ReviewCreateRequest, - @PathVariable sellerNickname: String, - @AuthUser user: User, ): ResponseEntity { - val review = reviewService.createReview(sellerNickname, user.nickname, request.content, request.location) + val review = reviewService.createReview(request) return ResponseEntity.status(201).body(review) } } @@ -27,6 +22,9 @@ class ReviewController( data class ReviewCreateRequest( val content: String, val location: String, + val isWritedByBuyer: Boolean, + val sellerId: String, + val buyerId: String, ) typealias ReviewCreateResponse = Review diff --git a/src/main/kotlin/com/toyProject7/karrot/review/service/ReviewService.kt b/src/main/kotlin/com/toyProject7/karrot/review/service/ReviewService.kt index bc74bcd..8b84c84 100644 --- a/src/main/kotlin/com/toyProject7/karrot/review/service/ReviewService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/review/service/ReviewService.kt @@ -3,9 +3,9 @@ package com.toyProject7.karrot.review.service import com.toyProject7.karrot.profile.service.ProfileService import com.toyProject7.karrot.review.ReviewContentLengthOutOfRangeException import com.toyProject7.karrot.review.controller.Review +import com.toyProject7.karrot.review.controller.ReviewCreateRequest import com.toyProject7.karrot.review.persistence.ReviewEntity import com.toyProject7.karrot.review.persistence.ReviewRepository -import com.toyProject7.karrot.user.controller.User import com.toyProject7.karrot.user.service.UserService import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -18,51 +18,36 @@ class ReviewService( private val profileService: ProfileService, ) { @Transactional - fun createReview( - sellerNickname: String, - buyerNickname: String, - content: String, - location: String, - ): Review { - validateContent(content) + fun createReview(request: ReviewCreateRequest): Review { + validateContent(request.content) - val sellerEntity = userService.getUserEntityByNickname(sellerNickname) - val seller = User.fromEntity(sellerEntity) - val sellerProfileEntity = profileService.getProfileEntityByUserId(seller.id) + val sellerEntity = userService.getUserEntityById(request.sellerId) + val sellerProfileEntity = profileService.getProfileEntityByUserId(request.sellerId) - val buyerEntity = userService.getUserEntityByNickname(buyerNickname) - val buyer = User.fromEntity(buyerEntity) - val buyerProfileEntity = profileService.getProfileEntityByUserId(buyer.id) + val buyerEntity = userService.getUserEntityById(request.buyerId) + val buyerProfileEntity = profileService.getProfileEntityByUserId(request.buyerId) val reviewEntity = ReviewEntity( seller = sellerEntity, buyer = buyerEntity, - content = content, - location = location, + content = request.content, + location = request.location, createdAt = Instant.now(), updatedAt = Instant.now(), ).let { reviewRepository.save(it) } - sellerProfileEntity.reviews += reviewEntity - buyerProfileEntity.reviews += reviewEntity + if (request.isWritedByBuyer) { + sellerProfileEntity.reviews += reviewEntity + } else { + buyerProfileEntity.reviews += reviewEntity + } return Review.fromEntity(reviewEntity) } - @Transactional - fun getPreviousReviews( - nickname: String, - reviewId: Long, - ): List { - return reviewRepository.findTop10BySellerNicknameOrBuyerNicknameAndIdBeforeOrderByCreatedAtDesc(nickname, nickname, reviewId).map { - reviewEntity -> - Review.fromEntity(reviewEntity) - } - } - private fun validateContent(content: String) { if (content.isBlank()) { throw ReviewContentLengthOutOfRangeException() diff --git a/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt b/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt index 0da6517..2977c13 100644 --- a/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt +++ b/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt @@ -8,5 +8,6 @@ object SecurityConstants { "/auth/**", "/login/oauth2/**", "/api/test", + "/ws/**", ) } diff --git a/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt b/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt index b59095b..319c3f5 100644 --- a/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt @@ -31,10 +31,9 @@ class CustomAuthenticationSuccessHandler( val attributes = oauth2User.attributes val providerId = extractProviderId(attributes, provider) val email = extractEmail(attributes, provider) - val name = extractName(attributes, provider) // Create or retrieve the user - val user = userService.createOrRetrieveSocialUser(email, providerId, provider, name) + val user = userService.createOrRetrieveSocialUser(email, providerId, provider) // Generate JWT val accessToken = UserAccessTokenUtil.generateAccessToken(user.id) @@ -86,13 +85,13 @@ class CustomAuthenticationSuccessHandler( } } - private fun extractName( + /*private fun extractName( attributes: Map, provider: String, ): String { return when (provider) { "google" -> attributes["name"] as String - "naver" -> (attributes["response"] as Map<*, *>)["name"] as String + "naver" -> (attributes["response"] as Map<*, *>)["nickname"] as String "kakao" -> { val kakaoAccount = attributes["kakao_account"] as Map<*, *> val profile = kakaoAccount["profile"] as Map<*, *> @@ -100,5 +99,5 @@ class CustomAuthenticationSuccessHandler( } else -> "Unknown" } - } + }*/ } diff --git a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt index bf5a8e5..e3ca5a6 100644 --- a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt @@ -108,16 +108,26 @@ class UserService( email: String, providerId: String, provider: String, - username: String, ): User { // Check if the user exists by email val existingUser = userRepository.findSocialUserByEmail(email) return existingUser?.let { - // Convert existingUser (of type SocialUser) to User DTO + // If the user exists, convert to User DTO and return User.fromEntity(it) } ?: run { - // If the user doesn't exist, create a new one + // If the user doesn't exist, generate a unique random username + var username = generateRandomString() + var isUnique = false + + while (!isUnique) { + if (!userRepository.existsByNickname(username)) { + isUnique = true + } + username = generateRandomString() + } + + // Create a new SocialUser with the generated username val newUser = SocialUser( email = email, @@ -129,18 +139,29 @@ class UserService( imageUrl = null, updatedAt = Instant.now(), ) - val savedUser = userRepository.save(newUser) // This should save as SocialUser + // Save the new user + val savedUser = userRepository.save(newUser) + + // Create and save the associated profile val profileEntity = ProfileEntity( user = newUser, ) profileService.saveProfileEntity(profileEntity) - User.fromEntity(savedUser) // Convert and return as User DTO + // Convert and return as User DTO + User.fromEntity(savedUser) } } + private fun generateRandomString(length: Int = 8): String { + val characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + return (1..length) + .map { characters.random() } + .joinToString("") + } + @Transactional fun getUserEntityById(id: String): UserEntity { return userRepository.findByIdOrNull(id) ?: throw UserNotFoundException() @@ -158,4 +179,9 @@ class UserService( fun getUserEntityByNickname(nickname: String): UserEntity { return userRepository.findByNickname(nickname) ?: throw UserNotFoundException() } + + @Transactional + fun existUserEntityByNickname(nickname: String): Boolean { + return userRepository.existsByNickname(nickname) + } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d6fa4fe..5fed258 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -29,9 +29,8 @@ spring: authorizationGrantType: authorization_code redirectUri: "https://toykarrot.shop/{action}/oauth2/code/{registrationId}" scope: - - nickname + - profile - email - - profile_image clientName: Naver kakao: clientId: '${KAKAO_CLI_ID}' @@ -41,7 +40,6 @@ spring: redirectUri: "https://toykarrot.shop/{action}/oauth2/code/{registrationId}" scope: - profile_nickname - - profile_image - account_email clientName: Kakao provider: