edit sources

This commit is contained in:
Abel Luck 2026-03-30 13:49:00 +02:00
parent 847aeae772
commit 328a70ff9b
7 changed files with 512 additions and 38 deletions

View file

@ -18,12 +18,15 @@ from repub.datastar import RefreshBroker, render_stream
from repub.model import (
create_source,
initialize_database,
load_source_form,
load_sources,
source_slug_exists,
update_source,
)
from repub.pages import (
create_source_page,
dashboard_page,
edit_source_page,
execution_logs_page,
runs_page,
shim_page,
@ -85,12 +88,15 @@ def create_app() -> Quart:
@app.get("/")
@app.get("/sources")
@app.get("/sources/create")
@app.get("/sources/<string:slug>/edit")
@app.get("/runs")
@app.get("/job/<int:job_id>/execution/<int:execution_id>/logs")
async def page_shim(
job_id: int | None = None, execution_id: int | None = None
slug: str | None = None,
job_id: int | None = None,
execution_id: int | None = None,
) -> Response:
del job_id, execution_id
del slug, job_id, execution_id
body, etag = _render_shim_page(
stylesheet_href=url_for("static", filename="app.css"),
datastar_src=url_for("static", filename="datastar@1.0.0-RC.8.js"),
@ -116,6 +122,10 @@ def create_app() -> Quart:
async def create_source_patch() -> DatastarResponse:
return _page_patch_response(app, lambda: render_create_source(app))
@app.post("/sources/<string:slug>/edit")
async def edit_source_patch(slug: str) -> DatastarResponse:
return _page_patch_response(app, lambda: render_edit_source(slug))
@app.post("/actions/sources/create")
async def create_source_action() -> DatastarResponse:
signals = cast(dict[str, object], await read_signals())
@ -140,6 +150,30 @@ def create_app() -> Quart:
trigger_refresh(app)
return DatastarResponse(SSE.redirect("/sources"))
@app.post("/actions/sources/<string:slug>/edit")
async def edit_source_action(slug: str) -> DatastarResponse:
signals = cast(dict[str, object], await read_signals())
source, error = validate_source_form(
signals,
slug_exists=lambda candidate: candidate != slug
and source_slug_exists(candidate),
immutable_slug=slug,
)
if error is not None:
return DatastarResponse(
SSE.patch_signals({"_formError": error, "_formSuccess": ""})
)
assert source is not None
if update_source(slug, **source) is None:
return DatastarResponse(
SSE.patch_signals(
{"_formError": "Source does not exist.", "_formSuccess": ""}
)
)
trigger_refresh(app)
return DatastarResponse(SSE.redirect("/sources"))
@app.post("/runs")
async def runs_patch() -> DatastarResponse:
return _page_patch_response(app, render_runs)
@ -176,6 +210,17 @@ async def render_create_source(app: Quart | None = None) -> Renderable:
return create_source_page()
async def render_edit_source(slug: str) -> Renderable:
source = load_source_form(slug)
if source is None:
return sources_page(sources=())
return edit_source_page(
slug=slug,
source=source,
action_path=f"/actions/sources/{slug}/edit",
)
async def render_runs() -> Renderable:
return runs_page()
@ -208,6 +253,7 @@ def validate_source_form(
signals: dict[str, object] | None,
*,
slug_exists: Callable[[str], bool],
immutable_slug: str | None = None,
) -> tuple[SourceFormData | None, str | None]:
if signals is None:
return None, "Missing form data."
@ -235,6 +281,8 @@ def validate_source_form(
errors.append("Source name is required.")
if source_slug == "":
errors.append("Slug is required.")
elif immutable_slug is not None and source_slug != immutable_slug:
errors.append("Slug is immutable.")
elif slug_exists(source_slug):
errors.append("Slug must be unique.")