from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, status from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_user, get_db from app.models.files import ReferenceFileType from app.schemas.files import ( FileUploadResponse, ReferenceFileResponse, TMFileResponse, ) from app.services.audit_service import AuditService from app.services.file_service import FileService router = APIRouter(prefix="/files", tags=["files"]) file_service = FileService() audit_service = AuditService() # ---- TM Files ---- @router.post("/tm", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED) async def upload_tm_file( client_id: UUID = Query(...), locale_code: str = Query(...), channel: str = Query(...), file: UploadFile = File(...), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> FileUploadResponse: """Upload a Translation Memory (JSONL) file.""" if not file.filename: raise HTTPException(status_code=400, detail="File must have a filename") if not file_service.validate_file_extension(file.filename, [".jsonl", ".json"]): raise HTTPException(status_code=400, detail="Only .jsonl/.json files accepted") tm = await file_service.upload_tm_file( db, client_id, locale_code, channel, file.file, file.filename, uploaded_by=current_user["user_id"], ) await audit_service.log( db, action="upload_tm", entity_type="tm_file", entity_id=str(tm.id), user_id=current_user["user_id"], details={"filename": tm.filename, "locale": locale_code, "channel": channel, "segments": tm.segment_count}, ) await db.commit() return FileUploadResponse( id=tm.id, filename=tm.filename, file_path=tm.file_path, message=f"Uploaded TM file with {tm.segment_count} segments", ) @router.get("/tm", response_model=list[TMFileResponse]) async def list_tm_files( client_id: UUID = Query(...), locale_code: str | None = Query(None), channel: str | None = Query(None), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> list[TMFileResponse]: """List TM files for a client.""" files = await file_service.list_tm_files(db, client_id, locale_code, channel) return [TMFileResponse.model_validate(f) for f in files] @router.get("/tm/{file_id}/download") async def download_tm_file( file_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> FileResponse: """Download a TM file.""" from sqlalchemy import select from app.models.files import TMFileRegistry result = await db.execute( select(TMFileRegistry).where(TMFileRegistry.id == file_id) ) tm = result.scalar_one_or_none() if tm is None: raise HTTPException(status_code=404, detail="TM file not found") path = file_service.get_file_path(tm.file_path) if path is None: raise HTTPException(status_code=404, detail="File not found on disk") return FileResponse(path=str(path), filename=tm.filename) @router.delete("/tm/{file_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_tm_file( file_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> None: """Delete a TM file.""" deleted = await file_service.delete_tm_file(db, file_id) if not deleted: raise HTTPException(status_code=404, detail="TM file not found") await audit_service.log( db, action="delete_tm", entity_type="tm_file", entity_id=str(file_id), user_id=current_user["user_id"], ) await db.commit() # ---- Reference Files ---- @router.post( "/reference", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED, ) async def upload_reference_file( client_id: UUID = Query(...), file_type: ReferenceFileType = Query(...), locale_scope: str = Query(...), file: UploadFile = File(...), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> FileUploadResponse: """Upload a reference file (glossary, blacklist, TOV, etc.).""" if not file.filename: raise HTTPException(status_code=400, detail="File must have a filename") ref = await file_service.upload_reference_file( db, client_id, file_type, locale_scope, file.file, file.filename, uploaded_by=current_user["user_id"], ) await audit_service.log( db, action="upload_reference", entity_type="reference_file", entity_id=str(ref.id), user_id=current_user["user_id"], details={"filename": ref.filename, "file_type": file_type.value, "locale_scope": locale_scope}, ) await db.commit() return FileUploadResponse( id=ref.id, filename=ref.filename, file_path=ref.file_path, message=f"Uploaded {file_type.value} reference file", ) @router.get("/reference", response_model=list[ReferenceFileResponse]) async def list_reference_files( client_id: UUID = Query(...), file_type: ReferenceFileType | None = Query(None), locale_scope: str | None = Query(None), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> list[ReferenceFileResponse]: """List reference files for a client.""" files = await file_service.list_reference_files( db, client_id, file_type, locale_scope ) return [ReferenceFileResponse.model_validate(f) for f in files] @router.get("/reference/{file_id}/download") async def download_reference_file( file_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> FileResponse: """Download a reference file.""" from sqlalchemy import select from app.models.files import ReferenceFile result = await db.execute( select(ReferenceFile).where(ReferenceFile.id == file_id) ) ref = result.scalar_one_or_none() if ref is None: raise HTTPException(status_code=404, detail="Reference file not found") path = file_service.get_file_path(ref.file_path) if path is None: raise HTTPException(status_code=404, detail="File not found on disk") return FileResponse(path=str(path), filename=ref.filename) @router.delete("/reference/{file_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_reference_file( file_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> None: """Delete a reference file.""" deleted = await file_service.delete_reference_file(db, file_id) if not deleted: raise HTTPException(status_code=404, detail="Reference file not found") await audit_service.log( db, action="delete_reference", entity_type="reference_file", entity_id=str(file_id), user_id=current_user["user_id"], ) await db.commit()