From 5e20da8356b6eba3fad50ac457d740f337af655a Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Wed, 13 Aug 2025 15:01:22 +0400 Subject: [PATCH] feat: projects create, update, delete --- repositories/project.py | 41 +++++++++++++++++++++++++++++++++++ routers/project.py | 48 +++++++++++++++++++++++++++++++++++++++-- schemas/project.py | 35 ++++++++++++++++++++++++++---- services/project.py | 39 ++++++++++++++++++++++++++++++++- 4 files changed, 156 insertions(+), 7 deletions(-) diff --git a/repositories/project.py b/repositories/project.py index 43436c8..fe5b845 100644 --- a/repositories/project.py +++ b/repositories/project.py @@ -1,7 +1,12 @@ +from datetime import datetime +from typing import Optional + from sqlalchemy import select +from sqlalchemy.orm import selectinload from models.project import Project from repositories.base import BaseRepository +from schemas.project import CreateProjectSchema, UpdateProjectSchema class ProjectRepository(BaseRepository): @@ -9,3 +14,39 @@ class ProjectRepository(BaseRepository): stmt = select(Project).where(Project.is_deleted.is_(False)) result = await self.session.execute(stmt) return list(result.scalars().all()) + + async def get_by_id(self, project_id: int) -> Optional[Project]: + stmt = ( + select(Project) + .where(Project.id == project_id, Project.is_deleted.is_(False)) + .options(selectinload(Project.boards)) + ) + result = await self.session.execute(stmt) + return result.scalar_one_or_none() + + async def create(self, data: CreateProjectSchema) -> Project: + project_data = data.model_dump() + project_data["created_at"] = datetime.now() + project = Project(**project_data) + self.session.add(project) + await self.session.commit() + await self.session.refresh(project) + return project + + async def update(self, project: Project, data: UpdateProjectSchema) -> Project: + project.name = data.name if data.name else project.name + + self.session.add(project) + await self.session.commit() + await self.session.refresh(project) + return project + + async def delete(self, project: Project, is_soft: bool): + if not is_soft: + await self.session.delete(project) + await self.session.commit() + return + + project.is_deleted = True + self.session.add(project) + await self.session.commit() diff --git a/routers/project.py b/routers/project.py index 974d670..9a006e6 100644 --- a/routers/project.py +++ b/routers/project.py @@ -1,7 +1,14 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Query from backend.dependecies import SessionDependency -from schemas.project import GetProjectsResponse +from schemas.project import ( + GetProjectsResponse, + CreateProjectResponse, + CreateProjectRequest, + UpdateProjectResponse, + UpdateProjectRequest, + DeleteProjectResponse, +) from services import ProjectService project_router = APIRouter( @@ -18,3 +25,40 @@ async def get_projects( session: SessionDependency, ): return await ProjectService(session).get_projects() + + +@project_router.post( + "/", + response_model=CreateProjectResponse, + operation_id="create_project", +) +async def create_project( + session: SessionDependency, + request: CreateProjectRequest, +): + return await ProjectService(session).create_project(request) + + +@project_router.patch( + "/{projectId}", + response_model=UpdateProjectResponse, + operation_id="update_project", +) +async def update_project( + session: SessionDependency, + request: UpdateProjectRequest, + project_id: int = Query(alias="projectId"), +): + return await ProjectService(session).update_project(project_id, request) + + +@project_router.delete( + "/{projectId}", + response_model=DeleteProjectResponse, + operation_id="delete_project", +) +async def delete_project( + session: SessionDependency, + project_id: int = Query(alias="projectId"), +): + return await ProjectService(session).delete_project(project_id) diff --git a/schemas/project.py b/schemas/project.py index 3b25302..f758c9e 100644 --- a/schemas/project.py +++ b/schemas/project.py @@ -1,15 +1,22 @@ -from schemas.base import BaseSchema +from typing import Optional + +from schemas.base import BaseSchema, BaseResponse # region Entity -class BaseProjectSchema(BaseSchema): +class ProjectSchema(BaseSchema): + id: int name: str -class ProjectSchema(BaseProjectSchema): - id: int +class CreateProjectSchema(BaseSchema): + name: str + + +class UpdateProjectSchema(BaseSchema): + name: Optional[str] = None # endregion @@ -17,6 +24,14 @@ class ProjectSchema(BaseProjectSchema): # region Requests +class CreateProjectRequest(BaseSchema): + project: CreateProjectSchema + + +class UpdateProjectRequest(BaseSchema): + project: UpdateProjectSchema + + # endregion # region Responses @@ -26,4 +41,16 @@ class GetProjectsResponse(BaseSchema): projects: list[ProjectSchema] +class CreateProjectResponse(BaseResponse): + project: ProjectSchema + + +class UpdateProjectResponse(BaseResponse): + pass + + +class DeleteProjectResponse(BaseResponse): + pass + + # endregion Responses diff --git a/services/project.py b/services/project.py index 7622315..c0975c1 100644 --- a/services/project.py +++ b/services/project.py @@ -1,7 +1,16 @@ +from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from repositories import ProjectRepository -from schemas.project import GetProjectsResponse, ProjectSchema +from schemas.project import ( + GetProjectsResponse, + ProjectSchema, + CreateProjectRequest, + CreateProjectResponse, + UpdateProjectRequest, + UpdateProjectResponse, + DeleteProjectResponse, +) class ProjectService: @@ -13,3 +22,31 @@ class ProjectService: return GetProjectsResponse( projects=[ProjectSchema.model_validate(project) for project in projects] ) + + async def create_project( + self, request: CreateProjectRequest + ) -> CreateProjectResponse: + project = await self.repository.create(request.project) + return CreateProjectResponse( + project=ProjectSchema.model_validate(project), + message="Проект успешно создан", + ) + + async def update_project( + self, project_id: int, request: UpdateProjectRequest + ) -> UpdateProjectResponse: + project = await self.repository.get_by_id(project_id) + if not project: + raise HTTPException(status_code=404, detail="Проект не найден") + + await self.repository.update(project, request.project) + return UpdateProjectResponse(message="Проект успешно обновлен") + + async def delete_project(self, project_id: int) -> DeleteProjectResponse: + project = await self.repository.get_by_id(project_id) + if not project: + raise HTTPException(status_code=404, detail="Проект не найден") + + is_soft_needed: bool = len(project.boards) > 0 + await self.repository.delete(project, is_soft_needed) + return DeleteProjectResponse(message="Проект успешно удален")