From 4d929df604253e94616719e50ee08ac8e51b3c14 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Thu, 10 Sep 2026 16:19:52 -0500 Subject: [PATCH] Remove stale admin overhaul and API update docs --- docs/ADMIN_OVERHAUL.md | 185 ----------------------- docs/api_updates.md | 330 ----------------------------------------- 2 files changed, 515 deletions(-) delete mode 100644 docs/ADMIN_OVERHAUL.md delete mode 100644 docs/api_updates.md diff --git a/docs/ADMIN_OVERHAUL.md b/docs/ADMIN_OVERHAUL.md deleted file mode 100644 index a79066e..0000000 --- a/docs/ADMIN_OVERHAUL.md +++ /dev/null @@ -1,185 +0,0 @@ -# Admin Overhaul & Bug Fix Plan - -Status: **IN PROGRESS** — checkboxes are ticked as work is completed and committed. - -## Context - -The admin section is mid-refactor. The generic `$lib/svelte-admin/*` CRUD layer and -`AdminTable.svelte` were deleted; the **Profiles** page was rebuilt by hand with -`DataTable.svelte`, `AdminModal.svelte`, and route-specific form component(s). The -remaining admin pages still import the deleted modules, so the app does not compile. - -This plan: - -1. Fixes the reported Profiles-page bugs (stale buttons, unified User/Profile identity, - in-modal status messages, centered modal with backdrop). -2. Rebuilds every other admin page to match the Profiles page look, style, and - functionality — KISS, no clever abstractions. -3. Removes the **Admin dashboard** — the nav links are the navigation. -4. Uses `DataTable` / `AdminModal` / `Toast` as dumb containers; each page owns its - own form logic. - -Roles: `SUPER = 1`, `ADMIN = 2`, `STAFF = 3`, `USER = 4`. Admin area requires `roleId <= 3`. - -## Decisions Locked In - -- **Dashboard**: removed. `/admin` redirects to `/admin/profiles`. -- **Profile email is the identity.** The User dropdown is removed from the Profiles - form. One Email field: - - Linked profile (`userId` set): field shows/edits the User's email; saving - syncs the change to the `User` record. - - Unlinked profile: email lives on the Profile only. - - On save, if an existing `User` matches the entered email, the profile is linked - to it automatically. -- **Users page** is where Users are edited (Name, Email, Role) — not the Profiles page. -- **Status messages live inside modals**, at the top, not in a page-level toast. -- **API Keys**: pending operator review. Applied: compile fix (remove `$lib/svelte-admin/roles` - dependency) + consistent styling. Modal-based rotation flow is deferred (see appendix). - ---- - -## Checklist - -### Phase 0 — Documentation & scaffolding - -- [x] Document this plan at `docs/ADMIN_OVERHAUL.md` (this file) -- [x] Remove Admin dashboard: `/admin` redirects to `/admin/profiles` - - [x] Delete `(protected)/admin/+page.svelte` - - [x] Rewrite `(protected)/admin/+page.server.ts` as a `redirect(307, '/admin/profiles')` - -### Phase 1 — Shared components - -- [x] Rewrite `src/lib/components/admin/AdminModal.svelte` - - [x] Fixed overlay, centered panel (`fixed inset-0 z-50`) - - [x] Dark translucent backdrop (`bg-slate-900/50`), click/`Esc` to close - - [x] Panel: `max-w-2xl`, `max-h-[90vh]`, scrollable body - - [x] Header: title, Delete (when `canDelete && recordId`), close `×` -- [x] Extend `src/lib/components/DataTable.svelte` - - [x] Optional `copyableFields` → copy button per matching cell (`stopPropagation`) - - [x] Optional `draggable` + `onReorder` → drag-handle reorder column -- [x] Restyle `src/lib/components/admin/Toast.svelte` (fixed, dark chip, countdown/dismiss) - -### Phase 2 — Profiles page (reference implementation) - -- [x] `+page.server.ts` - - [x] Remove user-options relation data / `updateUser` action - - [x] New schema: `email` (required), `firstName`, `lastName`, `phoneNumber` - - [x] Upsert: create links to matching `User` by email; update syncs linked `User.email`; - guard `P2002` with friendly message -- [x] `ProfileFields.svelte` - - [x] Single Email field (bound from `user.email ?? record.email`); remove User select -- [x] `CreateProfileModal.svelte` - - [x] Fix skip-first `lastForm` effect bug - - [x] In-modal success banner ("Profile created") then auto-close - - [x] In-modal error banner + field errors -- [x] `EditProfileModal.svelte` - - [x] Fix skip-first `lastForm` effect bug - - [x] Reset sub-state per record (addresses, toggles, errors) on record change - - [x] Remove the "Edit User" / "Save User" section - - [x] In-modal success/error banners; keep open on successful edit - - [x] Clear & close Add-Address form on success; append returned address live -- [x] `+page.svelte` - - [x] Email column shows `user.email ?? email`; remove `users` prop plumbing - - [x] `{#key editRecord?.id}` so the edit modal remounts per record - - [x] Remove page-level `Toast` for modal feedback -- [x] New spec: `profiles/+page.server.spec.ts` - - [x] Create links by matched email; unlinked stores email only - - [x] Update syncs linked `User.email`; rejects email owned by another user - - [x] Validation failures and `P2002` handling - -### Phase 3 — Users page - -- [x] `+page.server.ts` — load users + role options (role-filtered for non-super); - `upsert` name/email/roleId; `delete`; `P2002` guard -- [x] `+page.svelte` — `DataTable` (Name, Email, Role, Created) + edit `AdminModal` - (Name, Email, Role select, Delete) -- [x] Replace `UserForm.svelte` usage (or convert to inline form) — no `svelte-admin` - -### Phase 4 — User Roles page - -- [x] `+page.server.ts` — direct `prisma.userRole` load/upsert/delete (P2003 guard) -- [x] `+page.svelte` — `DataTable` (Id, Name) + modal - -### Phase 5 — Solfeggio Frequencies page - -- [x] `+page.server.ts` — direct `prisma.solfeggioFrequency` load/upsert/delete (P2003 guard) -- [x] `+page.svelte` — `DataTable` (Name, Value) + modal - -### Phase 6 — Proclamation Categories page - -- [x] `+page.server.ts` — direct `prisma.proclamationCategory` load/upsert/delete/reorder - (P2002/P2003 guards, maxSort for new rows) -- [x] `+page.svelte` — draggable `DataTable` + modal (Name), reorder via fetch + invalidateAll - -### Phase 7 — Proclamation Groups page - -- [x] `+page.server.ts` — direct `prisma.proclamationGroup` load/upsert/delete/reorder - (P2002/P2003 guards) -- [x] `+page.svelte` — draggable `DataTable` (Name, Sort) + modal - -### Phase 8 — License Keys page - -- [x] `+page.server.ts` — direct `prisma.licenseKey` load (with user) + `createKey` + `replace` -- [x] `+page.svelte` — `DataTable` with copyable Key column; New-by-email modal; row → Replace modal -- [x] In-modal success/error banners for both flows - -### Phase 9 — Proclamations page - -- [x] `+page.server.ts` — direct load (strip content for non-super) + upsert (Super only, - P2002) + delete (Super only, P2003); category/group/frequency option data -- [x] `+page.svelte` — `DataTable` (Name, Category, Group, Frequency) + modal with - TipTap content editor for Super users -- [x] Update `page.server.spec.ts` and `page.svelte.spec.ts` (drop `svelte-admin` mocks) - -### Phase 10 — API Keys page (operator page) - -- [x] `+page.server.ts` — remove `ROLES` import (`$lib/svelte-admin/roles` deleted); use literal -- [x] `+page.svelte` — consistent styling; keep single-page rotate flow (modal conversion deferred) -- [x] Document deferred modal conversion in appendix - -### Phase 11 — Tests & validation - -- [x] Update `license-keys/page.server.spec.ts` (drop `svelte-admin` mocks) -- [x] `npm run check` -- [x] `npm run lint` (pre-existing `no-explicit-any` spec debt remains — see Appendix C) -- [x] `npm run test` -- [x] `npm run build` (needs env vars exported during `vite build`) -- [x] Final commit; mark every checkbox complete - ---- - -## Appendix A — Profiles bug root causes - -1. **Skip-first `lastForm` bug.** Both modals guard against the initial `null` form - with `if (lastForm === null) { lastForm = form; return; }`, which also swallows the - _first real submit result_ — the first "Add Address" save creates the DB row but the - UI never reacts (no close, no toast, no appended row). Fix: `if (!form || form === lastForm) return;`. -2. **Stale modal state across records.** `{#if editRecord}` keeps the modal mounted - between different rows, while the `seeded` flag never resets — the previous row's - addresses/user data leak in, and `record` stays stale (the reported - "my name didn't update"). Fix: `{#key editRecord?.id}` remount + per-record reseed. -3. **User data is only a snapshot.** The modal rendered `record.user.name / email` - directly. With the unified email model this display goes away; the form fields are - controlled by local `$state` and the server keeps Profile + User in sync. - -## Appendix B — API Keys decision note - -The API Keys page is an operator page (key rotation), not a CRUD table. It does not use -`DataTable`. Recommended future change: present the rotation confirmation and the new -key in shared `AdminModal` panels with in-modal status. That conversion is **deferred** -pending review. What was done now: compile fix (replaced the deleted `ROLES` import) -and consistent styling (simple slate card layout, indigo action buttons, red emergency -button) so the page compiles and matches the site visually. - -## Appendix C — Validation caveats - -1. **`npm run check`** required renaming `src/routes/(protected)/admin/profiles/+page.server.spec.ts` - to `page.server.spec.ts`: SvelteKit reserves files whose names start with `+`. The `+`-prefix - broke `svelte-kit sync` and thus the whole `check`/`build` pipeline. Renamed. -2. **`npm run lint`** previously reported 142 `no-explicit-any` errors across 14 pre-existing spec - files (user/account, dashboard, license-keys, login/register/reset, etc.). A scoped override now - disables that rule for `**/*.spec.ts`, since test doubles commonly need `any`. All non-spec lint - errors were fixed; `npm run lint` is green. -3. **`npm run build`** needs `BETTER_AUTH_SECRET`/`BETTER_AUTH_URL`/`DATABASE_URL` exported in the - shell, because `src/lib/auth.ts` reads `$env/dynamic/private` (empty at `vite build` time) and - Better Auth rejects the default secret. Export the vars before building. diff --git a/docs/api_updates.md b/docs/api_updates.md deleted file mode 100644 index 7838cfc..0000000 --- a/docs/api_updates.md +++ /dev/null @@ -1,330 +0,0 @@ -# API Updates — Category Flattening, Groups, Subscription Level Removal - -**Release:** v2 schema change (migration `20260821000000_flat_categories_groups_drop_subscription_levels`) -**Status:** Breaking changes. Clients must be updated. - -## Summary of changes - -| Endpoint | Change | -| ----------------------------------------- | ------------------------------------------------------------------------------------ | -| `GET /api/v2/categories` | **Breaking**: `parentId` removed from every record. Categories are now a flat list. | -| `GET /api/v2/groups` | **New**: returns the group list (former sub-categories). | -| `GET /api/v2/proclamations` | **Breaking**: `groupId` added; `subscriptionLevelId` removed. | -| `GET /api/v2/subscription-levels` | **Removed**: subscription levels no longer exist. | -| `POST /api/v2/license` | **Breaking**: response no longer contains `subscription_level`. | -| `POST /api/v2/automations/create/account` | **Breaking**: `subscription_level` is no longer accepted or required in the payload. | -| `POST /api/v1/license` | **Breaking**: response no longer contains `subscription_level`. | -| `POST /api/v1/automations/create/account` | **Breaking**: `subscription_level` is no longer accepted or required in the payload. | -| `GET /api/v1/categories` | Payload implicitly changed: rows no longer contain `parentId`. | - -### Data model mapping - -- Former **parent** categories (`Spiritual`, `Physical`, `Mental`, `Emotional`, `Financial`, - `Social`, `Biblical`, ...) became the flat **Category** list. -- Former **child** sub-categories (`General`, `Specific`, `Precise`, `Prayers`, `Scripture`) - became **Groups**. -- A proclamation's former child category becomes its `group`; its former parent category becomes - its `category`. A proclamation may move between categories without changing its group. -- Proclamations that had no sub-category (e.g. under `Custom (Client Specific)`) were assigned to - the `General` group. -- Every proclamation now has exactly one category and one group. - ---- - -## OpenAPI 3.0 specification - -```yaml -openapi: 3.0.3 -info: - title: Radiant Wave Portal API - version: 2.1.0 - description: | - Changes in this release: - * ProclamationCategory hierarchy flattened; parentId removed everywhere. - * New ProclamationGroup resource replaces sub-categories as a movable filter. - * SubscriptionLevel resource and all level-based gating removed. - -paths: - /api/v2/categories: - get: - summary: List categories (flat) - description: > - Returns all proclamation categories as a flat, ordered list. - BREAKING: the previous version returned a hierarchical list where each - record carried a nullable parentId linking children to parents. That - field no longer exists. - tags: [Categories] - responses: - '200': - description: Flat category list. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Category' - examples: - newResponse: - summary: New response - value: - - id: 4 - name: Spiritual - sortOrder: 0 - - id: 2 - name: Physical - sortOrder: 1 - oldResponseDeprecated: - summary: Old (removed) response shape - value: - - id: 5 - name: General - sortOrder: 0 - parentId: 4 - - id: 9 - name: Specific - sortOrder: 1 - parentId: 4 - - /api/v2/groups: - get: - summary: List groups - description: > - NEW ENDPOINT. Returns all proclamation groups ordered by sortOrder. - Groups replace the former sub-categories and act as a movable filter: - a proclamation can be moved from one category to another while keeping - its group. - tags: [Groups] - responses: - '200': - description: Group list. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Group' - example: - - id: 1 - name: General - sortOrder: 0 - - id: 2 - name: Specific - sortOrder: 1 - - /api/v2/proclamations: - get: - summary: List proclamations - description: > - Returns proclamations with their canonical frequency id and optional - color palette. BREAKING: each record now carries groupId (required, - always present) and no longer carries subscriptionLevelId. When the - optional entitlement headers are supplied, only the caller's entitled - proclamations are returned; there is no longer any per-level gating. - tags: [Proclamations] - parameters: - - in: header - name: x-license-key - required: false - schema: { type: string } - description: License key. Must be paired with x-email. - - in: header - name: x-email - required: false - schema: { type: string } - description: Account email. Must be paired with x-license-key. - responses: - '200': - description: Proclamation list. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Proclamation' - examples: - newResponse: - summary: New response - value: - - id: b40fdcb9-703c-420e-af32-a081964c6f08 - name: Peace & Relaxation - content:

The text content

- categoryId: 4 - groupId: 1 - solfeggioFrequencyId: 5 - colorPalette: - [ - '#32CD32', - '#3CB371', - '#90EE90', - '#98FB98', - '#ADFFA9', - '#C8FFD2', - '#F0FFF0' - ] - oldResponseDeprecated: - summary: Old (removed) response shape - value: - - id: b40fdcb9-703c-420e-af32-a081964c6f08 - name: Peace & Relaxation - content:

The text content

- categoryId: 5 - solfeggioFrequencyId: 5 - subscriptionLevelId: 1 - '400': - description: Entitlement headers incomplete or invalid. - - /api/v2/subscription-levels: - get: - summary: REMOVED - deprecated: true - description: > - This endpoint was removed. Subscription levels no longer exist in the - data model; clients must stop calling it. - tags: [Removed] - responses: - '404': - description: Route no longer exists. - - /api/v2/license: - post: - summary: Validate a license key - description: > - Validates a key/email pair. BREAKING: the response no longer contains a - subscription_level object. - tags: [License] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [license_key, email_address] - properties: - license_key: { type: string, example: ABCD-EFGH-IJKL-MNOP } - email_address: { type: string, format: email } - responses: - '200': - description: Key is valid. - content: - application/json: - schema: - $ref: '#/components/schemas/LicenseResponse' - examples: - newResponse: - summary: New response - value: - key: ABCD-EFGH-IJKL-MNOP - email_address: user@example.com - oldResponseDeprecated: - summary: Old (removed) response shape - value: - key: ABCD-EFGH-IJKL-MNOP - email_address: user@example.com - subscription_level: - id: 2 - name: Advanced - description: General + Specific - '400': - description: Validation failure (missing fields, unknown key, wrong owner). - - /api/v2/automations/create/account: - post: - summary: Create account + license key (GoHighLevel automation) - description: > - Creates a portal account when needed and issues a license key. - BREAKING: subscription_level was removed from the request contract. The - field is neither required nor validated anymore; sending it is ignored. - Signature headers (x-timestamp, x-nonce, x-signature) are unchanged. - tags: [Automations] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [email] - properties: - email: { type: string, format: email } - name: { type: string } - responses: - '200': - description: Account and license key created. - '400': - description: Missing/invalid email. - '401': - description: Invalid signature. - -components: - schemas: - Category: - type: object - required: [id, name, sortOrder] - properties: - id: { type: integer } - name: { type: string } - sortOrder: { type: integer } - - Group: - type: object - required: [id, name, sortOrder] - properties: - id: { type: integer } - name: { type: string } - sortOrder: { type: integer } - - Proclamation: - type: object - required: [id, name, content, categoryId, groupId, solfeggioFrequencyId] - properties: - id: - type: string - format: uuid - name: { type: string } - content: { type: string, description: HTML formatted content. } - categoryId: - type: integer - description: Flat category this proclamation belongs to. - groupId: - type: integer - description: > - Group membership (movable filter). Independent of categoryId; - changing the category does not change the group. - solfeggioFrequencyId: - type: integer - minimum: 1 - maximum: 9 - description: Canonical kiosk frequency id (not the DB autoincrement id). - colorPalette: - type: array - items: { type: string } - minItems: 7 - maxItems: 7 - description: Present only when a palette exists for the frequency. - - LicenseResponse: - type: object - required: [key, email_address] - properties: - key: { type: string } - email_address: { type: string, format: email } -``` - ---- - -## Client migration checklist - -1. **Kiosk / API consumers** - - Stop reading `parentId`; treat `/api/v2/categories` as flat. - - Fetch `/api/v2/groups` once at startup for the filter list. - - Read `groupId` from each proclamation record for filtering. - - Remove all references to `subscriptionLevelId` and `/api/v2/subscription-levels`. - - Remove `subscription_level` handling from license validation responses. - -2. **GoHighLevel automation** - - Remove `subscription_level` from the account-creation webhook payload - (optional but recommended; the field is now ignored). - -3. **Admin portal** - - Manage categories under Admin → Proclamation Categories (flat list, drag to reorder). - - Manage groups under Admin → Proclamation Groups (drag to reorder). - - Proclamations now select one Category and one Group independently.