from __future__ import annotations from io import BytesIO from fastapi import APIRouter, Depends from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from govoplan_core.auth import ApiPrincipal, require_scope from govoplan_files.backend.schemas import ( BulkDeleteRequest, BulkDeleteResponse, FileAssetResponse, ) from govoplan_core.db.session import get_session from govoplan_files.backend.storage.common import FileStorageError from govoplan_files.backend.storage.files import ( get_asset_for_user, read_asset_bytes, soft_delete_assets, ) from govoplan_files.backend.route_support import ( _asset_response, _attachment_disposition, _audit_connector_event, _http_error, _is_admin, ) router = APIRouter(prefix="/files", tags=["files"]) @router.get("/{file_id}", response_model=FileAssetResponse) def get_file( file_id: str, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:read")), ): try: asset = get_asset_for_user( session, tenant_id=principal.tenant_id, user_id=principal.user.id, asset_id=file_id, is_admin=_is_admin(principal), ) return _asset_response(session, asset, include_shares=True) except FileStorageError as exc: raise _http_error(exc, not_found=True) from exc @router.get("/{file_id}/download") def download_file( file_id: str, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:download")), ): try: asset = get_asset_for_user( session, tenant_id=principal.tenant_id, user_id=principal.user.id, asset_id=file_id, is_admin=_is_admin(principal), ) data, version, blob = read_asset_bytes(session, asset) _audit_connector_event( session, principal, action="files.connector.accessed", asset=asset, version=version, blob=blob, operation="download", commit=True, ) except FileStorageError as exc: raise _http_error(exc, not_found=True) from exc headers = {"Content-Disposition": _attachment_disposition(asset.filename)} return StreamingResponse( BytesIO(data), media_type=blob.content_type or "application/octet-stream", headers=headers, ) @router.delete("/{file_id}", response_model=BulkDeleteResponse) def delete_file( file_id: str, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:delete")), ): try: asset = get_asset_for_user( session, tenant_id=principal.tenant_id, user_id=principal.user.id, asset_id=file_id, require_write=True, is_admin=_is_admin(principal), ) count = soft_delete_assets(session, [asset]) session.commit() return BulkDeleteResponse(deleted_count=count) except FileStorageError as exc: session.rollback() raise _http_error(exc, not_found=True) from exc @router.post("/bulk-delete", response_model=BulkDeleteResponse) def bulk_delete_files( payload: BulkDeleteRequest, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:delete")), ): try: assets = [ get_asset_for_user( session, tenant_id=principal.tenant_id, user_id=principal.user.id, asset_id=file_id, require_write=True, is_admin=_is_admin(principal), ) for file_id in payload.file_ids ] count = soft_delete_assets(session, assets) session.commit() return BulkDeleteResponse(deleted_count=count) except FileStorageError as exc: session.rollback() raise _http_error(exc) from exc