diff --git a/api/v1/need_requests.py b/api/v1/need_requests.py index 85727fc..fffc1a1 100644 --- a/api/v1/need_requests.py +++ b/api/v1/need_requests.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer from services.need_request_service import NeedRequestService from models.schemas import NeedRequestCreate, NeedRequestUpdate from config.database import get_db @@ -6,17 +7,22 @@ from services.auth_service import AuthService router = APIRouter() +# Configuration pour OAuth2 +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") @router.post("/", status_code=status.HTTP_201_CREATED) async def request_need(need: NeedRequestCreate, db=Depends(get_db)): return await NeedRequestService.create_need(need, db) +@router.get("/", status_code=status.HTTP_200_OK) +async def get_all_reports(db=Depends(get_db)): + return await NeedRequestService.get_all_needs(db) @router.get("/{need_id}", status_code=status.HTTP_200_OK) async def get_need(need_id: int, db=Depends(get_db)): - need = await NeedRequestService.get_need_by_id(need_id, db) + need = await NeedRequestService.get_need(need_id, db) if not need: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Need request not found") return need @@ -26,16 +32,16 @@ async def get_need(need_id: int, db=Depends(get_db)): async def update_need( need_id: int, need_update: NeedRequestUpdate, + token: str = Depends(oauth2_scheme), db=Depends(get_db), - current_user=Depends(AuthService.get_current_user), ): - need = await NeedRequestService.get_need_by_id(need_id, db) + need = await NeedRequestService.get_need(need_id, db) if not need: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Need request not found") # V�rifie si l'utilisateur est l'auteur ou un administrateur - if need.requester_email != current_user.email and not await AuthService.admin_required(db=db): + if NeedRequestService.verify_requester_or_admin(need_id, token, db): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You do not have permission to update this need request", @@ -47,16 +53,17 @@ async def update_need( @router.delete("/{need_id}", status_code=status.HTTP_200_OK) async def delete_need( need_id: int, + token: str = Depends(oauth2_scheme), db=Depends(get_db), - current_user=Depends(AuthService.get_current_user), + ): - need = await NeedRequestService.get_need_by_id(need_id, db) + need = await NeedRequestService.get_need(need_id, db) if not need: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Need request not found") # V�rifie si l'utilisateur est l'auteur ou un administrateur - if need.requester_email != current_user.email and not await AuthService.admin_required(db=db): + if NeedRequestService.verify_requester_or_admin(need_id, token, db): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You do not have permission to delete this need request", diff --git a/api/v1/person_reports.py b/api/v1/person_reports.py index c0e5c9f..0c596ff 100644 --- a/api/v1/person_reports.py +++ b/api/v1/person_reports.py @@ -1,8 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException from services.person_report_service import PersonReportService -from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse +from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse, UserResponse from config.database import get_db from typing import Optional +from services.auth_service import AuthService router = APIRouter() @@ -20,4 +21,8 @@ async def get_report(report_id: int, db=Depends(get_db)): @router.get("/", response_model=list[PersonReportResponse]) async def list_reports(status: Optional[str] = None, db=Depends(get_db)): - return await PersonReportService.list_reports(status, db) \ No newline at end of file + return await PersonReportService.list_reports(status, db) + +@router.delete("/{report_id}", status_code=204) +async def delete_report(report_id: int, db=Depends(get_db), current_user: UserResponse = Depends(AuthService.get_current_user)): + return await PersonReportService.delete_report(report_id, db, current_user) \ No newline at end of file diff --git a/api/v1/reports.py b/api/v1/reports.py index 35aae14..eb5b7c3 100644 --- a/api/v1/reports.py +++ b/api/v1/reports.py @@ -1 +1,30 @@ -from fastapi import APIRouter, Depends, HTTPException, status from services.report_service import ReportService from models.schemas import UserReport, UserReportUpdate from config.database import get_db from services.auth_service import AuthService admin_required = AuthService.admin_required router = APIRouter() @router.post("/", status_code=status.HTTP_201_CREATED) async def report_user(report: UserReport, db=Depends(get_db)): return await ReportService.create_report(report, db) @router.get("/{report_id}", status_code=status.HTTP_200_OK) async def get_report(report_id: int, db=Depends(get_db), current_user=Depends(admin_required)): return await ReportService.get_report_by_id(report_id, db) @router.get("/", status_code=status.HTTP_200_OK) async def get_all_reports(db=Depends(get_db), current_user=Depends(admin_required)): return await ReportService.get_all_reports(db) @router.put("/{report_id}", status_code=status.HTTP_200_OK) async def update_report(report_id: int, report_update: UserReportUpdate, db=Depends(get_db), current_user=Depends(admin_required)): return await ReportService.update_report(report_id, report_update, db) @router.delete("/{report_id}", status_code=status.HTTP_200_OK) async def delete_report(report_id: int, db=Depends(get_db), current_user=Depends(admin_required)): return await ReportService.delete_report(report_id, db) \ No newline at end of file +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from services.report_service import ReportService +from models.schemas import UserReport, UserReportUpdate +from config.database import get_db +from services.auth_service import AuthService + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") + +router = APIRouter() + +@router.post("/", status_code=status.HTTP_201_CREATED) +async def report_user(report: UserReport, db=Depends(get_db)): + return await ReportService.create_report(report, db) + +@router.get("/{report_id}", status_code=status.HTTP_200_OK) +async def get_report(report_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)): + return await ReportService.get_report_by_id(report_id, db, token) + +@router.get("/", status_code=status.HTTP_200_OK) +async def get_all_reports(db=Depends(get_db), token: str = Depends(oauth2_scheme)): + return await ReportService.get_all_reports(db, token) + +@router.put("/{report_id}", status_code=status.HTTP_200_OK) +async def update_report(report_id: int, report_update: UserReportUpdate, db=Depends(get_db), token: str = Depends(oauth2_scheme)): + return await ReportService.update_report(report_id, report_update, db, token) + +@router.delete("/{report_id}", status_code=status.HTTP_200_OK) +async def delete_report(report_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)): + return await ReportService.delete_report(report_id, db, token) diff --git a/api/v1/roles.py b/api/v1/roles.py index 9184fa8..aa08ce0 100644 --- a/api/v1/roles.py +++ b/api/v1/roles.py @@ -11,11 +11,11 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") @router.post("/", status_code=201) -async def create_role(name: str, permissions: List[str], db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)): +async def create_role(name: str, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)): """ Créer un nouveau rôle avec des permissions associées (réservé aux administrateurs). """ - return await RoleService.create_role(name, permissions, db, token) + return await RoleService.create_role(name, db, token) @router.get("/{role_id}", response_model=Role) diff --git a/models/schemas.py b/models/schemas.py index fc7b5e0..e189b0d 100644 --- a/models/schemas.py +++ b/models/schemas.py @@ -2,14 +2,20 @@ from pydantic import BaseModel, EmailStr, model_validator from datetime import datetime from typing import List, Optional -class RoleBase(BaseModel): - name: str - -class RoleCreate(RoleBase): - permissions: List[str] - -class RoleResponse(RoleBase): +class Role(BaseModel): id: int + name: str + permissions: List[str] + + class Config: + orm_mode = True + +class UserUpdateRole(BaseModel): + email: EmailStr + new_role: str + +class RoleResponse(BaseModel): + name: str permissions: List[str] class Config: @@ -24,7 +30,15 @@ class UserBase(BaseModel): class UserCreate(UserBase): password: str - role: str + role: Role + + # Validation au niveau du modèle + @model_validator(mode="before") + def convert_role_to_dict(cls, values): + role = values.get("role") + if isinstance(role, str): + values["role"] = {"id": 0, "name": role, "permissions": []} + return values class UserResponse(BaseModel): email: EmailStr @@ -52,18 +66,6 @@ class Permission(BaseModel): id: int name: str -class Role(BaseModel): - id: int - name: str - permissions: List[str] - - class Config: - orm_mode = True - -class UserUpdateRole(BaseModel): - email: EmailStr - new_role: str - class UserBlockBan(BaseModel): email: EmailStr @@ -112,7 +114,7 @@ class UserReport(BaseModel): status: Optional[str] = "pending" class UserReportUpdate(BaseModel): - reason: Optional[str] = None + #reason: Optional[str] = None status: Optional[str] = None # Exemples : "pending", "resolved" class TechnicalIssue(BaseModel): diff --git a/services/auth_service.py b/services/auth_service.py index 067a93a..d9a7c92 100644 --- a/services/auth_service.py +++ b/services/auth_service.py @@ -165,7 +165,7 @@ class AuthService: query = users_table.select().where(users_table.c.email == token_data.email) result = await db.execute(query) - user = result.fetchone() + user = result.mappings().first() if user is None: raise credentials_exception @@ -199,7 +199,7 @@ class AuthService: query = select(users_table).where(users_table.c.email == token_data.email) result = await db.execute(query) - current_user = result.fetchone() + current_user = result.mappings().first() if current_user is None: raise credentials_exception @@ -235,7 +235,7 @@ class AuthService: # Récupérer les informations mises à jour updated_user_query = select(users_table).where(users_table.c.id == user_id) result = await db.execute(updated_user_query) - updated_user = result.fetchone() + updated_user = result.mappings().first() return UserResponse(**updated_user) @@ -243,4 +243,4 @@ class AuthService: async def get_user_by_email(email: str, db: AsyncSession): query = select(users_table).where(users_table.c.email == email) result = await db.execute(query) - return result.fetchone() \ No newline at end of file + return result.mappings().first() \ No newline at end of file diff --git a/services/need_request_service.py b/services/need_request_service.py index 54b2b90..b165429 100644 --- a/services/need_request_service.py +++ b/services/need_request_service.py @@ -3,7 +3,7 @@ from models.schemas import NeedRequestCreate from config.database import get_db from models.db import need_requests_table, users_table from models.schemas import TokenData -from fastapi import HTTPException, Depends, status +from fastapi import HTTPException, Depends, logger, status from fastapi.security import OAuth2PasswordBearer from config.settings import settings from jose import jwt, JWTError @@ -29,7 +29,7 @@ class NeedRequestService: result = await db.execute(query) await db.commit() need_id = result.inserted_primary_key[0] - return {"id": need_id, **need.dict()} + return {"id": need_id, **need.model_dump()} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not create need request: {str(e)}") @@ -38,7 +38,7 @@ class NeedRequestService: async def get_need(need_id: int, db): query = select(need_requests_table).where(need_requests_table.c.id == need_id) result = await db.execute(query) - need = result.fetchone() + need = result.mappings().fetchone() if need is None: raise HTTPException(status_code=404, detail="Need request not found") return dict(need) @@ -47,7 +47,7 @@ class NeedRequestService: async def get_all_needs(db): query = select(need_requests_table).where(need_requests_table.c.deleted == None) result = await db.execute(query) - needs = result.fetchall() + needs = result.mappings().all() return [dict(need) for need in needs] @staticmethod @@ -78,7 +78,7 @@ class NeedRequestService: query = ( update(need_requests_table) .where(need_requests_table.c.id == need_id) - .values(deleted=datetime.utcnow()) + .values(deleted=datetime.now(datetime.timezone.utc())) ) try: result = await db.execute(query) @@ -108,20 +108,22 @@ class NeedRequestService: # R�cup�re l'utilisateur depuis la base de donn�es user_query = select(users_table).where(users_table.c.email == token_data.email) + result = await db.execute(user_query) - user = result.fetchone() + user = result.mappings().fetchone() + logger.info("user loooooooooooooooooooo: " +user) if user is None: raise credentials_exception # R�cup�re la demande de besoin need_query = select(need_requests_table).where(need_requests_table.c.id == need_id) result = await db.execute(need_query) - need = result.fetchone() + need = result.mappings().fetchone() if need is None: raise HTTPException(status_code=404, detail="Need request not found") # V�rifie si l'utilisateur est l'auteur ou un administrateur if need["requester_email"] != user["email"] and user["role"] != "admin": - raise credentials_exception + return False - return user + return True diff --git a/services/person_report_service.py b/services/person_report_service.py index dafc1b8..57c27d6 100644 --- a/services/person_report_service.py +++ b/services/person_report_service.py @@ -1,4 +1,4 @@ -from sqlalchemy import select, update +from sqlalchemy import delete, select, update from fastapi import HTTPException from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse from config.database import get_db @@ -13,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") class PersonReportService: @staticmethod async def create_report(report: PersonReportCreate, db): - query = person_reports_table.insert().values(**report.dict()) + query = person_reports_table.insert().values(**report.model_dump()) try: result = await db.execute(query) await db.commit() @@ -28,7 +28,7 @@ class PersonReportService: query = ( person_reports_table.update() .where(person_reports_table.c.id == report_id) - .values(**report.dict(exclude_unset=True)) + .values(**report.model_dump(exclude_unset=True)) ) try: await db.execute(query) @@ -54,4 +54,21 @@ class PersonReportService: query = query.where(person_reports_table.c.status == status) result = await db.execute(query) reports = result.fetchall() - return [PersonReportResponse(**report) for report in reports] \ No newline at end of file + return [PersonReportResponse(**report) for report in reports] + + @staticmethod + async def delete_report(report_id: int, db, current_user): + # Vérifier les droits de l'utilisateur + if not current_user.role.permissions or "delete_reports" not in current_user.role.permissions: + raise HTTPException(status_code=403, detail="Permission denied") + + query = delete(person_reports_table).where(person_reports_table.c.id == report_id) + try: + result = await db.execute(query) + if result.rowcount == 0: + raise HTTPException(status_code=404, detail="Report not found") + await db.commit() + return {"detail": "Report deleted successfully"} + except Exception as e: + await db.rollback() + raise HTTPException(status_code=500, detail=f"Could not delete report: {str(e)}") \ No newline at end of file diff --git a/services/report_service.py b/services/report_service.py index f77f9ec..84766ef 100644 --- a/services/report_service.py +++ b/services/report_service.py @@ -1 +1,87 @@ -from sqlalchemy import insert, update, select, delete from models.schemas import UserReport, UserReportUpdate from config.database import get_db from models.db import user_reports_table from fastapi import HTTPException, status class ReportService: @staticmethod async def create_report(report: UserReport, db): query = insert(user_reports_table).values( reporter_id=report.reporter_id, reported_user_id=report.reported_user_id, reason=report.reason, status="pending" # Par défaut, le statut est "pending" ) try: result = await db.execute(query) await db.commit() report_id = result.inserted_primary_key[0] return {"id": report_id, **report.dict()} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not create user report: {str(e)}") @staticmethod async def get_report_by_id(report_id: int, db): query = select(user_reports_table).where(user_reports_table.c.id == report_id) result = await db.execute(query) report = result.fetchone() if not report: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") return dict(report) @staticmethod async def get_all_reports(db): query = select(user_reports_table) result = await db.execute(query) reports = result.fetchall() return [dict(report) for report in reports] @staticmethod async def update_report(report_id: int, report_update: UserReportUpdate, db): query = ( update(user_reports_table) .where(user_reports_table.c.id == report_id) .values(**report_update.dict(exclude_unset=True)) .returning(user_reports_table.c.id) ) try: result = await db.execute(query) await db.commit() updated_id = result.fetchone() if not updated_id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") return {"id": updated_id[0], **report_update.dict()} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not update report: {str(e)}") @staticmethod async def delete_report(report_id: int, db): query = delete(user_reports_table).where(user_reports_table.c.id == report_id) try: result = await db.execute(query) await db.commit() if result.rowcount == 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") return {"detail": "Report deleted successfully"} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not delete report: {str(e)}") \ No newline at end of file +from fastapi.security import OAuth2PasswordBearer +from sqlalchemy import insert, update, select, delete +from models.schemas import UserReport, UserReportUpdate +from config.database import get_db +from models.db import user_reports_table +from fastapi import Depends, HTTPException, status + +from services.auth_service import AuthService + +# Configuration pour OAuth2 +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") + +class ReportService: + @staticmethod + async def create_report(report: UserReport, db): + query = insert(user_reports_table).values( + reporter_id=report.reporter_id, + reported_user_id=report.reported_user_id, + reason=report.reason, + status="pending" # Par défaut, le statut est "pending" + ) + try: + result = await db.execute(query) + await db.commit() + report_id = result.inserted_primary_key[0] + return {"id": report_id, **report.model_dump()} + except Exception as e: + await db.rollback() + raise HTTPException(status_code=500, detail=f"Could not create user report: {str(e)}") + + @staticmethod + async def get_report_by_id(report_id: int, db, token: str = Depends(oauth2_scheme)): + await AuthService.admin_required(token, db) + query = select(user_reports_table).where(user_reports_table.c.id == report_id) + result = await db.execute(query) + report = result.mappings().fetchone() + if not report: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") + return dict(report) + + @staticmethod + async def get_all_reports(db, token: str = Depends(oauth2_scheme)): + + await AuthService.admin_required(token, db) + query = select(user_reports_table) + result = await db.execute(query) + reports = result.mappings().all() + return [dict(report) for report in reports] + + @staticmethod + async def update_report(report_id: int, report_update: UserReportUpdate, db, token: str = Depends(oauth2_scheme)): + await AuthService.admin_required(token, db) + query = ( + update(user_reports_table) + .where(user_reports_table.c.id == report_id) + .values(**report_update.model_dump(exclude_unset=True)) + ) + try: + await db.execute(query) + await db.commit() + + # Récupérer le rapport mis à jour + select_query = select(user_reports_table).where(user_reports_table.c.id == report_id) + result = await db.execute(select_query) + updated_report = result.mappings().fetchone() + + if not updated_report: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") + + return dict(updated_report) + except Exception as e: + await db.rollback() + raise HTTPException(status_code=500, detail=f"Could not update report: {str(e)}") + + @staticmethod + async def delete_report(report_id: int, db, token: str): + await AuthService.admin_required(token, db) + query = delete(user_reports_table).where(user_reports_table.c.id == report_id) + try: + result = await db.execute(query) + await db.commit() + if result.rowcount == 0: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Report not found") + return {"detail": "Report deleted successfully"} + except Exception as e: + await db.rollback() + raise HTTPException(status_code=500, detail=f"Could not delete report: {str(e)}") diff --git a/services/role_service.py b/services/role_service.py index 4c29d56..68092bf 100644 --- a/services/role_service.py +++ b/services/role_service.py @@ -7,6 +7,7 @@ from config.database import get_db from config.settings import settings from jose import jwt, JWTError from sqlalchemy.ext.asyncio import AsyncSession +from services.auth_service import AuthService oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token") @@ -36,39 +37,18 @@ class RoleService: return roles_with_permissions @staticmethod - async def create_role(name: str, permissions: list, db, token: str): + async def create_role(name: str, db, token: str): """ Crée un nouveau rôle avec les permissions spécifiées (réservé aux administrateurs). """ - await RoleService.admin_required(token, db) - - # Vérifier que les permissions sont valides - valid_permissions = settings.available_permissions - filtered_permissions = [p for p in permissions if p in valid_permissions] - if not filtered_permissions: - raise HTTPException(status_code=400, detail="No valid permissions provided.") - + await AuthService.admin_required(token, db) # Insérer le rôle query = insert(roles_table).values(name=name) try: result = await db.execute(query) await db.commit() role_id = result.inserted_primary_key[0] - - # Associer les permissions valides au rôle - for permission in filtered_permissions: - permission_query = select(permissions_table).where(permissions_table.c.name == permission) - permission_result = await db.execute(permission_query) - permission_record = permission_result.mappings().fetchone() - - if permission_record: - insert_query = insert(role_permissions_table).values( - role_id=role_id, permission_id=permission_record['id'] - ) - await db.execute(insert_query) - - await db.commit() - return {"id": role_id, "name": name, "permissions": filtered_permissions} + return {"id": role_id, "name": name} except Exception as e: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not create role: {str(e)}") @@ -78,7 +58,7 @@ class RoleService: """ Met à jour un rôle par son ID (réservé aux administrateurs). """ - await RoleService.admin_required(token, db) + await AuthService.admin_required(token, db) if "permissions" in data: # Supprimer les anciennes permissions delete_query = delete(role_permissions_table).where(role_permissions_table.c.role_id == role_id) @@ -123,7 +103,7 @@ class RoleService: """ Supprime un rôle par son ID (réservé aux administrateurs). """ - await RoleService.admin_required(token, db) + await AuthService.admin_required(token, db) query = delete(roles_table).where(roles_table.c.id == role_id) try: result = await db.execute(query) @@ -135,38 +115,6 @@ class RoleService: await db.rollback() raise HTTPException(status_code=500, detail=f"Could not delete role: {str(e)}") - @staticmethod - async def admin_required(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)): - """ - Vérifie si l'utilisateur actuel est un administrateur. - """ - credentials_exception = HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="You do not have permission to perform this action.", - ) - try: - # Décodage du token JWT - payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm]) - email: str = payload.get("sub") - if email is None: - raise credentials_exception - token_data = TokenData(email=email) - except JWTError: - raise credentials_exception - - # Récupère l'utilisateur depuis la base de données - user_query = select(users_table).where(users_table.c.email == token_data.email) - result = await db.execute(user_query) - user = result.fetchone() - if user is None: - raise credentials_exception - - # Vérifie si l'utilisateur a le rôle d'administrateur - if user["role"] != "admin": - raise credentials_exception - - return user - @staticmethod async def get_all_permissions(db: AsyncSession): """