From 52c740b986e49ccbdda81d55fed4a8674dcddd7c Mon Sep 17 00:00:00 2001 From: NeilClack Date: Mon, 16 Mar 2026 13:23:25 -0500 Subject: [PATCH] Customize Proclamations Admin page --- CLAUDE.md | 84 ------------- do-ca-certificate.crt | 26 ++++ flake.lock | 27 ---- src/lib/components/admin/AdminForm.svelte | 8 +- src/lib/components/admin/ComboBox.svelte | 2 +- src/lib/svelte-admin/registry.ts | 6 +- src/lib/svelte-admin/types.ts | 2 +- .../(protected)/admin/[model]/+page.server.ts | 115 +++++++++++++++++- .../admin/[model]/[id]/+page.server.ts | 96 ++++++++++++++- src/routes/(protected)/dashboard/+page.svelte | 2 +- .../(protected)/proclamations/+page.server.ts | 38 ++++-- .../(protected)/proclamations/+page.svelte | 8 +- .../proclamations/[id]/+page.server.ts | 15 ++- .../proclamations/[id]/page.server.spec.ts | 2 +- .../proclamations/page.server.spec.ts | 23 ++-- 15 files changed, 294 insertions(+), 160 deletions(-) delete mode 100644 CLAUDE.md create mode 100644 do-ca-certificate.crt delete mode 100644 flake.lock diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index bc63993..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,84 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Rhema Wave Technologies User Portal — a SvelteKit web app managing proclamations (audio content tied to solfeggio frequencies), user accounts, license keys, and authentication. - -## Tech Stack - -- **Framework:** SvelteKit 2.x with Svelte 5 (runes API: `$props()`, `$state()`, `$derived()`, `$effect()`) -- **Language:** TypeScript (strict mode) -- **Styling:** Tailwind CSS 4.x (Vite plugin, not PostCSS) -- **Database:** PostgreSQL via Prisma 7.x (driver adapter with `pg` pool) -- **Auth:** `better-auth` with email/password + Google OAuth -- **Rich Text:** TipTap 3.x -- **Runtime:** Node.js 22, deployed via `@sveltejs/adapter-node` - -## Common Commands - -```bash -npm run dev # Start dev server -npm run build # Production build -npm run check # Type-check (svelte-kit sync + svelte-check) -npm run lint # Prettier check + ESLint -npm run format # Auto-format with Prettier -npm run test # Run all tests once (vitest --run) -npm run test:unit # Run tests in watch mode (vitest) -npx vitest run src/path/to/file.spec.ts # Run a single test file -npm run migrate -- --name # Create a new Prisma migration -npm run deploy # Apply pending Prisma migrations -``` - -## Architecture - -### Routing & Auth Guard - -- `src/routes/(protected)/` — route group requiring authentication. The `+layout.server.ts` in this group checks `locals.user` against the DB and throws 403 if missing. -- `src/routes/account/setup/` — intentionally outside `(protected)` so logged-in users without a profile can complete setup. -- `src/hooks.server.ts` — runs `auth.api.getSession()` on every request, injecting `session` and `user` into `event.locals`. - -### Server/Client Split - -- Auth config: `src/lib/auth.ts` (server) / `src/lib/auth-client.ts` (client) -- DB access: `src/lib/db.ts` (server-only) — creates a `pg` pool with optional SSL via `DO_CA_CERTIFICATE` env var for DigitalOcean managed DB. -- Prisma generated client outputs to `src/lib/generated/prisma/` (gitignored). - -### Key Patterns - -- **Svelte 5 runes throughout** — use `$props()`, `$state()`, `$derived()`, `$effect()` (not Svelte 4 reactivity). -- **SvelteKit form actions** for mutations (create/edit proclamations, registration, account setup). -- **API routes** at `src/routes/api/v1/` (proclamations, license keys). - -### Database Schema - -Core models: `User`, `Profile` (1:1 with User), `Address` (belongs to Profile), `Session`, `Account` (OAuth), `UserRole`, `Proclamation`, `SolfeggioFrequency`, `ProclamationCategory`, `LicenseKey`. Schema lives in `prisma/schema.prisma`. - -## Testing - -Vitest 4.x with two project configurations in `vite.config.ts`: -- **`client`** — browser tests (Playwright/Chromium) matching `*.svelte.{test,spec}.{js,ts}` -- **`server`** — Node environment tests matching `*.{test,spec}.{js,ts}` (excludes `.svelte.*`) - -Global config: `expect: { requireAssertions: true }`. - -## Code Style - -- **Prettier:** tabs, single quotes, no trailing commas, semicolons, 100 char width -- **ESLint:** flat config with typescript-eslint + eslint-plugin-svelte -- Svelte files parsed with `prettier-plugin-svelte`; class sorting via `prettier-plugin-tailwindcss` - -## Deployment - -- **Server infrastructure:** NixOS, configured declaratively in the `Webserver-Infra` repo -- **Secrets:** Managed via `sops-nix` (no plaintext `.env` files on the server) -- **CI/CD:** Forgejo Actions on push to `main` → `npm ci` → migrate → build → rsync to `/var/lib/portal/current/` → restart -- **Hosting:** DigitalOcean droplet (NixOS), managed PostgreSQL with SSL, self-hosted Forgejo runner -- **Portal path:** `/var/lib/portal/current` (deployed by CI, owned by `portal` user) -- **Reverse proxy:** Caddy with automatic HTTPS at `rhemawavetech.com` -- **Logs:** `journalctl -u portal -f` - -## Environment Variables - -Required: `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`. Optional: `DO_CA_CERTIFICATE` (path to CA cert for managed DB SSL). diff --git a/do-ca-certificate.crt b/do-ca-certificate.crt new file mode 100644 index 0000000..8dff6b2 --- /dev/null +++ b/do-ca-certificate.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIETTCCArWgAwIBAgIUSiKrBdf9KHXL2om54yA9sQTxeawwDQYJKoZIhvcNAQEM +BQAwQDE+MDwGA1UEAww1MTdmNmMxNDktZmQ1Yy00MTRmLTlkNjctYjIxYTc2MWE3 +ZDlhIEdFTiAxIFByb2plY3QgQ0EwHhcNMjUwNDI5MTc0NDA5WhcNMzUwNDI3MTc0 +NDA5WjBAMT4wPAYDVQQDDDUxN2Y2YzE0OS1mZDVjLTQxNGYtOWQ2Ny1iMjFhNzYx +YTdkOWEgR0VOIDEgUHJvamVjdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC +AYoCggGBANMf/9zhlInHTm5CrsnxHZQcqwy2sr4i4w2aWbpCa9j3rWcSyS7PAd+i +p2u1xTEoboJcbUghZYdmS92epVgejAHsHn4yx8LUsmdQvVF8imNlJfZA5mV09Voj +MpRwmXhQWCxyNqn/pmjMEKtpTm7TCr2GjEPDJC4gHSmPZF1EqJ3wWK+leoeZvRuE +omamd2oP60CxtHxt+8zcI6V80sC4QDze2qqRQ3rv1u2MkymUTAQT8kkb1pGmC/E+ +wanXI1gI5nLqhMcoY55WujA/BOgGLWM1fNCIUWkAoQXUV6G0rM8aYDbTUvuTR0Fk +6LP96/bhlxuhrkTSbezO7kwGngiD0rKL4zKFjdwqBbbL1ZjAgfncmzb9zE6NW6Ad +Z8zcLgGCC0Z74GhXNsqNuxY2Mmqo0B/YUa1ahiFjNeXAk14mMXVGG7pbdu+YUUpU +I3duyxwefZsr3KHvz31HhX2jtcaVFrbOF/Svu23MqRhvj9cCUhiF34h7BqEqMFJP +AeIJ/O8xjwIDAQABoz8wPTAdBgNVHQ4EFgQUQyY3hBxq86mcYi8vRRIkCcDsmkMw +DwYDVR0TBAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggGB +AJqSrBQ8Qi6nIT1N0jo4WXQ5J9p9cyxYcaoRKkdOnvtnB9IXYwX6Ea4w7btZI3O5 +eYamItTqNAC9ZZmwc2i16v1Nfpm50vGJjZZUMXYkxx1e9/kzOsUn4h6Q6H6+490Y +PAl7kn4Ngi7xU3bwxt0K80dU5p3a16r0A93y8GYW/WDuGRuGgrmRz6+psic4U8j4 +nzHPFeJqtg9I00XYtzsFIIYrbAZT+jMuViVWCNAHJPyQYip8LkEzAfnlrszo9P9G +ieFN/wZbaPaVdju8wsSXN35yxy/eOsJ1p6gA4vzNdTtOmbMAlTQJ65QLu+HJrPpA +M5gjIf6QDSJyNpvWcjVROn6DiUnhsbe6EhRIPf6e8R0xwTe6y59XiU3sdsSDC0xp +3R2v7khln6IUimCsgs9lFvhOSZp4DmGuMs+CVCbqr8rtPTj5xWkeitkjlmiVSkg/ +z2IItxzG4pGmlPc17TCgKzI6bkNoCZvJ92Wvq85q5LcQegJFQjASZKPFXNOm3SDq +wQ== +-----END CERTIFICATE----- diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 8048425..0000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1772956932, - "narHash": "sha256-M0yS4AafhKxPPmOHGqIV0iKxgNO8bHDWdl1kOwGBwRY=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "608d0cadfed240589a7eea422407a547ad626a14", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/src/lib/components/admin/AdminForm.svelte b/src/lib/components/admin/AdminForm.svelte index e78e902..ac43bfe 100644 --- a/src/lib/components/admin/AdminForm.svelte +++ b/src/lib/components/admin/AdminForm.svelte @@ -2,9 +2,8 @@ import { fieldNameToLabel } from '$lib/svelte-admin/introspect'; import type { PrismaScalarType, RelationOptionConfig } from '$lib/svelte-admin/types'; import ComboBox from './ComboBox.svelte'; - + import TipTap from '$lib/components/tiptap/TipTap.svelte'; type RelationOptionsData = Record; - type Props = { fields: string[]; fieldTypes: Record; @@ -56,6 +55,11 @@ placeholder="Select {label}" value={getValue(field) as string | number | undefined} /> + {:else if type === 'Tiptap'} + {:else if type === 'Boolean'} + {#if selectedValue != null && selectedValue !== ''}
| null | undefined) { + if (!category) return ''; + const name = String(category.name ?? ''); + const parent = category.parent as Record | undefined; + if (parent?.name) return `${String(parent.name)}/${name}`; + return name; +} + +function parseIntId(value: unknown): number | null { + if (typeof value === 'number' && Number.isInteger(value) && value > 0) return value; + if (typeof value === 'string' && value.trim() !== '') { + const parsed = parseInt(value, 10); + return Number.isNaN(parsed) || parsed <= 0 ? null : parsed; + } + return null; +} + +async function findOrCreateCategoryByLabel(label: string): Promise { + const normalized = label.trim(); + if (!normalized) throw new Error('Invalid category label'); + const [parentName, childName] = normalized.split('/').map((v) => v.trim()); + + if (childName) { + let parent = await prisma.proclamationCategory.findFirst({ + where: { name: parentName, parentId: null } + }); + if (!parent) { + parent = await prisma.proclamationCategory.create({ data: { name: parentName } }); + } + + let child = await prisma.proclamationCategory.findFirst({ + where: { name: childName, parentId: parent.id } + }); + if (!child) { + child = await prisma.proclamationCategory.create({ + data: { name: childName, parentId: parent.id } + }); + } + return child.id; + } + + let category = await prisma.proclamationCategory.findFirst({ + where: { name: normalized, parentId: null } + }); + if (!category) { + category = await prisma.proclamationCategory.create({ data: { name: normalized } }); + } + return category.id; +} + +async function resolveCategoryIdFromFormData(formData: FormData, categoryField: string): Promise { + const categoryValue = formData.get(categoryField); + const categoryLabelValue = formData.get(`${categoryField}Label`); + + const parsedId = parseIntId(categoryValue); + if (parsedId) return parsedId; + + const label = String(categoryLabelValue ?? categoryValue ?? '').trim(); + if (!label) return null; + + return await findOrCreateCategoryByLabel(label); +} + async function loadRelationOptions( relationOptions: Record | undefined ) { @@ -19,14 +82,30 @@ async function loadRelationOptions( const delegate = (prisma as any)[key]; if (!delegate) continue; + const shouldIncludeParent = config.model === 'ProclamationCategory'; + const select: Record = { + [config.valueField]: true, + [config.labelField]: true + }; + if (shouldIncludeParent) { + (select as Record).parent = { select: { name: true } }; + } + const records = await delegate.findMany({ - select: { [config.valueField]: true, [config.labelField]: true } + select }); - result[field] = records.map((r: Record) => ({ - label: String(r[config.labelField]), - value: r[config.valueField] as string | number - })); + result[field] = records.map((r: Record) => { + const value = r[config.valueField] as string | number; + const label = + config.model === 'ProclamationCategory' + ? getCategoryDisplayName(r) + : String(r[config.labelField]); + return { + label, + value + }; + }); } return result; @@ -41,11 +120,28 @@ export const load: PageServerLoad = async ({ params, parent }) => { const where = config.accessFilter ? config.accessFilter(adminUser.roleId) : undefined; - const records = await delegate.findMany({ + let records = await delegate.findMany({ ...(config.include ? { include: config.include } : {}), ...(where ? { where } : {}) }); + if (config.modelName === 'Proclamation') { + records = records.map((r: Record) => { + const category = r.category as Record | undefined; + if (category) { + (category as Record).displayName = getCategoryDisplayName(category); + } + return r; + }); + } + + if (config.modelName === 'ProclamationCategory') { + records = records.map((r: Record) => { + (r as Record).displayName = getCategoryDisplayName(r as Record); + return r; + }); + } + const relationOptionsData = await loadRelationOptions(config.relationOptions); return { @@ -77,6 +173,13 @@ export const actions: Actions = { (data as Record).key = generateLicenseKey(); } + if (config.modelName === 'Proclamation') { + const resolvedCategoryId = await resolveCategoryIdFromFormData(formData, 'categoryId'); + if (resolvedCategoryId != null) { + (data as Record).categoryId = resolvedCategoryId; + } + } + try { const delegate = getDelegate(config); await delegate.create({ data }); diff --git a/src/routes/(protected)/admin/[model]/[id]/+page.server.ts b/src/routes/(protected)/admin/[model]/[id]/+page.server.ts index 3226750..b9eab3e 100644 --- a/src/routes/(protected)/admin/[model]/[id]/+page.server.ts +++ b/src/routes/(protected)/admin/[model]/[id]/+page.server.ts @@ -6,6 +6,69 @@ import { extractFormData } from '$lib/svelte-admin/form-data'; import { prisma } from '$lib/db'; import type { RelationOptionConfig } from '$lib/svelte-admin/types'; +function getCategoryDisplayName(category: Record | null | undefined) { + if (!category) return ''; + const name = String(category.name ?? ''); + const parent = category.parent as Record | undefined; + if (parent?.name) return `${String(parent.name)}/${name}`; + return name; +} + +function parseIntId(value: unknown): number | null { + if (typeof value === 'number' && Number.isInteger(value) && value > 0) return value; + if (typeof value === 'string' && value.trim() !== '') { + const parsed = parseInt(value, 10); + return Number.isNaN(parsed) || parsed <= 0 ? null : parsed; + } + return null; +} + +async function findOrCreateCategoryByLabel(label: string): Promise { + const normalized = label.trim(); + if (!normalized) throw new Error('Invalid category label'); + const [parentName, childName] = normalized.split('/').map((v) => v.trim()); + + if (childName) { + let parent = await prisma.proclamationCategory.findFirst({ + where: { name: parentName, parentId: null } + }); + if (!parent) { + parent = await prisma.proclamationCategory.create({ data: { name: parentName } }); + } + + let child = await prisma.proclamationCategory.findFirst({ + where: { name: childName, parentId: parent.id } + }); + if (!child) { + child = await prisma.proclamationCategory.create({ + data: { name: childName, parentId: parent.id } + }); + } + return child.id; + } + + let category = await prisma.proclamationCategory.findFirst({ + where: { name: normalized, parentId: null } + }); + if (!category) { + category = await prisma.proclamationCategory.create({ data: { name: normalized } }); + } + return category.id; +} + +async function resolveCategoryIdFromFormData(formData: FormData, categoryField: string): Promise { + const categoryValue = formData.get(categoryField); + const categoryLabelValue = formData.get(`${categoryField}Label`); + + const parsedId = parseIntId(categoryValue); + if (parsedId) return parsedId; + + const label = String(categoryLabelValue ?? categoryValue ?? '').trim(); + if (!label) return null; + + return await findOrCreateCategoryByLabel(label); +} + async function loadRelationOptions( relationOptions: Record | undefined ) { @@ -18,14 +81,30 @@ async function loadRelationOptions( const delegate = (prisma as any)[key]; if (!delegate) continue; + const shouldIncludeParent = config.model === 'ProclamationCategory'; + const select: Record = { + [config.valueField]: true, + [config.labelField]: true + }; + if (shouldIncludeParent) { + (select as Record).parent = { select: { name: true } }; + } + const records = await delegate.findMany({ - select: { [config.valueField]: true, [config.labelField]: true } + select }); - result[field] = records.map((r: Record) => ({ - label: String(r[config.labelField]), - value: r[config.valueField] as string | number - })); + result[field] = records.map((r: Record) => { + const value = r[config.valueField] as string | number; + const label = + config.model === 'ProclamationCategory' + ? getCategoryDisplayName(r) + : String(r[config.labelField]); + return { + label, + value + }; + }); } return result; @@ -73,6 +152,13 @@ export const actions: Actions = { const id = parseId(config, params.id!); const data = extractFormData(formData, config.formFields, config.fieldTypes); + if (config.modelName === 'Proclamation') { + const resolvedCategoryId = await resolveCategoryIdFromFormData(formData, 'categoryId'); + if (resolvedCategoryId != null) { + (data as Record).categoryId = resolvedCategoryId; + } + } + try { const delegate = getDelegate(config); await delegate.update({ where: { id }, data }); diff --git a/src/routes/(protected)/dashboard/+page.svelte b/src/routes/(protected)/dashboard/+page.svelte index b6bbfb1..82bac86 100644 --- a/src/routes/(protected)/dashboard/+page.svelte +++ b/src/routes/(protected)/dashboard/+page.svelte @@ -18,7 +18,7 @@

Proclamations

diff --git a/src/routes/(protected)/proclamations/+page.server.ts b/src/routes/(protected)/proclamations/+page.server.ts index 59b2100..e31f6b7 100644 --- a/src/routes/(protected)/proclamations/+page.server.ts +++ b/src/routes/(protected)/proclamations/+page.server.ts @@ -1,16 +1,21 @@ import type { PageServerLoad } from './$types'; import { prisma } from '$lib/db'; +function getCategoryDisplayName(category: Record | null | undefined) { + if (!category) return ''; + const name = String(category.name ?? ''); + const parent = category.parent as Record | undefined; + if (parent?.name) return `${String(parent.name)}/${name}`; + return name; +} + export const load: PageServerLoad = async ({ url }) => { const search = url.searchParams.get('search') ?? ''; const categoryId = url.searchParams.get('category'); - const where: Record = {}; - if (search) { where.name = { contains: search, mode: 'insensitive' }; } - if (categoryId) { where.categoryId = parseInt(categoryId, 10); } @@ -18,20 +23,29 @@ export const load: PageServerLoad = async ({ url }) => { const [proclamations, categories] = await Promise.all([ prisma.proclamation.findMany({ where, - include: { - solfeggioFrequency: true, - category: true - }, + include: { solfeggioFrequency: true, category: { include: { parent: true } } }, orderBy: { name: 'asc' } }), - prisma.proclamationCategory.findMany({ - orderBy: { name: 'asc' } - }) + prisma.proclamationCategory.findMany({ include: { parent: true }, orderBy: { name: 'asc' } }) ]); + const normalizedProclamations = proclamations.map((p) => { + if (p.category) { + (p.category as Record).displayName = getCategoryDisplayName( + p.category as Record + ); + } + return p; + }); + + const normalizedCategories = categories.map((c) => ({ + ...c, + displayName: getCategoryDisplayName(c) + })); + return { - proclamations, - categories, + proclamations: normalizedProclamations, + categories: normalizedCategories, search, categoryId: categoryId ? parseInt(categoryId, 10) : null }; diff --git a/src/routes/(protected)/proclamations/+page.svelte b/src/routes/(protected)/proclamations/+page.svelte index 9e4a148..e6996ed 100644 --- a/src/routes/(protected)/proclamations/+page.svelte +++ b/src/routes/(protected)/proclamations/+page.svelte @@ -4,6 +4,10 @@ let { data }: PageProps = $props(); + function displayCategoryName(category: { name: string; displayName?: string }) { + return category.displayName ?? category.name; + } + let search = $state(''); let categoryId: number | null = $state(null); let initialized = $state(false); @@ -56,7 +60,7 @@ > {#each data.categories as cat} - + {/each}
@@ -87,7 +91,7 @@ class="flex flex-col rounded-lg border border-gray-200 bg-white p-5 shadow-sm transition-shadow hover:shadow-md" >

{proclamation.name}

-

{proclamation.category.name}

+

{displayCategoryName(proclamation.category)}

{proclamation.solfeggioFrequency.value} Hz — diff --git a/src/routes/(protected)/proclamations/[id]/+page.server.ts b/src/routes/(protected)/proclamations/[id]/+page.server.ts index c431265..1071c0f 100644 --- a/src/routes/(protected)/proclamations/[id]/+page.server.ts +++ b/src/routes/(protected)/proclamations/[id]/+page.server.ts @@ -1,15 +1,20 @@ import type { PageServerLoad } from './$types'; -import { prisma } from '$lib/db'; import { error } from '@sveltejs/kit'; +import { prisma } from '$lib/db'; +import { canAccessAdmin } from '$lib/svelte-admin/roles'; + +export const load: PageServerLoad = async ({ params, parent }) => { + const parentData = typeof parent === 'function' ? await parent() : {}; + const userRoleId = (parentData as { userRoleId?: number }).userRoleId ?? null; + if (userRoleId != null && !canAccessAdmin(userRoleId)) { + throw error(403, 'not authorized'); + } -export const load: PageServerLoad = async ({ params }) => { const proclamation = await prisma.proclamation.findUnique({ where: { id: params.id }, include: { solfeggioFrequency: true, - category: { - include: { parent: true } - } + category: { include: { parent: true } } } }); diff --git a/src/routes/(protected)/proclamations/[id]/page.server.spec.ts b/src/routes/(protected)/proclamations/[id]/page.server.spec.ts index 6a53df6..2c84034 100644 --- a/src/routes/(protected)/proclamations/[id]/page.server.spec.ts +++ b/src/routes/(protected)/proclamations/[id]/page.server.spec.ts @@ -13,7 +13,7 @@ import { load } from './+page.server'; const findUnique = prisma.proclamation.findUnique as ReturnType; -describe('(protected)/proclamations/[id] page server load', () => { +describe('(protected)/admin/proclamations/[id] page server load', () => { beforeEach(() => { findUnique.mockReset(); }); diff --git a/src/routes/(protected)/proclamations/page.server.spec.ts b/src/routes/(protected)/proclamations/page.server.spec.ts index 5302e60..85f27b5 100644 --- a/src/routes/(protected)/proclamations/page.server.spec.ts +++ b/src/routes/(protected)/proclamations/page.server.spec.ts @@ -14,7 +14,7 @@ import { load } from './+page.server'; const proclamationFindMany = prisma.proclamation.findMany as ReturnType; const categoryFindMany = prisma.proclamationCategory.findMany as ReturnType; -describe('(protected)/proclamations page server load', () => { + describe('(protected)/admin/proclamations page server load', () => { beforeEach(() => { proclamationFindMany.mockReset(); categoryFindMany.mockReset(); @@ -26,10 +26,13 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue(proclamations); categoryFindMany.mockResolvedValue(categories); - const url = createUrl('/proclamations'); + const url = createUrl('/admin/proclamations'); const result = (await load({ url } as any)) as any; expect(result.proclamations).toEqual(proclamations); - expect(result.categories).toEqual(categories); + expect(result.categories[0]).toEqual(expect.objectContaining({ + ...categories[0], + displayName: categories[0].name + })); expect(result.search).toBe(''); expect(result.categoryId).toBeNull(); }); @@ -38,7 +41,7 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations', { search: 'healing' }); + const url = createUrl('/admin/proclamations', { search: 'healing' }); await load({ url } as any); expect(proclamationFindMany).toHaveBeenCalledWith( @@ -52,7 +55,7 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations', { search: 'peace' }); + const url = createUrl('/admin/proclamations', { search: 'peace' }); const result = (await load({ url } as any)) as any; expect(result.search).toBe('peace'); }); @@ -61,7 +64,7 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations', { category: '3' }); + const url = createUrl('/admin/proclamations', { category: '3' }); await load({ url } as any); expect(proclamationFindMany).toHaveBeenCalledWith( @@ -75,7 +78,7 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations', { category: '5' }); + const url = createUrl('/admin/proclamations', { category: '5' }); const result = (await load({ url } as any)) as any; expect(result.categoryId).toBe(5); }); @@ -84,7 +87,7 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations', { search: 'love', category: '2' }); + const url = createUrl('/admin/proclamations', { search: 'love', category: '2' }); await load({ url } as any); expect(proclamationFindMany).toHaveBeenCalledWith( @@ -101,12 +104,12 @@ describe('(protected)/proclamations page server load', () => { proclamationFindMany.mockResolvedValue([]); categoryFindMany.mockResolvedValue([]); - const url = createUrl('/proclamations'); + const url = createUrl('/admin/proclamations'); await load({ url } as any); expect(proclamationFindMany).toHaveBeenCalledWith( expect.objectContaining({ - include: { solfeggioFrequency: true, category: true } + include: { solfeggioFrequency: true, category: { include: { parent: true } } } }) ); });