Compare commits
4 Commits
ebf27d9ad0
...
673f20188e
| Author | SHA1 | Date | |
|---|---|---|---|
| 673f20188e | |||
| 9d1d050746 | |||
| 1ed1731432 | |||
| 8c7aa7358d |
32
API/app/application/project_files_repository.py
Normal file
32
API/app/application/project_files_repository.py
Normal file
@ -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
|
||||||
47
API/app/application/project_members_repository.py
Normal file
47
API/app/application/project_members_repository.py
Normal file
@ -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
|
||||||
69
API/app/contollers/project_files_router.py
Normal file
69
API/app/contollers/project_files_router.py
Normal file
@ -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)
|
||||||
86
API/app/contollers/project_members_router.py
Normal file
86
API/app/contollers/project_members_router.py
Normal file
@ -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."}
|
||||||
70
API/app/contollers/projects_router.py
Normal file
70
API/app/contollers/projects_router.py
Normal file
@ -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)
|
||||||
@ -34,7 +34,7 @@ async def get_all_teams(
|
|||||||
async def create_team(
|
async def create_team(
|
||||||
team: TeamEntity,
|
team: TeamEntity,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(require_admin),
|
||||||
):
|
):
|
||||||
teams_service = TeamsService(db)
|
teams_service = TeamsService(db)
|
||||||
return await teams_service.create_team(team)
|
return await teams_service.create_team(team)
|
||||||
|
|||||||
@ -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 ###
|
||||||
@ -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 ###
|
||||||
@ -5,5 +5,6 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
class ProjectEntity(BaseModel):
|
class ProjectEntity(BaseModel):
|
||||||
id: Optional[int] = None
|
id: Optional[int] = None
|
||||||
|
title: str
|
||||||
description: str
|
description: str
|
||||||
repository_url: Optional[str] = None
|
repository_url: Optional[str] = None
|
||||||
8
API/app/domain/entities/project_file.py
Normal file
8
API/app/domain/entities/project_file.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectFileEntity(BaseModel):
|
||||||
|
id: int
|
||||||
|
filename: str
|
||||||
|
file_path: str
|
||||||
|
project_id: int
|
||||||
10
API/app/domain/entities/project_member.py
Normal file
10
API/app/domain/entities/project_member.py
Normal file
@ -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
|
||||||
@ -7,6 +7,7 @@ from app.domain.models.base import AdvancedBaseModel
|
|||||||
class ProjectFile(AdvancedBaseModel):
|
class ProjectFile(AdvancedBaseModel):
|
||||||
__tablename__ = 'project_files'
|
__tablename__ = 'project_files'
|
||||||
|
|
||||||
|
filename = Column(String, nullable=False)
|
||||||
file_path = Column(String, unique=True, nullable=False)
|
file_path = Column(String, unique=True, nullable=False)
|
||||||
|
|
||||||
project_id = Column(Integer, ForeignKey('projects.id'), nullable=False)
|
project_id = Column(Integer, ForeignKey('projects.id'), nullable=False)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from app.domain.models.base import AdvancedBaseModel
|
|||||||
class Project(AdvancedBaseModel):
|
class Project(AdvancedBaseModel):
|
||||||
__tablename__ = 'projects'
|
__tablename__ = 'projects'
|
||||||
|
|
||||||
|
title = Column(VARCHAR(150), nullable=False)
|
||||||
description = Column(VARCHAR(150))
|
description = Column(VARCHAR(150))
|
||||||
repository_url = Column(String, nullable=False)
|
repository_url = Column(String, nullable=False)
|
||||||
|
|
||||||
|
|||||||
134
API/app/infrastructure/project_files_service.py
Normal file
134
API/app/infrastructure/project_files_service.py
Normal file
@ -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"
|
||||||
130
API/app/infrastructure/project_members_service.py
Normal file
130
API/app/infrastructure/project_members_service.py
Normal file
@ -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
|
||||||
@ -1,6 +1,73 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import HTTPException, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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):
|
def __init__(self, db: AsyncSession):
|
||||||
self.projects_repository = ProjectsRepository(db)
|
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
|
||||||
|
|||||||
@ -4,6 +4,9 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from app.contollers.auth_router import router as auth_router
|
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.profile_photos_router import router as profile_photos_router
|
||||||
from app.contollers.profiles_router import router as profiles_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.register_router import router as register_router
|
||||||
from app.contollers.teams_router import router as team_router
|
from app.contollers.teams_router import router as team_router
|
||||||
from app.contollers.users_router import router as users_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(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(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(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(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams'])
|
||||||
api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users'])
|
api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users'])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user