сделал слои для сущности team и разделение прав доступа

This commit is contained in:
Андрей Дувакин 2025-04-27 19:17:18 +05:00
parent bdb428a1cb
commit ac64bbe24c
9 changed files with 242 additions and 3 deletions

View File

@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Sequence
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@ -10,7 +10,28 @@ class TeamsRepository:
def __init__(self, db: AsyncSession):
self.db = db
async def get_all(self) -> Sequence[Team]:
stmt = select(Team)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_by_id(self, team_id: int) -> Optional[Team]:
stmt = select(Team).filter_by(id=team_id)
result = await self.db.execute(stmt)
return result.scalars().first()
async def create(self, team: Team) -> Team:
self.db.add(team)
await self.db.commit()
await self.db.refresh(team)
return team
async def update(self, team: Team) -> Team:
await self.db.merge(team)
await self.db.commit()
return team
async def delete(self, team: Team) -> Team:
await self.db.delete(team)
await self.db.commit()
return team

View File

@ -15,10 +15,22 @@ class UsersRepository:
result = await self.db.execute(stmt)
return result.scalars().first()
async def get_by_id_with_role(self, user_id: int) -> Optional[User]:
stmt = (
select(User)
.filter_by(id=user_id)
.options(
joinedload(User.profile).joinedload(Profile.role)
)
)
result = await self.db.execute(stmt)
return result.scalars().first()
async def get_by_login(self, login: str) -> Optional[User]:
stmt = (
select(User)
.filter_by(login=login)
.options(joinedload(User.profile))
)
result = await self.db.execute(stmt)
return result.scalars().first()

View File

@ -0,0 +1,71 @@
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.team import TeamEntity
from app.infrastructure.dependencies import get_current_user, require_admin
from app.infrastructure.teams_service import TeamsService
router = APIRouter()
@router.get(
'/',
response_model=list[TeamEntity],
summary='Get all teams',
description='Returns all teams',
)
async def create_team(
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
teams_service = TeamsService(db)
return await teams_service.get_all_teams()
@router.post(
'/',
response_model=Optional[TeamEntity],
summary='Create a new team',
description='Creates a new team',
)
async def create_team(
team: TeamEntity,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
teams_service = TeamsService(db)
return await teams_service.create_team(team)
@router.put(
'/{team_id}/',
response_model=Optional[TeamEntity],
summary='Update a team',
description='Updates a team',
)
async def create_team(
team_id: int,
team: TeamEntity,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
teams_service = TeamsService(db)
return await teams_service.update_team(team_id, team)
@router.delete(
'/{team_id}/',
response_model=Optional[TeamEntity],
summary='Delete a team',
description='Delete a team',
)
async def create_team(
team_id: int,
db: AsyncSession = Depends(get_db),
user=Depends(require_admin),
):
teams_service = TeamsService(db)
return await teams_service.delete_team(team_id)

View File

@ -139,7 +139,7 @@ def upgrade() -> None:
)
op.create_table('users',
sa.Column('login', sa.VARCHAR(length=150), nullable=False),
sa.Column('password', sa.VARCHAR(), nullable=False),
sa.Column('password', sa.String(), nullable=False),
sa.Column('profile_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),

View File

@ -0,0 +1,15 @@
from typing import Optional
from pydantic import BaseModel
from app.domain.entities.profile import ProfileEntity
class TeamEntity(BaseModel):
id: Optional[int] = None
title: str
description: Optional[str] = None
logo: Optional[str] = None
git_url: Optional[str] = None
profiles: Optional[list[ProfileEntity]] = None

View File

@ -0,0 +1,43 @@
import jwt
from fastapi import Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status
from app.application.users_repository import UsersRepository
from app.database.session import get_db
from app.domain.models.users import User
from app.settings import get_auth_data
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Security(security),
db: AsyncSession = Depends(get_db)
):
auth_data = get_auth_data()
try:
payload = jwt.decode(credentials.credentials, auth_data["secret_key"], algorithms=[auth_data["algorithm"]])
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user_id = payload.get("user_id")
if user_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = await UsersRepository(db).get_by_id_with_role(user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return user
def require_admin(user: User = Depends(get_current_user)):
if user.profile.role.title != "Администратор":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
return user

View File

@ -0,0 +1,75 @@
from typing import Optional
from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.application.teams_repository import TeamsRepository
from app.domain.entities.team import TeamEntity
from app.domain.models import Team
class TeamsService:
def __init__(self, db: AsyncSession):
self.teams_repository = TeamsRepository(db)
async def get_all_teams(self) -> list[TeamEntity]:
teams = await self.teams_repository.get_all()
return [
self.model_to_entity(team)
for team in teams
]
async def create_team(self, team: TeamEntity) -> Optional[TeamEntity]:
team_model = self.entity_to_model(team)
await self.teams_repository.create(team_model)
return self.model_to_entity(team_model)
async def update_team(self, team_id: int, team: TeamEntity) -> Optional[TeamEntity]:
team_model = await self.teams_repository.get_by_id(team_id)
if not team_model:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Team not found")
team_model.title = team.title
team_model.description = team_model.description
team_model.git_url = team_model.git_url
await self.teams_repository.update(team_model)
return self.model_to_entity(team_model)
async def delete_team(self, team_id: int) -> Optional[TeamEntity]:
team_model = await self.teams_repository.get_by_id(team_id)
if not team_model:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Team not found")
result = await self.teams_repository.delete(team_model)
return self.model_to_entity(result)
@staticmethod
def model_to_entity(team_model: Team) -> TeamEntity:
return TeamEntity(
id=team_model.id,
title=team_model.title,
description=team_model.description,
logo=team_model.logo,
git_url=team_model.git_url,
)
@staticmethod
def entity_to_model(team_entity: TeamEntity) -> Team:
team_model = Team(
title=team_entity.title,
description=team_entity.description,
logo=team_entity.logo,
git_url=team_entity.git_url,
)
if team_entity.id:
team_model.id = team_entity.id
return team_model

View File

@ -1,8 +1,9 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.contollers.register_controller import router as register_router
from app.contollers.register_router import router as register_router
from app.contollers.auth_router import router as auth_router
from app.contollers.teams_router import router as team_router
from app.settings import settings
@ -19,6 +20,7 @@ def start_app():
api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register'])
api_app.include_router(auth_router, prefix=f'{settings.PREFIX}/auth', tags=['auth'])
api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams'])
return api_app