Rebuild admin pages around DataTable/AdminModal; restructure categories/groups and v2 groups API

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.
This commit is contained in:
NeilClack
2026-09-10 09:38:01 -05:00
parent f4e736c92d
commit 91d162c746
127 changed files with 7182 additions and 9785 deletions
+14 -35
View File
@@ -55,8 +55,8 @@ export type V2Proclamation = {
name: string;
content: string;
categoryId: number;
groupId: number;
solfeggioFrequencyId: number;
subscriptionLevelId: number | null;
colorPalette?: string[];
};
@@ -65,7 +65,7 @@ export type ProclamationWithFrequency = {
name: string;
content: string;
categoryId: number;
subscriptionLevelId: number | null;
groupId: number;
solfeggioFrequency: { value: number };
};
@@ -83,8 +83,8 @@ export function serializeProclamation(
name: p.name,
content: p.content,
categoryId: p.categoryId,
solfeggioFrequencyId,
subscriptionLevelId: p.subscriptionLevelId
groupId: p.groupId,
solfeggioFrequencyId
};
if (palette && palette.length > 0) {
@@ -98,53 +98,32 @@ export type V2Category = {
id: number;
name: string;
sortOrder: number;
parentId: number | null;
};
export function serializeCategory(c: {
export function serializeCategory(c: { id: number; name: string; sortOrder: number }): V2Category {
return { id: c.id, name: c.name, sortOrder: c.sortOrder };
}
export type V2Group = {
id: number;
name: string;
sortOrder: number;
parentId: number | null;
}): V2Category {
return { id: c.id, name: c.name, sortOrder: c.sortOrder, parentId: c.parentId };
}
export type V2SubscriptionLevel = {
id: number;
name: string;
description: string | null;
};
export function serializeSubscriptionLevel(level: {
id: number;
name: string;
description: string | null;
}): V2SubscriptionLevel {
return { id: level.id, name: level.name, description: level.description };
export function serializeGroup(g: { id: number; name: string; sortOrder: number }): V2Group {
return { id: g.id, name: g.name, sortOrder: g.sortOrder };
}
export type V2License = {
key: string;
email_address: string;
subscription_level: { id: number | null; name: string; description: string | null };
};
/** Build the POST /license response. `subscription_level` is always an object;
* when no SubscriptionLevel row matches the license's level name, fall back to
* a structurally consistent object with a null id/description. */
export function serializeLicense(opts: {
key: string;
emailAddress: string;
levelName: string;
level: { id: number; name: string; description: string | null } | null;
}): V2License {
/** Build the POST /license response. */
export function serializeLicense(opts: { key: string; emailAddress: string }): V2License {
return {
key: opts.key,
email_address: opts.emailAddress,
subscription_level: opts.level
? { id: opts.level.id, name: opts.level.name, description: opts.level.description }
: { id: null, name: opts.levelName, description: null }
email_address: opts.emailAddress
};
}