- Scope @typescript-eslint/no-explicit-any off for *.spec.ts test doubles
- Type enhance() submit callbacks with SubmitFunction in api-keys page
- Drop as-any casts in proclamations upsert by using typed form data
- Fix useless regex escapes in profiles phone validation
- Remove unused locals param from api-keys load
- Use resolve() for admin nav/error links; store literal hrefs in adminModels
- Update README admin section and ADMIN_OVERHAUL lint caveat
Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each
admin route now owns its own form/modal logic. Add DataTable.svelte,
superform-errors.ts, and profile-specific modal/field components.
Flatten proclamation categories/groups and drop subscription levels; add the
v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated
Prisma client with the new migrations.
Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
Problem:
The admin createKey action had two issues preventing the license key from
appearing in the delivery email:
1. Wrong variable name: passed 'licenseKey' (camelCase) but the Resend
template expects 'license_key' (snake_case).
2. Wrong template: used the default RESEND_TEMPLATE_ID (a generic
template) instead of the 'license-key-delivery' template that the
automation endpoint uses for license key emails.
Solution:
- Changed template variable from 'licenseKey' to 'license_key'
- Added explicit templateId: 'license-key-delivery' to match the
automation endpoint (api/v1/automations/create/account)
- Updated email subject to 'Your Rhemawave License Key' for consistency
Tests:
Added page.server.spec.ts for the admin license-keys createKey action
covering: unauthenticated rejection, missing email/level validation,
user-not-found error, successful key creation with correct template ID
and license_key variable, fallback to user.name when profile is missing,
and graceful handling when email sending fails.
- Replace raw Resend text emails with sendMail using Resend templates
- automated-user-registration template: firstName, tempPassword,
verifyUrl, loginUrl
- license-key-delivery template: firstName, license_key
- Remove requestPasswordReset call (temp password is the one-time
credential; verification link handles email confirmation)
- Add .catch() to Better Auth email callbacks to prevent unhandled
rejections from missing templates crashing the server
POST /api/v1/automations/create/account accepts JSON with email,
name, and subscription_level. Creates a Better Auth user via signUpEmail
with a temporary password, sends a password reset email so the user
can set their own password, generates and persists a license key,
and emails the key to the user. Returns success/error as JSON.
- Change /api/v1/license from GET with query params to POST with JSON body
- Request body uses license_key and email_address
- Response returns key, email_address, subscription_level
- Add active check (reject deactivated license keys)
- Exclude /api/v1/automations/ from API key auth (GHL webhook)
- Add copyableFields prop to AdminTable with clipboard icon
- Enable copy button on license key field in license-keys admin page
- Add ApiKey Prisma model (keyHash, expiresAt, revokedAt)
- Protect /api/v1/* routes with Authorization: Bearer header check in hooks
- create rotateApiKey() utility: normal (48h grace) and emergency (immediate)
- Add Super-only /admin/api-keys page for key rotation with one-time reveal
- Fix roleId check by using parent layout's DB-fetched adminUser
- Add API Keys link to admin sidebar
- Add sortOrder column to ProclamationCategory with migration
- Implement drag-and-drop reorder in AdminTable with persist-to-DB
- Refactor AdminTable to use $derived + dragOverride (no $effect sync)
- Replace window.location.reload with invalidateAll for reorder
- Convert license key create to modal with subscription level dropdown
- Add LicenseKeyForm.svelte following the CategoryForm pattern
- Add replace action (delete + generate new key with same level)
- Remove deactivateOld checkbox; each key has its own level
- Use use:enhance with update() to refresh list after create/replace