project-page westside-admin
project-westside-admin updated 2026-05-03

westside-admin

Vision

westside-admin is the data administration tool for the basketball-api Postgres database. It uses Drizzle ORM with live-introspected schemas to provide type-safe CRUD over visible tables and columns, gated by Keycloak admin role. Built as a SvelteKit adapter-node app so Drizzle queries run server-side and rows flow into components as typed data. Replaces direct psql access (banned per feedback_never_write_prod_db) and reduces dependency on basketball-api for admin-route work. Public URL: https://westside-admin.tail5b443a.ts.net.

User Stories

Key Story Note Role Success Metric
admin-row-crud story-westside-admin-admin-row-crud Admin (Lucas, Marcus) Zero psql UPDATEs by Lucas + zero "fix X" texts from Marcus over 30 days

One story for v1. Earlier draft had 5 stories; 3 of them (keycloak-admin-gate, tenant-scoping, schema-introspection) were re-classified as Safety Constraints (guarantees on the core story, not user-visible value). 2 of them (table-browser, row-edit) were two halves of the same story and collapsed into admin-row-crud.

Safety Constraints

These are guarantees enforced on the user story, not separate user stories. Every ticket on the board carries story:admin-row-crud; constraints are tracked here as project-level invariants.

  1. Keycloak admin gate. Cookie SSR auth via westside-basketball realm. JWKS validation per request. HttpOnly + Secure + SameSite=Lax token cookie — no Bearer in browser. Public Tailscale funnel permitted only because gate is airtight per feedback_funnel_requires_auth.
  2. Tenant scoping. Query helper wraps Drizzle db, auto-injects WHERE tenant_id = ? for tenant-scoped tables. v1: hardcoded TENANT_ID = 1. Direct unscoped db not exported. Lint or CI rule prevents bypass.
  3. Schema introspection. Drizzle schema generated from live Postgres via drizzle-kit pull; checked into git; CI fails on drift between checked-in schema and live DB.

Architecture

  1. arch-domain-westside-admin — Domain Model (22 entities, relationships, sensitive columns)
  2. arch-dataflow-westside-admin — Data Flow (auth + page load + mutation sequences)
  3. arch-deployment-westside-admin — Deployment (k3s + Tailscale funnel + Keycloak + cross-ns Postgres)

Key technical decisions inlined:

  • SvelteKit adapter-node (forced by Drizzle server-side requirement)
  • Cookie SSR auth (HttpOnly cookies, no keycloak-js in browser)
  • Public Tailscale funnel (matched to westside-app's accessibility model; admin gate satisfies funnel-auth rule)
  • Image-build pipeline: kaniko → Harbor → kustomize-tag bump → ArgoCD (matches every other westside-* and pal-e-* service in this cluster)
  • First feature: hand-rolled players list+edit; introspection-driven generic browser is the second iteration after the players seam ships

Board

Primary kanban: board-westside-admin

Columns: Backlog → Todo (review gate) → Next Up → In Progress → QA → Done. Every ticket carries story:admin-row-crud + an arch: label + a type: label per template-ticket. Tickets span 4 repos (see Repos below); board is the single coordination point.

Status

2026-04-25: Project bootstrapped end-to-end at the docs layer. Project entity, project page, board, repo registration, 1 user story, 3 architecture notes — all created. Forgejo repo forgejo_admin/westside-admin created (empty, README only). 12 backlog tickets being filed across 4 repos. Local clone, scaffolding, deployment overlay, Keycloak client, k8s namespace, secrets — all not yet started.

2026-05-03 (morning): M1 ACHIEVED. Three-layer bootstrap arc complete. Layer 1 (CI smoke check, westside-admin#13) → Layer 2 (PSA securityContext, pal-e-deployments#141) → Layer 3 (harbor-creds source-of-truth migration from SOPS-overlay to terraform, pal-e-deployments#145 + targeted tofu apply). Deployment 1/1 ready. ArgoCD Synced + Healthy. External funnel https://westside-admin.tail5b443a.ts.net returns HTTP 200 (was 502 since 2026-04-28). Validation notes: validation-12-2026-04-30, validation-137-2026-05-01, validation-143-2026-05-03. Five follow-on tickets filed during arc (#54, #139, #140, #142, #144) capturing every piece of discovered scope.

2026-05-03 (afternoon): Auth foundation landed. Keycloak westside-admin confidential OIDC client created in westside-basketball realm via Playwright-driven admin console flow per sop-keycloak-client-creation (S256 PKCE, exact /auth/callback, front-channel logout ON, no realm-level side effects). westside-admin#2 (cookie SSR auth) decomposed into 4 sub-tickets per review-1132-2026-05-03 (5-min rule violation): #14 keycloak.ts lib, #15 hooks.server.ts, #16 auth endpoints, #17 403 page. #14 merged via PR #18 — 25/25 vitest cases. pal-e-deployments#147 (KEYCLOAK_CLIENT_SECRET) merged with kustomize-build evidence; ArgoCD reconciled the new secret into the cluster.

2026-05-03 (evening): #2 CLOSED — auth foundation 4/4 shipped. All 4 decomposed sub-tasks landed on main: #15/PR #20 (handle hook with `/health` + `/auth/*` + `/__unauthorized` exclusions, layered cookie refresh, missing-admin internal rewrite), #16/PR #21 (PKCE-S256 OIDC code grant; state-validated-before-token-exchange CSRF protection; POST-only logout with Origin-header CSRF check; 502-with-no-upstream-body-relay on Keycloak failures), #17/PR #23 (`(unauthorized)` route group: `+layout@.svelte` reset + `+layout.server.ts` data persistence + `+page.server.ts` `error(403)` throw → `+error.svelte` rendered UI with sign-out form). End-to-end auth flow now testable against the live realm/secret stack: admin → cookie set → admin app; non-admin → 403 with sign-out. Two follow-ups in backlog: #19 (CI vitest gate — tests don't run on Woodpecker yet), #22 (extend KeycloakTokens with refresh_exp so cookie Max-Age tracks refresh window instead of access window). Standing scope: arch-keycloak + arch-westside-admin notes still missing — the four sub-tasks all carried arch: labels without backing notes; backfill is a deferred docs-layer task. Wave 4 feature work (Drizzle setup #1, tenant-scoped query helper #3, players list #4, players edit #5) now fully unblocked. Lucas to manually validate end-to-end SSO before items move from validation to done per feedback_validate_before_done.

Milestones

  • M1: First admin login. App scaffolded + deployed + Keycloak gate working + blank dashboard renders. Validates auth + deploy seam end-to-end.
  • M2: Players list visible. Drizzle pulls schema, players table renders SSR, tenant scoping enforced. Validates DB seam.
  • M3: First row edit committed. Marcus successfully edits a player record without my involvement. Validates the value proposition.

Repos

Repo Platform Role Modified for westside-admin? Status
forgejo_admin/westside-admin forgejo The SvelteKit app: code, Drizzle schema, hooks.server.ts, Dockerfile, .woodpecker.yaml Yes — 8 issues active (empty)
forgejo_admin/pal-e-deployments forgejo Kustomize overlay overlays/westside-admin/prod/ (deployment, ingress with funnel, harbor-creds, namespace) Yes — 1 issue active
forgejo_admin/pal-e-platform forgejo Terraform: Keycloak client westside-admin, Postgres admin_app user + grants on basketball db Yes — 2 issues active
forgejo_admin/pal-e-services forgejo Service onboarding registration (if pattern requires it; TBD) Maybe — 1 issue deferred until needed active
forgejo_admin/basketball-api forgejo Owns the Postgres DB schema westside-admin reads/writes (source of truth for drizzle-kit pull) No — read dependency only active