Files
ai-contract-platform/backend/app/api/files.py
T

187 lines
6.2 KiB
Python

from typing import Optional
from urllib.parse import quote
from fastapi import APIRouter, Depends, File as FastAPIFile, Form, Header, Request, Response, UploadFile
from sqlalchemy.orm import Session
from app.core.response import success_response
from app.db import get_db_session
from app.services.audit_log_service import AuditLogService
from app.services.auth_service import AuthService
from app.services.file_service import FileService
from app.services.tenant_service import TenantService
router = APIRouter(prefix="/files", tags=["files"])
ROUTE_PERMISSION_SCOPE = "file_management"
ROUTE_PERMISSION_ACTIONS = {
"list": "files:list",
"upload": "files:upload",
"detail": "files:detail",
"delete": "files:delete",
"download_url": "files:download_url",
"download": "files:download",
}
def get_file_service(db: Session = Depends(get_db_session)) -> FileService:
return FileService(db)
def get_audit_log_service(db: Session = Depends(get_db_session)) -> AuditLogService:
return AuditLogService(db)
def get_tenant_service(db: Session = Depends(get_db_session)) -> TenantService:
return TenantService(db)
def require_current_user(
authorization: Optional[str] = Header(default=None),
db: Session = Depends(get_db_session),
):
return AuthService(db).require_current_user(authorization)
@router.get("")
def list_files(
page: int = 1,
page_size: int = 20,
file_type: Optional[str] = None,
file_service: FileService = Depends(get_file_service),
current_user: dict = Depends(require_current_user),
):
return success_response(file_service.list_files(current_user, page, page_size, file_type))
@router.post("")
def upload_file(
file: UploadFile = FastAPIFile(...),
file_type: str = Form(...),
bucket: str = Form(default="default"),
file_service: FileService = Depends(get_file_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
tenant_service: TenantService = Depends(get_tenant_service),
current_user: dict = Depends(require_current_user),
):
tenant_service.assert_billing_allows(
current_user,
"storage",
requested_file_size_bytes=int(getattr(file, "size", 0) or 0),
)
result = file_service.upload_file(current_user, file, file_type, bucket)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="upload_file",
resource_type="file",
resource_id=result["id"],
detail_json={
"file_name": result["file_name"],
"file_type": result["file_type"],
"file_size": result["file_size"],
"mime_type": result["mime_type"],
"file_hash": result["file_hash"],
"bucket": result["bucket"],
},
)
return success_response(result)
@router.get("/{file_id}")
def get_file(
file_id: str,
file_service: FileService = Depends(get_file_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
result = file_service.get_file(current_user, file_id)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="get_file",
resource_type="file",
resource_id=file_id,
detail_json={
"file_name": result["file_name"],
"file_type": result["file_type"],
"mime_type": result["mime_type"],
"file_size": result["file_size"],
"file_hash": result["file_hash"],
},
)
return success_response(result)
@router.delete("/{file_id}")
def delete_file(
file_id: str,
file_service: FileService = Depends(get_file_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
file_detail = file_service.get_file(current_user, file_id)
success = file_service.delete_file(current_user, file_id)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="delete_file",
resource_type="file",
resource_id=file_id,
detail_json={
"file_name": file_detail["file_name"],
"file_hash": file_detail["file_hash"],
"mime_type": file_detail["mime_type"],
"file_size": file_detail["file_size"],
"file_type": file_detail["file_type"],
},
)
return success_response({"success": success})
@router.get("/{file_id}/download-url")
def get_download_url(
file_id: str,
file_service: FileService = Depends(get_file_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
result = file_service.get_download_url(current_user, file_id)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="get_download_url",
resource_type="file",
resource_id=file_id,
detail_json={
"file_name": result["file_name"],
"download_url": result["download_url"],
"expires_in": result["expires_in"],
},
)
return success_response(result)
@router.get("/{file_id}/download")
def download_file(
file_id: str,
request: Request,
file_service: FileService = Depends(get_file_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
result = file_service.get_download_payload(current_user, file_id)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="download_file",
resource_type="file",
resource_id=file_id,
detail_json={"file_name": result["file_name"]},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
headers = {
"Content-Disposition": f"attachment; filename*=UTF-8''{quote(result['file_name'])}",
}
return Response(content=result["content"], media_type=result["mime_type"], headers=headers)