feat(v2): enforce repository safety invariants

This commit is contained in:
2026-07-27 20:25:27 +02:00
parent 1115f63879
commit 448fa98629
2 changed files with 84 additions and 7 deletions
+42 -1
View File
@@ -3,6 +3,7 @@ import base64
import hashlib
from collections.abc import AsyncIterator
from datetime import UTC, datetime
from pathlib import Path
from typing import Annotated, Any, cast
from fastapi import Depends, FastAPI, Header, Request, Response
@@ -17,7 +18,7 @@ from backup_tool.cli import build_alembic_config
from backup_tool.config import Settings
from backup_tool.db.engine import SchemaNotCurrentError, assert_schema_current, create_engine
from backup_tool.db.models import ApiToken, AuditEvent, IdempotencyRecord, Repository, Secret, User
from backup_tool.repository import RepositoryError, initialize
from backup_tool.repository import RepositoryError, initialize, inspect_repository
from backup_tool.security.auth import (
hash_password,
hash_token,
@@ -490,6 +491,26 @@ def create_app(settings: Settings) -> FastAPI:
"encryption": repository.encryption,
}
@app.get("/api/v2/repositories")
async def list_repositories(
db: Annotated[AsyncSession, Depends(session)],
_: Annotated[tuple[User, set[str], bool], Depends(actor)],
) -> dict[str, Any]:
items = list((await db.scalars(select(Repository).order_by(Repository.name))).all())
return {
"items": [
{
"id": item.id,
"name": item.name,
"format_version": item.format_version,
"compression": item.compression,
"encryption": item.encryption,
"state": item.state,
}
for item in items
]
}
@app.get("/api/v2/repositories/{repository_id}")
async def get_repository(
repository_id: str,
@@ -508,6 +529,26 @@ def create_app(settings: Settings) -> FastAPI:
"state": repository.state,
}
@app.get("/api/v2/repositories/{repository_id}/inspection")
async def inspect_repository_endpoint(
repository_id: str,
db: Annotated[AsyncSession, Depends(session)],
_: Annotated[tuple[User, set[str], bool], Depends(actor)],
) -> dict[str, Any]:
repository = await db.get(Repository, repository_id)
if repository is None:
raise Problem(404, "resource_not_found", "Repository was not found.")
try:
inspected = inspect_repository(Path(repository.root))
except RepositoryError as error:
raise Problem(409, "repository_invalid", str(error)) from error
return {
"id": repository.id,
"format_version": inspected.format_version,
"compression": inspected.compression,
"encryption": inspected.encryption,
}
@app.patch("/api/v2/repositories/{repository_id}")
async def patch_repository(
repository_id: str,