Skip to content
2 changes: 1 addition & 1 deletion src/main/kotlin/com/doki/global/security/JwtProvider.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.doki.global.security

import com.doki.user.auth.infrastructure.dto.response.TokenResponse
import com.doki.user.domain.vo.UserRole
import com.doki.user.user.domain.vo.UserRole
import io.jsonwebtoken.*
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/com/doki/user/auth/application/AuthService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import com.doki.user.auth.domain.vo.AuthProvider
import com.doki.global.exceptions.CustomException
import com.doki.global.exceptions.auth.AuthExceptionType
import com.doki.global.security.JwtProvider
import com.doki.user.domain.User
import com.doki.user.user.domain.User
import com.doki.user.auth.domain.UserOAuth
import com.doki.user.infrastructure.UserOAuthRepository
import com.doki.user.infrastructure.UserRepository
import com.doki.user.user.domain.UserRepository
import com.doki.user.user.infrastructure.UserOAuthRepository
import jakarta.servlet.http.HttpServletResponse
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand Down Expand Up @@ -46,10 +46,10 @@ class AuthService(
}

val user = userRepository.findById(userId)
.orElseThrow { CustomException(AuthExceptionType.USER_NOT_FOUND) }
?: throw CustomException(AuthExceptionType.USER_NOT_FOUND)

// 마지막 로그인 시각 업데이트
user.updateLasLoginTime()
user.updateLastLoginTime()

return jwtProvider.generateToken(user.id, user.role)
}
Expand All @@ -67,7 +67,7 @@ class AuthService(

val userId = jwtProvider.getSubjectAsUserId(refreshToken)
val user = userRepository.findById(userId)
.orElseThrow { CustomException(AuthExceptionType.USER_NOT_FOUND) }
?: throw CustomException(AuthExceptionType.USER_NOT_FOUND)

// accessToken, refreshToken 재발급
val tokenResponse = jwtProvider.generateToken(user.id, user.role)
Expand Down
19 changes: 0 additions & 19 deletions src/main/kotlin/com/doki/user/dto/GetUserLoginInfoResponse.kt

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.doki.user.application
package com.doki.user.user.application

import com.doki.global.exceptions.CustomException
import com.doki.global.exceptions.auth.AuthExceptionType
import com.doki.user.application.dto.GetUserLoginInfoResponse
import com.doki.user.infrastructure.UserRepository
import com.doki.user.user.application.dto.GetUserLoginInfoResponse
import com.doki.user.user.domain.UserRepository
import org.springframework.stereotype.Service

@Service
Expand All @@ -12,7 +12,7 @@ class UserService(
) {
fun getUser(userId: Long): GetUserLoginInfoResponse {
val user = userRepository.findById(userId)
.orElseThrow{ CustomException(AuthExceptionType.USER_NOT_FOUND)}
?: throw CustomException(AuthExceptionType.USER_NOT_FOUND)

return GetUserLoginInfoResponse.from(user)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.doki.user.user.application

import com.doki.user.user.application.dto.CreateUserSurveyCommand
import com.doki.user.user.domain.UserRepository
import com.doki.user.user.domain.UserSurvey
import com.doki.user.user.domain.dto.UserSurveyCreatedResponse
import com.doki.user.user.domain.dto.UserSurveyResponse
import com.doki.user.user.domain.UserSurveyRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Transactional
@Service
class UserSurveyCommandService(
private val userRepository: UserRepository,
private val userSurveyRepository: UserSurveyRepository,
) {

fun findUserSurvey(userId: Long): UserSurveyResponse {
val survey = userSurveyRepository.findByUserId(userId)
?: throw IllegalArgumentException("UserSurvey does not exist.")

return UserSurveyResponse.from(survey)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 조회용으로 보여서 QueryService로 가는게 좋아보입니다!
현재 커맨드 서비스에서 사용한다면, private fun으로 만들어주세요

fun saveUserSurvey(userId: Long, command: CreateUserSurveyCommand): UserSurveyCreatedResponse {
val user = userRepository.findById(userId)
?: throw IllegalArgumentException("User with id $userId does not exist.")

if (userSurveyRepository.existsByUserId(userId)) {
throw IllegalArgumentException("UserSurvey already exists.")
}

val survey = UserSurvey.of(
user = user,
concept = command.concept,
idolName = command.idolName,
visitStartDate = command.visitStartDate,
visitEndDate = command.visitEndDate,
places = command.places
)

val saved = userSurveyRepository.save(survey)
return UserSurveyCreatedResponse(saved.id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.doki.user.user.application.dto

import com.doki.user.user.domain.vo.ConceptType
import java.time.LocalDate

data class CreateUserSurveyCommand (
val concept: ConceptType,
val idolName: String,
val visitStartDate: LocalDate,
val visitEndDate: LocalDate,
val places: List<String>
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.doki.user.application.dto
package com.doki.user.user.application.dto

import com.doki.user.domain.User
import com.doki.user.user.domain.User
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDateTime

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.doki.user.domain
package com.doki.user.user.domain

import com.doki.user.domain.vo.UserRole
import com.doki.user.user.domain.vo.UserRole
import jakarta.persistence.*
import java.time.LocalDateTime

Expand All @@ -22,7 +22,7 @@ class User(
var lastLoginAt: LocalDateTime? = null
) {
// 마지막 로그인 시점 업데이트
fun updateLasLoginTime(now : LocalDateTime = LocalDateTime.now()) {
fun updateLastLoginTime(now : LocalDateTime = LocalDateTime.now()) {
this.lastLoginAt = now
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/com/doki/user/user/domain/UserRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.doki.user.user.domain

interface UserRepository {

fun findById(userId: Long): User?

fun findByEmail(email: String): User?

fun save(user: User): User
}
70 changes: 70 additions & 0 deletions src/main/kotlin/com/doki/user/user/domain/UserSurvey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.doki.user.user.domain

import com.doki.global.BaseEntity
import com.doki.user.user.domain.vo.ConceptType
import jakarta.persistence.*
import java.time.LocalDate

@Entity
@Table(name = "user_survey")
class UserSurvey(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,

@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", unique = true)
val user: User,

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
val concept: ConceptType,

@Column(name = "idol_name", nullable = false)
val idolName: String,

@Column(name = "visit_start_date", nullable = false)
val visitStartDate: LocalDate,

@Column(name = "visit_end_date", nullable = false)
val visitEndDate: LocalDate,

@ElementCollection
@CollectionTable(
name = "user_survey_places",
joinColumns = [JoinColumn(name = "user_survey_id")]
)
@Column(name = "place", nullable = false, length = 100)
val places: List<String>
Comment on lines +32 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사전 위치 기반으로 데이터를 제공해준다면, 해당 정보가 필요할 거 같은데 조회할 때 복잡한 요구사항은 없을 것 같아서 지금 구조도 괜찮아 보입니다! 나중에 위치 데이터를 복잡하게 다룬다면 그때 개선하는 걸로 하시죠!!

) : BaseEntity() {

init {
require(places.isNotEmpty()) { "place must not be blank" }
require(!visitEndDate.isBefore(visitStartDate)) { "visitEndDate must be on or after visitStartDate" }
}

companion object {
private fun normalizeplaces(places: List<String>): List<String> =
places.map { it.trim() }
.filter { it.isNotBlank() }
.distinct()

fun of(
user: User,
concept: ConceptType,
idolName: String,
visitStartDate: LocalDate,
visitEndDate: LocalDate,
places: List<String>
): UserSurvey {
return UserSurvey(
user = user,
concept = concept,
idolName = idolName.trim(),
visitStartDate = visitStartDate,
visitEndDate = visitEndDate,
places = normalizeplaces(places)
)
}
}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/com/doki/user/user/domain/UserSurveyRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.doki.user.user.domain

interface UserSurveyRepository {

fun findByUserId(userId: Long): UserSurvey?

fun existsByUserId(userId: Long): Boolean

fun save(userSurvey: UserSurvey): UserSurvey
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.doki.user.user.domain.dto

import io.swagger.v3.oas.annotations.media.Schema

data class UserSurveyCreatedResponse(
@field:Schema(description = "생성된 설문조사 ID", example = "1")
val id: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.doki.user.user.domain.dto

import com.doki.user.user.domain.UserSurvey
import com.doki.user.user.domain.vo.ConceptType
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDate

data class UserSurveyResponse(
@field:Schema(description = "설문조사 ID", example = "1")
val id: Long,

@field:Schema(description = "선호 컨셉", example = "GIRL_CRUSH")
val concept: ConceptType,

@field:Schema(description = "선호 아이돌 또는 그룹", example = "에스파")
val idolName: String,

@field:Schema(description = "여행 시작일", example = "2025-07-20")
val visitStartDate: LocalDate,

@field:Schema(description = "여행 종료일", example = "2025-07-22")
val visitEndDate: LocalDate,

@field:Schema(description = "여행 장소", example = "['용산구', '마포구']")
val places: List<String>
) {
companion object {
fun from(userSurvey: UserSurvey): UserSurveyResponse {
return UserSurveyResponse(
id = userSurvey.id,
concept = userSurvey.concept,
idolName = userSurvey.idolName,
visitStartDate = userSurvey.visitStartDate,
visitEndDate = userSurvey.visitEndDate,
places = userSurvey.places
)
}
}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/com/doki/user/user/domain/vo/ConceptType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.doki.user.user.domain.vo

enum class ConceptType {
GIRL_CRUSH,
LOVELY_FRESH,
ELEGANT_GLAM,
DREAMY,
HIGHTEEN,
ETC
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.doki.user.domain.vo
package com.doki.user.user.domain.vo

enum class UserRole {
USER // 회원
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.doki.user.user.infrastructure

import com.doki.user.user.domain.User
import org.springframework.data.jpa.repository.JpaRepository

interface UserJpaRepository : JpaRepository<User, Long> {
fun findByEmail(email: String): User?
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.doki.user.infrastructure
package com.doki.user.user.infrastructure

import com.doki.user.auth.domain.vo.AuthProvider
import com.doki.user.auth.domain.UserOAuth
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.doki.user.user.infrastructure

import com.doki.user.user.domain.User
import com.doki.user.user.domain.UserRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Repository

@Repository
class UserRepositoryImpl(
private val userJpaRepository: UserJpaRepository
): UserRepository {

override fun findById(userId: Long): User? =
userJpaRepository.findByIdOrNull(userId)

override fun findByEmail(email: String): User? =
userJpaRepository.findByEmail(email)

override fun save(user: User): User =
userJpaRepository.save(user)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.doki.user.user.infrastructure

import com.doki.user.user.domain.UserSurvey
import org.springframework.data.jpa.repository.JpaRepository

interface UserSurveyJpaRepository: JpaRepository<UserSurvey, Long> {
fun findByUserId(userId: Long): UserSurvey?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코틀린 jpa에서는 findByIdOrNull 이라는 확장함수도 제공하는데 한 번 봐보시면 도움 되실 것 같아요~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설문은 회원당 1개만 존재하고, @LoginUser로 사용자를 식별하면 호출부가 단순해져서 조회할 때 surveyId가 아니라 userId로 구현한 상태예요. Repository에서도 findById가 아니라 findByUserId로 Survey를 조회하고 있는데, surveyId를 받아서 조회하는 게 좋을까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 이해했습니다~ 어차피 user랑 survey는 1:1이니 그대로 두셔도 될 거 같아요 (인덱스만 잘 걸어주세요!)

fun existsByUserId(userId: Long): Boolean
}
Loading