108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
"""Vault management endpoints (ROADMAP #85, tranche 8).
|
|
|
|
Handlers déplacés depuis :mod:`backend.main` sans changement de
|
|
comportement : mêmes chemins (``/api/vaults*``), mêmes modèles de réponse
|
|
(``VaultInfo`` déménagé dans :mod:`backend.schemas`), mêmes dépendances
|
|
d'authentification.
|
|
|
|
Le handle du file-watcher vit désormais dans :mod:`backend.watcher_state`
|
|
(partagé avec le lifespan de ``main``) au lieu du global de ``main``.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Body, Depends, HTTPException
|
|
|
|
from backend.auth.middleware import require_admin, require_auth
|
|
from backend.indexer import add_vault_to_index, index, remove_vault_from_index
|
|
from backend.schemas import VaultActionResponse, VaultInfo, VaultsStatusResponse, VaultStatsResponse
|
|
from backend.services.vaults import list_accessible_vaults
|
|
from backend.sse import sse_manager
|
|
from backend.watcher_state import get_watcher
|
|
|
|
router = APIRouter(tags=["vaults"])
|
|
|
|
|
|
@router.get("/api/vaults", response_model=list[VaultInfo])
|
|
async def api_vaults(current_user=Depends(require_auth)):
|
|
"""List configured vaults the user has access to.
|
|
|
|
Returns:
|
|
List of vault summary objects filtered by user permissions.
|
|
"""
|
|
return list_accessible_vaults(current_user)
|
|
|
|
|
|
@router.post("/api/vaults/add", response_model=VaultStatsResponse)
|
|
async def api_add_vault(body: dict = Body(...), current_user=Depends(require_admin)):
|
|
"""Add a new vault dynamically without restarting.
|
|
|
|
Body:
|
|
name: Display name for the vault.
|
|
path: Absolute filesystem path to the vault directory.
|
|
"""
|
|
name = body.get("name", "").strip()
|
|
vault_path = body.get("path", "").strip()
|
|
|
|
if not name or not vault_path:
|
|
raise HTTPException(status_code=400, detail="Both 'name' and 'path' are required")
|
|
|
|
if name in index:
|
|
raise HTTPException(status_code=409, detail=f"Vault '{name}' already exists")
|
|
|
|
if not Path(vault_path).exists():
|
|
raise HTTPException(status_code=400, detail=f"Path does not exist: {vault_path}")
|
|
|
|
stats = await add_vault_to_index(name, vault_path)
|
|
|
|
# Start watching the new vault
|
|
watcher = get_watcher()
|
|
if watcher:
|
|
await watcher.add_vault(name, vault_path)
|
|
|
|
await sse_manager.broadcast("vault_added", {"vault": name, "stats": stats})
|
|
return {"status": "ok", "vault": name, "stats": stats}
|
|
|
|
|
|
@router.delete("/api/vaults/{vault_name}", response_model=VaultActionResponse)
|
|
async def api_remove_vault(vault_name: str, current_user=Depends(require_admin)):
|
|
"""Remove a vault from the index and stop watching it.
|
|
|
|
Args:
|
|
vault_name: Name of the vault to remove.
|
|
"""
|
|
if vault_name not in index:
|
|
raise HTTPException(status_code=404, detail=f"Vault '{vault_name}' not found")
|
|
|
|
# Stop watching
|
|
watcher = get_watcher()
|
|
if watcher:
|
|
await watcher.remove_vault(vault_name)
|
|
|
|
await remove_vault_from_index(vault_name)
|
|
await sse_manager.broadcast("vault_removed", {"vault": vault_name})
|
|
return {"status": "ok", "vault": vault_name}
|
|
|
|
|
|
@router.get("/api/vaults/status", response_model=VaultsStatusResponse)
|
|
async def api_vaults_status(current_user=Depends(require_auth)):
|
|
"""Detailed status of all vaults including watcher state.
|
|
|
|
Returns per-vault: file count, tag count, watching status, vault path.
|
|
"""
|
|
watcher = get_watcher()
|
|
statuses = {}
|
|
for vname, vdata in index.items():
|
|
watching = watcher is not None and vname in watcher.observers
|
|
statuses[vname] = {
|
|
"file_count": len(vdata.get("files", [])),
|
|
"tag_count": len(vdata.get("tags", {})),
|
|
"path": vdata.get("path", ""),
|
|
"watching": watching,
|
|
}
|
|
return {
|
|
"vaults": statuses,
|
|
"watcher_active": watcher is not None,
|
|
"sse_clients": sse_manager.client_count,
|
|
}
|