# [DEF:storage_routes:Module] # # @SEMANTICS: storage, files, upload, download, backup, repository # @PURPOSE: API endpoints for file storage management (backups and repositories). # @LAYER: API # @RELATION: DEPENDS_ON -> backend.src.models.storage # # @INVARIANT: All paths must be validated against path traversal. # [SECTION: IMPORTS] from fastapi import APIRouter, Depends, UploadFile, File, Form, HTTPException from fastapi.responses import FileResponse from typing import List, Optional from ...models.storage import StoredFile, FileCategory from ...dependencies import get_plugin_loader from ...plugins.storage.plugin import StoragePlugin from ...core.logger import belief_scope # [/SECTION] router = APIRouter(tags=["storage"]) # [DEF:list_files:Function] # @PURPOSE: List all files and directories in the storage system. # # @PRE: None. # @POST: Returns a list of StoredFile objects. # # @PARAM: category (Optional[FileCategory]) - Filter by category. # @PARAM: path (Optional[str]) - Subpath within the category. # @RETURN: List[StoredFile] - List of files/directories. # # @RELATION: CALLS -> StoragePlugin.list_files @router.get("/files", response_model=List[StoredFile]) async def list_files( category: Optional[FileCategory] = None, path: Optional[str] = None, plugin_loader=Depends(get_plugin_loader) ): with belief_scope("list_files"): storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager") if not storage_plugin: raise HTTPException(status_code=500, detail="Storage plugin not loaded") return storage_plugin.list_files(category, path) # [/DEF:list_files:Function] # [DEF:upload_file:Function] # @PURPOSE: Upload a file to the storage system. # # @PRE: category must be a valid FileCategory. # @PRE: file must be a valid UploadFile. # @POST: Returns the StoredFile object of the uploaded file. # # @PARAM: category (FileCategory) - Target category. # @PARAM: path (Optional[str]) - Target subpath. # @PARAM: file (UploadFile) - The file content. # @RETURN: StoredFile - Metadata of the uploaded file. # # @SIDE_EFFECT: Writes file to the filesystem. # # @RELATION: CALLS -> StoragePlugin.save_file @router.post("/upload", response_model=StoredFile, status_code=201) async def upload_file( category: FileCategory = Form(...), path: Optional[str] = Form(None), file: UploadFile = File(...), plugin_loader=Depends(get_plugin_loader) ): with belief_scope("upload_file"): storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager") if not storage_plugin: raise HTTPException(status_code=500, detail="Storage plugin not loaded") try: return await storage_plugin.save_file(file, category, path) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) # [/DEF:upload_file:Function] # [DEF:delete_file:Function] # @PURPOSE: Delete a specific file or directory. # # @PRE: category must be a valid FileCategory. # @POST: Item is removed from storage. # # @PARAM: category (FileCategory) - File category. # @PARAM: path (str) - Relative path of the item. # @RETURN: None # # @SIDE_EFFECT: Deletes item from the filesystem. # # @RELATION: CALLS -> StoragePlugin.delete_file @router.delete("/files/{category}/{path:path}", status_code=204) async def delete_file(category: FileCategory, path: str, plugin_loader=Depends(get_plugin_loader)): with belief_scope("delete_file"): storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager") if not storage_plugin: raise HTTPException(status_code=500, detail="Storage plugin not loaded") try: storage_plugin.delete_file(category, path) except FileNotFoundError: raise HTTPException(status_code=404, detail="File not found") except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) # [/DEF:delete_file:Function] # [DEF:download_file:Function] # @PURPOSE: Retrieve a file for download. # # @PRE: category must be a valid FileCategory. # @POST: Returns a FileResponse. # # @PARAM: category (FileCategory) - File category. # @PARAM: path (str) - Relative path of the file. # @RETURN: FileResponse - The file content. # # @RELATION: CALLS -> StoragePlugin.get_file_path @router.get("/download/{category}/{path:path}") async def download_file(category: FileCategory, path: str, plugin_loader=Depends(get_plugin_loader)): with belief_scope("download_file"): storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager") if not storage_plugin: raise HTTPException(status_code=500, detail="Storage plugin not loaded") try: abs_path = storage_plugin.get_file_path(category, path) filename = Path(path).name return FileResponse(path=abs_path, filename=filename) except FileNotFoundError: raise HTTPException(status_code=404, detail="File not found") except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) # [/DEF:download_file:Function] # [/DEF:storage_routes:Module]