Skip to content

Commit 37216ba

Browse files
9keyyyyGukhee Jo
andauthored
[NL-50] 유저 모델 생성 및 API 구현 (#4)
* fix: user model migration * fix: four pillar test 용 schema, router 제거 * feat: 유저 생성(+ 사주명식 설정) 및 조회 API * fix: 불필요 response field 제거 및 조회 시 active 상태 검증 --------- Co-authored-by: Gukhee Jo <[email protected]>
1 parent 30849e2 commit 37216ba

File tree

12 files changed

+149
-70
lines changed

12 files changed

+149
-70
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""add user field
2+
3+
Revision ID: ec45a4f91988
4+
Revises: 41c657cd9376
5+
Create Date: 2025-07-29 23:46:58.201303
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects import mysql
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'ec45a4f91988'
16+
down_revision: Union[str, Sequence[str], None] = '41c657cd9376'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column('users', sa.Column('birth_date', sa.DateTime(), nullable=False))
25+
op.add_column('users', sa.Column('gender', sa.Enum('F', 'M', name='gender'), nullable=False))
26+
op.add_column('users', sa.Column('four_pillar', sa.JSON(), nullable=True))
27+
op.alter_column('users', 'id',
28+
existing_type=mysql.INTEGER(),
29+
type_=sa.String(length=255),
30+
existing_nullable=False)
31+
op.drop_index(op.f('ix_users_email'), table_name='users')
32+
op.drop_column('users', 'email')
33+
# ### end Alembic commands ###
34+
35+
36+
def downgrade() -> None:
37+
"""Downgrade schema."""
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.add_column('users', sa.Column('email', mysql.VARCHAR(length=255), nullable=False))
40+
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
41+
op.alter_column('users', 'id',
42+
existing_type=sa.String(length=255),
43+
type_=mysql.INTEGER(),
44+
existing_nullable=False)
45+
op.drop_column('users', 'four_pillar')
46+
op.drop_column('users', 'gender')
47+
op.drop_column('users', 'birth_date')
48+
# ### end Alembic commands ###

src/config/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from pydantic import BaseModel, ConfigDict
2+
3+
4+
class CommonBase(BaseModel):
5+
model_config = ConfigDict(from_attributes=True, use_enum_values=True)

src/four_pillars/common/calculator.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from pathlib import Path
33
from typing import List, Tuple, Dict
44

5+
from src.four_pillars.entities.schemas import FourPillar
6+
57

68
class FourPillarsCalculator:
79
# 천간 (天干)
@@ -113,7 +115,7 @@ def _calculate_kanshi(
113115

114116
return [year_pillar, month_pillar, day_pillar, time_pillar]
115117

116-
def calculate_four_pillars(self, birth_date: datetime) -> Dict[str, str]:
118+
def calculate_four_pillars(self, birth_date: datetime) -> FourPillar:
117119
"""사주 계산 메인 함수"""
118120
year = birth_date.year
119121
month = birth_date.month
@@ -127,7 +129,7 @@ def calculate_four_pillars(self, birth_date: datetime) -> Dict[str, str]:
127129
minute = None
128130

129131
pillars = self._calculate_kanshi(year, month, day, hour, minute)
130-
result = {
132+
result: FourPillar = {
131133
"year_pillar": pillars[0], # 년주
132134
"month_pillar": pillars[1], # 월주
133135
"day_pillar": pillars[2], # 일주

src/four_pillars/entities/schemas.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
from datetime import datetime
21
from typing import Optional
32

4-
from pydantic import BaseModel, Field
3+
from typing_extensions import TypedDict
54

65

7-
# ========= 테스트 용 요청/응답 모델 ==========
8-
class FourPillarRequest(BaseModel):
9-
name: str
10-
gender: str = Field(..., pattern="^[mf]$") # m 또는 f만 허용
11-
birth_date: datetime
12-
13-
14-
class FourPillarResponse(BaseModel):
6+
class FourPillar(TypedDict):
157
year_pillar: str
168
month_pillar: str
179
day_pillar: str

src/four_pillars/router.py

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

src/main.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from src.config.config import app_config
55
from src.config.lifespan import lifespan
66
from src.config.middleware import DBMiddleware
7-
from src.four_pillars.router import four_pillar_router
87
from src.users.router import user_router
98

109
app = FastAPI(lifespan=lifespan, root_path="/api/v1")
@@ -19,4 +18,3 @@
1918
app.add_middleware(DBMiddleware)
2019

2120
app.include_router(user_router)
22-
app.include_router(four_pillar_router)

src/users/entities/enums.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from enum import Enum
2+
3+
4+
class Gender(str, Enum):
5+
F = "F"
6+
M = "M"

src/users/entities/models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
from sqlalchemy import Column, Integer, String, Boolean
1+
from sqlalchemy import Column, String, Boolean, DateTime, Enum, JSON
22

33
from src.config.database import Base
4+
from src.users.entities.enums import Gender
45

56

67
class User(Base):
78
__tablename__ = "users"
89

9-
id = Column(Integer, primary_key=True, index=True)
10+
id = Column(String(255), primary_key=True, index=True)
1011
name = Column(String(255), nullable=False)
11-
email = Column(String(255), unique=True, index=True, nullable=False)
12+
birth_date = Column(DateTime, nullable=False)
13+
gender = Column(Enum(Gender), nullable=False)
14+
four_pillar = Column(JSON, nullable=True)
1215
is_active = Column(Boolean, default=True, nullable=False)

src/users/entities/schemas.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
from pydantic import BaseModel, EmailStr
2-
from typing import List
2+
from typing import List, Optional
3+
from datetime import datetime
34

5+
from src.config.schemas import CommonBase
6+
from src.four_pillars.entities.schemas import FourPillar
7+
from src.users.entities.enums import Gender
48

5-
class UserBase(BaseModel):
9+
10+
class UserBase(CommonBase):
611
name: str
7-
email: EmailStr
12+
birth_date: datetime
13+
gender: Gender
814
is_active: bool = True
915

1016

11-
class UserCreateRequest(UserBase):
12-
pass
13-
17+
class UserCreate(CommonBase):
18+
id: str
19+
name: str
20+
birth_date: datetime
21+
gender: Gender
1422

15-
class UserResponse(UserBase):
16-
id: int
1723

18-
class Config:
19-
from_attributes = True
24+
class UserDetail(CommonBase):
25+
id: str
26+
name: str
27+
birth_date: datetime
28+
gender: Gender
29+
four_pillar: FourPillar
2030

2131

22-
class UserListResponse(BaseModel):
23-
users: List[UserResponse]
32+
class UserList(CommonBase):
33+
users: List[UserDetail]
2434
total: int

src/users/repository.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
1-
from typing import List
1+
from typing import List, Optional
22

33
from fastapi import Depends
44
from sqlalchemy import select
55
from sqlalchemy.ext.asyncio import AsyncSession
66

77
from src.common.dependencies import get_db_session
8+
from src.four_pillars.entities.schemas import FourPillar
89
from src.users.entities.models import User
10+
from src.users.entities.schemas import UserCreate
911

1012

1113
class UserRepository:
1214
def __init__(self, session: AsyncSession = Depends(get_db_session)):
1315
self.session = session
1416

1517
async def get_users(self, skip: int = 0, limit: int = 100) -> List[User]:
16-
query = select(User).offset(skip).limit(limit)
18+
query = select(User).where(User.is_active == True).offset(skip).limit(limit)
1719
result = await self.session.execute(query)
1820
return result.scalars().all()
1921

20-
async def get_user_by_email(self, email: str) -> User:
21-
query = select(User).where(User.email == email)
22+
async def get_user_by_id(self, user_id: str) -> Optional[User]:
23+
query = select(User).where(User.id == user_id, User.is_active == True)
2224
result = await self.session.execute(query)
2325
return result.scalar_one_or_none()
2426

25-
async def create_user(self, user: User) -> User:
27+
async def create_user(
28+
self, user_create: UserCreate, four_pillar: FourPillar
29+
) -> User:
30+
user = User(
31+
id=user_create.id,
32+
name=user_create.name,
33+
birth_date=user_create.birth_date,
34+
gender=user_create.gender,
35+
four_pillar=four_pillar,
36+
is_active=True,
37+
)
38+
2639
self.session.add(user)
27-
await self.session.commit()
40+
await self.session.flush()
2841
await self.session.refresh(user)
42+
2943
return user

0 commit comments

Comments
 (0)