guarded repository delete flow

This commit is contained in:
2026-04-26 02:36:27 +02:00
parent 9a6b8ea6bf
commit 856fc1cfa0
2 changed files with 43 additions and 2 deletions

View File

@@ -3,10 +3,11 @@ from __future__ import annotations
from dataclasses import asdict
from html import escape
from fastapi import APIRouter, Depends, Form
from fastapi import APIRouter, Depends, Form, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from repo_registry.core.service import RegistryService
from repo_registry.storage.sqlite import NotFoundError
from repo_registry.web_api.app import get_service
@@ -264,7 +265,10 @@ def repository_detail(
repository_id: int,
service: RegistryService = Depends(get_service),
) -> HTMLResponse:
repository = service.get_repository(repository_id)
try:
repository = service.get_repository(repository_id)
except NotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
runs = service.list_analysis_runs(repository_id)
ability_map = service.ability_map(repository_id)
decisions = service.list_review_decisions(repository_id)
@@ -358,6 +362,13 @@ def repository_detail(
<h2>Review Decisions</h2>
{render_review_decisions(decisions)}
</section>
<section class="panel" style="margin-top:18px">
<h2>Delete Repository</h2>
<form class="stack" method="post" action="/ui/repos/{repository_id}/delete">
<label>Confirm repository name <input name="confirm_name" placeholder="{escape(repository.name)}" required></label>
<button class="secondary" type="submit">Delete Repository</button>
</form>
</section>
"""
return page(repository.name, body)
@@ -379,6 +390,19 @@ def edit_repository_from_form(
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
@router.post("/ui/repos/{repository_id}/delete")
def delete_repository_from_form(
repository_id: int,
confirm_name: str = Form(...),
service: RegistryService = Depends(get_service),
) -> RedirectResponse:
repository = service.get_repository(repository_id)
if confirm_name != repository.name:
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
service.delete_repository(repository_id)
return RedirectResponse("/ui", status_code=303)
@router.post("/ui/repos/{repository_id}/abilities")
def create_ability_from_form(
repository_id: int,

View File

@@ -594,6 +594,23 @@ def test_ui_manual_registry_entry_loop(tmp_path):
detail_response = client.get(repository_path)
assert "Edited Manual API" not in detail_response.text
assert "tests/test_manual.py" not in detail_response.text
failed_delete_response = client.post(
f"{repository_path}/delete",
data={"confirm_name": "wrong name"},
follow_redirects=False,
)
assert failed_delete_response.status_code == 303
assert client.get(repository_path).status_code == 200
delete_repository_response = client.post(
f"{repository_path}/delete",
data={"confirm_name": "Manual Repo"},
follow_redirects=False,
)
assert delete_repository_response.status_code == 303
assert delete_repository_response.headers["location"] == "/ui"
assert client.get(repository_path).status_code == 404
finally:
app.dependency_overrides.clear()