diff --git a/API/app/application/project_files_repository.py b/API/app/application/project_files_repository.py new file mode 100644 index 0000000..7639e9a --- /dev/null +++ b/API/app/application/project_files_repository.py @@ -0,0 +1,32 @@ +from typing import Optional, Sequence + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.domain.models import ProjectFile + + +class ProjectFilesRepository: + def __init__(self, db: AsyncSession): + self.db = db + + async def get_by_id(self, file_id: int) -> Optional[ProjectFile]: + stmt = select(ProjectFile).filter_by(id=file_id) + result = await self.db.execute(stmt) + return result.scalars().first() + + async def get_by_project_id(self, project_id: int) -> Sequence[ProjectFile]: + stmt = select(ProjectFile).filter_by(project_id=project_id) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def create(self, project_file: ProjectFile) -> ProjectFile: + self.db.add(project_file) + await self.db.commit() + await self.db.refresh(project_file) + return project_file + + async def delete(self, project_file: ProjectFile) -> ProjectFile: + await self.db.delete(project_file) + await self.db.commit() + return project_file diff --git a/API/app/application/project_members_repository.py b/API/app/application/project_members_repository.py new file mode 100644 index 0000000..7bf226e --- /dev/null +++ b/API/app/application/project_members_repository.py @@ -0,0 +1,47 @@ +from typing import Optional, Sequence + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.domain.models import ProjectMember + + +class ProjectMembersRepository: + def __init__(self, db: AsyncSession): + self.db = db + + async def get_by_id(self, project_member_id: int) -> Optional[ProjectMember]: + stmt = select(ProjectMember).filter_by(id=project_member_id) + result = await self.db.execute(stmt) + return result.scalars().first() + + async def get_by_project_id(self, project_id: int) -> Sequence[ProjectMember]: + stmt = select(ProjectMember).filter_by(project_id=project_id) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_profile_id(self, profile_id: int) -> Sequence[ProjectMember]: + stmt = select(ProjectMember).filter_by(profile_id=profile_id) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_project_id_and_profile_id(self, project_id: int, profile_id: int) -> Optional[ProjectMember]: + stmt = select(ProjectMember).filter_by(project_id=project_id, profile_id=profile_id) + result = await self.db.execute(stmt) + return result.scalars().first() + + async def create_list(self, project_members: list[ProjectMember]) -> list[ProjectMember]: + self.db.add_all(project_members) + await self.db.commit() + + for project_member in project_members: + await self.db.refresh(project_member) + + return project_members + + async def delete_list_members(self, project_members: list[ProjectMember]) -> list[ProjectMember]: + for project_member in project_members: + await self.db.delete(project_member) + + await self.db.commit() + return project_members diff --git a/API/app/contollers/project_files_router.py b/API/app/contollers/project_files_router.py new file mode 100644 index 0000000..0bdefc6 --- /dev/null +++ b/API/app/contollers/project_files_router.py @@ -0,0 +1,69 @@ +from fastapi import Depends, File, UploadFile, APIRouter +from sqlalchemy.ext.asyncio import AsyncSession +from starlette.responses import FileResponse + +from app.database.session import get_db +from app.domain.entities.project_file import ProjectFileEntity +from app.infrastructure.dependencies import get_current_user, require_admin +from app.infrastructure.project_files_service import ProjectFilesService + +router = APIRouter() + + +@router.get( + "/projects/{project_id}/", + response_model=list[ProjectFileEntity], + summary="Get all project files", + description="Returns metadata of all files uploaded for the specified project." +) +async def get_files_by_project_id( + project_id: int, + db: AsyncSession = Depends(get_db), +): + service = ProjectFilesService(db) + return await service.get_files_by_project_id(project_id) + + +@router.get( + "/{file_id}/file", + response_class=FileResponse, + summary="Download project file by ID", + description="Returns the file for the specified file ID." +) +async def download_project_file( + file_id: int, + db: AsyncSession = Depends(get_db), +): + service = ProjectFilesService(db) + return await service.get_file_by_id(file_id) + + +@router.post( + "/projects/{project_id}/upload", + response_model=ProjectFileEntity, + summary="Upload a new file for the project", + description="Uploads a new file and associates it with the specified project." +) +async def upload_project_file( + project_id: int, + file: UploadFile = File(...), + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + service = ProjectFilesService(db) + return await service.upload_file(project_id, file, user) + + +@router.delete( + "/{file_id}/", + response_model=ProjectFileEntity, + summary="Delete a project file by ID", + description="Deletes the file and its database entry." +) +async def delete_project_file( + file_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + service = ProjectFilesService(db) + return await service.delete_file(file_id, user) diff --git a/API/app/contollers/project_members_router.py b/API/app/contollers/project_members_router.py new file mode 100644 index 0000000..c85381d --- /dev/null +++ b/API/app/contollers/project_members_router.py @@ -0,0 +1,86 @@ +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.project_member import ProjectMemberEntity +from app.infrastructure.dependencies import require_admin +from app.infrastructure.project_members_service import ProjectMembersService + +router = APIRouter() + + +@router.get( + '/by-project/{project_id}/', + response_model=list[ProjectMemberEntity], + summary='Get project members by project ID', + description='Returns all project members with the specified project ID' +) +async def get_members_by_project_id( + project_id: int, + db: AsyncSession = Depends(get_db) +): + service = ProjectMembersService(db) + return await service.get_project_members_by_project_id(project_id) + + +@router.get( + '/by-profile/{profile_id}/', + response_model=list[ProjectMemberEntity], + summary='Get project members by profile ID', + description='Returns all project member records where the profile is involved' +) +async def get_members_by_profile_id( + profile_id: int, + db: AsyncSession = Depends(get_db) +): + service = ProjectMembersService(db) + return await service.get_project_members_by_profile_id(profile_id) + + +@router.post( + '/{project_id}/', + response_model=Optional[list[ProjectMemberEntity]], + summary='Create a list of project members', + description='Creates a list of project members for the specified project ID' +) +async def create_project_members( + project_id: int, + project_members: list[ProjectMemberEntity], + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + service = ProjectMembersService(db) + return await service.create_list_project_members(project_id, project_members) + + +@router.put( + '/{project_id}/', + response_model=Optional[list[ProjectMemberEntity]], + summary='Update the list of project members', + description='Deletes all current project members and creates new records' +) +async def update_project_members( + project_id: int, + project_members: list[ProjectMemberEntity], + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + service = ProjectMembersService(db) + return await service.update_list_project_members(project_id, project_members) + + +@router.delete( + '/{project_id}/', + summary='Delete all project members by project ID', + description='Deletes all project members with the specified project ID', +) +async def delete_project_members( + project_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + service = ProjectMembersService(db) + await service.delete_project_members_by_project_id(project_id) + return {"message": "All project members have been successfully deleted."} diff --git a/API/app/contollers/projects_router.py b/API/app/contollers/projects_router.py new file mode 100644 index 0000000..3979ded --- /dev/null +++ b/API/app/contollers/projects_router.py @@ -0,0 +1,70 @@ +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.project import ProjectEntity +from app.infrastructure.dependencies import require_admin +from app.infrastructure.projects_service import ProjectsService + +router = APIRouter() + + +@router.get( + '/', + response_model=list[ProjectEntity], + summary='Get all projects', + description='Returns all projects', +) +async def get_all_projects( + db: AsyncSession = Depends(get_db), +): + projects_service = ProjectsService(db) + return await projects_service.get_all_projects() + + +@router.post( + '/', + response_model=Optional[ProjectEntity], + summary='Create a new project', + description='Creates a new project', +) +async def create_project( + project: ProjectEntity, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + projects_service = ProjectsService(db) + return await projects_service.create_project(project) + + +@router.put( + '/{project_id}/', + response_model=Optional[ProjectEntity], + summary='Update a project', + description='Updates a project', +) +async def update_project( + project_id: int, + project: ProjectEntity, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + projects_service = ProjectsService(db) + return await projects_service.update_project(project_id, project) + + +@router.delete( + '/{project_id}/', + response_model=Optional[ProjectEntity], + summary='Delete a project', + description='Delete a project', +) +async def delete_project( + project_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + projects_service = ProjectsService(db) + return await projects_service.delete_project(project_id) diff --git a/API/app/contollers/teams_router.py b/API/app/contollers/teams_router.py index d3adb8a..a6cf332 100644 --- a/API/app/contollers/teams_router.py +++ b/API/app/contollers/teams_router.py @@ -34,7 +34,7 @@ async def get_all_teams( async def create_team( team: TeamEntity, db: AsyncSession = Depends(get_db), - user=Depends(get_current_user), + user=Depends(require_admin), ): teams_service = TeamsService(db) return await teams_service.create_team(team) diff --git a/API/app/database/migrations/versions/be10a7640a29_0003_добавил_поле_название_для_проектов_.py b/API/app/database/migrations/versions/be10a7640a29_0003_добавил_поле_название_для_проектов_.py new file mode 100644 index 0000000..c62e94b --- /dev/null +++ b/API/app/database/migrations/versions/be10a7640a29_0003_добавил_поле_название_для_проектов_.py @@ -0,0 +1,32 @@ +"""0003_добавил_поле_название_для_проектов_в_таблицу + +Revision ID: be10a7640a29 +Revises: b4056dda0936 +Create Date: 2025-05-31 10:50:44.163131 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'be10a7640a29' +down_revision: Union[str, None] = 'b4056dda0936' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('projects', sa.Column('title', sa.VARCHAR(length=150), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('projects', 'title') + # ### end Alembic commands ### diff --git a/API/app/database/migrations/versions/e53896c51cf8_0004_добавил_поле_filename_в_таблицу_.py b/API/app/database/migrations/versions/e53896c51cf8_0004_добавил_поле_filename_в_таблицу_.py new file mode 100644 index 0000000..c1c2649 --- /dev/null +++ b/API/app/database/migrations/versions/e53896c51cf8_0004_добавил_поле_filename_в_таблицу_.py @@ -0,0 +1,32 @@ +"""0004_добавил_поле_filename_в_таблицу_profect_files + +Revision ID: e53896c51cf8 +Revises: be10a7640a29 +Create Date: 2025-05-31 11:14:31.502960 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'e53896c51cf8' +down_revision: Union[str, None] = 'be10a7640a29' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('project_files', sa.Column('filename', sa.String(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('project_files', 'filename') + # ### end Alembic commands ### diff --git a/API/app/domain/entities/project.py b/API/app/domain/entities/project.py index 32d53b0..ce54093 100644 --- a/API/app/domain/entities/project.py +++ b/API/app/domain/entities/project.py @@ -5,5 +5,6 @@ from pydantic import BaseModel class ProjectEntity(BaseModel): id: Optional[int] = None + title: str description: str repository_url: Optional[str] = None \ No newline at end of file diff --git a/API/app/domain/entities/project_file.py b/API/app/domain/entities/project_file.py new file mode 100644 index 0000000..9392fed --- /dev/null +++ b/API/app/domain/entities/project_file.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + + +class ProjectFileEntity(BaseModel): + id: int + filename: str + file_path: str + project_id: int diff --git a/API/app/domain/entities/project_member.py b/API/app/domain/entities/project_member.py new file mode 100644 index 0000000..c5623db --- /dev/null +++ b/API/app/domain/entities/project_member.py @@ -0,0 +1,10 @@ +from typing import Optional + +from pydantic import BaseModel + + +class ProjectMemberEntity(BaseModel): + id: Optional[int] = None + description: str + project_id: int + profile_id: int diff --git a/API/app/domain/models/project_files.py b/API/app/domain/models/project_files.py index 81e978c..81f96fb 100644 --- a/API/app/domain/models/project_files.py +++ b/API/app/domain/models/project_files.py @@ -7,6 +7,7 @@ from app.domain.models.base import AdvancedBaseModel class ProjectFile(AdvancedBaseModel): __tablename__ = 'project_files' + filename = Column(String, nullable=False) file_path = Column(String, unique=True, nullable=False) project_id = Column(Integer, ForeignKey('projects.id'), nullable=False) diff --git a/API/app/domain/models/projects.py b/API/app/domain/models/projects.py index 141c494..96f35ff 100644 --- a/API/app/domain/models/projects.py +++ b/API/app/domain/models/projects.py @@ -7,6 +7,7 @@ from app.domain.models.base import AdvancedBaseModel class Project(AdvancedBaseModel): __tablename__ = 'projects' + title = Column(VARCHAR(150), nullable=False) description = Column(VARCHAR(150)) repository_url = Column(String, nullable=False) diff --git a/API/app/infrastructure/project_files_service.py b/API/app/infrastructure/project_files_service.py new file mode 100644 index 0000000..4a8ae00 --- /dev/null +++ b/API/app/infrastructure/project_files_service.py @@ -0,0 +1,134 @@ +import os +import uuid + +import aiofiles +import magic +from fastapi import HTTPException, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from starlette.responses import FileResponse +from werkzeug.utils import secure_filename + +from app.application.project_files_repository import ProjectFilesRepository +from app.application.projects_repository import ProjectsRepository +from app.domain.entities.project_file import ProjectFileEntity +from app.domain.models import ProjectFile, User + + +class ProjectFilesService: + def __init__(self, db: AsyncSession): + self.project_files_repository = ProjectFilesRepository(db) + self.projects_repository = ProjectsRepository(db) + + async def get_file_by_id(self, file_id: int) -> FileResponse: + project_file = await self.project_files_repository.get_by_id(file_id) + + if not project_file: + raise HTTPException(404, "File not found") + + if not os.path.exists(project_file.file_path): + raise HTTPException(404, "File not found on disk") + + return FileResponse( + project_file.file_path, + media_type=self.get_media_type(project_file.file_path), + filename=os.path.basename(project_file.file_path), + ) + + async def get_files_by_project_id(self, project_id: int) -> list[ProjectFileEntity]: + files = await self.project_files_repository.get_by_project_id(project_id) + return [self.model_to_entity(file) for file in files] + + async def upload_file(self, project_id: int, file: UploadFile, user: User) -> ProjectFileEntity: + project = await self.projects_repository.get_by_id(project_id) + if not project: + raise HTTPException(404, "Project not found") + + self.validate_file_type(file) + file_path = await self.save_file(file) + + project_file = ProjectFile( + filename=file.filename, + file_path=file_path, + project_id=project_id, + ) + + return self.model_to_entity( + await self.project_files_repository.create(project_file) + ) + + async def delete_file(self, file_id: int, user: User) -> ProjectFileEntity: + project_file = await self.project_files_repository.get_by_id(file_id) + if not project_file: + raise HTTPException(404, "File not found") + + if os.path.exists(project_file.file_path): + os.remove(project_file.file_path) + + return self.model_to_entity( + await self.project_files_repository.delete(project_file) + ) + + async def save_file(self, file: UploadFile, upload_dir: str = "uploads/project_files") -> str: + os.makedirs(upload_dir, exist_ok=True) + filename = self.generate_filename(file) + file_path = os.path.join(upload_dir, filename) + + async with aiofiles.open(file_path, 'wb') as out_file: + content = await file.read() + await out_file.write(content) + return file_path + + @staticmethod + def validate_file_type(file: UploadFile): + mime = magic.Magic(mime=True) + file_type = mime.from_buffer(file.file.read(1024)) + file.file.seek(0) + + allowed_types = [ + "application/pdf", + "image/jpeg", + "image/png", + "application/zip", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/plain", + ] + + if file_type not in allowed_types: + raise HTTPException(400, f"Invalid file type: {file_type}") + + @staticmethod + def generate_filename(file: UploadFile) -> str: + return secure_filename(f"{uuid.uuid4()}_{file.filename}") + + @staticmethod + def model_to_entity(project_file_model: ProjectFile) -> ProjectFileEntity: + return ProjectFileEntity( + id=project_file_model.id, + filename=project_file_model.filename, + file_path=project_file_model.file_path, + project_id=project_file_model.project_id, + ) + + @staticmethod + def get_media_type(filename: str) -> str: + extension = filename.split('.')[-1].lower() + if extension in ['jpeg', 'jpg', 'png']: + return f"image/{extension}" + if extension == 'pdf': + return "application/pdf" + if extension in ['zip']: + return "application/zip" + if extension in ['doc', 'docx']: + return "application/msword" + if extension in ['xls', 'xlsx']: + return "application/vnd.ms-excel" + if extension in ['ppt', 'pptx']: + return "application/vnd.ms-powerpoint" + if extension in ['txt']: + return "text/plain" + return "application/octet-stream" diff --git a/API/app/infrastructure/project_members_service.py b/API/app/infrastructure/project_members_service.py new file mode 100644 index 0000000..06e3952 --- /dev/null +++ b/API/app/infrastructure/project_members_service.py @@ -0,0 +1,130 @@ +from typing import Optional + +from fastapi import HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.application.profiles_repository import ProfilesRepository +from app.application.project_members_repository import ProjectMembersRepository +from app.application.projects_repository import ProjectsRepository +from app.domain.entities.project_member import ProjectMemberEntity +from app.domain.models import ProjectMember + + +class ProjectMembersService: + def __init__(self, db: AsyncSession): + self.project_members_repository = ProjectMembersRepository(db) + self.projects_repository = ProjectsRepository(db) + self.profiles_repository = ProfilesRepository(db) + + async def get_project_members_by_project_id(self, project_id: int) -> list[ProjectMemberEntity]: + project_members = await self.project_members_repository.get_by_project_id(project_id) + return [ + self.model_to_entity(project_member) + for project_member in project_members + ] + + async def get_project_members_by_profile_id(self, profile_id: int) -> list[ProjectMemberEntity]: + project_members = await self.project_members_repository.get_by_profile_id(profile_id) + return [ + self.model_to_entity(project_member) + for project_member in project_members + ] + + async def create_list_project_members(self, project_id: int, project_members: list[ProjectMemberEntity]) -> \ + Optional[ + list[ProjectMemberEntity] + ]: + project = await self.projects_repository.get_by_id(project_id) + + if not project: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The project with this ID was not found', + ) + + project_members_models = [] + + for project_member in project_members: + if project_member.project_id != project_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The project ID from the request parameter and the project ID from the transmitted data do not match' + ) + + profile = await self.profiles_repository.get_by_id(project_member.profile_id) + + if not profile: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The profile with this ID was not found', + ) + + project_members_models.append( + self.entity_to_model(project_member) + ) + + await self.project_members_repository.create_list(project_members_models) + + return [ + self.model_to_entity(member) + for member in project_members_models + ] + + async def update_list_project_members( + self, + project_id: int, + project_members: list[ProjectMemberEntity] + ) -> Optional[list[ProjectMemberEntity]]: + project = await self.projects_repository.get_by_id(project_id) + if not project: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The project with this ID was not found', + ) + + old_members = await self.project_members_repository.get_by_project_id(project_id) + + if old_members: + await self.project_members_repository.delete_list_members(list(old_members)) + + return await self.create_list_project_members(project_id, project_members) + + async def delete_project_members_by_project_id(self, project_id: int) -> Optional[list[ProjectMemberEntity]]: + project = await self.projects_repository.get_by_id(project_id) + if not project: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The project with this ID was not found', + ) + + project_members = await self.project_members_repository.get_by_project_id(project_id) + + if project_members: + await self.project_members_repository.delete_list_members(list(project_members)) + + return [ + self.model_to_entity(project_member) + for project_member in project_members + ] + + @staticmethod + def model_to_entity(project: ProjectMember) -> ProjectMemberEntity: + return ProjectMemberEntity( + id=project.id, + description=project.description, + project_id=project.project_id, + profile_id=project.profile_id, + ) + + @staticmethod + def entity_to_model(project: ProjectMemberEntity) -> ProjectMember: + project_model = ProjectMember( + description=project.description, + project_id=project.project_id, + profile_id=project.profile_id, + ) + + if project.id: + project_model.id = project.id + + return project_model diff --git a/API/app/infrastructure/projects_service.py b/API/app/infrastructure/projects_service.py index b1e71ff..ba933fe 100644 --- a/API/app/infrastructure/projects_service.py +++ b/API/app/infrastructure/projects_service.py @@ -1,6 +1,73 @@ +from typing import Optional + +from fastapi import HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession +from app.application.projects_repository import ProjectsRepository +from app.domain.entities.project import ProjectEntity +from app.domain.models import Project -class ProjectsRepository: + +class ProjectsService: def __init__(self, db: AsyncSession): self.projects_repository = ProjectsRepository(db) + + async def get_all_projects(self) -> list[ProjectEntity]: + projects = await self.projects_repository.get_all() + return [ + self.model_to_entity(project) + for project in projects + ] + + async def create_project(self, project: ProjectEntity) -> Optional[ProjectEntity]: + project_model = self.entity_to_model(project) + + await self.projects_repository.create(project_model) + + return self.model_to_entity(project_model) + + async def update_project(self, project_id: int, project: ProjectEntity) -> Optional[ProjectEntity]: + project_model = await self.projects_repository.get_by_id(project_id) + + if not project_model: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") + + project_model.title = project.title + project_model.description = project.description + project_model.repository_url = project.repository_url + + await self.projects_repository.update(project_model) + + return self.model_to_entity(project_model) + + async def delete_project(self, project_id: int) -> Optional[ProjectEntity]: + project_model = await self.projects_repository.get_by_id(project_id) + + if not project_model: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") + + result = await self.projects_repository.delete(project_model) + + return self.model_to_entity(result) + + @staticmethod + def model_to_entity(project_model: Project) -> ProjectEntity: + return ProjectEntity( + id=project_model.id, + title=project_model.title, + description=project_model.description, + repository_url=project_model.repository_url, + ) + + @staticmethod + def entity_to_model(project_entity: ProjectEntity) -> Project: + project_model = Project( + title=project_entity.title, + description=project_entity.description, + repository_url=project_entity.repository_url, + ) + + if project_entity.id: + project_model.id = project_entity.id + + return project_model diff --git a/API/app/main.py b/API/app/main.py index 8d351b0..38a2a14 100644 --- a/API/app/main.py +++ b/API/app/main.py @@ -4,6 +4,9 @@ from fastapi.middleware.cors import CORSMiddleware from app.contollers.auth_router import router as auth_router from app.contollers.profile_photos_router import router as profile_photos_router from app.contollers.profiles_router import router as profiles_router +from app.contollers.project_files_router import router as project_files_router +from app.contollers.project_members_router import router as project_members_router +from app.contollers.projects_router import router as projects_router from app.contollers.register_router import router as register_router from app.contollers.teams_router import router as team_router from app.contollers.users_router import router as users_router @@ -22,8 +25,11 @@ def start_app(): ) api_app.include_router(auth_router, prefix=f'{settings.PREFIX}/auth', tags=['auth']) - api_app.include_router(profile_photos_router, prefix=f'{settings.PREFIX}/profile_photos', tags=['profile_photos_router']) + api_app.include_router(profile_photos_router, prefix=f'{settings.PREFIX}/profile_photos', tags=['profile_photos']) api_app.include_router(profiles_router, prefix=f'{settings.PREFIX}/profiles', tags=['profiles']) + api_app.include_router(project_files_router, prefix=f'{settings.PREFIX}/project_files', tags=['project_files']) + api_app.include_router(project_members_router, prefix=f'{settings.PREFIX}/project_members', tags=['project_members']) + api_app.include_router(projects_router, prefix=f'{settings.PREFIX}/projects', tags=['projects']) api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register']) api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams']) api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users'])