방명록

Database Table 생성

APP/
ㄴ models.py --> 작업할 공간

models.py

class GuestBook(BaseMin, Base):
    __tablename__ = "guestbook"

    message = Column(String(255), nullable=False)
    message_owner = Column(String(20), nullable=False)
    owner_id = Column(Integer, ForeignKey("user.id"))

    book_owner = relationship("User", back_populates="guestbooks")
  1. message: 방명록에 남길 메세지
  2. message_owner: 방명록에 남길 이름
  3. owner_id: (외래키) 추가한 회원의 식별 ID

기능

controller.py

from fastapi import Depends, APIRouter, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm.session import Session
from typing import List

from app.database import get_db
from .schema import GuestBookAdd, GuestBookReturn, GuestBookUpdate
from .service import addGuestBook, findAllGuestBook, updateGuestBook

router = APIRouter()
security = HTTPBearer()

# 방명록 작성
@router.post("", response_model=GuestBookReturn, status_code=status.HTTP_201_CREATED)
async def guestbook_add(
    data: GuestBookAdd,
    cred: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_db),
):
    return await addGuestBook(data, cred, db)

# 모든 방명록 조회
@router.get("", response_model=List[GuestBookReturn], status_code=status.HTTP_200_OK)
async def guestbook(db: Session = Depends(get_db)):
    return await findAllGuestBook(db)

# 방명록 수정
@router.put("/{id}", response_model=GuestBookReturn, status_code=status.HTTP_202_ACCEPTED)
async def guestbook_update(
    data: GuestBookUpdate,
    id: int,
    cred: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_db),
):
    return await updateGuestBook(data, id, cred, db)

1. 방명록 작성

@router.post("", response_model=GuestBookReturn, status_code=status.HTTP_201_CREATED)
async def guestbook_add(
    data: GuestBookAdd,
    cred: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_db),
):
    return await addGuestBook(data, cred, db)
  1. GuestBookAdd 형태에 맞추어 Body 정보를 받는다.
  2. 로그인 할 때 받았던 토큰을 cred 인자로 받는다.
  3. addGuestBook에서 받아온 정보를 GuestBookReturn 형태에 맞추어 반환한다.
  4. 문제없이 작동했다면 반환할 때 201 상태 코드를 반환한다.

2. 모든 방명록 조회

@router.get("", response_model=List[GuestBookReturn], status_code=status.HTTP_200_OK)
async def guestbook(db: Session = Depends(get_db)):
    return await findAllGuestBook(db)
  1. findAllGuestBook에서 받아온 정보를 GuestBookReturn으로 이루어진 List 형태에 맞춰 반환한다.
  2. 문제없이 작동했다면 반환할 때 200 상태 코드를 반환한다.

3. 방명록 수정

@router.put("/{id}", response_model=GuestBookReturn, status_code=status.HTTP_202_ACCEPTED)
async def guestbook_update(
    data: GuestBookUpdate,
    id: int,
    cred: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_db),
):
    return await updateGuestBook(data, id, cred, db)
  1. GuestBookUpdate 형태에 맞추어 Body 정보를 받는다.
  2. 수정할 GuestBook 객체의 ID를 Path 파라미터로 받는다.
  3. 로그인 할 때 받았던 토큰을 cred 인자로 받는다.
  4. updateGuestBook에서 받아온 정보를 GuestBookReturn 형태에 맞추어 반환한다.
  5. 문제없이 작동했다면 반환할 때 202 상태 코드를 반환한다.

service.py

from ..user import utils
from .utils import add_guestbook, find_all_guestbook, update_guestbook


async def addGuestBook(data, cred, db):
    decoded_dict = await utils.verify_user(cred)
    return await add_guestbook(data, decoded_dict.get("id"), db)


async def findAllGuestBook(db):
    return await find_all_guestbook(db)


async def updateGuestBook(data, id, cred, db):
    decoded_dict = await utils.verify_user(cred)
    return await update_guestbook(data.message, id, decoded_dict.get("id"), db)

1. 방명록 생성

async def addGuestBook(data, cred, db):
    decoded_dict = await utils.verify_user(cred)
    return await add_guestbook(data, decoded_dict.get("id"), db)
  1. verify_user 함수에 받아온 토큰 값을 넣어 해독된 딕셔너리를 받아온다.
  2. add_guestbook으로 메세지와 작성자를 DB에 저장한 뒤 반환한다.

2. 모든 방명록 조회

async def findAllGuestBook(db):
    return await find_all_guestbook(db)
  1. find_all_guestbook에서 받은 정보를 반환한다.

3. 방명록 수정

async def updateGuestBook(data, id, cred, db):
    decoded_dict = await utils.verify_user(cred)
    return await update_guestbook(data.message, id, decoded_dict.get("id"), db)
  1. verify_user 함수에 받아온 토큰 값을 넣어 해독된 딕셔너리를 받아온다.
  2. update_guestbook에서 방명록을 수정한 뒤 정보를 반환한다.

utils.py

from fastapi import HTTPException, status
from sqlalchemy import desc

from app.models import GuestBook


async def add_guestbook(data, id, db):
    try:
        row = GuestBook(message=data.message, owner_id=id, message_owner=data.message_owner)
        db.add(row)
        db.commit()

        return row
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"{e} occured while adding guestbook",
        )


async def find_all_guestbook(db):
    return db.query(GuestBook).order_by(desc(GuestBook.created_at)).all()


async def update_guestbook(message, id, owner_id, db):
    row = db.query(GuestBook).filter_by(id=id).first()
    if row is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Guestbook Not Found",
        )
    if row.owner_id == owner_id:
        row.message = message
        db.commit()
    else:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="It's not owner")

    return row

1. 방명록 생성

async def add_guestbook(data, id, db):
    try:
        row = GuestBook(message=data.message, owner_id=id, message_owner=data.message_owner)
        db.add(row)
        db.commit()

        return row
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"{e} occured while adding guestbook",
        )
  1. 넘어온 데이터로 models.GuestBook 객체를 생성한다.
  2. DB에 추가한다.
  3. Commit

2. 모든 방명록 조회

async def find_all_guestbook(db):
    return db.query(GuestBook).order_by(desc(GuestBook.created_at)).all()

sqlalchemy에서 쿼리문을 작성할 때 .order_by, .options, .offset, .limit 등으로 여러 옵션을 줘서 작성할 수 있다. desc() 옵션을 추가할 시 내부 인자를 기준으로 내림차순 정렬해서 반환해준다.

  1. GuestBook 테이블의 모든 row를 내림차순으로 정렬해서 반환한다.

3. 방명록 수정

async def update_guestbook(message, id, owner_id, db):
    row = db.query(GuestBook).filter_by(id=id).first()
    if row is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Guestbook Not Found",
        )
    if row.owner_id == owner_id:
        row.message = message
        db.commit()
    else:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="It's not owner")
  1. Path 파라미터로 들어온 id로 TsItem 객체를 검색한다.
  2. row가 None이라면, 즉시 400 상태 코드를 반환한다.
  3. models.TsItem 객체의 owner_id(회원 식별 ID)와 토큰 해독 후 나온 ID가 일치하지 않는다면 즉시 403을 반환한다.
  4. 일치한다면 방명록 내용을 수정하고 Commit

schema.py

from pydantic import BaseModel


class GuestBookAdd(BaseModel):
    message: str
    message_owner: str


class GuestBookReturn(GuestBookAdd):
    id: int
    owner_id: int


class GuestBookUpdate(BaseModel):
    message: str

생각해볼 점

지난 번과 마찬가지로 정의된 relationship을 가지고 회원 별 방명록 확인도 구현해보는 것도 재밌을 것이다.

[참고]

필자는 어차피 방명록인데 모두가 봐야하지 않나?는 생각에 전체 방명록만 불러오게 구현했다. 테이블 작성할 때 relationship이 적혀있는건 오늘 벨로그 글을 쓰게 되면서 알게 된 사실이다..


이번에도 Swagger로 보면 이렇게 잘 생성되어 있을 것이다!

이 때까지 글을 읽어준 독자님들께 감사하다고 말씀을 드리고 싶다. 이번 게시글까지 해서 현재 작성되어있는 API는 모두 업로드했다. 두 편 정도로 나누어 Test 코드 작성과 회고를 끝으로 이 시리즈를 마무리해보고자 한다.

profile
가치를 창출하는 개발자! 가 목표입니다

0개의 댓글