departements

This commit is contained in:
el 2025-07-01 18:25:10 +02:00
parent 6b53a419c9
commit 4c180fe1f8
19 changed files with 21999 additions and 431 deletions

View file

@ -1,17 +1,23 @@
# backend/routers/document.py
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
import os
import uuid # Pour générer des noms de fichiers uniques
import uuid # For generating unique filenames
import logging
from typing import List # Required for list type hint in get_user_documents
from core.database import get_db
from core.security import create_access_token # Non utilisé directement ici mais potentiellement dans d'autres routers
from core.config import settings # Pour accéder au chemin d'upload
# Removed unused 'create_access_token'
from core.security import get_current_user # Ensure this is the correct import for your get_current_user dependency
from core.config import settings # To access upload directory
from crud import document as crud_document
from crud import user as crud_user # Pour récupérer l'utilisateur courant
# Removed unused 'crud_user' as it's not directly used in this router
from schemas import document as schemas_document
from schemas import user as schemas_user # Pour le modèle UserInDBBase ou UserResponse
from dependencies import get_current_user # Pour la protection des routes
from schemas import user as schemas_user # For UserResponse schema
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/documents",
@ -19,7 +25,7 @@ router = APIRouter(
responses={404: {"description": "Not found"}},
)
@router.post("/upload-cv", response_model=schemas_document.DocumentResponse, status_code=status.HTTP_201_CREATED)
@router.post("/upload-cv", response_model=schemas_document.DocumentResponse, status_code=status.HTTP_201_CREATED, summary="Uploader un CV")
async def upload_cv(
file: UploadFile = File(...),
db: Session = Depends(get_db),
@ -29,39 +35,53 @@ async def upload_cv(
Permet à un utilisateur authentifié d'uploader un CV.
Le fichier est stocké sur le serveur et ses métadonnées sont enregistrées en base de données.
"""
if not file.filename.lower().endswith(('.pdf', '.doc', '.docx')):
logger.info(f"Tentative d'upload de CV par l'utilisateur {current_user.id} - Nom du fichier: {file.filename}")
if not file.filename:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Le nom du fichier est manquant.")
allowed_extensions = ('.pdf', '.doc', '.docx')
# Use os.path.splitext to safely get the extension
file_extension = os.path.splitext(file.filename)[1].lower()
if file_extension not in allowed_extensions:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Seuls les fichiers PDF, DOC, DOCX sont autorisés."
detail=f"Seuls les fichiers {', '.join(allowed_extensions).upper()} sont autorisés."
)
# Créer un nom de fichier unique pour éviter les collisions et les problèmes de sécurité
unique_filename = f"{uuid.uuid4()}_{file.filename}"
file_path = os.path.join(settings.UPLOADS_DIR, unique_filename)
upload_dir = settings.UPLOADS_DIR # Utilisez le chemin absolu configuré dans settings
os.makedirs(upload_dir, exist_ok=True)
# S'assurer que le répertoire d'uploads existe
os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
# Generate a unique filename using UUID to prevent collisions and potential path traversal issues
unique_filename = f"{uuid.uuid4()}{file_extension}"
file_path = os.path.join(upload_dir, unique_filename)
try:
# Write the file in chunks for efficiency with large files
with open(file_path, "wb") as buffer:
# Écrit le fichier par morceaux pour les gros fichiers
while content := await file.read(1024 * 1024): # Lire par blocs de 1MB
while content := await file.read(1024 * 1024): # Read in 1MB chunks
buffer.write(content)
logger.info(f"Fichier '{file.filename}' enregistré sous '{file_path}' pour l'utilisateur {current_user.id}")
except Exception as e:
logger.error(f"Erreur lors de l'enregistrement du fichier {file.filename}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de l'enregistrement du fichier: {e}"
)
finally:
# Ensure the UploadFile is closed even if an error occurs
await file.close()
# Enregistrer les métadonnées du document dans la base de données
# Save document metadata in the database
# The DocumentCreate schema might not need 'filename' as a field if you pass it directly to crud
# Assuming DocumentCreate schema only takes filename and crud.create_document handles filepath
document_data = schemas_document.DocumentCreate(filename=file.filename)
db_document = crud_document.create_document(db, document_data, file_path, current_user.id)
logger.info(f"Document ID {db_document.id} créé en base de données pour l'utilisateur {current_user.id}")
return db_document
@router.get("/", response_model=list[schemas_document.DocumentResponse])
@router.get("/", response_model=List[schemas_document.DocumentResponse], summary="Lister les documents de l'utilisateur")
def get_user_documents(
db: Session = Depends(get_db),
current_user: schemas_user.UserResponse = Depends(get_current_user)
@ -69,26 +89,34 @@ def get_user_documents(
"""
Récupère tous les documents uploadés par l'utilisateur authentifié.
"""
logger.info(f"Tentative de listage des documents pour l'utilisateur {current_user.id}")
documents = crud_document.get_documents_by_owner(db, current_user.id)
return documents
@router.get("/{document_id}", response_model=schemas_document.DocumentResponse)
@router.get("/{document_id}", response_model=schemas_document.DocumentResponse, summary="Récupérer un document par ID")
def get_document_details(
document_id: int,
db: Session = Depends(get_db),
current_user: schemas_user.UserResponse = Depends(get_current_user)
):
"""
Récupère les détails d'un document spécifique de l'utilisateur authentifié.
Récupère les détails d'un document spécifique appartenant à l'utilisateur courant.
"""
document = crud_document.get_document_by_id(db, document_id)
logger.info(f"Tentative de récupération du document {document_id} pour l'utilisateur {current_user.id}")
# Appel à la fonction CRUD qui filtre déjà par owner_id
document = crud_document.get_document_by_id(db, document_id, current_user.id)
if not document:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document non trouvé.")
if document.owner_id != current_user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Vous n'avez pas accès à ce document.")
# Si le document n'est pas trouvé (soit il n'existe pas, soit il n'appartient pas à cet utilisateur)
logger.warning(f"Document {document_id} non trouvé ou non autorisé pour l'utilisateur {current_user.id}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Document non trouvé ou vous n'avez pas l'autorisation d'y accéder."
)
return document
@router.delete("/{document_id}", status_code=status.HTTP_204_NO_CONTENT)
@router.delete("/{document_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Supprimer un document par ID")
async def delete_document(
document_id: int,
db: Session = Depends(get_db),
@ -98,22 +126,62 @@ async def delete_document(
Supprime un document spécifique de l'utilisateur authentifié,
à la fois de la base de données et du système de fichiers.
"""
db_document = crud_document.get_document_by_id(db, document_id)
if not db_document:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document non trouvé.")
if db_document.owner_id != current_user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Vous n'avez pas la permission de supprimer ce document.")
logger.info(f"Tentative de suppression du document {document_id} pour l'utilisateur {current_user.id}")
# Supprimer le fichier du système de fichiers
if os.path.exists(db_document.filepath):
# Appel à la fonction CRUD qui filtre déjà par owner_id
db_document = crud_document.get_document_by_id(db, document_id, current_user.id)
if not db_document:
# Si le document n'est pas trouvé (soit il n'existe pas, soit il n'appartient pas à cet utilisateur)
logger.warning(f"Document {document_id} non trouvé ou non autorisé pour la suppression par l'utilisateur {current_user.id}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Document non trouvé ou vous n'avez pas la permission de le supprimer."
)
# Supprimer le fichier du système de fichiers s'il existe et si un chemin est défini
if db_document.filepath and os.path.exists(db_document.filepath):
try:
os.remove(db_document.filepath)
logger.info(f"Fichier physique '{db_document.filepath}' supprimé pour le document {document_id}.")
except OSError as e:
logger.error(f"Erreur lors de la suppression du fichier physique '{db_document.filepath}' pour le document {document_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de la suppression du fichier sur le serveur: {e}"
)
else:
logger.warning(f"Le document {document_id} n'a pas de chemin de fichier ou le fichier n'existe pas: {db_document.filepath}")
# Supprimer l'entrée de la base de données
crud_document.delete_document(db, document_id)
return {"message": "Document supprimé avec succès."}
success = crud_document.delete_document(db, document_id)
if not success:
logger.error(f"Échec de la suppression de l'entrée du document {document_id} de la base de données.")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Échec de la suppression du document de la base de données.")
logger.info(f"Document {document_id} et son fichier physique supprimés avec succès pour l'utilisateur {current_user.id}.")
return {} # 204 No Content typically returns an empty body
# Optional: Add a route to download the actual file if needed
@router.get("/{document_id}/download", summary="Télécharger un document")
async def download_document(
document_id: int,
db: Session = Depends(get_db),
current_user: schemas_user.UserResponse = Depends(get_current_user)
):
"""
Permet à l'utilisateur authentifié de télécharger un de ses documents.
"""
logger.info(f"Tentative de téléchargement du document {document_id} par l'utilisateur {current_user.id}")
db_document = crud_document.get_document_by_id(db, document_id, current_user.id)
if not db_document:
logger.warning(f"Document {document_id} non trouvé ou non autorisé pour le téléchargement par l'utilisateur {current_user.id}")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document non trouvé ou non autorisé.")
if not os.path.exists(db_document.filepath):
logger.error(f"Fichier physique non trouvé pour le document {document_id} à l'emplacement: {db_document.filepath}")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Fichier physique non trouvé sur le serveur.")
# Return the file as a FastAPI FileResponse
return FileResponse(path=db_document.filepath, filename=db_document.filename, media_type="application/octet-stream")