-
Notifications
You must be signed in to change notification settings - Fork 1
feature: 사용자 로그인 계정 모델과 저장소 추가 #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cf4f05d
8d1b3c5
077a2c3
0b46cb3
d1f99d1
727cd5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| DB_HOST=localhost | ||
| DB_PORT=5432 | ||
| DB_NAME=momogo | ||
| DB_USERNAME=momogo | ||
| DB_PASSWORD=momogo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,37 @@ | ||
| # momogo-server | ||
| # momogo-server | ||
|
|
||
| ## 패키지 규칙 | ||
|
|
||
| - `presentation`: Controller를 두고 HTTP 요청과 응답 변환을 담당한다. | ||
| - `application`: Service를 두고 도메인 흐름과 트랜잭션을 담당한다. | ||
| - `domain`: 프레임워크에 의존하지 않는 순수 도메인 객체와 비즈니스 규칙을 둔다. | ||
| - `infrastructure.database.entity`: JPA 매핑 클래스를 두며 이름은 `Entity`로 끝낸다. | ||
| - `infrastructure.database.repository`: Service가 직접 사용하는 Spring Data `Repository`를 둔다. | ||
| - 값 검증은 도메인 객체가 담당하고 JPA Entity에는 비즈니스 로직을 두지 않는다. | ||
| - 의존 흐름은 `Controller → Service → Repository` 순서를 따른다. | ||
|
|
||
| ## 로컬 PostgreSQL 실행 | ||
|
|
||
| Docker Compose로 PostgreSQL을 실행한다. | ||
|
|
||
| ```shell | ||
| docker compose up -d postgres | ||
| ``` | ||
|
|
||
| `local` 프로필로 애플리케이션을 실행한다. | ||
|
|
||
| ```shell | ||
| ./gradlew bootRun --args='--spring.profiles.active=local' | ||
| ``` | ||
|
|
||
| PostgreSQL에 직접 접속할 때는 컨테이너의 `psql`을 사용한다. | ||
|
|
||
| ```shell | ||
| docker compose exec postgres psql -U momogo -d momogo | ||
| ``` | ||
|
|
||
| 종료할 때는 다음 명령을 사용한다. 데이터는 Docker 볼륨에 유지된다. | ||
|
|
||
| ```shell | ||
| docker compose down | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| services: | ||
| postgres: | ||
| image: postgres:17-alpine | ||
| environment: | ||
| POSTGRES_DB: ${DB_NAME:-momogo} | ||
| POSTGRES_USER: ${DB_USERNAME:-momogo} | ||
| POSTGRES_PASSWORD: ${DB_PASSWORD:-momogo} | ||
| ports: | ||
| - "${DB_PORT:-5432}:5432" | ||
| volumes: | ||
| - postgres-data:/var/lib/postgresql/data | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] | ||
| interval: 5s | ||
| timeout: 5s | ||
| retries: 10 | ||
|
|
||
| volumes: | ||
| postgres-data: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.mogumogu.momogo.domain.user | ||
|
|
||
| class LoginAccount( | ||
| val id: Long? = null, | ||
| val userId: Long, | ||
| var provider: LoginProvider, | ||
| providerId: String, | ||
| ) { | ||
| var providerId: String = providerId | ||
| set(value) { | ||
| validateProviderId(value) | ||
| field = value | ||
| } | ||
|
|
||
| init { | ||
| validateProviderId(providerId) | ||
| } | ||
|
|
||
| fun changeProvider( | ||
| provider: LoginProvider, | ||
| providerId: String, | ||
| ) { | ||
| this.providerId = providerId | ||
| this.provider = provider | ||
| } | ||
|
|
||
| private fun validateProviderId(providerId: String) { | ||
| require(providerId.length <= MAX_PROVIDER_ID_LENGTH) { | ||
| "로그인 제공자 식별자는 ${MAX_PROVIDER_ID_LENGTH}자 이하여야 합니다." | ||
| } | ||
| } | ||
|
|
||
| private companion object { | ||
| const val MAX_PROVIDER_ID_LENGTH = 255 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.mogumogu.momogo.domain.user | ||
|
|
||
| enum class LoginProvider { | ||
| GUEST, | ||
| KAKAO, | ||
| NAVER, | ||
| APPLE, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.mogumogu.momogo.domain.user | ||
|
|
||
| class User( | ||
| val id: Long? = null, | ||
| var nickname: String, | ||
| ) { | ||
| init { | ||
| validateNickname(nickname) | ||
| } | ||
|
|
||
| fun changeNickname(nickname: String) { | ||
| validateNickname(nickname) | ||
| this.nickname = nickname | ||
| } | ||
|
|
||
| private fun validateNickname(nickname: String) { | ||
| require(nickname.length in MIN_NICKNAME_LENGTH..MAX_NICKNAME_LENGTH) { | ||
| "닉네임은 ${MIN_NICKNAME_LENGTH}자 이상 ${MAX_NICKNAME_LENGTH}자 이하여야 합니다." | ||
| } | ||
| } | ||
|
|
||
| private companion object { | ||
| const val MIN_NICKNAME_LENGTH = 1 | ||
| const val MAX_NICKNAME_LENGTH = 12 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.mogumogu.momogo.infrastructure.database.entity | ||
|
|
||
| import com.mogumogu.momogo.domain.user.LoginAccount | ||
| import com.mogumogu.momogo.domain.user.LoginProvider | ||
| import com.mogumogu.momogo.global.entity.BaseEntity | ||
| import jakarta.persistence.Column | ||
| import jakarta.persistence.Entity | ||
| import jakarta.persistence.EnumType | ||
| import jakarta.persistence.Enumerated | ||
| import jakarta.persistence.GeneratedValue | ||
| import jakarta.persistence.GenerationType | ||
| import jakarta.persistence.Id | ||
| import jakarta.persistence.Table | ||
|
|
||
| @Entity | ||
| @Table(name = "login_account") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| class LoginAccountEntity( | ||
| @field:Column(name = "user_id", nullable = false) | ||
| var userId: Long, | ||
|
ddingmin marked this conversation as resolved.
|
||
| @field:Enumerated(EnumType.STRING) | ||
| @field:Column(name = "provider", nullable = false, length = 20) | ||
| var provider: LoginProvider, | ||
| @field:Column(name = "provider_id", nullable = false, length = 255) | ||
| var providerId: String, | ||
|
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ) : BaseEntity() { | ||
|
|
||
| @field:Id | ||
| @field:GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @field:Column(name = "id", nullable = false, updatable = false) | ||
| var id: Long? = null | ||
| protected set | ||
|
|
||
| fun toDomain(): LoginAccount = | ||
| LoginAccount( | ||
| id = id, | ||
| userId = userId, | ||
| provider = provider, | ||
| providerId = providerId, | ||
| ) | ||
|
|
||
| companion object { | ||
| fun fromDomain(loginAccount: LoginAccount): LoginAccountEntity = | ||
| LoginAccountEntity( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저장된 Useful? React with 👍 / 👎. |
||
| userId = loginAccount.userId, | ||
| provider = loginAccount.provider, | ||
| providerId = loginAccount.providerId, | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.mogumogu.momogo.infrastructure.database.entity | ||
|
|
||
| import com.mogumogu.momogo.domain.user.User | ||
| import com.mogumogu.momogo.global.entity.BaseEntity | ||
| import jakarta.persistence.Column | ||
| import jakarta.persistence.Entity | ||
| import jakarta.persistence.GeneratedValue | ||
| import jakarta.persistence.GenerationType | ||
| import jakarta.persistence.Id | ||
| import jakarta.persistence.Table | ||
|
|
||
| @Entity | ||
| @Table(name = "\"user\"") | ||
| class UserEntity( | ||
| @field:Column(name = "nickname", nullable = false, length = 12) | ||
| var nickname: String, | ||
| ) : BaseEntity() { | ||
|
|
||
| @field:Id | ||
| @field:GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @field:Column(name = "id", nullable = false, updatable = false) | ||
| var id: Long? = null | ||
| protected set | ||
|
|
||
| fun toDomain(): User = | ||
| User( | ||
| id = id, | ||
| nickname = nickname, | ||
| ) | ||
|
|
||
| companion object { | ||
| fun fromDomain(user: User): UserEntity = | ||
| UserEntity(nickname = user.nickname) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미 저장된 Useful? React with 👍 / 👎. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.mogumogu.momogo.infrastructure.database.repository | ||
|
|
||
| import com.mogumogu.momogo.infrastructure.database.entity.LoginAccountEntity | ||
| import org.springframework.data.jpa.repository.JpaRepository | ||
|
|
||
| interface LoginAccountRepository : JpaRepository<LoginAccountEntity, Long> { | ||
| fun findAllByUserId(userId: Long): List<LoginAccountEntity> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.mogumogu.momogo.infrastructure.database.repository | ||
|
|
||
| import com.mogumogu.momogo.infrastructure.database.entity.UserEntity | ||
| import org.springframework.data.jpa.repository.JpaRepository | ||
|
|
||
| interface UserRepository : JpaRepository<UserEntity, Long> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
" "처럼 공백으로만 구성된 값은 길이가 1 이상이므로 현재 검증을 통과하고,nickname이nullable = false인UserEntity에도 그대로 저장됩니다. 사용자 입력에서 공백 닉네임을 허용하면 다른 사용자가 식별할 수 없는 계정이 생성되므로, 길이를 검사하기 전에 공백 여부를 함께 검증해야 합니다.Useful? React with 👍 / 👎.