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 email.mime.text import MIMEText
from fastapi import APIRouter, Depends, HTTPException, status, Body from fastapi import APIRouter, Depends, HTTPException, status, Body
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy import update, select from sqlalchemy import update, select
from services.auth_service import AuthService from services.auth_service import AuthService
from models.schemas import Token, UserCreate, UserResponse 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 from utils.security import verify_password, get_password_hash, pwd_context
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/signup", response_model=UserResponse, status_code=201, summary="User Signup") @router.post("/signup", response_model=UserResponse, status_code=201, summary="User Signup")
async def signup(user: UserCreate, db=Depends(get_db)): 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"} return {"access_token": access_token, "token_type": "bearer"}
@router.get("/me", response_model=UserResponse, summary="Get current user") @router.get("/me", response_model=UserResponse, summary="Get current user")
async def read_users_me(current_user: UserResponse = Depends(AuthService.get_current_user)): async def read_users_me(token:str = Depends(oauth2_scheme) , db=Depends(get_db)):
return current_user return await AuthService.get_current_user(token, db)
@router.post("/reset-password") @router.post("/reset-password")
async def reset_password(token: str = Body(...), new_password: str = Body(...), db: AsyncSession = Depends(get_db)): 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 services.person_report_service import PersonReportService
from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse, UserResponse from models.schemas import PersonReportCreate, PersonReportUpdate, PersonReportResponse, UserResponse
from config.database import get_db from config.database import get_db
@ -6,10 +7,11 @@ from typing import Optional
from services.auth_service import AuthService from services.auth_service import AuthService
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", response_model=PersonReportResponse, status_code=201) @router.post("/", response_model=PersonReportResponse, status_code=201)
async def create_report(report: PersonReportCreate, db=Depends(get_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) return await PersonReportService.create_report(report, db, image_file, token)
@router.put("/{report_id}", response_model=PersonReportResponse) @router.put("/{report_id}", response_model=PersonReportResponse)
async def update_report(report_id: int, report: PersonReportUpdate, db=Depends(get_db)): 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 import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from services.points_of_interest_service import PointsOfInterestService 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 from config.database import get_db
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", status_code=201) @router.post("/", status_code=201)
async def create_point(point: PointOfInterest, db=Depends(get_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) return await PointsOfInterestService.create_point_of_interest(point, db, token)
@router.get("/{point_id}") @router.get("/{point_id}")
@ -22,10 +23,10 @@ async def get_all_points(db=Depends(get_db)):
@router.put("/{point_id}") @router.put("/{point_id}")
async def update_point(point_id: int, data: dict, db=Depends(get_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, data, db) return await PointsOfInterestService.update_point_of_interest(point_id, point, db, token)
@router.delete("/{point_id}") @router.delete("/{point_id}")
async def delete_point(point_id: int, db=Depends(get_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) 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 import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from services.shelter_service import ShelterService from services.shelter_service import ShelterService
from models.schemas import Shelter from models.schemas import CreateShelter, UpdateShelter
from config.database import get_db from config.database import get_db
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.post("/", status_code=201) @router.post("/", status_code=201)
async def create_shelter(shelter: Shelter, db=Depends(get_db)): async def create_shelter(shelter: CreateShelter, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await ShelterService.create_shelter(shelter, db) return await ShelterService.create_shelter(shelter, db, token)
@router.get("/{shelter_id}") @router.get("/{shelter_id}")
@ -22,10 +23,10 @@ async def get_all_shelters(db=Depends(get_db)):
@router.put("/{shelter_id}") @router.put("/{shelter_id}")
async def update_shelter(shelter_id: int, data: dict, db=Depends(get_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, data, db) return await ShelterService.update_shelter(shelter_id, shelter, db, token)
@router.delete("/{shelter_id}") @router.delete("/{shelter_id}")
async def delete_shelter(shelter_id: int, db=Depends(get_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) return await ShelterService.delete_shelter(shelter_id, db, token)

View File

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

View File

@ -1,28 +1,16 @@
# app/api/v1/upload.py # 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 from services.upload_service import UploadService
import uuid from utils.logging import logger
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
# Instancier UploadService avec le nom de ton bucket S3
upload_service = UploadService(bucket_name="ton-nom-de-bucket")
@router.post("/") @router.post("/")
async def upload_file(file: UploadFile = File(...)): async def upload_file(file: UploadFile, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
# Générer un nom de fichier unique user = await AuthService.get_current_user(token, db)
file_extension = file.filename.split(".")[-1] # Récupérer l'extension du fichier logger.info(f"User {user} is uploading a file")
unique_filename = f"{uuid.uuid4()}.{file_extension}" # Générer un nom unique return await UploadService.upload_image_to_s3(file, user["id"])
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))

View File

@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from services.user_service import UserService from services.user_service import UserService
from models.schemas import UserUpdateRole, UserBlockBan, UserResponse from models.schemas import UserUpdateRole, UserBlockBan, UserResponse
@ -7,27 +8,28 @@ from services.auth_service import AuthService
from typing import Optional from typing import Optional
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
@router.get("/", response_model=list[UserResponse]) @router.get("/", response_model=list[UserResponse])
async def list_users(status: Optional[str] = None, db=Depends(get_db)): async def list_users(status: Optional[str] = None, db=Depends(get_db), token: str = Depends(oauth2_scheme)):
return await UserService.list_users(status, db) return await UserService.list_users(token, status, db)
@router.patch("/role", status_code=200, dependencies=[Depends(AuthService.admin_required)]) @router.patch("/role", status_code=200)
async def change_role(user_update: UserUpdateRole, db: AsyncSession = Depends(get_db)): 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) return await UserService.change_user_role(user_update, db, token)
@router.post("/block", status_code=200, dependencies=[Depends(AuthService.admin_required)]) @router.post("/block", status_code=200)
async def block_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)): 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) return await UserService.block_user(user_action, db, token)
@router.post("/ban", status_code=200, dependencies=[Depends(AuthService.admin_required)]) @router.post("/ban", status_code=200)
async def ban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)): 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) return await UserService.ban_user(user_action, db, token)
@router.post("/unblock", status_code=200, dependencies=[Depends(AuthService.admin_required)]) @router.post("/unblock", status_code=200)
async def unblock_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)): 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) return await UserService.unblock_user(user_action, db, token)
@router.post("/unban", status_code=200, dependencies=[Depends(AuthService.admin_required)]) @router.post("/unban", status_code=200)
async def unban_user(user_action: UserBlockBan, db: AsyncSession = Depends(get_db)): 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) 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 sqlalchemy import Table, Column, Integer, String, DateTime, Boolean, MetaData, ForeignKey, Text
from datetime import datetime from datetime import datetime, timezone
metadata = MetaData() metadata = MetaData()
@ -100,6 +100,8 @@ shelters_table = Table(
Column("description", Text), Column("description", Text),
Column("status", String(50), default="available"), Column("status", String(50), default="available"),
Column("contact_person", String(255)), Column("contact_person", String(255)),
Column('contact_email', String(255)),
Column('contact_phone', String(20)),
Column("gps_coordinates", String(100), nullable=False), Column("gps_coordinates", String(100), nullable=False),
Column("added_by", Integer, ForeignKey("users.id"), 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("gps_coordinates", String(100)),
Column("photo_url", String(255)), Column("photo_url", String(255)),
Column("reporter_email", String(255), ForeignKey("users.email"), nullable=False), Column("reporter_email", String(255), ForeignKey("users.email"), nullable=False),
Column("created_at", DateTime, default=datetime.utcnow), Column("created_at", DateTime, default=datetime.now(timezone.utc)),
Column("updated_at", DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) 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 description: str
status: str status: str
class CreateTechnicalIssue(BaseModel):
description: str
status: str
class UpdateTechnicalIssue(BaseModel): class UpdateTechnicalIssue(BaseModel):
status: Optional[str] = None status: Optional[str] = None
description: Optional[str] = None
class PersonReportBase(BaseModel): class PersonReportBase(BaseModel):
full_name: str full_name: str
@ -135,8 +138,12 @@ class PersonReportBase(BaseModel):
photo_url: Optional[str] = None photo_url: Optional[str] = None
reporter_email: str reporter_email: str
class PersonReportCreate(PersonReportBase): class PersonReportCreate(BaseModel):
pass full_name: str
date_of_birth: datetime
status: str
location: Optional[str] = None
gps_coordinates: Optional[str] = None
class PersonReportUpdate(BaseModel): class PersonReportUpdate(BaseModel):
status: Optional[str] = None status: Optional[str] = None
@ -159,6 +166,19 @@ class PointOfInterest(BaseModel):
gps_coordinates: str gps_coordinates: str
added_by: int # ID de l'utilisateur qui a ajouté ce point 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): class Shelter(BaseModel):
label: str label: str
description: Optional[str] = None description: Optional[str] = None
@ -167,6 +187,24 @@ class Shelter(BaseModel):
gps_coordinates: str gps_coordinates: str
added_by: int # ID de l'utilisateur qui a ajouté cet abri 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): class PersonReportResponse(PersonReportBase):
id: int id: int
created_at: datetime 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 typing import Optional
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
@ -19,9 +19,10 @@ logger.info("Test log message")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configuration pour OAuth2 # Configuration pour OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
class AuthService: class AuthService:
@staticmethod @staticmethod
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:
passEncr = pwd_context.hash(plain_password) passEncr = pwd_context.hash(plain_password)
@ -58,14 +59,14 @@ class AuthService:
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy() to_encode = data.copy()
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = datetime.now(timezone.utc) + expires_delta
else: 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}) to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm) return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
@staticmethod @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( credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials", detail="Could not validate credentials",
@ -147,7 +148,7 @@ class AuthService:
@staticmethod @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( credentials_exception = HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to perform this action.", detail="You do not have permission to perform this action.",
@ -177,7 +178,7 @@ class AuthService:
@staticmethod @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. Met à jour les informations d'un utilisateur.
- Un utilisateur peut mettre à jour ses propres informations. - Un utilisateur peut mettre à jour ses propres informations.
@ -240,7 +241,35 @@ class AuthService:
return UserResponse(**updated_user) return UserResponse(**updated_user)
@staticmethod @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) query = select(users_table).where(users_table.c.email == email)
result = await db.execute(query) 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 # D<>codage du token JWT
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm]) payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
email: str = payload.get("sub") email: str = payload.get("sub")
logger.info("email from payload.get(sub) " +email)
if email is None: if email is None:
raise credentials_exception raise credentials_exception
token_data = TokenData(email=email) token_data = TokenData(email=email)
logger.info("token_data from TokenData(email=email) " +token_data)
except JWTError: except JWTError:
raise credentials_exception raise credentials_exception
@ -111,7 +113,7 @@ class NeedRequestService:
result = await db.execute(user_query) result = await db.execute(user_query)
user = result.mappings().fetchone() user = result.mappings().fetchone()
logger.info("user loooooooooooooooooooo: " +user) logger.info("user from database: " +user)
if user is None: if user is None:
raise credentials_exception raise credentials_exception

View File

@ -7,13 +7,21 @@ from typing import Optional
from fastapi import Depends from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer 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") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
class PersonReportService: class PersonReportService:
@staticmethod @staticmethod
async def create_report(report: PersonReportCreate, db): async def create_report(report: PersonReportCreate, db, image_file, token):
query = person_reports_table.insert().values(**report.model_dump()) 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: try:
result = await db.execute(query) result = await db.execute(query)
await db.commit() await db.commit()
@ -53,7 +61,7 @@ class PersonReportService:
if status: if status:
query = query.where(person_reports_table.c.status == status) query = query.where(person_reports_table.c.status == status)
result = await db.execute(query) result = await db.execute(query)
reports = result.fetchall() reports = result.mappings().all()
return [PersonReportResponse(**report) for report in reports] return [PersonReportResponse(**report) for report in reports]
@staticmethod @staticmethod

View File

@ -1,20 +1,23 @@
from sqlalchemy import insert, select, update, delete from sqlalchemy import insert, select, update, delete
from fastapi import HTTPException from fastapi import HTTPException
from models.db import points_of_interest_table 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 sqlalchemy.ext.asyncio import AsyncSession
from services.auth_service import AuthService
class PointsOfInterestService: class PointsOfInterestService:
@staticmethod @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( query = insert(points_of_interest_table).values(
label=point.label, label=point.label,
description=point.description, description=point.description,
icon=point.icon, icon=point.icon,
organization=point.organization, organization=point.organization,
gps_coordinates=point.gps_coordinates, gps_coordinates=point.gps_coordinates,
added_by=point.added_by, added_by=user["id"],
) )
try: try:
result = await db.execute(query) 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)}") raise HTTPException(status_code=500, detail=f"Could not create point of interest: {str(e)}")
@staticmethod @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) query = select(points_of_interest_table).where(points_of_interest_table.c.id == point_id)
result = await db.execute(query) result = await db.execute(query)
point = result.fetchone() point = result.mappings().fetchone()
if not point: if not point:
raise HTTPException(status_code=404, detail="Point of interest not found") raise HTTPException(status_code=404, detail="Point of interest not found")
return dict(point) return dict(point)
@staticmethod @staticmethod
async def get_all_points_of_interest(db: AsyncSession): async def get_all_points_of_interest(db):
query = select(points_of_interest_table) query = select(points_of_interest_table)
result = await db.execute(query) 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 @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 = ( query = (
update(points_of_interest_table) update(points_of_interest_table)
.where(points_of_interest_table.c.id == point_id) .where(points_of_interest_table.c.id == point_id)
.values(**data) .values(**update_values, added_by=user["id"])
) )
try: try:
result = await db.execute(query) result = await db.execute(query)
@ -57,8 +67,10 @@ class PointsOfInterestService:
await db.rollback() await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not update point of interest: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not update point of interest: {str(e)}")
@staticmethod @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) query = delete(points_of_interest_table).where(points_of_interest_table.c.id == point_id)
try: try:
result = await db.execute(query) result = await db.execute(query)

View File

@ -7,8 +7,6 @@ from fastapi import Depends, HTTPException, status
from services.auth_service import AuthService from services.auth_service import AuthService
# Configuration pour OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
class ReportService: class ReportService:
@staticmethod @staticmethod
@ -29,7 +27,7 @@ class ReportService:
raise HTTPException(status_code=500, detail=f"Could not create user report: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not create user report: {str(e)}")
@staticmethod @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) await AuthService.admin_required(token, db)
query = select(user_reports_table).where(user_reports_table.c.id == report_id) query = select(user_reports_table).where(user_reports_table.c.id == report_id)
result = await db.execute(query) result = await db.execute(query)
@ -39,7 +37,7 @@ class ReportService:
return dict(report) return dict(report)
@staticmethod @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) await AuthService.admin_required(token, db)
query = select(user_reports_table) query = select(user_reports_table)
@ -48,7 +46,7 @@ class ReportService:
return [dict(report) for report in reports] return [dict(report) for report in reports]
@staticmethod @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) await AuthService.admin_required(token, db)
query = ( query = (
update(user_reports_table) update(user_reports_table)

View File

@ -9,9 +9,6 @@ from jose import jwt, JWTError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from services.auth_service import AuthService from services.auth_service import AuthService
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
class RoleService: class RoleService:
@staticmethod @staticmethod
async def get_all_roles(db: AsyncSession): async def get_all_roles(db: AsyncSession):
@ -37,7 +34,7 @@ class RoleService:
return roles_with_permissions return roles_with_permissions
@staticmethod @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). 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 return permissions_result
@staticmethod @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. 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 sqlalchemy import insert, select, update, delete
from fastapi import HTTPException from fastapi import HTTPException
from models.db import shelters_table from models.db import shelters_table
from models.schemas import Shelter from models.schemas import Shelter, UpdateShelter
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from services.auth_service import AuthService
class ShelterService: class ShelterService:
permissions = ["admin", "moderator"]
@staticmethod @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( query = insert(shelters_table).values(
label=shelter.label, label=shelter.label,
description=shelter.description, description=shelter.description,
status=shelter.status, status=shelter.status,
contact_person=shelter.contact_person, contact_person=shelter.contact_person,
gps_coordinates=shelter.gps_coordinates, gps_coordinates=shelter.gps_coordinates,
added_by=shelter.added_by, added_by=user["id"],
) )
try: try:
result = await db.execute(query) result = await db.execute(query)
await db.commit() await db.commit()
shelter_id = result.inserted_primary_key[0] shelter_id = result.inserted_primary_key[0]
return {"id": shelter_id, **shelter.dict()} return {"id": shelter_id, **shelter.model_dump()}
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not create shelter: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not create shelter: {str(e)}")
@staticmethod @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) query = select(shelters_table).where(shelters_table.c.id == shelter_id)
result = await db.execute(query) result = await db.execute(query)
shelter = result.fetchone() shelter = result.mappings().fetchone()
if not shelter: if not shelter:
raise HTTPException(status_code=404, detail="Shelter not found") raise HTTPException(status_code=404, detail="Shelter not found")
return dict(shelter) return dict(shelter)
@staticmethod @staticmethod
async def get_all_shelters(db: AsyncSession): async def get_all_shelters(db):
query = select(shelters_table) query = select(shelters_table)
result = await db.execute(query) 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 @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 = ( query = (
update(shelters_table) update(shelters_table)
.where(shelters_table.c.id == shelter_id) .where(shelters_table.c.id == shelter_id)
.values(**data) .values(**update_values,
added_by=user["id"])
) )
try: try:
result = await db.execute(query) result = await db.execute(query)
@ -58,7 +71,9 @@ class ShelterService:
raise HTTPException(status_code=500, detail=f"Could not update shelter: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not update shelter: {str(e)}")
@staticmethod @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) query = delete(shelters_table).where(shelters_table.c.id == shelter_id)
try: try:
result = await db.execute(query) result = await db.execute(query)

View File

@ -1,14 +1,16 @@
from sqlalchemy import insert, select, update, delete from sqlalchemy import insert, select, update, delete
from fastapi import HTTPException from fastapi import HTTPException
from models.db import technical_issues_table 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: class TechnicalIssueService:
@staticmethod @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( query = insert(technical_issues_table).values(
user_id=issue.user_id, user_id=user["id"],
description=issue.description, description=issue.description,
status=issue.status status=issue.status
) )
@ -16,32 +18,36 @@ class TechnicalIssueService:
result = await db.execute(query) result = await db.execute(query)
await db.commit() await db.commit()
issue_id = result.inserted_primary_key[0] issue_id = result.inserted_primary_key[0]
return {"id": issue_id, **issue.dict()} return {"id": issue_id, **issue.model_dump()}
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
raise HTTPException(status_code=500, detail=f"Could not create issue: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not create issue: {str(e)}")
@staticmethod @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) query = select(technical_issues_table).where(technical_issues_table.c.id == issue_id)
result = await db.execute(query) result = await db.execute(query)
issue = result.fetchone() issue = result.mappings().fetchone()
if not issue: if not issue:
raise HTTPException(status_code=404, detail="Technical issue not found") raise HTTPException(status_code=404, detail="Technical issue not found")
return dict(issue) return dict(issue)
@staticmethod @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) query = select(technical_issues_table)
result = await db.execute(query) 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 @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 = ( query = (
update(technical_issues_table) update(technical_issues_table)
.where(technical_issues_table.c.id == issue_id) .where(technical_issues_table.c.id == issue_id)
.values(**issue_data.dict(exclude_unset=True)) .values(**issue_data.model_dump(exclude_unset=True))
) )
try: try:
result = await db.execute(query) result = await db.execute(query)
@ -54,7 +60,8 @@ class TechnicalIssueService:
raise HTTPException(status_code=500, detail=f"Could not update issue: {str(e)}") raise HTTPException(status_code=500, detail=f"Could not update issue: {str(e)}")
@staticmethod @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) query = delete(technical_issues_table).where(technical_issues_table.c.id == issue_id)
try: try:
result = await db.execute(query) result = await db.execute(query)

View File

@ -1,17 +1,71 @@
# app/services/upload_service.py # app/services/upload_service.py
from typing import Self
import uuid
import boto3 import boto3
from botocore.exceptions import NoCredentialsError from botocore.exceptions import NoCredentialsError, BotoCoreError, ClientError
from fastapi import HTTPException, UploadFile
class UploadService: 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: try:
self.s3_client.upload_fileobj(file, self.bucket_name, file_name) s3_client = boto3.client(
return f"https://{self.bucket_name}.s3.amazonaws.com/{file_name}" "s3",
except NoCredentialsError: endpoint_url=S3_CONFIG["endpoint_url"],
raise Exception("AWS credentials not found") region_name=S3_CONFIG["region_name"],
except Exception as e: aws_access_key_id=S3_CONFIG["aws_access_key_id"],
raise e 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 models.schemas import UserCreate, UserResponse, UserUpdateRole, UserBlockBan
from config.database import get_db from config.database import get_db
from models.db import users_table from models.db import users_table
from services.auth_service import AuthService
from utils.security import get_password_hash from utils.security import get_password_hash
from typing import Optional from typing import Optional
@ -11,7 +12,9 @@ class UserService:
@staticmethod @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) query = select(users_table)
if status: if status:
query = query.where(users_table.c.status == status) query = query.where(users_table.c.status == status)
@ -20,7 +23,7 @@ class UserService:
return [UserResponse(**user) for user in users] return [UserResponse(**user) for user in users]
@staticmethod @staticmethod
async def create_user(user: UserCreate, db: AsyncSession): async def create_user(user: UserCreate, db):
hashed_password = get_password_hash(user.password) hashed_password = get_password_hash(user.password)
query = users_table.insert().values( query = users_table.insert().values(
email=user.email, email=user.email,
@ -42,7 +45,9 @@ class UserService:
raise HTTPException(status_code=500, detail=f"Error creating user: {str(e)}") raise HTTPException(status_code=500, detail=f"Error creating user: {str(e)}")
@staticmethod @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 = ( query = (
update(users_table) update(users_table)
.where(users_table.c.email == user_update.email) .where(users_table.c.email == user_update.email)
@ -55,7 +60,8 @@ class UserService:
return {"message": f"Role updated to {user_update.new_role}"} return {"message": f"Role updated to {user_update.new_role}"}
@staticmethod @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 = ( query = (
update(users_table) update(users_table)
.where(users_table.c.email == user_action.email) .where(users_table.c.email == user_action.email)
@ -68,7 +74,9 @@ class UserService:
return {"message": f"User {user_action.email} blocked"} return {"message": f"User {user_action.email} blocked"}
@staticmethod @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 = ( query = (
update(users_table) update(users_table)
.where(users_table.c.email == user_action.email) .where(users_table.c.email == user_action.email)
@ -81,7 +89,8 @@ class UserService:
return {"message": f"User {user_action.email} banned"} return {"message": f"User {user_action.email} banned"}
@staticmethod @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 = ( query = (
update(users_table) update(users_table)
.where(users_table.c.email == user_action.email) .where(users_table.c.email == user_action.email)
@ -94,7 +103,8 @@ class UserService:
return {"message": f"User {user_action.email} unblocked"} return {"message": f"User {user_action.email} unblocked"}
@staticmethod @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 = ( query = (
update(users_table) update(users_table)
.where(users_table.c.email == user_action.email) .where(users_table.c.email == user_action.email)