westside-admin

project-westside-admin Project Page

active project-page

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
<code>admin-row-crud</code> <code>story-westside-admin-admin-row-crud</code> 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.
  • 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.
  • 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.
  • 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

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 + arch-dev-overlay notes still missing — 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.

2026-05-04 (overnight): Dev overlay arc shipped to main. Marcus needed visibility into his data without waiting for image rebuilds, so a hostPath dev overlay was scoped on board-westside-admin. Two sequential tickets: pal-e-services#73 (Keycloak client) + pal-e-deployments#148 (kustomize overlay) — both MERGED. pal-e-services#73/PR #74 brought the live westside-admin client under tofu management (was created out-of-band in the realm) and added https://westside-admin-dev.tail5b443a.ts.net as a second redirect URI + web origin; required schema extension use_refresh_tokens (optional, default false, additive — verified zero-drift on 4 sibling clients) because the live client carries the Keycloak default true while the existing schema hardcoded false; targeted tofu apply ran post-merge — admin-API GET confirms both prod + dev URIs/origins live. pal-e-deployments#148/PR #149 added overlays/westside-admin/dev/ mirroring pal-e-dictionary/dev/ with three intentional deviations (port 5179, no funnel, PSA-restricted securityContext); 3 review passes caught a port collision with pal-e-projects-dev, the PSA admission requirement, and the ArgoCD path mismatch (the existing Application sources prod/ only; dev is intentionally manual-apply). Pending: manual kubectl apply -k overlays/westside-admin/dev/ from archbox + Lucas e2e validation as draneylucas@gmail.com.

2026-05-05: #1 CLOSED — Drizzle ORM foundation merged (PR #30). Drizzle ORM setup complete: schema definitions, db client, drizzle.config.ts, migration infrastructure. Issue westside-admin#1 closed. M1 (deploy + auth + DB foundation) fully achieved. Board item moved to validation pending deployment verification. Next target: M2 (players list visible) — tenant-scoped query helper (#3), players list route (#4), players edit (#5).

2026-05-05: #19 CLOSED — CI vitest gate merged (PR #32). Woodpecker test step now runs npm ci && npm test before kaniko build on every push. All future PRs are test-gated.

2026-05-05: #3 CLOSED — Tenant-scoped Drizzle query helper merged (PR #34). scopedDb auto-injects WHERE tenant_id=1 on all tenant tables. 12 new unit tests. Grep-based lint prevents direct db imports.

2026-05-05: #4 CLOSED — Players list view merged (PR #36). First user-visible feature: SSR table at /players with 10-column allowlist, pagination, name filter, EnumBadge/DateCell components. 70 tests, pure CSS. M2 milestone pending deployment validation.

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