from __future__ import annotations from typing import Literal from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from govoplan_core.auth import ApiPrincipal, require_scope from govoplan_files.backend.schemas import ( FileFolderCreateRequest, FileFolderDeleteRequest, FileFolderDeleteResponse, FileFolderResponse, FileFoldersResponse, ) from govoplan_core.db.session import get_session from govoplan_files.backend.storage.paths import UnsafeFilePathError from govoplan_files.backend.storage.common import FileStorageError from govoplan_files.backend.storage.folders import ( create_folder, list_folders_for_user, list_folders_for_user_window, soft_delete_folder, ) from govoplan_files.backend.route_support import ( _folder_response, _http_error, _is_admin, ) from govoplan_files.backend.services.list_queries import ( FOLDERS_LIST_CURSOR_SCOPE, _cursor_page_size, _files_delta_watermark, _folder_cursor_values, _folders_list_fingerprint, _next_folder_list_cursor, ) router = APIRouter(prefix="/files", tags=["files"]) @router.get("/folders", response_model=FileFoldersResponse) def list_file_folders( owner_type: Literal["user", "group"], owner_id: str, page_size: int | None = Query(default=None, ge=1, le=1000), cursor: str | None = None, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:read")), ): try: watermark = _files_delta_watermark(session, principal.tenant_id) effective_page_size = _cursor_page_size( FOLDERS_LIST_CURSOR_SCOPE, cursor, page_size ) if effective_page_size is None: folders = list_folders_for_user( session, tenant_id=principal.tenant_id, user_id=principal.user.id, owner_type=owner_type, owner_id=owner_id, is_admin=_is_admin(principal), ) return FileFoldersResponse( folders=[_folder_response(folder) for folder in folders], watermark=watermark, ) fingerprint = _folders_list_fingerprint( principal, owner_type=owner_type, owner_id=owner_id, page_size=effective_page_size, ) after_path, after_id = _folder_cursor_values(cursor, fingerprint=fingerprint) folders, has_more = list_folders_for_user_window( session, tenant_id=principal.tenant_id, user_id=principal.user.id, owner_type=owner_type, owner_id=owner_id, is_admin=_is_admin(principal), page_size=effective_page_size, after_path=after_path, after_id=after_id, ) return FileFoldersResponse( folders=[_folder_response(folder) for folder in folders], cursor=cursor, next_cursor=_next_folder_list_cursor( principal, folders, owner_type=owner_type, owner_id=owner_id, page_size=effective_page_size, has_more=has_more, ), watermark=watermark, ) except FileStorageError as exc: raise _http_error(exc) from exc @router.post("/folders", response_model=FileFolderResponse) def create_file_folder( payload: FileFolderCreateRequest, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:organize")), ): try: folder = create_folder( session, tenant_id=principal.tenant_id, owner_type=payload.owner_type, owner_id=payload.owner_id, user_id=principal.user.id, path=payload.path, is_admin=_is_admin(principal), ) session.commit() return _folder_response(folder) except (FileStorageError, UnsafeFilePathError, ValueError) as exc: session.rollback() raise _http_error(exc) from exc @router.post("/folders/delete", response_model=FileFolderDeleteResponse) def delete_file_folder( payload: FileFolderDeleteRequest, session: Session = Depends(get_session), principal: ApiPrincipal = Depends(require_scope("files:file:delete")), ): try: deleted_folders, deleted_files = soft_delete_folder( session, tenant_id=principal.tenant_id, owner_type=payload.owner_type, owner_id=payload.owner_id, user_id=principal.user.id, path=payload.path, recursive=payload.recursive, is_admin=_is_admin(principal), ) session.commit() return FileFolderDeleteResponse( deleted_folders=deleted_folders, deleted_files=deleted_files ) except (FileStorageError, UnsafeFilePathError, ValueError) as exc: session.rollback() raise _http_error(exc) from exc