Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions alembic/versions/a97f8719f6f8_update_member_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""update member field

Revision ID: a97f8719f6f8
Revises: 19316f07bc9b
Create Date: 2026-04-19 16:37:03.279205

"""
from typing import Sequence, Union

import sqlmodel
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'a97f8719f6f8'
down_revision: Union[str, Sequence[str], None] = '19316f07bc9b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('members', sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=True))
op.drop_column('members', 'gender')
op.drop_column('members', 'nickname')
op.drop_column('members', 'phone_num')
op.drop_column('members', 'dept')
op.drop_column('members', 'birth')
op.drop_column('members', 'name')
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('members', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('members', sa.Column('birth', sa.DATE(), autoincrement=False, nullable=False))
op.add_column('members', sa.Column('dept', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('members', sa.Column('phone_num', sa.VARCHAR(length=20), autoincrement=False, nullable=False))
op.add_column('members', sa.Column('nickname', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('members', sa.Column('gender', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.drop_column('members', 'username')
# ### end Alembic commands ###
5 changes: 3 additions & 2 deletions app/modules/member/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
from app.modules.member.repository import MemberRepository
from app.modules.member.service import MemberService
from app.shared.enums import MemberRole
from app.shared.storage.oci_object_storage import OCIObjectStorageClientDep


#전체 흐름
#Client -> HTTP 요청 -> Router -> Service -> Repository -> DB

#MemberService를 모든 Member api 함수들에게 의존성 주입을 하기 위한 과정
def get_member_service(session: DbSessionDep) -> MemberService:
def get_member_service(session: DbSessionDep, storage: OCIObjectStorageClientDep) -> MemberService:
repository = MemberRepository(session)
return MemberService(session, repository)
return MemberService(session, repository, storage)

#get_member_service() 실행하고 MemberService 만들어서 service에 넣어줘
MemberServiceDep = Annotated[MemberService, Depends(get_member_service)]
Expand Down
32 changes: 2 additions & 30 deletions app/modules/member/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,10 @@ class Member(BaseModel, table=True):
description="회원 비밀번호"
)

name: str = Field(
nullable=False,
description="회원 이름"
)

birth: date = Field(
nullable=False,
description="회원 생년월일"
)

gender: bool | None = Field(
username: str | None = Field(
default=None,
nullable=True,
description="회원 성별(0: 남, 1: 여, null: 성별을 밝히고 싶지 않음)"
)

phone_num: str = Field(
max_length=20,
nullable=False,
description="회원 전화번호"
)

nickname: str | None = Field(
default=None,
nullable=True,
description="회원 닉네임"
description="회원 사용자 이름"
)

organization: str | None = Field(
Expand All @@ -96,12 +74,6 @@ class Member(BaseModel, table=True):
description="회원 소속"
)

dept: str | None = Field(
default=None,
nullable=True,
description="회원 부서"
)

profile_url: str | None = Field(
default=None,
nullable=True,
Expand Down
10 changes: 5 additions & 5 deletions app/modules/member/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,26 @@ async def list(
stmt = select(Member)

if keyword:
#Member.name(이름)에 keyword가 포함된 회원만 찾으라는 뜻
#Member.username에 keyword가 포함된 회원만 찾으라는 뜻
#ilike: 대소문자 구분 없이 검색

#f"": 변수 값을 문자열에 넣는 문법
#ex) keyword = "python" f"{keyword}" => 결과: "python"
#keyword%: keyword로 시작, %keyword: keyword로 끝, %keyword%: keyword 포함
stmt = stmt.where(Member.name.ilike(f"%{keyword}%")) #"keyword" 포함(대소문자 구분X)
stmt = stmt.where(Member.username.ilike(f"%{keyword}%")) #"keyword" 포함(대소문자 구분X)
#삭제된 회원을 숨길지 말지 정하는 코드
#include_deleted는 기본값이 false인데 not이 붙었으므로 true가 됨.
#false일 때 조건문이 실행X, true일 때 조건문 실행(파이썬 문법)
if not include_deleted: #삭제된 거 안 보여줄 거면
stmt = stmt.where(Member.is_deleted == False) #삭제 안 된 애들만 가져와

#정렬 & 페이지
#order_by, asc: 이름 오름차순으로 정렬
#order_by, asc: 사용자 이름 오름차순으로 정렬
#offset: 앞에서부터 몇 개 건너뜀(스킵)
#limit: 몇 개 가져올지 제한
#ex) offset = 0, limit = 10 => 1~10번째 회원
#ex) offset = 10, limit = 10 => 11~20번째 회원
stmt = stmt.order_by(Member.name.asc()).offset(offset).limit(limit)
stmt = stmt.order_by(Member.username.asc()).offset(offset).limit(limit)
#실행!!
#scalars(): Member 객체만 꺼냄
#.all(): 리스트로 반환
Expand All @@ -97,7 +97,7 @@ async def count(

#list랑 count는 조건이 완전히 같아야 페이지가 맞아서 조건 부분이 동일.
if keyword:
stmt = stmt.where(Member.name.ilike(f"%{keyword}%"))
stmt = stmt.where(Member.username.ilike(f"%{keyword}%"))

if not include_deleted:
stmt = stmt.where(Member.is_deleted == False)
Expand Down
20 changes: 12 additions & 8 deletions app/modules/member/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from typing import Annotated
from uuid import UUID

from fastapi import APIRouter, Path, Query, Depends, status
from fastapi import APIRouter, Path, Query, Depends, status, UploadFile
from fastapi.params import File
from fastapi.security import OAuth2PasswordRequestForm

from app.modules.member.dependencies import CurrentMemberDep, MemberServiceDep, AdminMemberDep
Expand All @@ -12,7 +13,7 @@
SignupVerifyRequestIn, SignupVerifyConfirmIn, MemberRoleUpdateIn
)
from app.shared.schemas import ApiResponse, PageOut

from app.shared.utils.form import as_form

#prefix: 앞에 공통적으로 들어갈 경로명
#tags: Swagger에서 API를 분류할 때 사용하는 것
Expand All @@ -29,7 +30,7 @@
async def list_members(
service: MemberServiceDep,
admin: AdminMemberDep,
keyword: Annotated[str | None, Query(description="검색 키워드", example="수진")] = None,
keyword: Annotated[str | None, Query(description="검색 키워드", example="쏴리쏭")] = None,
#ge: 이상, le: 이하
page: Annotated[int,Query(ge=1, description="페이지 번호")] = 1,
size: Annotated[int, Query(ge=1, le=100, description="페이지 크기")] = 50,
Expand Down Expand Up @@ -85,7 +86,7 @@ async def get_my_info(
#DbSessionDep: DB 접속 정보
#Path: 경로 변수에게 설명을 추가할 수 있도록 하는 것
#Query: 쿼리 파라미터 변수에게 설명을 추가할 수 있도록 하는 것
#쿼리 파라미터: ex) /items?name=apple&page=2라고 한다면 name과 page가 쿼리 파라미터
#쿼리 파라미터: ex) /items?username=sujin&page=2라고 한다면 username과 page가 쿼리 파라미터
async def get_member(
service: MemberServiceDep,
member_id: Annotated[UUID, Path(..., description="조회할 member의 id")],
Expand All @@ -107,10 +108,11 @@ async def get_member(
description="회원가입 기능입니다.",
)
async def create_member(
data: MemberCreateIn,
service: MemberServiceDep,
data: Annotated[MemberCreateIn, Depends(as_form(MemberCreateIn))],
profile_file: Annotated[UploadFile | None, File(description="업로드할 프로필 이미지 파일")] = None,
):
created = await service.create(data)
created = await service.create(data, profile_file=profile_file)
return ApiResponse.success(
code="MEMBER_CREATED",
message="회원가입 성공",
Expand All @@ -133,13 +135,15 @@ async def update_member(
member_id: Annotated[UUID, Path(description="수정할 member의 ID")],
#요청 JSON → (검증 + 파싱) → MemberUpdateIn 객체 → data로 들어옴
#data는 검증 완료 된 Pydantic 객체
data: MemberUpdateIn,
data: Annotated[MemberUpdateIn, Depends(as_form(MemberUpdateIn))],
profile_file: Annotated[UploadFile | None, File(description="업로드할 프로필 이미지 파일")] = None,
):
#DB 수정 → 수정된 객체 받음
updated = await service.update(
target_member_id=member_id,
actor=current_member,
data=data
data=data,
profile_file=profile_file,
)
return ApiResponse.success(
code="MEMBER_UPDATED",
Expand Down
70 changes: 21 additions & 49 deletions app/modules/member/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,60 +25,40 @@
class MemberCreateIn(SQLModel):
email: EmailStr = Field(description="회원 이메일")
password: str = Field(description="비밀번호")
name: str = Field(description="이름")
birth: date = Field(description="생년월일")
gender: bool | None = Field(default=None, description="성별")
phone_num: str = Field(description="전화번호")
nickname: str | None = Field(default=None, description="닉네임")
username: str | None = Field(default=None, description="사용자 이름")
organization: str | None = Field(default=None, description="소속")
dept: str | None = Field(default=None, description="부서")
profile_url: HttpUrl | None = Field(default=None, description="프로필 이미지 URL")
detail: str | None = Field(default=None, description="상세 소개")

model_config = {
"json_schema_extra": {
"example": {
"email": "test@naver.com",
"password": "test1234!",
"name": "송시월",
"birth": "2001-05-21",
"gender": True,
"phone_num": "01012345678",
"nickname": "쏴리쏭",
"organization": "한성대학교",
"dept": "컴퓨터공학과",
"profile_url": "https://example.com/profile.jpg",
"detail": "안녕하세요!"
}
"examples": [
{
"email": "test@naver.com",
"password": "test1234!",
"username": "쏴리쏭",
"organization": "한성대학교",
"detail": "안녕하세요!"
}
]
}
}

class MemberUpdateIn(SQLModel):
password: str | None = Field(default=None, description="비밀번호")
name: str | None = Field(default=None, description="이름")
birth: date | None = Field(default=None, description="생년월일")
gender: bool | None = Field(default=None, description="성별")
phone_num: str | None = Field(default=None, description="전화번호")
nickname: str | None = Field(default=None, description="닉네임")
username: str | None = Field(default=None, description="사용자 이름")
organization: str | None = Field(default=None, description="소속")
dept: str | None = Field(default=None, description="부서")
profile_url: HttpUrl | None = Field(default=None, description="프로필 이미지 URL")
detail: str | None = Field(default=None, description="상세 소개")

model_config = {
"json_schema_extra": {
"example": {
"password": "test1234!",
"name": "송시월",
"birth": "2001-05-21",
"gender": True,
"phone_num": "01012345678",
"nickname": "쏴리쏭",
"organization": "한성대학교",
"dept": "컴퓨터공학과",
"profile_url": "https://example.com/profile.jpg",
"detail": "안녕하세요!"
}
"examples": [
{
"password": "test1234!",
"username": "쏴리쏭",
"organization": "한성대학교",
"detail": "안녕하세요!"
}
]
}
}

Expand All @@ -87,12 +67,8 @@ class MemberOut(SQLModel):
id: UUID = Field(description="회원 ID")
email: EmailStr = Field(description="회원 이메일")
role: MemberRole = Field(description="회원 권한")
name: str = Field(description="이름")
birth: date = Field(description="생년월일")
gender: bool | None = Field(default=None, description="성별")
nickname: str | None = Field(default=None, description="닉네임")
username: str | None = Field(default=None, description="사용자 이름")
organization: str | None = Field(default=None, description="소속")
dept: str | None = Field(default=None, description="부서")
profile_url: HttpUrl | None = Field(default=None, description="프로필 이미지 URL")
detail: str | None = Field(default=None, description="상세 소개")

Expand All @@ -104,12 +80,8 @@ class MemberOut(SQLModel):
"example": {
"id": "3e1672cf-8d99-4b1c-9b5e-9c3ece11b089",
"email": "test@example.com",
"name": "송시월",
"birth": "2001-05-21",
"gender": True,
"nickname": "쏴리쏭",
"username": "쏴리쏭",
"organization": "한성대학교",
"dept": "컴퓨터공학과",
"profile_url": "https://example.com/profile.jpg",
"detail": "안녕하세요!"
}
Expand Down
Loading
Loading