Programming/Project Log

[가계부 만들기] Backend - 회원 관리 기능#1

minarae7 2023. 3. 27. 15:02
728x90
반응형

Concept

프로그램을 만들면서 가장 기본이 되는 기능이 바로 회원 정보를 관리하는 것이다. 생성되는 모든 정보는 회원 정보에 종속되게 되므로 회원의 정보를 잘 관리하는 것이 매우 중요하다.
이 프로젝트에서는 회원의 많은 정보를 사용하지 않는다. 이전에 공유했던 회원 정보 테이블을 다시 보자.

CREATE TABLE `tb_members` (
  `member_no` smallint unsigned NOT NULL AUTO_INCREMENT COMMENT '멤버번호',
  `member_id` varchar(30) COLLATE utf8mb4_general_ci NOT NULL COMMENT '사용자 아이디',
  `member_pw` varchar(128) COLLATE utf8mb4_general_ci NOT NULL COMMENT '사용자 패스워드',
  `member_name` varchar(20) COLLATE utf8mb4_general_ci NOT NULL COMMENT '사용자 이름',
  `member_email` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '이메일 주소',
  `reg_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시',
  `upd_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '수정일시',
  `is_deleted` char(1) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'F' COMMENT '삭제여부(T|F)',
  `del_dt` datetime DEFAULT NULL COMMENT '삭제일시',
  PRIMARY KEY (`member_no`),
  UNIQUE KEY `ixn_members__member_id` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='사용자 정보';

테이블 스키마에서 보는 것과 같이 회원 정보라고 할 만한 정보는 아이디, 패스워드, 이름, 이메일 정도이다. 여기서 아이디와 이메일을 합쳐서 아이디를 이메일로 사용할 수도 있을 것이다. 일단 해당 프로젝트에서는 아이디와 이메일을 분리하여서 사용한다.
이메일은 안 받아도 상관없지만 추후에 패스워드를 잊어버렸을 때 초기화하기 위한 수단으로 일단 저장하도록 한다.
그리고 패스워드는 사용자에게 입력받은 그대로 저장하면 안되기 때문에 해당 정보는 암호화하여서 저장할 것이다.
그럼 필요한 Router는 다음과 같다.

  • 회원가입
  • 회원정보수정(패스워드 변경 포함)
  • 로그인
  • 로그아웃
  • 회원탈퇴

이 기준으로 프로그램을 만들어보도록 하겠다.

구조추가

앞에서 프로그램에 대한 테스트를 진행하기 위해서 app 디렉토리 안에 main.py 파일을 생성했고 데이터베이스에 대한 연결 및 테이블 정의를 하기 위해서 databases라는 디렉토리를 추가하였다.
여기서는 두 가지 디렉토리를 추가로 생성할 것이다. 우선 접속하는 주소를 만들기 위해서 routers 라는 디렉토리를 사용할 것이며 각 router에 대해서 실제로 처리해 줄 services 라는 디렉토리를 생성할 것이다.
router에서는 각 URI 별로 전달받을 request의 형태와 역할, 그리고 반환하는 값의 형태를 정의할 것이다. 그리고 반복적이지 않으면서 아주 간단한 코드에 대해서는 router 자체에서 처리할 것이며 그렇지 않은 코드는 액션을 정의하는 services의 처리 로직을 호출할 것이다.
services는 흔히 말하는 비즈니스 로직을 처리하도록 한다. 비슷한 역할을 하는 함수를 여러 번 정의하기 보다는 통합하여서 재활용성을 높이고 유지보수에 대한 편의성을 높이고자 한다.
이외 추가적인 디렉토리가 추가적으로 필요할 수도 있지만 일단 이렇게 구성하고 개발을 시작하도록 한다.

728x90

Routers

우선 API 호출에 사용될 각 URI를 정의하여야 한다. 여기서는 여러 API를 사용할 것이고 각 API의 성격에 맞춰서 그룹화를 하여서 정의하도록 할 것이다. 먼저 app 디렉토리 하단에 routers라는 디렉토리를 생성하고 회원 관련 API를 모아두는 members.py 파일을 생성하도록 한다.
위에서 언급한 회원 관리에 필요한 기능 5가지 중에서 로그아웃은 Frontend에서 토큰을 삭제하는 것으로 처리하도록 할 것이다. Backend에서는 로그인시 JWT만 생성해서 전달하고 이를 관리하는 기능은 Frontend에서 할 것이며 로그아웃은 Frontend에서 이 JWT를 삭제하는 것으로 대체한다.
그럼 우선 회원 관리에 필요한 4가지 URI를 생성하도록 한다. 아래 내용을 참고하면 된다.

from fastapi import APIRouter

router = APIRouter(
    prefix="/members",
    tags=["member"],
    responses={
        404: {"description": "Not Found"},
    }
)

# 회원 가입
@router.post("/create", description="회원 가입")
async def create():
    pass

# 회원 정보 수정
@router.put("/modify", description="회원 정보 수정")
async def modify():
    pass

# 로그인
@router.post("/login", description="로그인")
async def login():
    pass

# 회원탈퇴
@router.post("/unsubscribing", description="회원탈퇴")
async def unsubscribing():
    pass

이 코드에서는 아직 내용을 채우지는 않고 우선 구조만 잡아둔다. router를 먼저 정의하고 회원 관련된 URI에는 prefix로 /members를 붙이도록 하였다. 만약 해당 파일에 정의되지 않은 URI로 접근하면 404에서 처리되어서 response로 "Not Found"라는 메시지가 전송된다.
이제 생성한 router를 main.py에 연결하는 코드를 넣어야 한다.

...
from .routers import members

app = FastAPI(title="account-book-api")

app.include_router(members.router)
...

위와 같이 정의하면 브라우저의 redoc에서 다음과 같은 화면을 볼 수 있다.

members routers의 redoc 문서

아직 request 내용과 response 내용을 정의하지 않았기 때문에 단순히 리스트만 보여지는 볼 수 있다.
이제부터 기본 내용을 채우도록 해보자.

반응형

Schema

Schema는 ORM을 기반으로 받을 데이터의 형태와 전달할 결과값을 형태를 정의한다. 이렇게 정의된 스키마는 JSON 형태로 주고 받는 형태를 정의하게 된다.
여기서는 각 request에 따른 parameter를 정의하고 결과로 어떤 값을 전송해줄 것인지 정의하도록 할 것이다.

from pydantic import BaseModel, Field

class Member(BaseModel):
    member_id: str = Field(title="사용자 아이디", max_length=30)
    member_pw: str = Field(title="사용자 패스워드")
    member_name: str = Field(title="사용자 이름", max_length=20)
    member_email: str = Field(title="사용자 이메일", max_length=50)

    class Config:
        orm_mode = True

class LoginResponse(BaseModel):
    member_no: int = Field(title="사용자 번호")
    memeber_id: str = Field(title="사용자 아이디")
    member_name: str = Field(title="사용자 이름")
    member_email: str = Field(title="사용자 이메일")
    access_token: str = Field(title="Access Token")
    refresh_totke: str = Field(title="Refresh Token")

app/databases/schemas.py 파일을 생성하고 위의 코드를 만들었다. 해당 schema는 회원 가입시에 사용할 정보와 로그인시 로그인에 성공했을 때 사용자가 인증된 사용자임을 알려주는 정보에 대한 Schema를 정의한다.
이제 각 router 별로 request와 response를 정의하도록 한다. 위의 router 파일을 아래와 같이 변경해보자.

from fastapi import APIRouter, Depends, Body
from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_201_CREATED, HTTP_202_ACCEPTED
from typing import Optional
from ..database.connection import get_db
from ..database import schemas

router = APIRouter(
    prefix="/members",
    tags=["member"],
    responses={
        404: {"description": "Not Found"},
    }
)

# 회원 가입
@router.post("/create", description="회원 가입", response_class=Response)
async def create(
    member: schemas.Member = Body(
        title="회원정보",
        example={
            "member_id": "foo",
            "member_pw": "1234567890",
            "member_name": "홍길동",
            "member_email": "test@example.com",
        }
    ),
    db: Session = Depends(get_db)
):
    return Response(status_code=HTTP_201_CREATED)

# 회원 정보 수정
@router.put("/modify", description="회원 정보 수정")
async def modify(
    member_no: int = Body(title="사용자 번호"),
    member_pw: Optional[str] = Body(title="사용자 패스워드"),
    member_name: Optional[str] = Body(title="사용자 이름"),
    member_email: Optional[str] = Body(title="사용자 이메일"),
    db: Session = Depends(get_db)
):
    return Response(HTTP_202_ACCEPTED)

# 로그인
@router.post("/login", description="로그인", response_model=schemas.LoginResponse)
async def login(
    member_id: str = Body(title="사용자 아이디"),
    member_pw: str = Body(title="사용자 패스워드"),
    db: Session = Depends(get_db)
):
    pass

# 회원탈퇴
@router.post("/unsubscribing", description="회원탈퇴", response_class=Response)
async def unsubscribing(
    member_no: int = Body(title="삭제할 사용자의 번호"),
    db: Session = Depends(get_db)
):
    return Response(status_code=HTTP_202_ACCEPTED)

이제 routers/members.py 파일이 제법 코드 작동하는 코드 같이 보인다. 이렇게 해서 각 API 주소별로 request에서 어떤 parameter를 받고 수행 결과로 어떤 response를 전달하는지 모양을 만들었다.
해당 파일을 만들고 나면 위에서 캡쳐했던 redoc 파일은 아래 이미지와 같이 보일 것이다.

request과 response를 정의한 이후의 redoc

그리고 swagger 문서인 docs의 모양은 아래와 같다.

docs

docs에서는 실제로 try out을 해볼 수 있다. 정상 작동하는 API도 있고 내용을 작성하지 않았기 때문에 정상 작동하지 않는 API도 있다.

create API를 호출

여기까지 해서 회원 관리를 위한 기본적인 모양을 잡았다. 다음 포스팅에서는 실제로 사용자 정보를 생성해서 디비에 입력해보고 입력된 사용자 정보로 로그인도 해보고 정보 수정해볼 것이다.

728x90
반응형