From b4ea8cf889df9b40daf7d01b6b0ce04d971f9d0e Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Fri, 10 Jan 2025 21:02:47 +0900 Subject: [PATCH 1/7] :pencil: added 'login/oauth2/**' path to public_paths --- .../kotlin/com/toyProject7/karrot/security/SecurityConstants.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt b/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt index 7c93f8b..3f680e1 100644 --- a/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt +++ b/src/main/kotlin/com/toyProject7/karrot/security/SecurityConstants.kt @@ -6,5 +6,6 @@ object SecurityConstants { "/api/auth/**", "/oauth2/**", "/auth/**", + "/login/oauth2/**" ) } From c2d7b3c56a56d58036e3cc70ad0efdddc3296afc Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Sun, 26 Jan 2025 13:54:42 +0900 Subject: [PATCH 2/7] :wrench: changed all {nickname} endpoints to decode the nickname parameter --- .../manner/controller/MannerController.kt | 6 +++++- .../profile/controller/ProfileController.kt | 21 +++++++++++++++---- .../review/controller/ReviewController.kt | 6 +++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/toyProject7/karrot/manner/controller/MannerController.kt b/src/main/kotlin/com/toyProject7/karrot/manner/controller/MannerController.kt index 3af6fd2..225c1e9 100644 --- a/src/main/kotlin/com/toyProject7/karrot/manner/controller/MannerController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/manner/controller/MannerController.kt @@ -5,6 +5,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RestController +import java.net.URLDecoder @RestController class MannerController( @@ -15,7 +16,10 @@ class MannerController( @PathVariable nickname: String, @PathVariable mannerType: MannerType, ): ResponseEntity { - mannerService.increaseMannerCount(nickname, mannerType) + // Decode the nickname + val decodedNickname = URLDecoder.decode(nickname, "UTF-8") + + mannerService.increaseMannerCount(decodedNickname, mannerType) return ResponseEntity.noContent().build() } } diff --git a/src/main/kotlin/com/toyProject7/karrot/profile/controller/ProfileController.kt b/src/main/kotlin/com/toyProject7/karrot/profile/controller/ProfileController.kt index 4403a10..87f90c6 100644 --- a/src/main/kotlin/com/toyProject7/karrot/profile/controller/ProfileController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/profile/controller/ProfileController.kt @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import java.net.URLDecoder @RestController class ProfileController( @@ -37,7 +38,10 @@ class ProfileController( fun getProfile( @PathVariable nickname: String, ): ResponseEntity { - val profile = profileService.getProfile(nickname) + // Decode nickname + val decodedNickname = URLDecoder.decode(nickname, "UTF-8") + + val profile = profileService.getProfile(decodedNickname) return ResponseEntity.ok(profile) } @@ -46,7 +50,10 @@ class ProfileController( @PathVariable nickname: String, @RequestParam articleId: Long, ): ResponseEntity> { - val itemList: List = profileService.getProfileSells(nickname, articleId) + // Decode nickname + val decodedNickname = URLDecoder.decode(nickname, "UTF-8") + + val itemList: List = profileService.getProfileSells(decodedNickname, articleId) return ResponseEntity.ok(itemList) } @@ -63,7 +70,10 @@ class ProfileController( fun getManners( @PathVariable nickname: String, ): ResponseEntity { - val manners = profileService.getManner(nickname) + // Decode nickname + val decodedNickname = URLDecoder.decode(nickname, "UTF-8") + + val manners = profileService.getManner(decodedNickname) return ResponseEntity.ok(manners) } @@ -72,7 +82,10 @@ class ProfileController( @PathVariable nickname: String, @RequestParam("reviewId") reviewId: Long, ): ResponseEntity { - val reviews = profileService.getPreviousReviews(nickname, reviewId) + // Decode nickname + val decodedNickname = URLDecoder.decode(nickname, "UTF-8") + + val reviews = profileService.getPreviousReviews(decodedNickname, reviewId) return ResponseEntity.ok(reviews) } } 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..18f05fd 100644 --- a/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt +++ b/src/main/kotlin/com/toyProject7/karrot/review/controller/ReviewController.kt @@ -8,6 +8,7 @@ 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 +import java.net.URLDecoder @RestController class ReviewController( @@ -19,7 +20,10 @@ class ReviewController( @PathVariable sellerNickname: String, @AuthUser user: User, ): ResponseEntity { - val review = reviewService.createReview(sellerNickname, user.nickname, request.content, request.location) + // Decode the sellerNickname + val decodedSellerNickname = URLDecoder.decode(sellerNickname, "UTF-8") + + val review = reviewService.createReview(decodedSellerNickname, user.nickname, request.content, request.location) return ResponseEntity.status(201).body(review) } } From d8613c6cddbeaaf39306d690eaf66b1fff6b8b31 Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Sun, 26 Jan 2025 13:56:19 +0900 Subject: [PATCH 3/7] :pencil: included /ws/** to PUBLIC_PATHS --- src/main/kotlin/com/toyProject7/karrot/SecurityConfig.kt | 1 - .../kotlin/com/toyProject7/karrot/security/SecurityConstants.kt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) 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/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/**", ) } From 789834c0ed0f77354d7bd68852d52ae9484d430e Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Sun, 26 Jan 2025 20:35:00 +0900 Subject: [PATCH 4/7] :bug: fixed naver login bugs --- .../socialLogin/handler/CustomAuthenticationSuccessHandler.kt | 2 +- src/main/resources/application.yaml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) 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..5d84393 100644 --- a/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/com/toyProject7/karrot/socialLogin/handler/CustomAuthenticationSuccessHandler.kt @@ -92,7 +92,7 @@ class CustomAuthenticationSuccessHandler( ): 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<*, *> 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: From 33c8c47c7800df74a72417084367ea426345b94a Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Fri, 31 Jan 2025 00:32:06 +0900 Subject: [PATCH 5/7] :sparkles: added README --- README.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 README.md 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을 이용하여 실시간 채팅을 구현하였습니다. + +### 나의 당근 +- **프로필 수정**: 사진, 닉네임, 동네를 수정할 수 있습니다. +- **매너온도 확인**: 사용자의 매너온도를 확인할 수 있습니다. +- **판매내역**: 판매한 물품의 내역을 조회할 수 있습니다. +- **내 매너 평가 조회**: 다른 사용자로부터 받은 매너 평가를 확인할 수 있습니다. +- **거래 후기 조회**: 거래 후기를 조회할 수 있습니다. + +### 타사용자 프로필 +- **매너 칭찬**: 다른 사용자의 매너를 칭찬할 수 있습니다. +- **판매물품 목록**: 해당 사용자가 판매 중인 물품을 조회할 수 있습니다. +- **받은 매너 평가**: 해당 사용자가 받은 매너 평가를 확인할 수 있습니다. +- **받은 거래 후기 조회**: 해당 사용자가 받은 거래 후기를 조회할 수 있습니다. From b6623b81320c42173ac6b5c1520a8e720efd76dc Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Fri, 31 Jan 2025 14:27:14 +0900 Subject: [PATCH 6/7] :pencil: when new social user is created its given a random 8 length nickname --- .../CustomAuthenticationSuccessHandler.kt | 7 ++-- .../karrot/user/service/UserService.kt | 33 +++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) 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 5d84393..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,7 +85,7 @@ class CustomAuthenticationSuccessHandler( } } - private fun extractName( + /*private fun extractName( attributes: Map, provider: String, ): String { @@ -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..5d1ef37 100644 --- a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt @@ -108,20 +108,30 @@ 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, - nickname = username, + nickname = username, // Assign the generated username provider = provider, providerId = providerId, location = "void", @@ -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() From d69d40374e76ad1cb5dc425c59aefc6d824add16 Mon Sep 17 00:00:00 2001 From: Junghoon Kim Date: Fri, 31 Jan 2025 14:33:03 +0900 Subject: [PATCH 7/7] :art: lint --- .../kotlin/com/toyProject7/karrot/user/service/UserService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5d1ef37..1510012 100644 --- a/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt +++ b/src/main/kotlin/com/toyProject7/karrot/user/service/UserService.kt @@ -131,7 +131,7 @@ class UserService( val newUser = SocialUser( email = email, - nickname = username, // Assign the generated username + nickname = username, provider = provider, providerId = providerId, location = "void",