187 lines
No EOL
9.2 KiB
Python
187 lines
No EOL
9.2 KiB
Python
# 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 # 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
|
|
# 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
|
|
# 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 # For UserResponse schema
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(
|
|
prefix="/documents",
|
|
tags=["Documents"],
|
|
responses={404: {"description": "Not found"}},
|
|
)
|
|
|
|
@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),
|
|
current_user: schemas_user.UserResponse = Depends(get_current_user)
|
|
):
|
|
"""
|
|
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.
|
|
"""
|
|
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=f"Seuls les fichiers {', '.join(allowed_extensions).upper()} sont autorisés."
|
|
)
|
|
|
|
upload_dir = settings.UPLOADS_DIR # Utilisez le chemin absolu configuré dans settings
|
|
os.makedirs(upload_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:
|
|
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()
|
|
|
|
# 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], 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)
|
|
):
|
|
"""
|
|
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, 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 appartenant à l'utilisateur courant.
|
|
"""
|
|
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:
|
|
# 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, summary="Supprimer un document par ID")
|
|
async def delete_document(
|
|
document_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: schemas_user.UserResponse = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Supprime un document spécifique de l'utilisateur authentifié,
|
|
à la fois de la base de données et du système de fichiers.
|
|
"""
|
|
logger.info(f"Tentative de suppression du document {document_id} pour l'utilisateur {current_user.id}")
|
|
|
|
# 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
|
|
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") |