c6f1d73ff5
Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
2.4 KiB
Python
78 lines
2.4 KiB
Python
"""RESTful file browser API (GET + ETag). Legacy mutations remain on /api/sync/*."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from server.api.auth_jwt import get_current_admin
|
|
from server.application.services.files_browse_service import (
|
|
etag_for_payload,
|
|
get_cached_browse,
|
|
list_remote_directory,
|
|
set_cached_browse,
|
|
)
|
|
from server.domain.models import Admin
|
|
|
|
router = APIRouter(prefix="/api/files", tags=["files"])
|
|
|
|
|
|
async def _audit_browse(
|
|
request: Request,
|
|
server_id: int,
|
|
path: str,
|
|
admin_username: str,
|
|
server_name: str,
|
|
) -> None:
|
|
from server.api.sync_v2 import _audit_sync
|
|
|
|
await _audit_sync(
|
|
"browse_directory",
|
|
"server",
|
|
server_id,
|
|
f"Browse {server_name}:{path}",
|
|
admin_username,
|
|
request,
|
|
)
|
|
|
|
|
|
@router.get("/browse")
|
|
async def browse_directory_get(
|
|
request: Request,
|
|
server_id: int,
|
|
path: str = "/",
|
|
admin: Admin = Depends(get_current_admin),
|
|
if_none_match: str | None = Header(default=None, alias="If-None-Match"),
|
|
):
|
|
"""Browse remote directory via SSH. Supports ETag / If-None-Match to skip SSH when unchanged."""
|
|
from server.infrastructure.database.server_repo import ServerRepositoryImpl
|
|
from server.utils.posix_paths import PosixPathError, normalize_remote_abs_path
|
|
|
|
session = request.state.db
|
|
repo = ServerRepositoryImpl(session)
|
|
server = await repo.get_by_id(server_id)
|
|
if not server:
|
|
raise HTTPException(status_code=404, detail="Server not found")
|
|
|
|
try:
|
|
norm_path = normalize_remote_abs_path(path)
|
|
except PosixPathError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
|
|
client_etag = (if_none_match or "").strip()
|
|
if client_etag:
|
|
cached = get_cached_browse(server_id, norm_path)
|
|
if cached and cached[0] == client_etag:
|
|
return Response(status_code=304, headers={"ETag": client_etag})
|
|
|
|
payload = await list_remote_directory(session, server_id, norm_path)
|
|
etag = etag_for_payload(payload)
|
|
set_cached_browse(server_id, norm_path, etag, payload)
|
|
|
|
await _audit_browse(request, server_id, norm_path, admin.username, server.name)
|
|
|
|
if client_etag and client_etag == etag:
|
|
return Response(status_code=304, headers={"ETag": etag})
|
|
|
|
return JSONResponse(content=payload, headers={"ETag": etag})
|