From 24405f1e0691cc82761ea4b82972a2b54beb4ac1 Mon Sep 17 00:00:00 2001 From: Andrei Duvakin Date: Thu, 20 Feb 2025 20:47:30 +0500 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D0=BB=D0=BE=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B8=D0=BC=D0=BE=D0=B3=D0=BE=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BE=D0=B2=20=D0=BB=D0=B8=D0=BD=D0=B7=20?= =?UTF-8?q?=D0=B2=20=D0=B0=D0=BF=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/application/set_content_repository.py | 42 ++++ api/app/controllers/set_content_router.py | 70 +++++++ api/app/controllers/sets_router.py | 2 +- ..._добавил_тип_линзы_у_содержимого_отчета.py | 32 +++ ...сделал_у_содержания_набора_тип_стороны_.py | 29 +++ api/app/domain/entities/set_content.py | 1 + api/app/domain/models/set_contents.py | 7 +- api/app/infrastructure/lenses_service.py | 4 +- api/app/infrastructure/set_content_service.py | 198 ++++++++++++++++++ api/app/main.py | 2 + 10 files changed, 382 insertions(+), 5 deletions(-) create mode 100644 api/app/application/set_content_repository.py create mode 100644 api/app/controllers/set_content_router.py create mode 100644 api/app/database/migrations/versions/15df0d2bfad5_добавил_тип_линзы_у_содержимого_отчета.py create mode 100644 api/app/database/migrations/versions/a5c09be17888_сделал_у_содержания_набора_тип_стороны_.py create mode 100644 api/app/infrastructure/set_content_service.py diff --git a/api/app/application/set_content_repository.py b/api/app/application/set_content_repository.py new file mode 100644 index 0000000..3ea7c9d --- /dev/null +++ b/api/app/application/set_content_repository.py @@ -0,0 +1,42 @@ +from typing import Sequence, Optional + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.domain.models import SetContent + + +class SetContentRepository: + def __init__(self, db: AsyncSession): + self.db = db + + async def get_all(self) -> Sequence[SetContent]: + stmt = select(SetContent) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_id(self, set_content_id: int) -> Optional[SetContent]: + stmt = select(SetContent).filter(SetContent.id == set_content_id) + result = await self.db.execute(stmt) + return result.scalars().first() + + async def get_by_set_id(self, set_id: int) -> Sequence[SetContent]: + stmt = select(SetContent).filter(SetContent.set_id == set_id) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def create(self, set_content: SetContent) -> SetContent: + self.db.add(set_content) + await self.db.commit() + await self.db.refresh(set_content) + return set_content + + async def update(self, set_content: SetContent) -> SetContent: + await self.db.merge(set_content) + await self.db.commit() + return set_content + + async def delete(self, set_content: SetContent) -> SetContent: + await self.db.delete(set_content) + await self.db.commit() + return set_content diff --git a/api/app/controllers/set_content_router.py b/api/app/controllers/set_content_router.py new file mode 100644 index 0000000..b36ce78 --- /dev/null +++ b/api/app/controllers/set_content_router.py @@ -0,0 +1,70 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.set_content import SetContentEntity +from app.infrastructure.dependencies import get_current_user +from app.infrastructure.set_content_service import SetContentService + +router = APIRouter() + + +@router.get( + '/set_content/{set_id}/', + response_model=list[SetContentEntity], + summary='Get all set content by set ID', + description='Returns a list of set content by set ID', +) +async def get_set_content_by_set_id( + set_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + set_content_service = SetContentService(db) + return await set_content_service.get_content_by_set_id(set_id) + + +@router.post( + '/set_content/', + response_model=SetContentEntity, + summary='Create a new set content', + description='Create a new set content', +) +async def create_set_content( + set_content: SetContentEntity, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + set_content_service = SetContentService(db) + return await set_content_service.create_set_content(set_content) + + +@router.put( + '/set_content/{set_content_id}/', + response_model=SetContentEntity, + summary='Update a set content', + description='Update a set content', +) +async def update_set_content( + set_content_id: int, + set_content: SetContentEntity, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + set_content_service = SetContentService(db) + return await set_content_service.update_set_content(set_content_id, set_content) + + +@router.delete( + '/set_content/{set_content_id}/', + response_model=SetContentEntity, + summary='Delete set content', + description='Delete an existing set content', +) +async def delete_set_content( + set_content_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + set_content_service = SetContentService(db) + return await set_content_service.delete_set_content(set_content_id) diff --git a/api/app/controllers/sets_router.py b/api/app/controllers/sets_router.py index 299fb40..5031344 100644 --- a/api/app/controllers/sets_router.py +++ b/api/app/controllers/sets_router.py @@ -51,7 +51,7 @@ async def update_set( user=Depends(get_current_user), ): sets_service = SetsService(db) - return sets_service.update_set(set_id, _set) + return await sets_service.update_set(set_id, _set) @router.delete( diff --git a/api/app/database/migrations/versions/15df0d2bfad5_добавил_тип_линзы_у_содержимого_отчета.py b/api/app/database/migrations/versions/15df0d2bfad5_добавил_тип_линзы_у_содержимого_отчета.py new file mode 100644 index 0000000..1cce22e --- /dev/null +++ b/api/app/database/migrations/versions/15df0d2bfad5_добавил_тип_линзы_у_содержимого_отчета.py @@ -0,0 +1,32 @@ +"""добавил тип линзы у содержимого отчета + +Revision ID: 15df0d2bfad5 +Revises: a5c09be17888 +Create Date: 2025-02-20 19:51:20.196094 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '15df0d2bfad5' +down_revision: Union[str, None] = 'a5c09be17888' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('set_contents', sa.Column('type_id', sa.Integer(), nullable=False)) + op.create_foreign_key(None, 'set_contents', 'lens_types', ['type_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'set_contents', type_='foreignkey') + op.drop_column('set_contents', 'type_id') + # ### end Alembic commands ### diff --git a/api/app/database/migrations/versions/a5c09be17888_сделал_у_содержания_набора_тип_стороны_.py b/api/app/database/migrations/versions/a5c09be17888_сделал_у_содержания_набора_тип_стороны_.py new file mode 100644 index 0000000..63d76a5 --- /dev/null +++ b/api/app/database/migrations/versions/a5c09be17888_сделал_у_содержания_набора_тип_стороны_.py @@ -0,0 +1,29 @@ +"""сделал у содержания набора тип стороны enum + +Revision ID: a5c09be17888 +Revises: 0249759985c3 +Create Date: 2025-02-20 19:39:57.756998 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = 'a5c09be17888' +down_revision: Union[str, None] = '0249759985c3' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute("ALTER TABLE set_contents ALTER COLUMN side TYPE sideenum USING CASE " + "WHEN side = 0 THEN 'LEFT'::sideenum " + "WHEN side = 1 THEN 'RIGHT'::sideenum END") + + +def downgrade() -> None: + op.execute("ALTER TABLE set_contents ALTER COLUMN side TYPE INTEGER USING CASE " + "WHEN side = 'LEFT' THEN 0 " + "WHEN side = 'RIGHT' THEN 1 END") diff --git a/api/app/domain/entities/set_content.py b/api/app/domain/entities/set_content.py index 5281346..c968b5a 100644 --- a/api/app/domain/entities/set_content.py +++ b/api/app/domain/entities/set_content.py @@ -15,4 +15,5 @@ class SetContentEntity(BaseModel): side: str count: int + type_id: int set_id: int diff --git a/api/app/domain/models/set_contents.py b/api/app/domain/models/set_contents.py index fb0bb25..cc4142a 100644 --- a/api/app/domain/models/set_contents.py +++ b/api/app/domain/models/set_contents.py @@ -1,7 +1,8 @@ -from sqlalchemy import Column, Integer, ForeignKey +from sqlalchemy import Column, Integer, ForeignKey, Enum from sqlalchemy.orm import relationship from app.domain.models.base import BaseModel +from app.domain.models.lens import SideEnum class SetContent(BaseModel): @@ -14,9 +15,11 @@ class SetContent(BaseModel): preset_refraction = Column(Integer, nullable=False) diameter = Column(Integer, nullable=False) periphery_toricity = Column(Integer, nullable=False) - side = Column(Integer, nullable=False) + side = Column(Enum(SideEnum), nullable=False) count = Column(Integer, nullable=False) + type_id = Column(Integer, ForeignKey('lens_types.id'), nullable=False) set_id = Column(Integer, ForeignKey('sets.id'), nullable=False) + type = relationship('LensType', back_populates='lenses') set = relationship('Set', back_populates='contents') diff --git a/api/app/infrastructure/lenses_service.py b/api/app/infrastructure/lenses_service.py index 109b99f..e679643 100644 --- a/api/app/infrastructure/lenses_service.py +++ b/api/app/infrastructure/lenses_service.py @@ -1,4 +1,4 @@ -from typing import Optional, Any, Coroutine +from typing import Optional from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession @@ -41,7 +41,7 @@ class LensesService: if not lens_type: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail='The lens type with this ID was not found' + detail='The lens type with this ID was not found', ) try: diff --git a/api/app/infrastructure/set_content_service.py b/api/app/infrastructure/set_content_service.py new file mode 100644 index 0000000..f4baf39 --- /dev/null +++ b/api/app/infrastructure/set_content_service.py @@ -0,0 +1,198 @@ +from typing import Optional + +from fastapi import HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from starlette import status + +from app.application.lens_types_repository import LensTypesRepository +from app.application.set_content_repository import SetContentRepository +from app.application.sets_repository import SetsRepository +from app.domain.entities.set_content import SetContentEntity +from app.domain.models import SetContent +from app.domain.models.lens import SideEnum + + +class SetContentService: + def __init__(self, db: AsyncSession): + self.set_content_repository = SetContentRepository(db) + self.set_repository = SetsRepository(db) + self.lens_types_repository = LensTypesRepository(db) + + async def get_all_set_content(self) -> list[SetContentEntity]: + set_content = await self.set_content_repository.get_all() + return [ + SetContentEntity( + id=content.id, + tor=content.tor, + trial=content.trial, + esa=content.esa, + fvc=content.fvc, + preset_refraction=content.preset_refraction, + diameter=content.diameter, + periphery_toricity=content.periphery_toricity, + side=content.side, + count=content.count, + type_id=content.type_id, + set_id=content.set_id, + ) + for content in set_content + ] + + async def get_content_by_set_id(self, set_id: int) -> Optional[list[SetContentEntity]]: + _set = self.set_repository.get_by_id(set_id) + + if not _set: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The set with this ID was not found', + ) + + set_content = await self.set_content_repository.get_by_set_id(set_id) + + return [ + SetContentEntity( + id=content.id, + tor=content.tor, + trial=content.trial, + esa=content.esa, + fvc=content.fvc, + preset_refraction=content.preset_refraction, + diameter=content.diameter, + periphery_toricity=content.periphery_toricity, + side=content.side, + count=content.count, + type_id=content.type_id, + set_id=content.set_id, + ) + for content in set_content + ] + + async def create_set_content(self, set_content: SetContentEntity) -> SetContentEntity: + lens_type = await self.lens_types_repository.get_by_id(set_content.type_id) + + if not lens_type: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The lens type with this ID was not found', + ) + + _set = self.set_repository.get_by_id(set_content.set_id) + + if not _set: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The set with this ID was not found', + ) + + try: + side_enum = SideEnum(set_content.side) + except ValueError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid side value: {set_content.side}. Must be 'левая' or 'правая'." + ) + + set_content_model = SetContent( + tor=set_content.tor, + trial=set_content.trial, + esa=set_content.esa, + fvc=set_content.fvc, + preset_refraction=set_content.preset_refraction, + diameter=set_content.diameter, + periphery_toricity=set_content.periphery_toricity, + side=side_enum, + count=set_content.count, + type_id=set_content.type_id, + set_id=set_content.set_id, + ) + + await self.set_content_repository.create(set_content_model) + + return SetContentEntity( + id=set_content_model.id, + tor=set_content_model.tor, + trial=set_content_model.trial, + esa=set_content_model.esa, + fvc=set_content_model.fvc, + preset_refraction=set_content_model.preset_refraction, + diameter=set_content_model.diameter, + periphery_toricity=set_content_model.periphery_toricity, + side=set_content_model.side.value, + count=set_content_model.count, + type_id=set_content_model.type_id, + set_id=set_content_model.set_id, + ) + + async def update_set_content(self, set_content_id: int, set_content: SetContentEntity): + set_content_model = await self.set_content_repository.get_by_id(set_content_id) + + if not set_content_model: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Set content not found") + + _set = await self.set_repository.get_by_id(set_content.set_id) + + if not _set: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The set with this ID was not found', + ) + + try: + side_enum = SideEnum(set_content_model.side) + except ValueError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid side value: {set_content_model.side}. Must be 'левая' or 'правая'." + ) + + set_content_model.tor = set_content.tor + set_content_model.trial = set_content.trial + set_content_model.esa = set_content.esa + set_content_model.fvc = set_content.fvc + set_content_model.preset_refraction = set_content.preset_refraction + set_content_model.diameter = set_content.diameter + set_content_model.periphery_toricity = set_content.periphery_toricity + set_content_model.side = side_enum + set_content_model.count = set_content.count + set_content_model.type_id = set_content.type_id + set_content_model.set_id = set_content.set_id + + await self.set_content_repository.update(set_content_model) + + return SetContentEntity( + id=set_content_model.id, + tor=set_content_model.tor, + trial=set_content_model.trial, + esa=set_content_model.esa, + fvc=set_content_model.fvc, + preset_refraction=set_content_model.preset_refraction, + diameter=set_content_model.diameter, + periphery_toricity=set_content_model.periphery_toricity, + side=set_content_model.side.value, + count=set_content_model.count, + type_id=set_content_model.type_id, + set_id=set_content_model.set_id, + ) + + async def delete_set_content(self, set_content_id: int) -> Optional[SetContentEntity]: + set_content = await self.set_content_repository.get_by_id(set_content_id) + + if not set_content: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Set content not found") + + result = await self.set_content_repository.delete(set_content) + + return SetContentEntity( + id=result.id, + tor=result.tor, + trial=result.trial, + esa=result.esa, + fvc=result.fvc, + preset_refraction=result.preset_refraction, + diameter=result.diameter, + periphery_toricity=result.periphery_toricity, + side=result.side.value, + count=result.count, + type_id=result.type_id, + set_id=result.set_id, + ) diff --git a/api/app/main.py b/api/app/main.py index 400c731..3fee108 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -6,6 +6,7 @@ from app.controllers.lens_types_router import router as lens_types_router from app.controllers.lenses_router import router as lenses_router from app.controllers.patients_router import router as patients_router from app.controllers.register_routes import router as register_router +from app.controllers.set_content_router import router as set_content_router from app.controllers.sets_router import router as sets_router from app.settings import settings @@ -27,6 +28,7 @@ def start_app(): api_app.include_router(lenses_router, prefix=settings.APP_PREFIX, tags=['lenses']) api_app.include_router(lens_types_router, prefix=settings.APP_PREFIX, tags=['lens_types']) api_app.include_router(sets_router, prefix=settings.APP_PREFIX, tags=['sets']) + api_app.include_router(set_content_router, prefix=settings.APP_PREFIX, tags=['set_content']) return api_app