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/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/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 bd6269f..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()
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: