Skip to content

Commit c82a5ae

Browse files
정동하정동하
authored andcommitted
feat(be):User도메인 1차 구현
1 parent 0c1a69c commit c82a5ae

File tree

10 files changed

+235
-23
lines changed

10 files changed

+235
-23
lines changed

setup-git-hooks.sh

100644100755
File mode changed.
Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,64 @@
11
package com.back.koreaTravelGuide.domain.user.controller
22

3-
// TODO: 사용자 컨트롤러 - 사용자 관련 API 엔드포인트 (향후 구현)
4-
class UserController
3+
import com.back.koreaTravelGuide.common.ApiResponse
4+
import com.back.koreaTravelGuide.domain.user.dto.request.UserUpdateRequest
5+
import com.back.koreaTravelGuide.domain.user.dto.response.UserResponse
6+
import com.back.koreaTravelGuide.domain.user.service.UserService
7+
import org.springframework.http.ResponseEntity
8+
import org.springframework.security.core.annotation.AuthenticationPrincipal
9+
import org.springframework.security.core.userdetails.UserDetails
10+
import org.springframework.web.bind.annotation.DeleteMapping
11+
import org.springframework.web.bind.annotation.GetMapping
12+
import org.springframework.web.bind.annotation.PatchMapping
13+
import org.springframework.web.bind.annotation.PathVariable
14+
import org.springframework.web.bind.annotation.RequestBody
15+
import org.springframework.web.bind.annotation.RestController
16+
17+
@RestController
18+
class UserController(
19+
private val userService: UserService,
20+
) {
21+
@GetMapping("/api/users/me")
22+
fun getMyProfile(
23+
@AuthenticationPrincipal userDetails: UserDetails,
24+
): ResponseEntity<ApiResponse<UserResponse>> {
25+
// username 이메일로 설정할것임
26+
val userProfile = userService.getUserProfileByEmail(userDetails.username)
27+
val response = ApiResponse("내 정보를 성공적으로 조회했습니다.", userProfile)
28+
return ResponseEntity.ok(response)
29+
}
30+
31+
@PatchMapping("/api/users/me")
32+
fun updateMyProfile(
33+
@AuthenticationPrincipal userDetails: UserDetails,
34+
@RequestBody request: UserUpdateRequest,
35+
): ResponseEntity<ApiResponse<UserResponse>> {
36+
val updatedProfile = userService.updateProfile(userDetails.username, request)
37+
val response = ApiResponse("정보가 성공적으로 수정되었습니다.", updatedProfile)
38+
return ResponseEntity.ok(response)
39+
}
40+
41+
@DeleteMapping("/api/users/me")
42+
fun deleteUser(
43+
@AuthenticationPrincipal userDetails: UserDetails,
44+
): ResponseEntity<ApiResponse<Unit>> {
45+
userService.deleteUser(userDetails.username)
46+
return ResponseEntity.ok(ApiResponse("회원 탈퇴가 완료되었습니다."))
47+
}
48+
49+
@GetMapping("/api/guides")
50+
fun getAllGuides(): ResponseEntity<ApiResponse<List<UserResponse>>> {
51+
val guides = userService.getAllGuides()
52+
val response = ApiResponse("전체 가이드 목록을 조회했습니다.", guides)
53+
return ResponseEntity.ok(response)
54+
}
55+
56+
@GetMapping("/api/guides/{guideId}")
57+
fun getGuideById(
58+
@PathVariable guideId: Long,
59+
): ResponseEntity<ApiResponse<UserResponse>> {
60+
val guide = userService.getGuideById(guideId)
61+
val response = ApiResponse("가이드 정보를 성공적으로 조회했습니다.", guide)
62+
return ResponseEntity.ok(response)
63+
}
64+
}

src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/UserRequest.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/UserResponse.kt

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.back.koreaTravelGuide.domain.user.dto.request
2+
3+
data class UserUpdateRequest(
4+
val nickname: String?,
5+
val profileImageUrl: String?,
6+
val location: String?,
7+
val description: String?,
8+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.back.koreaTravelGuide.domain.user.dto.response
2+
3+
import com.back.koreaTravelGuide.domain.user.entity.User
4+
import com.back.koreaTravelGuide.domain.user.enums.UserRole
5+
6+
data class UserResponse(
7+
val id: Long,
8+
val email: String,
9+
val nickname: String,
10+
val profileImageUrl: String?,
11+
val role: UserRole,
12+
// Guide
13+
val location: String?,
14+
// Guide
15+
val description: String?,
16+
) {
17+
companion object {
18+
fun from(user: User): UserResponse {
19+
return UserResponse(
20+
id = user.id!!,
21+
email = user.email,
22+
nickname = user.nickname,
23+
profileImageUrl = user.profileImageUrl,
24+
role = user.role,
25+
location = user.location,
26+
description = user.description,
27+
)
28+
}
29+
}
30+
}
Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,50 @@
11
package com.back.koreaTravelGuide.domain.user.entity
22

3-
// TODO: 사용자 엔티티 - 사용자 정보 저장 (향후 구현)
4-
class User
3+
import com.back.koreaTravelGuide.domain.user.enums.UserRole
4+
import jakarta.persistence.Column
5+
import jakarta.persistence.Entity
6+
import jakarta.persistence.EntityListeners
7+
import jakarta.persistence.EnumType
8+
import jakarta.persistence.Enumerated
9+
import jakarta.persistence.GeneratedValue
10+
import jakarta.persistence.GenerationType
11+
import jakarta.persistence.Id
12+
import jakarta.persistence.Table
13+
import org.springframework.data.annotation.CreatedDate
14+
import org.springframework.data.annotation.LastModifiedDate
15+
import org.springframework.data.jpa.domain.support.AuditingEntityListener
16+
import java.time.LocalDateTime
17+
18+
@Entity
19+
@Table(name = "users")
20+
@EntityListeners(AuditingEntityListener::class)
21+
class User(
22+
@Id
23+
@GeneratedValue(strategy = GenerationType.IDENTITY)
24+
val id: Long? = null,
25+
@Column(name = "oauth_provider", nullable = false)
26+
val oauthProvider: String,
27+
@Column(name = "oauth_id", nullable = false)
28+
val oauthId: String,
29+
@Column(unique = true, nullable = false)
30+
val email: String,
31+
@Column(nullable = false)
32+
var nickname: String,
33+
@Column(name = "profile_image_url")
34+
var profileImageUrl: String? = null,
35+
@Enumerated(EnumType.STRING)
36+
@Column(nullable = false)
37+
var role: UserRole = UserRole.USER,
38+
// GUIDE
39+
@Column
40+
var location: String? = null,
41+
// GUIDE
42+
@Column(columnDefinition = "TEXT")
43+
var description: String? = null,
44+
@CreatedDate
45+
@Column(name = "created_at", nullable = false, updatable = false)
46+
val createdAt: LocalDateTime = LocalDateTime.now(),
47+
@LastModifiedDate
48+
@Column(name = "last_login_at")
49+
var lastLoginAt: LocalDateTime = LocalDateTime.now(),
50+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.back.koreaTravelGuide.domain.user.enums
2+
3+
enum class UserRole {
4+
USER,
5+
ADMIN,
6+
GUIDE,
7+
}
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
package com.back.koreaTravelGuide.domain.user.repository
22

3-
// TODO: 사용자 리포지토리 - 사용자 데이터 저장/조회 (향후 구현)
4-
interface UserRepository
3+
import com.back.koreaTravelGuide.domain.user.entity.User
4+
import com.back.koreaTravelGuide.domain.user.enums.UserRole
5+
import org.springframework.data.jpa.repository.JpaRepository
6+
import org.springframework.stereotype.Repository
7+
8+
@Repository
9+
interface UserRepository : JpaRepository<User, Long> {
10+
fun findByOauthProviderAndOauthId(
11+
oauthProvider: String,
12+
oauthId: String,
13+
): User?
14+
15+
fun findByEmail(email: String): User?
16+
17+
fun findByRole(role: UserRole): List<User>
18+
}
Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,66 @@
11
package com.back.koreaTravelGuide.domain.user.service
22

3-
// TODO: 사용자 서비스 - 사용자 관리 비즈니스 로직 (향후 구현)
4-
class UserService
3+
import com.back.koreaTravelGuide.domain.user.dto.request.UserUpdateRequest
4+
import com.back.koreaTravelGuide.domain.user.dto.response.UserResponse
5+
import com.back.koreaTravelGuide.domain.user.enums.UserRole
6+
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
7+
import jakarta.persistence.EntityNotFoundException
8+
import org.springframework.stereotype.Service
9+
import org.springframework.transaction.annotation.Transactional
10+
11+
@Service
12+
@Transactional
13+
class UserService(
14+
private val userRepository: UserRepository,
15+
) {
16+
// 회원 이메일 단건 조회
17+
@Transactional(readOnly = true)
18+
fun getUserProfileByEmail(email: String): UserResponse {
19+
val user =
20+
userRepository.findByEmail(email)
21+
?: throw EntityNotFoundException("해당 이메일을 가진 사용자를 찾을 수 없습니다")
22+
return UserResponse.from(user)
23+
}
24+
25+
// 회원 업데이트
26+
fun updateProfile(
27+
email: String,
28+
request: UserUpdateRequest,
29+
): UserResponse {
30+
val user =
31+
userRepository.findByEmail(email)
32+
?: throw EntityNotFoundException("해당 이메일을 가진 사용자를 찾을 수 없습니다")
33+
34+
user.nickname = request.nickname ?: user.nickname
35+
user.profileImageUrl = request.profileImageUrl ?: user.profileImageUrl
36+
user.location = request.location ?: user.location
37+
user.description = request.description ?: user.description
38+
39+
val updatedUser = userRepository.save(user)
40+
return UserResponse.from(updatedUser)
41+
}
42+
43+
// 회원 삭제
44+
fun deleteUser(email: String) {
45+
val user =
46+
userRepository.findByEmail(email)
47+
?: throw EntityNotFoundException("해당 이메일을 가진 사용자를 찾을 수 없습니다")
48+
userRepository.delete(user)
49+
}
50+
51+
// 전체 가이드 목록 조회
52+
@Transactional(readOnly = true)
53+
fun getAllGuides(): List<UserResponse> {
54+
return userRepository.findByRole(UserRole.GUIDE)
55+
.map { UserResponse.from(it) }
56+
}
57+
58+
// 가이드 정보 단건 조회
59+
@Transactional(readOnly = true)
60+
fun getGuideById(guideId: Long): UserResponse {
61+
val user =
62+
userRepository.findById(guideId)
63+
.orElseThrow { EntityNotFoundException("해당 ID의 사용자를 찾을 수 없습니다") }
64+
return UserResponse.from(user)
65+
}
66+
}

0 commit comments

Comments
 (0)