corrections and improvements

main
Anaz 2025-01-20 21:40:25 +04:00
parent 0e041336a4
commit bac9b4ac82
19 changed files with 321 additions and 151 deletions

View File

@ -3,7 +3,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from fastapi import APIRouter, Depends, HTTPException, status, Body
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy import update, select
from services.auth_service import AuthService
from models.schemas import Token, UserCreate, UserResponse
@ -17,6 +17,7 @@ from utils.logging import logger
from utils.security import verify_password, get_password_hash, pwd_context
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/signup", response_model=UserResponse, status_code=201, summary="User Signup")
async def signup(user: UserCreate, db=Depends(get_db)):
@ -42,8 +43,8 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/me", response_model=UserResponse, summary="Get current user")
async def read_users_me(current_user: UserResponse = Depends(AuthService.get_current_user)):
return current_user
async def read_users_me(token:str = Depends(oauth2_scheme) , db=Depends(get_db)):
return await AuthService.get_current_user(token, db)
@router.post("/reset-password")
async def reset_password(token: str = Body(...), new_password: str = Body(...), db: AsyncSession = Depends(get_db)):

View File

@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.security import OAuth2PasswordBearer
from services.person_report_service import PersonReportService
from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse, UserResponse
from config.database import get_db
@ -6,10 +7,11 @@ from typing import Optional
from services.auth_service import AuthService
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", response_model=PersonReportResponse, status_code=201)
async def create_report(report: PersonReportCreate, db=Depends(get_db)):
return await PersonReportService.create_report(report, db)
async def create_report(report: PersonReportCreate, image_file: UploadFile = File(None), db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await PersonReportService.create_report(report, db, image_file, token)
@router.put("/{report_id}", response_model=PersonReportResponse)
async def update_report(report_id: int, report: PersonReportUpdate, db=Depends(get_db)):

View File

@ -1,14 +1,15 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from services.points_of_interest_service import PointsOfInterestService
from models.schemas import PointOfInterest
from models.schemas import CreatePointOfInterest, UpdatePointOfInterest
from config.database import get_db
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", status_code=201)
async def create_point(point: PointOfInterest, db=Depends(get_db)):
return await PointsOfInterestService.create_point_of_interest(point, db)
async def create_point(point: CreatePointOfInterest, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await PointsOfInterestService.create_point_of_interest(point, db, token)
@router.get("/{point_id}")
@ -22,10 +23,10 @@ async def get_all_points(db=Depends(get_db)):
@router.put("/{point_id}")
async def update_point(point_id: int, data: dict, db=Depends(get_db)):
return await PointsOfInterestService.update_point_of_interest(point_id, data, db)
async def update_point(point_id: int, point: UpdatePointOfInterest, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await PointsOfInterestService.update_point_of_interest(point_id, point, db, token)
@router.delete("/{point_id}")
async def delete_point(point_id: int, db=Depends(get_db)):
return await PointsOfInterestService.delete_point_of_interest(point_id, db)
async def delete_point(point_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await PointsOfInterestService.delete_point_of_interest(point_id, db, token)

View File

@ -1,14 +1,15 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from services.shelter_service import ShelterService
from models.schemas import Shelter
from models.schemas import CreateShelter, UpdateShelter
from config.database import get_db
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", status_code=201)
async def create_shelter(shelter: Shelter, db=Depends(get_db)):
return await ShelterService.create_shelter(shelter, db)
async def create_shelter(shelter: CreateShelter, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await ShelterService.create_shelter(shelter, db, token)
@router.get("/{shelter_id}")
@ -22,10 +23,10 @@ async def get_all_shelters(db=Depends(get_db)):
@router.put("/{shelter_id}")
async def update_shelter(shelter_id: int, data: dict, db=Depends(get_db)):
return await ShelterService.update_shelter(shelter_id, data, db)
async def update_shelter(shelter_id: int, shelter: UpdateShelter, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await ShelterService.update_shelter(shelter_id, shelter, db, token)
@router.delete("/{shelter_id}")
async def delete_shelter(shelter_id: int, db=Depends(get_db)):
return await ShelterService.delete_shelter(shelter_id, db)
async def delete_shelter(shelter_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await ShelterService.delete_shelter(shelter_id, db, token)

View File

@ -1,26 +1,29 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from services.auth_service import AuthService
from services.technical_issue_service import TechnicalIssueService
from models.schemas import TechnicalIssue, UpdateTechnicalIssue
from models.schemas import CreateTechnicalIssue, UpdateTechnicalIssue
from config.database import get_db
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", status_code=201)
async def create_issue(issue: TechnicalIssue, db=Depends(get_db)):
return await TechnicalIssueService.create_issue(issue, db)
async def create_issue(issue: CreateTechnicalIssue, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await TechnicalIssueService.create_issue(issue, db, token)
@router.get("/{issue_id}")
async def get_issue(issue_id: int, db=Depends(get_db)):
return await TechnicalIssueService.get_issue(issue_id, db)
async def get_issue(issue_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await TechnicalIssueService.get_issue(issue_id, db, token)
@router.get("/")
async def get_all_issues(db=Depends(get_db)):
return await TechnicalIssueService.get_all_issues(db)
async def get_all_issues(db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await TechnicalIssueService.get_all_issues(db, token)
@router.put("/{issue_id}")
async def update_issue(issue_id: int, issue_data: UpdateTechnicalIssue, db=Depends(get_db)):
return await TechnicalIssueService.update_issue(issue_id, issue_data, db)
async def update_issue(issue_id: int, issue_data: UpdateTechnicalIssue, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await TechnicalIssueService.update_issue(issue_id, issue_data, db, token)
@router.delete("/{issue_id}")
async def delete_issue(issue_id: int, db=Depends(get_db)):
return await TechnicalIssueService.delete_issue(issue_id, db)
async def delete_issue(issue_id: int, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await TechnicalIssueService.delete_issue(issue_id, db, token)

View File

@ -1,28 +1,16 @@
# app/api/v1/upload.py
from fastapi import APIRouter, UploadFile, File, HTTPException
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
from fastapi.security import OAuth2PasswordBearer
from config.database import get_db
from services.auth_service import AuthService
from services.upload_service import UploadService
import uuid
from utils.logging import logger
router = APIRouter()
# Instancier UploadService avec le nom de ton bucket S3
upload_service = UploadService(bucket_name="ton-nom-de-bucket")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/")
async def upload_file(file: UploadFile = File(...)):
# Générer un nom de fichier unique
file_extension = file.filename.split(".")[-1] # Récupérer l'extension du fichier
unique_filename = f"{uuid.uuid4()}.{file_extension}" # Générer un nom unique
try:
# Lire le contenu du fichier de manière asynchron
file_content = await file.read()
# Uploader le fichier sur S3
file_url = upload_service.upload_file(file_content, unique_filename)
# Retourner l'URL du fichier uploadé
return {"file_url": file_url}
except Exception as e:
# Gérer les erreurs
raise HTTPException(status_code=500, detail=str(e))
async def upload_file(file: UploadFile, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
user = await AuthService.get_current_user(token, db)
logger.info(f"User {user} is uploading a file")
return await UploadService.upload_image_to_s3(file, user["id"])

View File

@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from services.user_service import UserService
from models.schemas import UserUpdateRole, UserBlockBan, UserResponse
@ -7,27 +8,28 @@ from services.auth_service import AuthService
from typing import Optional
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.get("/", response_model=list[UserResponse])
async def list_users(status: Optional[str] = None, db=Depends(get_db)):
return await UserService.list_users(status, db)
async def list_users(status: Optional[str] = None, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.list_users(token, status, db)
@router.patch("/role", status_code=200, dependencies=[Depends(AuthService.admin_required)])
async def change_role(user_update: UserUpdateRole, db: AsyncSession = Depends(get_db)):
return await UserService.change_user_role(user_update, db)
@router.patch("/role", status_code=200)
async def change_role(user_update: UserUpdateRole, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.change_user_role(user_update, db, token)
@router.post("/block", status_code=200, dependencies=[Depends(AuthService.admin_required)])
async def block_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)):
return await UserService.block_user(user_action, db)
@router.post("/block", status_code=200)
async def block_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.block_user(user_action, db, token)
@router.post("/ban", status_code=200, dependencies=[Depends(AuthService.admin_required)])
async def ban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)):
return await UserService.ban_user(user_action, db)
@router.post("/ban", status_code=200)
async def ban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.ban_user(user_action, db, token)
@router.post("/unblock", status_code=200, dependencies=[Depends(AuthService.admin_required)])
async def unblock_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)):
return await UserService.unblock_user(user_action, db)
@router.post("/unblock", status_code=200)
async def unblock_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.unblock_user(user_action, db, token)
@router.post("/unban", status_code=200, dependencies=[Depends(AuthService.admin_required)])
async def unban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)):
return await UserService.unban_user(user_action, db)
@router.post("/unban", status_code=200)
async def unban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.unban_user(user_action, db, token)

View File

@ -1,5 +1,5 @@
from sqlalchemy import Table, Column, Integer, String, DateTime, Boolean, MetaData, ForeignKey, Text
from datetime import datetime
from datetime import datetime, timezone
metadata = MetaData()
@ -100,6 +100,8 @@ shelters_table = Table(
Column("description", Text),
Column("status", String(50), default="available"),
Column("contact_person", String(255)),
Column('contact_email', String(255)),
Column('contact_phone', String(20)),
Column("gps_coordinates", String(100), nullable=False),
Column("added_by", Integer, ForeignKey("users.id"), nullable=False)
)
@ -115,6 +117,6 @@ person_reports_table = Table(
Column("gps_coordinates", String(100)),
Column("photo_url", String(255)),
Column("reporter_email", String(255), ForeignKey("users.email"), nullable=False),
Column("created_at", DateTime, default=datetime.utcnow),
Column("updated_at", DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
Column("created_at", DateTime, default=datetime.now(timezone.utc)),
Column("updated_at", DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
)

View File

@ -122,9 +122,12 @@ class TechnicalIssue(BaseModel):
description: str
status: str
class CreateTechnicalIssue(BaseModel):
description: str
status: str
class UpdateTechnicalIssue(BaseModel):
status: Optional[str] = None
description: Optional[str] = None
class PersonReportBase(BaseModel):
full_name: str
@ -135,8 +138,12 @@ class PersonReportBase(BaseModel):
photo_url: Optional[str] = None
reporter_email: str
class PersonReportCreate(PersonReportBase):
pass
class PersonReportCreate(BaseModel):
full_name: str
date_of_birth: datetime
status: str
location: Optional[str] = None
gps_coordinates: Optional[str] = None
class PersonReportUpdate(BaseModel):
status: Optional[str] = None
@ -159,6 +166,19 @@ class PointOfInterest(BaseModel):
gps_coordinates: str
added_by: int # ID de l'utilisateur qui a ajouté ce point
class CreatePointOfInterest(BaseModel):
label: str
description: Optional[str] = None
icon: Optional[str] = None # URL de l'icône
organization: Optional[str] = None
gps_coordinates: str
class UpdatePointOfInterest(BaseModel):
description: Optional[str] = None
icon: Optional[str] = None # URL de l'icône
organization: Optional[str] = None
gps_coordinates: Optional[str] = None
class Shelter(BaseModel):
label: str
description: Optional[str] = None
@ -167,6 +187,24 @@ class Shelter(BaseModel):
gps_coordinates: str
added_by: int # ID de l'utilisateur qui a ajouté cet abri
class CreateShelter(BaseModel):
label: str
description: Optional[str] = None
status: str # "available", "full", "closed"
contact_person: Optional[str] = None
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
gps_coordinates: str
class UpdateShelter(BaseModel):
label: Optional[str] = None
description: Optional[str] = None
status: Optional[str] = None # "available", "full", "closed"
contact_person: Optional[str] = None
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
gps_coordinates: Optional[str] = None
class PersonReportResponse(PersonReportBase):
id: int
created_at: datetime

View File

@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
@ -19,9 +19,10 @@ logger.info("Test log message")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configuration pour OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
class AuthService:
@staticmethod
def verify_password(plain_password: str, hashed_password: str) -> bool:
passEncr = pwd_context.hash(plain_password)
@ -58,14 +59,14 @@ class AuthService:
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.access_token_expire_minutes)
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
@staticmethod
async def get_current_user(token: str = Depends(oauth2_scheme), db=Depends(get_db)):
async def get_current_user(token: str, db):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
@ -147,7 +148,7 @@ class AuthService:
@staticmethod
async def admin_required(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)):
async def admin_required(token: str, db):
credentials_exception = HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to perform this action.",
@ -177,7 +178,7 @@ class AuthService:
@staticmethod
async def update_user(user_id: int, updates: dict, token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)):
async def update_user(user_id: int, updates: dict, token: str, db):
"""
Met à jour les informations d'un utilisateur.
- Un utilisateur peut mettre à jour ses propres informations.
@ -240,7 +241,35 @@ class AuthService:
return UserResponse(**updated_user)
@staticmethod
async def get_user_by_email(email: str, db: AsyncSession):
async def get_user_by_email(email: str, db):
query = select(users_table).where(users_table.c.email == email)
result = await db.execute(query)
return result.mappings().first()
return result.mappings().first()
@staticmethod
async def check_permissions(token: str, db, required_permissions: list):
credentials_exception = HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to perform this action.",
)
try:
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:
logger.error("could not decode token", extra={"token": token})
raise credentials_exception
query = users_table.select().where(users_table.c.email == token_data.email)
result = await db.execute(query)
user = result.mappings().first()
if user is None:
raise credentials_exception
if user["role"] not in required_permissions:
logger.error("permission denied", extra={"email": token_data.email, "required_permissions": required_permissions})
raise credentials_exception
logger.info("permission granted", extra={"email": token_data.email, "role": user["role"]})

View File

@ -100,9 +100,11 @@ class NeedRequestService:
# D<>codage du token JWT
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
email: str = payload.get("sub")
logger.info("email from payload.get(sub) " +email)
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
logger.info("token_data from TokenData(email=email) " +token_data)
except JWTError:
raise credentials_exception
@ -111,7 +113,7 @@ class NeedRequestService:
result = await db.execute(user_query)
user = result.mappings().fetchone()
logger.info("user loooooooooooooooooooo: " +user)
logger.info("user from database: " +user)
if user is None:
raise credentials_exception

View File

@ -7,13 +7,21 @@ from typing import Optional
from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer
from services.auth_service import AuthService
from services.s3_service import UploadService
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.model_dump())
async def create_report(report: PersonReportCreate, db, image_file, token):
user = await AuthService.get_current_user(token, db)
image_url = await UploadService.upload_image_to_s3(image_file, user.email) if image_file else None
query = person_reports_table.insert().values(**report.model_dump(),
photo_url=image_url,
reporter_email=user["email"])
try:
result = await db.execute(query)
await db.commit()
@ -53,7 +61,7 @@ class PersonReportService:
if status:
query = query.where(person_reports_table.c.status == status)
result = await db.execute(query)
reports = result.fetchall()
reports = result.mappings().all()
return [PersonReportResponse(**report) for report in reports]
@staticmethod

View File

@ -1,20 +1,23 @@
from sqlalchemy import insert, select, update, delete
from fastapi import HTTPException
from models.db import points_of_interest_table
from models.schemas import PointOfInterest
from models.schemas import CreatePointOfInterest, PointOfInterest, UpdatePointOfInterest
from sqlalchemy.ext.asyncio import AsyncSession
from services.auth_service import AuthService
class PointsOfInterestService:
@staticmethod
async def create_point_of_interest(point: PointOfInterest, db: AsyncSession):
async def create_point_of_interest(point: CreatePointOfInterest, db, token):
user = await AuthService.get_current_user(token, db)
query = insert(points_of_interest_table).values(
label=point.label,
description=point.description,
icon=point.icon,
organization=point.organization,
gps_coordinates=point.gps_coordinates,
added_by=point.added_by,
added_by=user["id"],
)
try:
result = await db.execute(query)
@ -26,26 +29,33 @@ class PointsOfInterestService:
raise HTTPException(status_code=500, detail=f"Could not create point of interest: {str(e)}")
@staticmethod
async def get_point_of_interest(point_id: int, db: AsyncSession):
async def get_point_of_interest(point_id: int, db):
query = select(points_of_interest_table).where(points_of_interest_table.c.id == point_id)
result = await db.execute(query)
point = result.fetchone()
point = result.mappings().fetchone()
if not point:
raise HTTPException(status_code=404, detail="Point of interest not found")
return dict(point)
@staticmethod
async def get_all_points_of_interest(db: AsyncSession):
async def get_all_points_of_interest(db):
query = select(points_of_interest_table)
result = await db.execute(query)
return [dict(row) for row in result.fetchall()]
points = result.mappings().all()
return [dict(point) for point in points]
@staticmethod
async def update_point_of_interest(point_id: int, data: dict, db: AsyncSession):
async def update_point_of_interest(point_id: int, point: UpdatePointOfInterest, db, token):
user = await AuthService.get_current_user(token, db)
update_values = point.model_dump(exclude_unset=True)
if not update_values:
raise HTTPException(status_code=400, detail="No fields provided for update")
query = (
update(points_of_interest_table)
.where(points_of_interest_table.c.id == point_id)
.values(**data)
.values(**update_values, added_by=user["id"])
)
try:
result = await db.execute(query)
@ -57,8 +67,10 @@ class PointsOfInterestService:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not update point of interest: {str(e)}")
@staticmethod
async def delete_point_of_interest(point_id: int, db: AsyncSession):
async def delete_point_of_interest(point_id: int, db, token):
await AuthService.admin_required(token, db)
query = delete(points_of_interest_table).where(points_of_interest_table.c.id == point_id)
try:
result = await db.execute(query)

View File

@ -7,8 +7,6 @@ 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
@ -29,7 +27,7 @@ class ReportService:
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)):
async def get_report_by_id(report_id: int, db, token: str):
await AuthService.admin_required(token, db)
query = select(user_reports_table).where(user_reports_table.c.id == report_id)
result = await db.execute(query)
@ -39,7 +37,7 @@ class ReportService:
return dict(report)
@staticmethod
async def get_all_reports(db, token: str = Depends(oauth2_scheme)):
async def get_all_reports(db, token: str):
await AuthService.admin_required(token, db)
query = select(user_reports_table)
@ -48,7 +46,7 @@ class ReportService:
return [dict(report) for report in reports]
@staticmethod
async def update_report(report_id: int, report_update: UserReportUpdate, db, token: str = Depends(oauth2_scheme)):
async def update_report(report_id: int, report_update: UserReportUpdate, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(user_reports_table)

View File

@ -9,9 +9,6 @@ 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")
class RoleService:
@staticmethod
async def get_all_roles(db: AsyncSession):
@ -37,7 +34,7 @@ class RoleService:
return roles_with_permissions
@staticmethod
async def create_role(name: str, db, token: str):
async def create_role(name: str, db: AsyncSession, token: str):
"""
Crée un nouveau rôle avec les permissions spécifiées (réservé aux administrateurs).
"""
@ -126,7 +123,7 @@ class RoleService:
return permissions_result
@staticmethod
async def get_role(role_id: int, db):
async def get_role(role_id: int, db: AsyncSession):
"""
Récupère les détails d'un rôle spécifique, y compris ses permissions associées.
"""

View File

@ -1,51 +1,64 @@
from sqlalchemy import insert, select, update, delete
from fastapi import HTTPException
from models.db import shelters_table
from models.schemas import Shelter
from models.schemas import Shelter, UpdateShelter
from sqlalchemy.ext.asyncio import AsyncSession
from services.auth_service import AuthService
class ShelterService:
permissions = ["admin", "moderator"]
@staticmethod
async def create_shelter(shelter: Shelter, db: AsyncSession):
async def create_shelter(shelter: Shelter, db, token):
user = await AuthService.get_current_user(token, db)
query = insert(shelters_table).values(
label=shelter.label,
description=shelter.description,
status=shelter.status,
contact_person=shelter.contact_person,
gps_coordinates=shelter.gps_coordinates,
added_by=shelter.added_by,
added_by=user["id"],
)
try:
result = await db.execute(query)
await db.commit()
shelter_id = result.inserted_primary_key[0]
return {"id": shelter_id, **shelter.dict()}
return {"id": shelter_id, **shelter.model_dump()}
except Exception as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not create shelter: {str(e)}")
@staticmethod
async def get_shelter(shelter_id: int, db: AsyncSession):
async def get_shelter(shelter_id: int, db):
query = select(shelters_table).where(shelters_table.c.id == shelter_id)
result = await db.execute(query)
shelter = result.fetchone()
shelter = result.mappings().fetchone()
if not shelter:
raise HTTPException(status_code=404, detail="Shelter not found")
return dict(shelter)
@staticmethod
async def get_all_shelters(db: AsyncSession):
async def get_all_shelters(db):
query = select(shelters_table)
result = await db.execute(query)
return [dict(row) for row in result.fetchall()]
shelters = result.mappings().fetchall()
return [dict(shelter) for shelter in shelters]
@staticmethod
async def update_shelter(shelter_id: int, data: dict, db: AsyncSession):
async def update_shelter(shelter_id: int, shelter: UpdateShelter, db, token):
user = await AuthService.get_current_user(token, db)
update_values = shelter.model_dump(exclude_unset=True)
if not update_values:
raise HTTPException(status_code=400, detail="No fields provided for update")
query = (
update(shelters_table)
.where(shelters_table.c.id == shelter_id)
.values(**data)
.values(**update_values,
added_by=user["id"])
)
try:
result = await db.execute(query)
@ -58,7 +71,9 @@ class ShelterService:
raise HTTPException(status_code=500, detail=f"Could not update shelter: {str(e)}")
@staticmethod
async def delete_shelter(shelter_id: int, db: AsyncSession):
async def delete_shelter(shelter_id: int, db, token):
#await AuthService.check_permissions(token, db, ShelterService.permissions)
await AuthService.admin_required(token, db)
query = delete(shelters_table).where(shelters_table.c.id == shelter_id)
try:
result = await db.execute(query)

View File

@ -1,14 +1,16 @@
from sqlalchemy import insert, select, update, delete
from fastapi import HTTPException
from models.db import technical_issues_table
from models.schemas import TechnicalIssue, UpdateTechnicalIssue
from models.schemas import CreateTechnicalIssue, UpdateTechnicalIssue
from services.auth_service import AuthService
class TechnicalIssueService:
@staticmethod
async def create_issue(issue: TechnicalIssue, db):
async def create_issue(issue: CreateTechnicalIssue, db, token: str):
user = await AuthService.get_current_user(token, db);
query = insert(technical_issues_table).values(
user_id=issue.user_id,
user_id=user["id"],
description=issue.description,
status=issue.status
)
@ -16,32 +18,36 @@ class TechnicalIssueService:
result = await db.execute(query)
await db.commit()
issue_id = result.inserted_primary_key[0]
return {"id": issue_id, **issue.dict()}
return {"id": issue_id, **issue.model_dump()}
except Exception as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not create issue: {str(e)}")
@staticmethod
async def get_issue(issue_id: int, db):
async def get_issue(issue_id: int, db, token: str):
await AuthService.admin_required(token, db)
query = select(technical_issues_table).where(technical_issues_table.c.id == issue_id)
result = await db.execute(query)
issue = result.fetchone()
issue = result.mappings().fetchone()
if not issue:
raise HTTPException(status_code=404, detail="Technical issue not found")
return dict(issue)
@staticmethod
async def get_all_issues(db):
async def get_all_issues(db, token: str):
await AuthService.admin_required(token, db)
query = select(technical_issues_table)
result = await db.execute(query)
return [dict(row) for row in result.fetchall()]
technical_issues = result.mappings().all()
return [dict(technical_issue) for technical_issue in technical_issues]
@staticmethod
async def update_issue(issue_id: int, issue_data: UpdateTechnicalIssue, db):
async def update_issue(issue_id: int, issue_data: UpdateTechnicalIssue, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(technical_issues_table)
.where(technical_issues_table.c.id == issue_id)
.values(**issue_data.dict(exclude_unset=True))
.values(**issue_data.model_dump(exclude_unset=True))
)
try:
result = await db.execute(query)
@ -54,7 +60,8 @@ class TechnicalIssueService:
raise HTTPException(status_code=500, detail=f"Could not update issue: {str(e)}")
@staticmethod
async def delete_issue(issue_id: int, db):
async def delete_issue(issue_id: int, db, token: str):
await AuthService.admin_required(token, db)
query = delete(technical_issues_table).where(technical_issues_table.c.id == issue_id)
try:
result = await db.execute(query)

View File

@ -1,17 +1,71 @@
# app/services/upload_service.py
from typing import Self
import uuid
import boto3
from botocore.exceptions import NoCredentialsError
from botocore.exceptions import NoCredentialsError, BotoCoreError, ClientError
from fastapi import HTTPException, UploadFile
class UploadService:
def __init__(self, bucket_name):
self.s3_client = boto3.client('s3')
self.bucket_name = bucket_name
def upload_file(self, file, file_name):
async def upload_image_to_s3(file: UploadFile, account_id: str) -> dict:
# Configuration globale
EXTENSIONS_AUTHORIZED = {
"docx": "doc",
"pdf": "doc",
"png": "image",
"jpg": "image",
"jpeg": "image",
"mp4": "video",
}
FILE_PATH_S3 = {
"image": "images/",
"video": "videos/",
"doc": "docs/",
}
S3_CONFIG = {
"bucket": "sywmtnsg",
"endpoint_url": "https://ht2-storage.n0c.com:5443",
"region_name": "ht2-storage",
"aws_access_key_id": "X89EU8NHL54CIKGMZP8Q",
"aws_secret_access_key": "71RsicRiiSgXDcAZLM4vCpEZESMJ4iA9sOKp0UQy",
}
# Validate file extension
extension = file.filename.split(".")[-1].lower()
if extension not in EXTENSIONS_AUTHORIZED:
raise HTTPException(status_code=400, detail="Unsupported file extension.")
# Generate unique file name
new_filename = f"{uuid.uuid4()}.{extension}"
file_type = EXTENSIONS_AUTHORIZED[extension]
storage_path = f"public/{FILE_PATH_S3[file_type]}{account_id}/{new_filename}"
# Initialize S3 client
try:
self.s3_client.upload_fileobj(file, self.bucket_name, file_name)
return f"https://{self.bucket_name}.s3.amazonaws.com/{file_name}"
except NoCredentialsError:
raise Exception("AWS credentials not found")
except Exception as e:
raise e
s3_client = boto3.client(
"s3",
endpoint_url=S3_CONFIG["endpoint_url"],
region_name=S3_CONFIG["region_name"],
aws_access_key_id=S3_CONFIG["aws_access_key_id"],
aws_secret_access_key=S3_CONFIG["aws_secret_access_key"],
)
# Upload file
s3_client.upload_fileobj(
file.file,
S3_CONFIG["bucket"],
storage_path,
ExtraArgs={"ACL": "public-read"},
)
except (BotoCoreError, ClientError) as e:
raise HTTPException(status_code=500, detail=f"Failed to upload file: {str(e)}")
return {
"success": True,
"path_with_name": storage_path.replace("public", ""),
"filename": new_filename,
"original_filename": file.filename,
"filetype": extension,
}

View File

@ -4,6 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from models.schemas import UserCreate, UserResponse, UserUpdateRole, UserBlockBan
from config.database import get_db
from models.db import users_table
from services.auth_service import AuthService
from utils.security import get_password_hash
from typing import Optional
@ -11,7 +12,9 @@ class UserService:
@staticmethod
async def list_users(status: Optional[str] = None, db=Depends(get_db)):
async def list_users(token: str, status: Optional[str] = None, db=Depends(get_db)):
await AuthService.check_permissions(token, ["admin"], db)
query = select(users_table)
if status:
query = query.where(users_table.c.status == status)
@ -20,7 +23,7 @@ class UserService:
return [UserResponse(**user) for user in users]
@staticmethod
async def create_user(user: UserCreate, db: AsyncSession):
async def create_user(user: UserCreate, db):
hashed_password = get_password_hash(user.password)
query = users_table.insert().values(
email=user.email,
@ -42,7 +45,9 @@ class UserService:
raise HTTPException(status_code=500, detail=f"Error creating user: {str(e)}")
@staticmethod
async def change_user_role(user_update: UserUpdateRole, db: AsyncSession):
async def change_user_role(user_update: UserUpdateRole, db, token: str):
await AuthService.check_permissions(token, ["admin"], db)
query = (
update(users_table)
.where(users_table.c.email == user_update.email)
@ -55,7 +60,8 @@ class UserService:
return {"message": f"Role updated to {user_update.new_role}"}
@staticmethod
async def block_user(user_action: UserBlockBan, db: AsyncSession):
async def block_user(user_action: UserBlockBan, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(users_table)
.where(users_table.c.email == user_action.email)
@ -68,7 +74,9 @@ class UserService:
return {"message": f"User {user_action.email} blocked"}
@staticmethod
async def ban_user(user_action: UserBlockBan, db: AsyncSession):
async def ban_user(user_action: UserBlockBan, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(users_table)
.where(users_table.c.email == user_action.email)
@ -81,7 +89,8 @@ class UserService:
return {"message": f"User {user_action.email} banned"}
@staticmethod
async def unblock_user(user_action: UserBlockBan, db: AsyncSession):
async def unblock_user(user_action: UserBlockBan, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(users_table)
.where(users_table.c.email == user_action.email)
@ -94,7 +103,8 @@ class UserService:
return {"message": f"User {user_action.email} unblocked"}
@staticmethod
async def unban_user(user_action: UserBlockBan, db: AsyncSession):
async def unban_user(user_action: UserBlockBan, db, token: str):
await AuthService.admin_required(token, db)
query = (
update(users_table)
.where(users_table.c.email == user_action.email)