Skip to content

Commit

Permalink
feat(room): 방 매물 정보 crud 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Mincheol Kim committed Jul 15, 2020
1 parent a1bbb2a commit 80c3e44
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 2 deletions.
Empty file added src/apps/__init__.py
Empty file.
Empty file added src/apps/rooms/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions src/apps/rooms/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class RoomNotFoundException(Exception):
""" 방 매물 정보 없음 """
Empty file.
114 changes: 114 additions & 0 deletions src/apps/rooms/models/entity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from enum import IntEnum

from sqlalchemy import Column, text
from sqlalchemy.dialects import mysql
from sqlalchemy.sql import func

from .....core.database import Base


class Description(IntEnum):
max_length = 200


class Uid(IntEnum):
max_length = 100


class Address(IntEnum):
max_length = 100


class Room(Base):
""" 방 entity
[부동산 관련 field명 참고 자료](https://news.joins.com/article/23191326)
"""

__tablename__ = "rooms"
__table_args__ = {"mysql_collate": "utf8mb4_unicode_ci"}

uid = Column(
"uid",
mysql.VARCHAR(Uid.max_length),
primary_key=True,
comment="방의 고유ID",
)

deposit = Column(
"deposit",
mysql.INTEGER(unsigned=True),
nullable=False,
default="0",
server_default=text("0"),
comment="보증금",
)

monthly_rent = Column(
"monthly_rent",
mysql.INTEGER(unsigned=True),
nullable=False,
default="0",
server_default=text("0"),
comment="월세",
)

is_jeonse = Column(
"is_jeonse",
mysql.TINYINT(unsigned=True),
nullable=False,
default="0",
server_default=text("0"),
comment="전세유무",
)

address = Column(
"address",
mysql.VARCHAR(Address.max_length),
nullable=False,
default="0",
server_default=text("0"),
comment="주소",
)

description = Column(
"description",
mysql.VARCHAR(Description.max_length),
nullable=False,
default="0",
server_default=text("0"),
comment="방에 대한 간략한 설명",
)

created = Column(
"Created",
mysql.DATETIME(),
nullable=False,
server_default=text("CURRENT_TIMESTAMP"),
comment="생성일자",
)

updated = Column(
"Updated",
mysql.DATETIME(),
nullable=False,
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
onupdate=func.now(),
comment="마지막 수정시간",
)

def __init__(
self,
uid: str,
deposit: int,
monthly_rent: int,
is_jeonse: bool,
address: str,
description: str,
) -> None:
self.uid = uid
self.deposit = deposit
self.monthly_rent = monthly_rent
self.is_jeonse = int(is_jeonse)
self.address = address
self.description = description
9 changes: 9 additions & 0 deletions src/apps/rooms/models/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .responses import RoomItemResponse


class RoomItemCreateRequest(RoomItemResponse):
""" 부동산 매물 정보 생성 요청"""


class RoomItemUpdateRequest(RoomItemResponse):
""" 부동산 매물 정보 생성 요청"""

This comment has been minimized.

Copy link
@mcauto

mcauto Jul 16, 2020

Member

부동산 매물 정보 수정 요청

21 changes: 21 additions & 0 deletions src/apps/rooms/models/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import List

from pydantic import BaseModel, Field

from .entity import Address, Description, Uid


class RoomItemResponse(BaseModel):
uid: str = Field(max_length=Uid.max_length)
deposit: int = Field(...)
monthly_rent: int = Field(...)
is_jeonse: bool = Field(...)
address: str = Field(max_length=Address.max_length)
description: str = Field(max_length=Description.max_length)

class Config:
orm_mode = True


class RoomItemsResponse(BaseModel):
rooms: List[RoomItemResponse]
102 changes: 102 additions & 0 deletions src/apps/rooms/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from typing import List

from fastapi import status
from fastapi.param_functions import Depends, Path
from fastapi.routing import APIRouter
from sqlalchemy.orm import Session

from ...core.database import get_database_session
from .exceptions import RoomNotFoundException
from .models.entity import Room
from .models.requests import RoomItemCreateRequest, RoomItemUpdateRequest
from .models.responses import RoomItemResponse, RoomItemsResponse

router = APIRouter()
__valid_uid = Path(..., min_length=1)


@router.get(
path="/{uid}",
name="방 매물 정보 불러오기",
status_code=status.HTTP_200_OK,
response_model=RoomItemResponse,
)
async def get_room(
uid: str = __valid_uid, session: Session = Depends(get_database_session)
) -> RoomItemResponse:
room_orm = session.query(Room).filter(Room.uid == uid).first()
if not room_orm:
raise RoomNotFoundException("등록된 방이 아닙니다")
return RoomItemResponse.from_orm(room_orm)


@router.get(
path="",
name="방 매물 리스트 불러오기",
status_code=status.HTTP_200_OK,
response_model=RoomItemsResponse,
)
async def get_rooms(
session: Session = Depends(get_database_session)
) -> RoomItemsResponse:
rooms_orm: List[Room] = session.query(Room).all()
if not rooms_orm:
raise RoomNotFoundException("등록된 방 매물이 없습니다")
return RoomItemsResponse(
rooms=[RoomItemResponse.from_orm(room) for room in rooms_orm]
)


@router.post(
path="",
name="방 매물 정보 등록",
status_code=status.HTTP_201_CREATED,
response_model=RoomItemResponse,
)
async def post_room(
request: RoomItemCreateRequest,
session: Session = Depends(get_database_session),
) -> RoomItemResponse:
room_orm = Room(**request.dict())
session.add(room_orm)
session.commit()
session.refresh(room_orm)
return RoomItemResponse.from_orm(room_orm)


@router.patch(
path="/{uid}",
name="방 매물 정보 업데이트",
status_code=status.HTTP_200_OK,
response_model=RoomItemResponse,
)
async def update_room(
update_request: RoomItemUpdateRequest,
uid: str = __valid_uid,
session: Session = Depends(get_database_session),
) -> RoomItemResponse:
room = session.query(Room).filter(Room.uid == uid).first()
if not room:
raise RoomNotFoundException(f"{uid}에 해당하는 방 매물이 없습니다")
old_room = RoomItemResponse.from_orm(room)
update_data = update_request.dict()
for field, value in old_room:
if field in update_data:
setattr(room, field, update_data[field])
session.add(room)
session.commit()

return RoomItemResponse.from_orm(room)


@router.delete(
path="/{uid}", name="방 매물 정보 삭제", status_code=status.HTTP_204_NO_CONTENT
)
async def delete_room(
uid: str = __valid_uid, session: Session = Depends(get_database_session)
) -> None:
room = session.query(Room).filter(Room.uid == uid).first()
if not room:
raise RoomNotFoundException(f"{uid}에 해당하는 방 매물이 없습니다")
session.delete(room)
session.commit()
2 changes: 2 additions & 0 deletions src/asgi.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
"""
from .core.config import settings
from .core.database import Base, engine
from .main import create_app
from .routes import api_v1

Base.metadata.create_all(bind=engine)
app = create_app()
app.include_router(api_v1, prefix=f"{settings.API_VERSION_PREFIX}")
2 changes: 1 addition & 1 deletion src/core/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import Session, sessionmaker

from .config import sqlalchemy_settings

Expand Down
29 changes: 29 additions & 0 deletions src/core/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,38 @@
from fastapi.openapi.constants import REF_PREFIX
from fastapi.openapi.utils import validation_error_response_definition
from pydantic import ValidationError
from sqlalchemy.exc import SQLAlchemyError
from starlette.requests import Request
from starlette.responses import JSONResponse

from ..apps.rooms.exceptions import RoomNotFoundException

NotImplementedResponse = JSONResponse(
{"errors": "not implemented"}, status_code=status.HTTP_501_NOT_IMPLEMENTED
)


async def database_exception_handler(
_: Request, exc: SQLAlchemyError
) -> JSONResponse:
response = NotImplementedResponse
if isinstance(exc, RoomNotFoundException):
response = JSONResponse(
{"errors": str(exc)}, status_code=status.HTTP_404_NOT_FOUND
)
if isinstance(exc, SQLAlchemyError):
if "Duplicate entry" in exc.args[0]:
response = JSONResponse(
{"errors": "이미 등록된 리소스 입니다"},
status_code=status.HTTP_409_CONFLICT,
)
else:
response = JSONResponse(
{"errros": "관리자에게 문의 바랍니다"},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return response


async def http_exception_handler(
_: Request, exc: HTTPException
Expand Down
15 changes: 14 additions & 1 deletion src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
"""
from fastapi.applications import FastAPI
from fastapi.exceptions import RequestValidationError
from sqlalchemy.exc import SQLAlchemyError
from starlette.exceptions import HTTPException
from .core.handlers import http_exception_handler, validation_exception_handler

from .apps.rooms.exceptions import RoomNotFoundException
from .core.handlers import (
database_exception_handler,
http_exception_handler,
validation_exception_handler,
)


def create_app() -> FastAPI:
Expand All @@ -14,6 +21,12 @@ def create_app() -> FastAPI:
app.add_exception_handler(
RequestValidationError, handler=validation_exception_handler
)
app.add_exception_handler(
SQLAlchemyError, handler=database_exception_handler
)
app.add_exception_handler(
RoomNotFoundException, handler=database_exception_handler
)
app.add_exception_handler(HTTPException, handler=http_exception_handler)

return app
2 changes: 2 additions & 0 deletions src/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"""
from fastapi.routing import APIRouter

from ..apps.rooms.routes import router as room_router
from .health import health as health_router

api_v1 = APIRouter()

api_v1.include_router(health_router, prefix="/health", tags=["manage"])
api_v1.include_router(room_router, prefix="/rooms", tags=["rooms"])

0 comments on commit 80c3e44

Please sign in to comment.