Players row edit: form action UPDATE + audit log in same transaction #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Feature
Lineage
Depends on players list view ticket. Completes the WRITE half of the core story.
Repo
forgejo_admin/westside-adminUser Story
story-westside-admin-admin-row-crud. The WRITE half. Once this lands, M3 milestone (Marcus successfully edits a row without Lucas's involvement) is achievable.Context
Click a row in the players list → editable form for that player. SvelteKit form action handles POST, validates input via Drizzle column metadata (NOT NULL, length, enum values), runs UPDATE within a transaction that also writes a
contract_audit_logrow capturing the diff. Either both succeed or both fail.This is the "legitimized write surface" replacing banned ad-hoc psql UPDATEs. The audit log is non-negotiable — every mutation must be traceable to a Keycloak user via
actor(=locals.user.email) + timestamp + table + before/after state.See
arch-dataflow-westside-adminFlow 2.File Targets
Create:
src/routes/players/[id]/+page.server.ts—load()fetches single player;actions.defaultvalidates + UPDATEs + writes audit row in transactionsrc/routes/players/[id]/+page.svelte— form with type-correct inputs per column; uses SvelteKitenhance; shows ActionResult feedbacksrc/lib/components/inputs/EnumSelect.svelte—<select>for enum columnssrc/lib/components/inputs/JsonbEditor.svelte—<textarea>with JSON validation on blursrc/lib/components/inputs/DatePicker.svelte—<input type="date">with formattingsrc/lib/server/audit.ts— helper:writeAudit({ table, rowId, actor, oldState, newState }, tx)— writes tocontract_audit_logModify:
src/routes/players/+page.svelte— make rows clickable, link to/players/:idDo NOT create:
Acceptance Criteria
/players/:idshows form with current values populated/players/:idwith a flash message "Saved"Test Expectations
monthly_fee, assert DB row updated AND contract_audit_log row written, both committedConstraints
db.transaction()actorfield is the user's email (most human-readable); consider also storingsub(Keycloak UUID) for stabilityuse:enhanceupgrades but doesn't require)session_2026_04_12_contract_emailsChecklist
Related
project-westside-adminarch-dataflow-westside-adminFlow 2feedback_never_write_prod_db(this ticket replaces it)Scope Review: READY
Review note:
review-1093-2026-04-25Spec is solid. Transaction atomicity is rigorously specified (UPDATE + audit INSERT in same Drizzle tx with deliberate-failure test). Traceability complete (story-westside-admin-admin-row-crud verified, arch:page-server pattern documented in arch-dataflow-westside-admin Flow 2). All 6 Create file targets use conventional SvelteKit paths.
Pre-flight checks for Ava at todo->next_up:
[SCOPE]Confirmcontract_audit_logtable exists in westside Postgres. If missing, a migration ticket must land before this one.src/routes/players/+page.svelte) is created by #4.Optional body polish (non-blocking):
[BODY]Add a one-liner thatdb.transaction(async (tx) => ...)requires usingtxfor both statements (not the outerdb) — common Drizzle footgun.[BODY]Specify "Saved" flash mechanism (query param / cookie / load() return).Decomposition: NOT needed. 9 AC and ~7 files but single transaction unit + 3 tightly-coupled inputs — splitting would fragment the atomicity story. If the dev agent overruns 5 minutes, follow-up ticket for input components as reusable primitives.
Cleared for
backlog -> todo.