From 743e6c6e6d673593c136eef0eaa3e1e7761529d8 Mon Sep 17 00:00:00 2001 From: bebra_dev Date: Sun, 3 Nov 2024 22:37:13 +0300 Subject: [PATCH] feat: human-readable error responses --- src/api/schemes.py | 5 ++++ src/modules/workshops/enums.py | 15 ++++++++++++ src/modules/workshops/repository.py | 27 +++++++++++++--------- src/modules/workshops/routes.py | 36 +++++++++++++++-------------- 4 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 src/api/schemes.py create mode 100644 src/modules/workshops/enums.py diff --git a/src/api/schemes.py b/src/api/schemes.py new file mode 100644 index 0000000..5dc1e88 --- /dev/null +++ b/src/api/schemes.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class ErrorScheme(BaseModel): + detail: str diff --git a/src/modules/workshops/enums.py b/src/modules/workshops/enums.py new file mode 100644 index 0000000..27202c0 --- /dev/null +++ b/src/modules/workshops/enums.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class CheckInEnum(Enum): + SUCCESS = "Success" + ALREADY_CHECKED_IN = "You already checked in this workshop" + WORKSHOP_DOES_NOT_EXIST = "Workshop does not exist" + NO_PLACES = "No places available" + INVALID_TIME = "You can not check in this workshop now" + CHECK_IN_DOES_NOT_EXIST = "You did not check in this workshop" + + +class WorkshopEnum(Enum): + CREATED = "Created" + ALIAS_ALREADY_EXISTS = "Alias already exists" diff --git a/src/modules/workshops/repository.py b/src/modules/workshops/repository.py index d000f0a..4b70ffb 100644 --- a/src/modules/workshops/repository.py +++ b/src/modules/workshops/repository.py @@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from src.modules.users.schemes import ViewUserScheme +from src.modules.workshops.enums import CheckInEnum, WorkshopEnum from src.modules.workshops.schemes import CreateWorkshopScheme, ViewWorkshopScheme from src.storages.sql.models.users import User from src.storages.sql.models.workshops import CheckIn, Workshop @@ -14,7 +15,7 @@ class SqlWorkshopRepository: def __init__(self, session: AsyncSession) -> None: self.session = session - async def create(self, workshop: CreateWorkshopScheme) -> ViewWorkshopScheme | None: + async def create(self, workshop: CreateWorkshopScheme) -> tuple[ViewWorkshopScheme | None, WorkshopEnum]: to_insert = workshop.model_dump() to_insert["remain_places"] = workshop.capacity query = insert(Workshop).values(**to_insert).on_conflict_do_nothing().returning(Workshop) @@ -22,7 +23,8 @@ async def create(self, workshop: CreateWorkshopScheme) -> ViewWorkshopScheme | N workshop = workshop.scalar() await self.session.commit() if workshop is not None: - return ViewWorkshopScheme.model_validate(workshop, from_attributes=True) + return ViewWorkshopScheme.model_validate(workshop, from_attributes=True), WorkshopEnum.CREATED + return None, WorkshopEnum.ALIAS_ALREADY_EXISTS async def read_all(self) -> list[ViewWorkshopScheme]: query = select(Workshop) @@ -57,39 +59,42 @@ async def exists(self, workshop_id: int, user_id: int) -> bool: result = result.scalar() return bool(result) - async def create(self, workshop_id: int, user_id: int) -> bool: + async def create(self, workshop_id: int, user_id: int) -> CheckInEnum: workshop = await self.workshops_repository.get_by_id(workshop_id) - if workshop is None or workshop.remain_places == 0: - return False + if workshop is None: + return CheckInEnum.WORKSHOP_DOES_NOT_EXIST + + if workshop.remain_places == 0: + return CheckInEnum.NO_PLACES exists = await self.exists(workshop_id, user_id) if exists: - return False + return CheckInEnum.ALREADY_CHECKED_IN time_now = datetime.datetime.now(tz=None) registration_start_time = workshop.dtend.replace(hour=0, minute=0, second=0) if time_now < registration_start_time or time_now > workshop.dtstart: - return False + return CheckInEnum.INVALID_TIME request = insert(CheckIn).values(workshop_id=workshop_id, user_id=user_id) await self.session.execute(request) await self.workshops_repository.update_remain_places(workshop_id, -1) await self.session.commit() - return True + return CheckInEnum.SUCCESS - async def delete(self, workshop_id: int, user_id: int) -> bool: + async def delete(self, workshop_id: int, user_id: int) -> CheckInEnum: exists = await self.exists(workshop_id, user_id) if not exists: - return False + return CheckInEnum.CHECK_IN_DOES_NOT_EXIST request = delete(CheckIn).where(CheckIn.workshop_id == workshop_id, CheckIn.user_id == user_id) await self.session.execute(request) await self.workshops_repository.update_remain_places(workshop_id, 1) await self.session.commit() - return True + return CheckInEnum.SUCCESS async def get_check_inned_users_by_workshop_id(self, workshop_id: int) -> list[ViewUserScheme] | None: workshop = await self.workshops_repository.get_by_id(workshop_id) diff --git a/src/modules/workshops/routes.py b/src/modules/workshops/routes.py index 54a8b50..06bd82a 100644 --- a/src/modules/workshops/routes.py +++ b/src/modules/workshops/routes.py @@ -1,9 +1,11 @@ -from fastapi import APIRouter, Response, status +from fastapi import APIRouter, HTTPException, Response, status from src.api.dependencies import CurrentUserIdDep from src.api.exceptions import IncorrectCredentialsException +from src.api.schemes import ErrorScheme from src.modules.users.schemes import ViewUserScheme from src.modules.workshops.dependencies import SqlCheckInRepositoryDep, SqlWorkshopRepositoryDep +from src.modules.workshops.enums import CheckInEnum, WorkshopEnum from src.modules.workshops.schemes import CreateWorkshopScheme, ViewWorkshopScheme router = APIRouter( @@ -31,16 +33,16 @@ async def read_all_workshops(workshop_repository: SqlWorkshopRepositoryDep) -> l status_code=status.HTTP_201_CREATED, responses={ status.HTTP_201_CREATED: {"description": "Workshop successfully created"}, - status.HTTP_400_BAD_REQUEST: {"description": "Creation failed"}, + status.HTTP_400_BAD_REQUEST: {"description": "Creation failed", "model": ErrorScheme}, }, ) async def create_workshop( user_id: CurrentUserIdDep, workshop_repository: SqlWorkshopRepositoryDep, workshop: CreateWorkshopScheme ) -> ViewWorkshopScheme: - workshop = await workshop_repository.create(workshop) - if workshop: - return workshop - return Response(status_code=status.HTTP_400_BAD_REQUEST) + workshop, create_status = await workshop_repository.create(workshop) + if create_status != WorkshopEnum.CREATED: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=create_status.value) + return workshop @router.get( @@ -61,33 +63,33 @@ async def get_my_check_ins( status_code=status.HTTP_200_OK, responses={ status.HTTP_200_OK: {"description": "Check-in successful"}, - status.HTTP_400_BAD_REQUEST: {"description": "Check-in failed"}, + status.HTTP_400_BAD_REQUEST: {"description": "Check-in failed", "model": ErrorScheme}, }, ) async def check_in_workshop( workshop_id: int, user_id: CurrentUserIdDep, check_ins_repository: SqlCheckInRepositoryDep ) -> Response: - checked_in = await check_ins_repository.create(workshop_id, user_id) - if checked_in: - return Response(status_code=status.HTTP_200_OK) - return Response(status_code=status.HTTP_400_BAD_REQUEST) + check_in_status = await check_ins_repository.create(workshop_id, user_id) + if check_in_status != CheckInEnum.SUCCESS: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=check_in_status.value) + return Response(status_code=status.HTTP_200_OK) @router.post( "/check-out/{workshop_id}", status_code=status.HTTP_200_OK, responses={ - status.HTTP_200_OK: {"description": "Check-out successful"}, - status.HTTP_400_BAD_REQUEST: {"description": "Check-out failed"}, + status.HTTP_200_OK: {"description": "Check-out successful", "model": None}, + status.HTTP_400_BAD_REQUEST: {"description": "Check-out failed", "model": ErrorScheme}, }, ) async def check_out_workshop( workshop_id: int, user_id: CurrentUserIdDep, check_ins_repository: SqlCheckInRepositoryDep ) -> Response: - deleted = await check_ins_repository.delete(workshop_id, user_id) - if deleted: - return Response(status_code=status.HTTP_200_OK) - return Response(status_code=status.HTTP_400_BAD_REQUEST) + delete_status = await check_ins_repository.delete(workshop_id, user_id) + if delete_status != CheckInEnum.SUCCESS: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=delete_status.value) + return Response(status_code=status.HTTP_200_OK) @router.get(