docs: update AGENTS.md with API routes, GHL webhook, templates, drag-and-drop, key generation

This commit is contained in:
NeilClack
2026-07-08 15:34:12 -05:00
parent 92f54e829e
commit 3243f1d4a1
+70
View File
@@ -42,6 +42,7 @@ Requires `.env` with at least `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH
- **Better-Auth** server instance in `src/lib/auth.ts`, browser client in `src/lib/auth-client.ts`. Session fetched in `src/hooks.server.ts`. - **Better-Auth** server instance in `src/lib/auth.ts`, browser client in `src/lib/auth-client.ts`. Session fetched in `src/hooks.server.ts`.
- **Roles**: integer hierarchy (SUPER=1, ADMIN=2, STAFF=3, USER=4). `canAccessAdmin` checks roleId <= 3. - **Roles**: integer hierarchy (SUPER=1, ADMIN=2, STAFF=3, USER=4). `canAccessAdmin` checks roleId <= 3.
- **Payments are stubbed** (`src/lib/payments.ts` — setTimeout + console.log). - **Payments are stubbed** (`src/lib/payments.ts` — setTimeout + console.log).
- **API keys**: SHA-256 hashed before storage (`ApiKey` model); only prefix (11 chars) stored for identification. `/api/v1/*` requires `Authorization: Bearer <key>`, except `/api/v1/automations/` (GHL webhook, no API key needed). Rotation: normal (48h grace via `expiresAt`) or emergency (immediate via `revokedAt`). Super-only admin page at `/admin/api-keys`.
## Formatting conventions ## Formatting conventions
@@ -291,3 +292,72 @@ The `$effect` resets `dirty = false` on mount (when the record prop changes).
- `.svelte-kit/` — SvelteKit generated types (gitignored) - `.svelte-kit/` — SvelteKit generated types (gitignored)
- `src/lib/generated/prisma/` — Prisma client output (not gitignored; regenerate with `npx prisma generate`) - `src/lib/generated/prisma/` — Prisma client output (not gitignored; regenerate with `npx prisma generate`)
---
## API v1 Endpoints
| Route | Method | Auth | Description |
| ------------------------------------ | ------ | -------- | ------------------------------------------------ |
| `/api/v1/categories` | GET | API key | List all proclamation categories |
| `/api/v1/frequencies` | GET | API key | List all solfeggio frequencies |
| `/api/v1/proclamations` | GET | API key | List proclamations (with optional `?key` + `?email` filter) |
| `/api/v1/license` | POST | API key | Validate key: `{ license_key, email_address }` |
| `/api/v1/automations/create/account` | POST | None | GHL webhook: create user + license key + emails |
## GHL Webhook — Auto Account Creation
`POST /api/v1/automations/create/account` — excluded from API key auth in `hooks.server.ts`.
**Payload:**
```json
{ "email": "...", "name": "...", "subscription_level": "Basic" }
```
`subscription_level` must match a `SubscriptionLevel.name` in the database.
**Flow:**
1. If user email doesn't exist → `auth.api.signUpEmail()` with random temp password
2. Query `Verification` table for the auto-generated verification token → construct verify URL
3. Send `automated-user-registration` Resend template (variables: `firstName`, `tempPassword`, `verifyUrl`, `loginUrl`)
4. Create `LicenseKey` record (`active: true`, `level: subscription_level`)
5. Send `license-key-delivery` Resend template (variables: `firstName`, `license_key`)
6. If user already exists → skip signup, just create key + send delivery email
**Better Auth email callbacks** (`sendVerificationEmail`, `sendResetPassword`) use `void` + `.catch()` — template send failures are logged, never crash the server.
## Resend Email Templates
| Template ID | Variables | Subject (API-level) |
| ------------------------------ | --------------------------------------------------------------- | ------------------------------ |
| `automated-user-registration` | `firstName`, `tempPassword`, `verifyUrl`, `loginUrl` | "Your account is ready" |
| `license-key-delivery` | `firstName`, `license_key` | "Your Rhemawave License Key" |
| `email-verification-email` | `target_email_address`, `verification_link` | "Verify your email address" |
Variables must be declared in the Resend template editor's Variables section. Undeclared variables render as literal `{{{varName}}}` text.
Controlled by `src/lib/server/resend.ts` (`sendMail`) and `src/lib/server/email-verification.ts`.
## AdminTable Drag-and-Drop
AdminTable supports full-`<tr>` drag-and-drop reorder via props:
- `draggable: boolean` — enables drag handles on rows
- `onReorder: (ids: string[]) => Promise<void>` — called after drop with new row ID order
- `copyableFields: string[]` — field keys to show a copy-to-clipboard icon
**Pattern:** `$derived` + `dragOverride` — no `$effect` sync. `handleDragOver` always calls `preventDefault()` for reliable drop targets. `handleDrop` awaits `onReorder` then calls `invalidateAll()`.
See `src/lib/components/admin/AdminTable.svelte` and `src/routes/(protected)/admin/proclamation-categories/+page.svelte` for example usage.
## License Key Admin
- **Create**: `/admin/license-keys` form → `?/createKey` action. Requires email + subscription level. Generates key, persists to DB, emails the key via Resend template.
- **Replace**: Table row action → `?/replace` action. Deletes old key, creates new one with same user + level. No email sent.
- **Subscription level** stored as `SubscriptionLevel.name` string (not a FK).
## Key Generation
- **License keys**: 16 uppercase hex chars in `XXXX-XXXX-XXXX-XXXX` format. `crypto.randomUUID()` → strip dashes → uppercase → 4x4 chunks. Level stored as `SubscriptionLevel.name` string (not FK).
- **API keys**: `sk_` + 64 random hex chars. SHA-256 hashed for storage; first 11 chars stored as `keyPrefix` for identification.