Merge dev
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
import { createAuthClient } from "better-auth/svelte"
|
||||
import { createAuthClient } from 'better-auth/svelte';
|
||||
|
||||
export const authClient = createAuthClient({});
|
||||
|
||||
const signIn = async () => {
|
||||
const data = await authClient.signIn.social({
|
||||
provider: "google",
|
||||
});
|
||||
};
|
||||
+54
-24
@@ -1,26 +1,56 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import { prisma } from "$lib/db";
|
||||
import { sveltekitCookies } from "better-auth/svelte-kit";
|
||||
import { getRequestEvent } from "$app/server";
|
||||
import { env } from "$env/dynamic/private";
|
||||
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
import { prisma } from '$lib/db';
|
||||
import { sveltekitCookies } from 'better-auth/svelte-kit';
|
||||
import { getRequestEvent } from '$app/server';
|
||||
import { sendMail } from './server/resend';
|
||||
import { sendVerificationEmail } from './server/email-verification';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
export const auth = betterAuth({
|
||||
baseURL: env.BETTER_AUTH_URL,
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: "postgresql",
|
||||
}),
|
||||
secret: env.BETTER_AUTH_SECRET,
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
socialProviders: {
|
||||
google: {
|
||||
prompt: "select_account",
|
||||
clientId: env.GOOGLE_CLIENT_ID as string,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
||||
}
|
||||
},
|
||||
plugins: [sveltekitCookies(getRequestEvent)],
|
||||
})
|
||||
baseURL: env.BETTER_AUTH_URL,
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'postgresql'
|
||||
}),
|
||||
emailVerification: {
|
||||
sendOnSignUp: true,
|
||||
sendVerificationEmail: async ({ user, token }) => {
|
||||
void sendVerificationEmail({
|
||||
email: user.email,
|
||||
token
|
||||
}).catch((e) => {
|
||||
console.error('sendVerificationEmail template email failed:', e);
|
||||
});
|
||||
}
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
requireEmailVerification: true,
|
||||
sendResetPassword: async ({ user, url }) => {
|
||||
void sendMail({
|
||||
to: user.email,
|
||||
templateId: 'password-reset-email',
|
||||
data: {
|
||||
target_email_address: user.email,
|
||||
reset_link: url
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error('sendResetPassword template email failed:', e);
|
||||
});
|
||||
}
|
||||
},
|
||||
socialProviders: {
|
||||
google: {
|
||||
prompt: 'select_account',
|
||||
clientId: env.GOOGLE_CLIENT_ID as string,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET as string
|
||||
}
|
||||
},
|
||||
rateLimit: {
|
||||
enabled: true,
|
||||
window: 60,
|
||||
max: 100
|
||||
},
|
||||
trustedOrigins: ['http://localhost:5173', 'https://portal.rhemawavetech.com'],
|
||||
plugins: [sveltekitCookies(getRequestEvent)]
|
||||
});
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
open = false,
|
||||
title = '',
|
||||
onClose,
|
||||
children,
|
||||
canDelete = false,
|
||||
recordId,
|
||||
deleteAction = '?/delete'
|
||||
}: {
|
||||
open?: boolean;
|
||||
title?: string;
|
||||
onClose?: () => void;
|
||||
children: import('svelte').Snippet;
|
||||
canDelete?: boolean;
|
||||
recordId?: string;
|
||||
deleteAction?: string;
|
||||
} = $props();
|
||||
|
||||
function handleBackdrop(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) onClose?.();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') onClose?.();
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (!recordId) return;
|
||||
if (confirm('Are you sure you want to delete this record? This cannot be undone.')) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = deleteAction;
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'id';
|
||||
input.value = recordId;
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
role="presentation"
|
||||
class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/50 pt-12"
|
||||
onclick={handleBackdrop}
|
||||
onkeydown={handleKeydown}
|
||||
>
|
||||
<div
|
||||
class="my-8 w-full max-w-2xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">{title}</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if canDelete && recordId}
|
||||
<button
|
||||
type="button"
|
||||
onclick={confirmDelete}
|
||||
class="px-3 py-1 text-sm text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="px-2 py-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>×</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-4">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,248 @@
|
||||
<script lang="ts">
|
||||
import type { PrismaScalarType } from '$lib/svelte-admin/types';
|
||||
import { fieldNameToLabel, resolveFieldValue } from '$lib/svelte-admin/introspect';
|
||||
|
||||
let {
|
||||
records = [],
|
||||
listFields = [],
|
||||
searchFields = [],
|
||||
fieldTypes = {} as Record<string, PrismaScalarType>,
|
||||
singular = 'record',
|
||||
draggable = false,
|
||||
copyableFields = [] as string[],
|
||||
onRowClick,
|
||||
onNew,
|
||||
onReorder
|
||||
}: {
|
||||
records: Record<string, unknown>[];
|
||||
listFields: string[];
|
||||
searchFields?: string[];
|
||||
fieldTypes?: Record<string, PrismaScalarType>;
|
||||
singular?: string;
|
||||
draggable?: boolean;
|
||||
copyableFields?: string[];
|
||||
onRowClick?: (record: Record<string, unknown>) => void;
|
||||
onNew?: () => void;
|
||||
onReorder?: (ids: (string | number)[]) => void;
|
||||
} = $props();
|
||||
|
||||
let search = $state('');
|
||||
let sortCol = $state<string | null>(null);
|
||||
let sortAsc = $state(true);
|
||||
|
||||
let filtered = $derived(
|
||||
search
|
||||
? records.filter((r) =>
|
||||
searchFields.some((f) => {
|
||||
const v = resolveFieldValue(r, f);
|
||||
return v != null && String(v).toLowerCase().includes(search.toLowerCase());
|
||||
})
|
||||
)
|
||||
: records
|
||||
);
|
||||
|
||||
let sorted = $derived(
|
||||
sortCol
|
||||
? [...filtered].sort((a, b) => {
|
||||
const va = resolveFieldValue(a, sortCol!);
|
||||
const vb = resolveFieldValue(b, sortCol!);
|
||||
if (va == null) return 1;
|
||||
if (vb == null) return -1;
|
||||
const cmp = String(va).localeCompare(String(vb), undefined, { numeric: true });
|
||||
return sortAsc ? cmp : -cmp;
|
||||
})
|
||||
: filtered
|
||||
);
|
||||
|
||||
let isDragging = $state(false);
|
||||
|
||||
let dragOverride = $state<Record<string, unknown>[] | null>(null);
|
||||
|
||||
let rows = $derived(dragOverride ?? sorted);
|
||||
|
||||
let dragIndex = $state<number | null>(null);
|
||||
let hoverIndex = $state<number | null>(null);
|
||||
|
||||
function toggleSort(field: string) {
|
||||
if (sortCol === field) {
|
||||
sortAsc = !sortAsc;
|
||||
} else {
|
||||
sortCol = field;
|
||||
sortAsc = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragStart(e: DragEvent, i: number) {
|
||||
dragOverride = sorted;
|
||||
isDragging = true;
|
||||
dragIndex = i;
|
||||
hoverIndex = i;
|
||||
e.dataTransfer!.effectAllowed = 'move';
|
||||
e.dataTransfer!.setData('text/plain', '');
|
||||
}
|
||||
|
||||
function handleDragOver(e: DragEvent, i: number) {
|
||||
if (dragIndex === null) return;
|
||||
e.preventDefault();
|
||||
if (i === hoverIndex) return;
|
||||
e.dataTransfer!.dropEffect = 'move';
|
||||
|
||||
const reordered = rows.slice();
|
||||
const [moved] = reordered.splice(dragIndex, 1);
|
||||
reordered.splice(i, 0, moved);
|
||||
dragOverride = reordered;
|
||||
dragIndex = i;
|
||||
hoverIndex = i;
|
||||
}
|
||||
|
||||
async function handleDrop() {
|
||||
if (dragIndex === null) return;
|
||||
const ids = rows.map((r) => r.id as string | number);
|
||||
await onReorder?.(ids);
|
||||
isDragging = false;
|
||||
dragIndex = null;
|
||||
hoverIndex = null;
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
dragOverride = null;
|
||||
isDragging = false;
|
||||
dragIndex = null;
|
||||
hoverIndex = null;
|
||||
}
|
||||
|
||||
function copyValue(v: unknown) {
|
||||
navigator.clipboard.writeText(String(v ?? ''));
|
||||
}
|
||||
|
||||
function formatCellValue(v: unknown, field: string): string {
|
||||
if (v == null) return '';
|
||||
const ft = fieldTypes[field];
|
||||
if (ft === 'DateTime') {
|
||||
if (v instanceof Date) return v.toLocaleString();
|
||||
return new Date(v as string).toLocaleString();
|
||||
}
|
||||
if (ft === 'Boolean') return v ? 'Active' : 'Inactive';
|
||||
if (ft === 'Float') {
|
||||
const n = Number(v);
|
||||
return Number.isNaN(n) ? String(v) : n.toFixed(2);
|
||||
}
|
||||
return String(v);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
{#if searchFields.length > 0}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search {singular}s..."
|
||||
bind:value={search}
|
||||
class="max-w-xs border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
{#if onNew}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onNew}
|
||||
class="border border-gray-300 px-3 py-1.5 text-sm font-medium dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
New {singular} +
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto border border-gray-200 dark:border-gray-700">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800">
|
||||
{#if draggable}
|
||||
<th scope="col" class="w-10 px-2 py-2"></th>
|
||||
{/if}
|
||||
{#each listFields as field (field)}
|
||||
<th
|
||||
scope="col"
|
||||
onclick={() => toggleSort(field)}
|
||||
class="cursor-pointer px-4 py-2 text-left text-xs font-medium tracking-wider text-gray-500 uppercase hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
{fieldNameToLabel(field)}
|
||||
{#if sortCol === field}
|
||||
<span class="ml-1">{sortAsc ? '\u25B2' : '\u25BC'}</span>
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as record, i ((record['id'] as string) ?? (record['key'] as string) ?? i)}
|
||||
<tr
|
||||
{draggable}
|
||||
ondragstart={(e) => handleDragStart(e, i)}
|
||||
ondragover={(e) => handleDragOver(e, i)}
|
||||
ondrop={handleDrop}
|
||||
ondragend={handleDragEnd}
|
||||
onclick={() => onRowClick?.(record)}
|
||||
class="border-b border-gray-200 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-100 dark:hover:bg-gray-800 {draggable &&
|
||||
isDragging &&
|
||||
dragIndex === i
|
||||
? 'opacity-40'
|
||||
: ''} {draggable && isDragging && hoverIndex === i
|
||||
? 'border-t-2 border-t-blue-500'
|
||||
: ''}"
|
||||
>
|
||||
{#if draggable}
|
||||
<td
|
||||
class="w-10 cursor-grab px-2 py-2 text-center"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span class="text-gray-400 select-none">⠿</span>
|
||||
</td>
|
||||
{/if}
|
||||
{#each listFields as field (field)}
|
||||
<td class="px-4 py-2 text-sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
{formatCellValue(resolveFieldValue(record, field), field)}
|
||||
{#if copyableFields.includes(field)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyValue(resolveFieldValue(record, field));
|
||||
}}
|
||||
title="Copy to clipboard"
|
||||
class="shrink-0 leading-none text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg
|
||||
class="size-3.5"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="3.5" y="2.5" width="11" height="11" rx="1" />
|
||||
<path d="M5.5 2.5V1.5a1 1 0 011-1h3a1 1 0 011 1v1" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td
|
||||
colspan={listFields.length + (draggable ? 1 : 0)}
|
||||
class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
No {singular}s found.
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,160 +0,0 @@
|
||||
<script lang="ts">
|
||||
type Option = { label: string; value: string | number };
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
name: string;
|
||||
options: Option[];
|
||||
placeholder?: string;
|
||||
value?: string | number;
|
||||
};
|
||||
|
||||
let { id, name, options, placeholder = 'Search...', value }: Props = $props();
|
||||
|
||||
let search = $state('');
|
||||
let open = $state(false);
|
||||
let highlightIndex = $state(-1);
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
let listEl = $state<HTMLUListElement | null>(null);
|
||||
|
||||
let selectedValue = $state<string | number | undefined>(undefined);
|
||||
|
||||
$effect(() => {
|
||||
if (value != null && value !== '') {
|
||||
selectedValue = value;
|
||||
}
|
||||
});
|
||||
|
||||
let selectedLabel = $derived(() => {
|
||||
if (selectedValue == null || selectedValue === '') return '';
|
||||
const match = options.find((o) => String(o.value) === String(selectedValue));
|
||||
return match?.label ?? '';
|
||||
});
|
||||
|
||||
let filtered = $derived(() => {
|
||||
if (!search) return options;
|
||||
const q = search.toLowerCase();
|
||||
return options.filter((o) => o.label.toLowerCase().includes(q));
|
||||
});
|
||||
|
||||
function select(opt: Option) {
|
||||
selectedValue = opt.value;
|
||||
search = '';
|
||||
open = false;
|
||||
highlightIndex = -1;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
selectedValue = undefined;
|
||||
search = '';
|
||||
open = true;
|
||||
inputEl?.focus();
|
||||
}
|
||||
|
||||
function handleInputFocus() {
|
||||
open = true;
|
||||
highlightIndex = -1;
|
||||
}
|
||||
|
||||
function handleInputBlur(e: FocusEvent) {
|
||||
const related = e.relatedTarget as HTMLElement | null;
|
||||
if (related?.closest(`[data-combobox="${id}"]`)) return;
|
||||
open = false;
|
||||
highlightIndex = -1;
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
const items = filtered();
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
open = true;
|
||||
highlightIndex = Math.min(highlightIndex + 1, items.length - 1);
|
||||
scrollToHighlighted();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
highlightIndex = Math.max(highlightIndex - 1, 0);
|
||||
scrollToHighlighted();
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (open && highlightIndex >= 0 && highlightIndex < items.length) {
|
||||
select(items[highlightIndex]);
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
open = false;
|
||||
search = '';
|
||||
highlightIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToHighlighted() {
|
||||
requestAnimationFrame(() => {
|
||||
const el = listEl?.children[highlightIndex] as HTMLElement | undefined;
|
||||
el?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative" data-combobox={id}>
|
||||
<input type="hidden" {name} value={selectedValue ?? ''} />
|
||||
<input type="hidden" name={name + 'Label'} value={selectedValue != null && selectedValue !== '' ? selectedLabel() : search} />
|
||||
|
||||
{#if selectedValue != null && selectedValue !== ''}
|
||||
<div
|
||||
class="flex w-full items-center justify-between rounded-lg border border-gray-300 bg-white px-3 py-2 transition-shadow outline-none focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 cursor-pointer text-left text-gray-900"
|
||||
onclick={() => clear()}
|
||||
>
|
||||
{selectedLabel()}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 shrink-0 text-gray-400 hover:text-gray-600"
|
||||
onclick={() => clear()}
|
||||
tabindex={-1}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
bind:this={inputEl}
|
||||
bind:value={search}
|
||||
{placeholder}
|
||||
autocomplete="off"
|
||||
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 transition-shadow outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
|
||||
onfocus={handleInputFocus}
|
||||
onblur={handleInputBlur}
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if open}
|
||||
<ul
|
||||
bind:this={listEl}
|
||||
class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-lg border border-gray-200 bg-white py-1 shadow-lg"
|
||||
role="listbox"
|
||||
>
|
||||
{#each filtered() as opt, i}
|
||||
<li role="option" aria-selected={highlightIndex === i}>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm {highlightIndex === i
|
||||
? 'bg-blue-50 text-blue-700'
|
||||
: 'text-gray-700 hover:bg-gray-50'}"
|
||||
onmousedown={(e: MouseEvent) => { e.preventDefault(); select(opt); }}
|
||||
onmouseenter={() => (highlightIndex = i)}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="px-3 py-2 text-sm text-gray-400">No matches</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
message = '',
|
||||
type = 'success' as 'success' | 'error'
|
||||
}: {
|
||||
message?: string;
|
||||
type?: 'success' | 'error';
|
||||
} = $props();
|
||||
|
||||
let remaining = $state(5);
|
||||
let visible = $state(true);
|
||||
|
||||
$effect(() => {
|
||||
remaining = 5;
|
||||
const timer = setInterval(() => {
|
||||
remaining -= 1;
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (remaining <= 0) {
|
||||
visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
function dismiss() {
|
||||
visible = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div
|
||||
role="alert"
|
||||
class="fixed bottom-4 left-4 z-50 flex min-w-64 items-center gap-3 border px-4 py-3 text-sm shadow-lg {type ===
|
||||
'success'
|
||||
? 'border-green-600 bg-green-50 text-green-800'
|
||||
: 'border-red-600 bg-red-50 text-red-800'}"
|
||||
>
|
||||
<span class="flex-1">{message}</span>
|
||||
<span class="text-xs tabular-nums opacity-70">{remaining}s</span>
|
||||
<button type="button" onclick={dismiss} class="ml-1 leading-none font-bold hover:opacity-70"
|
||||
>×</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col items-center justify-center">
|
||||
{@render children()}
|
||||
</div>
|
||||
@@ -1,72 +1,103 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { onMount } from 'svelte';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Highlight from '@tiptap/extension-highlight';
|
||||
import { TextStyle } from '@tiptap/extension-text-style';
|
||||
import { Color } from '@tiptap/extension-color';
|
||||
|
||||
let { initialContent = '', name = 'content' } = $props<{ initialContent: string; name?: string }>();
|
||||
let { initialContent = '', name = 'content' }: { initialContent?: string; name?: string } =
|
||||
$props();
|
||||
|
||||
let element: HTMLElement;
|
||||
let editor: Editor;
|
||||
let content = $state('');
|
||||
let element: HTMLElement;
|
||||
let editor: Editor;
|
||||
let content = $state('');
|
||||
|
||||
onMount(() => {
|
||||
content = initialContent;
|
||||
onMount(() => {
|
||||
content = initialContent;
|
||||
|
||||
editor = new Editor({
|
||||
element,
|
||||
extensions: [StarterKit],
|
||||
content: initialContent,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'outline-none h-full'
|
||||
}
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
content = editor.getHTML();
|
||||
}
|
||||
});
|
||||
editor = new Editor({
|
||||
element,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
bulletList: false,
|
||||
orderedList: false
|
||||
}),
|
||||
TextStyle,
|
||||
Color,
|
||||
Highlight.configure({ multicolor: true })
|
||||
],
|
||||
content: initialContent,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class:
|
||||
'prose prose-sm max-w-none outline-none h-full min-h-40 p-3 dark:prose-invert dark:text-gray-100'
|
||||
}
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
content = editor.getHTML();
|
||||
}
|
||||
});
|
||||
|
||||
return () => editor.destroy();
|
||||
});
|
||||
editor.setEditable(true);
|
||||
|
||||
return () => editor.destroy();
|
||||
});
|
||||
|
||||
function toggleBold() {
|
||||
editor?.chain().focus().toggleBold().run();
|
||||
}
|
||||
function toggleItalic() {
|
||||
editor?.chain().focus().toggleItalic().run();
|
||||
}
|
||||
function toggleHighlight() {
|
||||
editor?.chain().focus().toggleHighlight().run();
|
||||
}
|
||||
|
||||
function setColor() {
|
||||
const current = editor?.getAttributes('textStyle').color ?? '#000000';
|
||||
const colors = ['#000000', '#FF0000', '#0000FF', '#008000', '#FFA500', '#800080', '#808080'];
|
||||
const idx = colors.indexOf(current);
|
||||
const next = colors[(idx + 1) % colors.length];
|
||||
editor?.chain().focus().setColor(next).run();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="border border-gray-300 rounded-lg overflow-hidden">
|
||||
<div class="flex gap-1 p-2 bg-gray-100 border-b border-gray-300">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => editor.chain().focus().toggleBold().run()}
|
||||
class="px-3 py-1 rounded font-bold hover:bg-gray-200 transition-colors"
|
||||
>B</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => editor.chain().focus().toggleItalic().run()}
|
||||
class="px-3 py-1 rounded italic hover:bg-gray-200 transition-colors"
|
||||
>I</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => editor.chain().focus().toggleStrike().run()}
|
||||
class="px-3 py-1 rounded line-through hover:bg-gray-200 transition-colors"
|
||||
>S</button>
|
||||
<div class="w-px bg-gray-300 mx-1"></div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
class="px-3 py-1 rounded hover:bg-gray-200 transition-colors"
|
||||
>• List</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
class="px-3 py-1 rounded hover:bg-gray-200 transition-colors"
|
||||
>1. List</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
bind:this={element}
|
||||
role="textbox"
|
||||
tabindex="-1"
|
||||
class="p-3 min-h-40 prose prose-sm max-w-none cursor-text [&_.tiptap]:outline-none [&_.tiptap]:h-full [&_.tiptap_p]:my-1 [&_.tiptap_p:first-child]:mt-0 [&_.tiptap_p:last-child]:mb-0"
|
||||
onmousedown={() => editor?.commands.focus()}
|
||||
></div>
|
||||
<div class="border border-gray-300 dark:border-gray-600">
|
||||
<div
|
||||
class="flex gap-1 border-b border-gray-300 bg-gray-100 p-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleBold}
|
||||
class="min-w-8 px-2 py-1 font-bold hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
title="Bold">B</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleItalic}
|
||||
class="min-w-8 px-2 py-1 italic hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
title="Italic">I</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleHighlight}
|
||||
class="min-w-8 px-2 py-1 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
title="Highlight"><span class="bg-yellow-200 px-1 dark:bg-yellow-600">H</span></button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={setColor}
|
||||
class="min-w-8 px-2 py-1 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
title="Cycle text color">A</button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
bind:this={element}
|
||||
role="textbox"
|
||||
tabindex="0"
|
||||
contenteditable="true"
|
||||
class="min-h-40 cursor-text bg-white dark:bg-gray-900"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" {name} value={content} />
|
||||
<input type="hidden" {name} value={content} />
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { authClient } from '$lib/auth-client';
|
||||
import { canAccessAdmin } from '$lib/svelte-admin/roles';
|
||||
|
||||
let { userRoleId = null }: { userRoleId: number | null } = $props();
|
||||
const session = authClient.useSession();
|
||||
let isAdmin = $derived(canAccessAdmin(userRoleId));
|
||||
</script>
|
||||
|
||||
<nav class="flex w-full justify-between px-8 py-6">
|
||||
<span><a href="/">Rhema Wave Technologies</a></span>
|
||||
{#if $session.data}
|
||||
<div>
|
||||
{#if isAdmin}
|
||||
<a href="/admin" class="mx-2">Admin Panel</a>
|
||||
{/if}
|
||||
<a href="/dashboard" class="mx-2">Dashboard</a>
|
||||
<button
|
||||
onclick={async () => {
|
||||
authClient.signOut();
|
||||
}}>Log Out</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<span><a href="/login">Log In</a> | <a href="/register">Register</a></span>
|
||||
{/if}
|
||||
</nav>
|
||||
@@ -1,84 +1,94 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||
* Use it to get access to models, enums, and input types.
|
||||
*
|
||||
*
|
||||
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||
* See `client.ts` for the standard, server-side entry point.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
|
||||
export { Prisma }
|
||||
export * as $Enums from './enums.ts'
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser.ts';
|
||||
export { Prisma };
|
||||
export * as $Enums from './enums.ts';
|
||||
export * from './enums.ts';
|
||||
/**
|
||||
* Model User
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
export type User = Prisma.UserModel;
|
||||
/**
|
||||
* Model Profile
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Profile = Prisma.ProfileModel
|
||||
export type Profile = Prisma.ProfileModel;
|
||||
/**
|
||||
* Model Address
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Address = Prisma.AddressModel
|
||||
export type Address = Prisma.AddressModel;
|
||||
/**
|
||||
* Model Session
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Session = Prisma.SessionModel
|
||||
export type Session = Prisma.SessionModel;
|
||||
/**
|
||||
* Model Account
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Account = Prisma.AccountModel
|
||||
export type Account = Prisma.AccountModel;
|
||||
/**
|
||||
* Model UserRole
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type UserRole = Prisma.UserRoleModel
|
||||
export type UserRole = Prisma.UserRoleModel;
|
||||
/**
|
||||
* Model Verification
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Verification = Prisma.VerificationModel
|
||||
export type Verification = Prisma.VerificationModel;
|
||||
/**
|
||||
* Model Proclamation
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Proclamation = Prisma.ProclamationModel
|
||||
export type Proclamation = Prisma.ProclamationModel;
|
||||
/**
|
||||
* Model UserProclamations
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type UserProclamations = Prisma.UserProclamationsModel
|
||||
export type UserProclamations = Prisma.UserProclamationsModel;
|
||||
/**
|
||||
* Model SolfeggioFrequency
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel
|
||||
export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel;
|
||||
/**
|
||||
* Model ProclamationCategory
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel;
|
||||
/**
|
||||
* Model ProclamationCategorySubscriptionLevel
|
||||
*
|
||||
*/
|
||||
export type ProclamationCategorySubscriptionLevel =
|
||||
Prisma.ProclamationCategorySubscriptionLevelModel;
|
||||
/**
|
||||
* Model LicenseKey
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type LicenseKey = Prisma.LicenseKeyModel
|
||||
export type LicenseKey = Prisma.LicenseKeyModel;
|
||||
/**
|
||||
* Model ApiKey
|
||||
*
|
||||
*/
|
||||
export type ApiKey = Prisma.ApiKeyModel;
|
||||
/**
|
||||
* Model SubscriptionLevel
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel
|
||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||
@@ -10,97 +9,114 @@
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as process from 'node:process'
|
||||
import * as path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||
import * as process from 'node:process';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums.ts"
|
||||
import * as $Class from "./internal/class.ts"
|
||||
import * as Prisma from "./internal/prismaNamespace.ts"
|
||||
import * as runtime from '@prisma/client/runtime/client';
|
||||
import * as $Enums from './enums.ts';
|
||||
import * as $Class from './internal/class.ts';
|
||||
import * as Prisma from './internal/prismaNamespace.ts';
|
||||
|
||||
export * as $Enums from './enums.ts'
|
||||
export * from "./enums.ts"
|
||||
export * as $Enums from './enums.ts';
|
||||
export * from './enums.ts';
|
||||
/**
|
||||
* ## Prisma Client
|
||||
*
|
||||
*
|
||||
* Type-safe database client for TypeScript
|
||||
* @example
|
||||
* ```
|
||||
* const prisma = new PrismaClient()
|
||||
* const prisma = new PrismaClient({
|
||||
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
||||
* })
|
||||
* // Fetch zero or more Users
|
||||
* const users = await prisma.user.findMany()
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Read more in our [docs](https://pris.ly/d/client).
|
||||
*/
|
||||
export const PrismaClient = $Class.getPrismaClientClass()
|
||||
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||
export { Prisma }
|
||||
export const PrismaClient = $Class.getPrismaClientClass();
|
||||
export type PrismaClient<
|
||||
LogOpts extends Prisma.LogLevel = never,
|
||||
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Prisma.PrismaClientOptions['omit'],
|
||||
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||
> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>;
|
||||
export { Prisma };
|
||||
|
||||
/**
|
||||
* Model User
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
export type User = Prisma.UserModel;
|
||||
/**
|
||||
* Model Profile
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Profile = Prisma.ProfileModel
|
||||
export type Profile = Prisma.ProfileModel;
|
||||
/**
|
||||
* Model Address
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Address = Prisma.AddressModel
|
||||
export type Address = Prisma.AddressModel;
|
||||
/**
|
||||
* Model Session
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Session = Prisma.SessionModel
|
||||
export type Session = Prisma.SessionModel;
|
||||
/**
|
||||
* Model Account
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Account = Prisma.AccountModel
|
||||
export type Account = Prisma.AccountModel;
|
||||
/**
|
||||
* Model UserRole
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type UserRole = Prisma.UserRoleModel
|
||||
export type UserRole = Prisma.UserRoleModel;
|
||||
/**
|
||||
* Model Verification
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Verification = Prisma.VerificationModel
|
||||
export type Verification = Prisma.VerificationModel;
|
||||
/**
|
||||
* Model Proclamation
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Proclamation = Prisma.ProclamationModel
|
||||
export type Proclamation = Prisma.ProclamationModel;
|
||||
/**
|
||||
* Model UserProclamations
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type UserProclamations = Prisma.UserProclamationsModel
|
||||
export type UserProclamations = Prisma.UserProclamationsModel;
|
||||
/**
|
||||
* Model SolfeggioFrequency
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel
|
||||
export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel;
|
||||
/**
|
||||
* Model ProclamationCategory
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel;
|
||||
/**
|
||||
* Model ProclamationCategorySubscriptionLevel
|
||||
*
|
||||
*/
|
||||
export type ProclamationCategorySubscriptionLevel =
|
||||
Prisma.ProclamationCategorySubscriptionLevelModel;
|
||||
/**
|
||||
* Model LicenseKey
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type LicenseKey = Prisma.LicenseKeyModel
|
||||
export type LicenseKey = Prisma.LicenseKeyModel;
|
||||
/**
|
||||
* Model ApiKey
|
||||
*
|
||||
*/
|
||||
export type ApiKey = Prisma.ApiKeyModel;
|
||||
/**
|
||||
* Model SubscriptionLevel
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel
|
||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel;
|
||||
|
||||
@@ -1,449 +1,402 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import type * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums.ts"
|
||||
import type * as Prisma from "./internal/prismaNamespace.ts"
|
||||
|
||||
import type * as runtime from '@prisma/client/runtime/client';
|
||||
import * as $Enums from './enums.ts';
|
||||
import type * as Prisma from './internal/prismaNamespace.ts';
|
||||
|
||||
export type StringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
mode?: Prisma.QueryMode;
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string;
|
||||
};
|
||||
|
||||
export type BoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean;
|
||||
};
|
||||
|
||||
export type StringNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
mode?: Prisma.QueryMode;
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null;
|
||||
};
|
||||
|
||||
export type DateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string;
|
||||
};
|
||||
|
||||
export type IntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type SortOrderInput = {
|
||||
sort: Prisma.SortOrder
|
||||
nulls?: Prisma.NullsOrder
|
||||
}
|
||||
sort: Prisma.SortOrder;
|
||||
nulls?: Prisma.NullsOrder;
|
||||
};
|
||||
|
||||
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
mode?: Prisma.QueryMode;
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
mode?: Prisma.QueryMode;
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type DateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null;
|
||||
};
|
||||
|
||||
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type IntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type FloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type FloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedStringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string;
|
||||
};
|
||||
|
||||
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean;
|
||||
};
|
||||
|
||||
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string;
|
||||
};
|
||||
|
||||
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null;
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>;
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>;
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports all enum related types from the schema.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
|
||||
* This file exports all enum related types from the schema.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
// This file is empty because there are no enums in the schema.
|
||||
export {}
|
||||
export {};
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* WARNING: This is an internal file that is subject to change!
|
||||
*
|
||||
@@ -15,245 +14,266 @@
|
||||
* model files in the `model` directory!
|
||||
*/
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||
import * as runtime from '@prisma/client/runtime/index-browser';
|
||||
|
||||
export type * from '../models.ts'
|
||||
export type * from './prismaNamespace.ts'
|
||||
|
||||
export const Decimal = runtime.Decimal
|
||||
export type * from '../models.ts';
|
||||
export type * from './prismaNamespace.ts';
|
||||
|
||||
export const Decimal = runtime.Decimal;
|
||||
|
||||
export const NullTypes = {
|
||||
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||
}
|
||||
DbNull: runtime.NullTypes.DbNull as new (secret: never) => typeof runtime.DbNull,
|
||||
JsonNull: runtime.NullTypes.JsonNull as new (secret: never) => typeof runtime.JsonNull,
|
||||
AnyNull: runtime.NullTypes.AnyNull as new (secret: never) => typeof runtime.AnyNull
|
||||
};
|
||||
/**
|
||||
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const DbNull = runtime.DbNull
|
||||
export const DbNull = runtime.DbNull;
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const JsonNull = runtime.JsonNull
|
||||
export const JsonNull = runtime.JsonNull;
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const AnyNull = runtime.AnyNull
|
||||
|
||||
export const AnyNull = runtime.AnyNull;
|
||||
|
||||
export const ModelName = {
|
||||
User: 'User',
|
||||
Profile: 'Profile',
|
||||
Address: 'Address',
|
||||
Session: 'Session',
|
||||
Account: 'Account',
|
||||
UserRole: 'UserRole',
|
||||
Verification: 'Verification',
|
||||
Proclamation: 'Proclamation',
|
||||
UserProclamations: 'UserProclamations',
|
||||
SolfeggioFrequency: 'SolfeggioFrequency',
|
||||
ProclamationCategory: 'ProclamationCategory',
|
||||
LicenseKey: 'LicenseKey',
|
||||
SubscriptionLevel: 'SubscriptionLevel'
|
||||
} as const
|
||||
User: 'User',
|
||||
Profile: 'Profile',
|
||||
Address: 'Address',
|
||||
Session: 'Session',
|
||||
Account: 'Account',
|
||||
UserRole: 'UserRole',
|
||||
Verification: 'Verification',
|
||||
Proclamation: 'Proclamation',
|
||||
UserProclamations: 'UserProclamations',
|
||||
SolfeggioFrequency: 'SolfeggioFrequency',
|
||||
ProclamationCategory: 'ProclamationCategory',
|
||||
ProclamationCategorySubscriptionLevel: 'ProclamationCategorySubscriptionLevel',
|
||||
LicenseKey: 'LicenseKey',
|
||||
ApiKey: 'ApiKey',
|
||||
SubscriptionLevel: 'SubscriptionLevel'
|
||||
} as const;
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName];
|
||||
|
||||
/*
|
||||
* Enums
|
||||
*/
|
||||
|
||||
export const TransactionIsolationLevel = {
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
} as const
|
||||
|
||||
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
} as const);
|
||||
|
||||
export type TransactionIsolationLevel =
|
||||
(typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel];
|
||||
|
||||
export const UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
roleId: 'roleId'
|
||||
} as const
|
||||
|
||||
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
roleId: 'roleId'
|
||||
} as const;
|
||||
|
||||
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum];
|
||||
|
||||
export const ProfileScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
firstName: 'firstName',
|
||||
lastName: 'lastName',
|
||||
phoneNumber: 'phoneNumber'
|
||||
} as const
|
||||
|
||||
export type ProfileScalarFieldEnum = (typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum]
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
firstName: 'firstName',
|
||||
lastName: 'lastName',
|
||||
phoneNumber: 'phoneNumber',
|
||||
ghlContactId: 'ghlContactId'
|
||||
} as const;
|
||||
|
||||
export type ProfileScalarFieldEnum =
|
||||
(typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum];
|
||||
|
||||
export const AddressScalarFieldEnum = {
|
||||
id: 'id',
|
||||
profileId: 'profileId',
|
||||
street: 'street',
|
||||
city: 'city',
|
||||
state: 'state',
|
||||
postalCode: 'postalCode',
|
||||
country: 'country'
|
||||
} as const
|
||||
|
||||
export type AddressScalarFieldEnum = (typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum]
|
||||
id: 'id',
|
||||
profileId: 'profileId',
|
||||
street: 'street',
|
||||
city: 'city',
|
||||
state: 'state',
|
||||
postalCode: 'postalCode',
|
||||
country: 'country',
|
||||
type: 'type',
|
||||
isDefaultMailing: 'isDefaultMailing',
|
||||
isDefaultBilling: 'isDefaultBilling',
|
||||
isDefaultShipping: 'isDefaultShipping'
|
||||
} as const;
|
||||
|
||||
export type AddressScalarFieldEnum =
|
||||
(typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum];
|
||||
|
||||
export const SessionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
expiresAt: 'expiresAt',
|
||||
token: 'token',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
userId: 'userId'
|
||||
} as const
|
||||
|
||||
export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum]
|
||||
id: 'id',
|
||||
expiresAt: 'expiresAt',
|
||||
token: 'token',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
userId: 'userId'
|
||||
} as const;
|
||||
|
||||
export type SessionScalarFieldEnum =
|
||||
(typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum];
|
||||
|
||||
export const AccountScalarFieldEnum = {
|
||||
id: 'id',
|
||||
accountId: 'accountId',
|
||||
providerId: 'providerId',
|
||||
userId: 'userId',
|
||||
accessToken: 'accessToken',
|
||||
refreshToken: 'refreshToken',
|
||||
idToken: 'idToken',
|
||||
accessTokenExpiresAt: 'accessTokenExpiresAt',
|
||||
refreshTokenExpiresAt: 'refreshTokenExpiresAt',
|
||||
scope: 'scope',
|
||||
password: 'password',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type AccountScalarFieldEnum = (typeof AccountScalarFieldEnum)[keyof typeof AccountScalarFieldEnum]
|
||||
id: 'id',
|
||||
accountId: 'accountId',
|
||||
providerId: 'providerId',
|
||||
userId: 'userId',
|
||||
accessToken: 'accessToken',
|
||||
refreshToken: 'refreshToken',
|
||||
idToken: 'idToken',
|
||||
accessTokenExpiresAt: 'accessTokenExpiresAt',
|
||||
refreshTokenExpiresAt: 'refreshTokenExpiresAt',
|
||||
scope: 'scope',
|
||||
password: 'password',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const;
|
||||
|
||||
export type AccountScalarFieldEnum =
|
||||
(typeof AccountScalarFieldEnum)[keyof typeof AccountScalarFieldEnum];
|
||||
|
||||
export const UserRoleScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name'
|
||||
} as const
|
||||
|
||||
export type UserRoleScalarFieldEnum = (typeof UserRoleScalarFieldEnum)[keyof typeof UserRoleScalarFieldEnum]
|
||||
id: 'id',
|
||||
name: 'name'
|
||||
} as const;
|
||||
|
||||
export type UserRoleScalarFieldEnum =
|
||||
(typeof UserRoleScalarFieldEnum)[keyof typeof UserRoleScalarFieldEnum];
|
||||
|
||||
export const VerificationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
identifier: 'identifier',
|
||||
value: 'value',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type VerificationScalarFieldEnum = (typeof VerificationScalarFieldEnum)[keyof typeof VerificationScalarFieldEnum]
|
||||
id: 'id',
|
||||
identifier: 'identifier',
|
||||
value: 'value',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const;
|
||||
|
||||
export type VerificationScalarFieldEnum =
|
||||
(typeof VerificationScalarFieldEnum)[keyof typeof VerificationScalarFieldEnum];
|
||||
|
||||
export const ProclamationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
content: 'content',
|
||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||
categoryId: 'categoryId',
|
||||
price: 'price',
|
||||
subscriptionLevelId: 'subscriptionLevelId'
|
||||
} as const
|
||||
|
||||
export type ProclamationScalarFieldEnum = (typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum]
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
content: 'content',
|
||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||
categoryId: 'categoryId',
|
||||
subscriptionLevelId: 'subscriptionLevelId'
|
||||
} as const;
|
||||
|
||||
export type ProclamationScalarFieldEnum =
|
||||
(typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum];
|
||||
|
||||
export const UserProclamationsScalarFieldEnum = {
|
||||
userId: 'userId',
|
||||
proclamationId: 'proclamationId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type UserProclamationsScalarFieldEnum = (typeof UserProclamationsScalarFieldEnum)[keyof typeof UserProclamationsScalarFieldEnum]
|
||||
userId: 'userId',
|
||||
proclamationId: 'proclamationId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const;
|
||||
|
||||
export type UserProclamationsScalarFieldEnum =
|
||||
(typeof UserProclamationsScalarFieldEnum)[keyof typeof UserProclamationsScalarFieldEnum];
|
||||
|
||||
export const SolfeggioFrequencyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
value: 'value'
|
||||
} as const
|
||||
|
||||
export type SolfeggioFrequencyScalarFieldEnum = (typeof SolfeggioFrequencyScalarFieldEnum)[keyof typeof SolfeggioFrequencyScalarFieldEnum]
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
value: 'value'
|
||||
} as const;
|
||||
|
||||
export type SolfeggioFrequencyScalarFieldEnum =
|
||||
(typeof SolfeggioFrequencyScalarFieldEnum)[keyof typeof SolfeggioFrequencyScalarFieldEnum];
|
||||
|
||||
export const ProclamationCategoryScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
parentId: 'parentId'
|
||||
} as const
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
sortOrder: 'sortOrder',
|
||||
parentId: 'parentId'
|
||||
} as const;
|
||||
|
||||
export type ProclamationCategoryScalarFieldEnum = (typeof ProclamationCategoryScalarFieldEnum)[keyof typeof ProclamationCategoryScalarFieldEnum]
|
||||
export type ProclamationCategoryScalarFieldEnum =
|
||||
(typeof ProclamationCategoryScalarFieldEnum)[keyof typeof ProclamationCategoryScalarFieldEnum];
|
||||
|
||||
export const ProclamationCategorySubscriptionLevelScalarFieldEnum = {
|
||||
categoryId: 'categoryId',
|
||||
subscriptionLevelId: 'subscriptionLevelId'
|
||||
} as const;
|
||||
|
||||
export type ProclamationCategorySubscriptionLevelScalarFieldEnum =
|
||||
(typeof ProclamationCategorySubscriptionLevelScalarFieldEnum)[keyof typeof ProclamationCategorySubscriptionLevelScalarFieldEnum];
|
||||
|
||||
export const LicenseKeyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
active: 'active',
|
||||
level: 'level',
|
||||
userId: 'userId'
|
||||
} as const
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
active: 'active',
|
||||
level: 'level',
|
||||
userId: 'userId'
|
||||
} as const;
|
||||
|
||||
export type LicenseKeyScalarFieldEnum = (typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum]
|
||||
export type LicenseKeyScalarFieldEnum =
|
||||
(typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum];
|
||||
|
||||
export const ApiKeyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
keyPrefix: 'keyPrefix',
|
||||
keyHash: 'keyHash',
|
||||
createdAt: 'createdAt',
|
||||
expiresAt: 'expiresAt',
|
||||
revokedAt: 'revokedAt'
|
||||
} as const;
|
||||
|
||||
export type ApiKeyScalarFieldEnum =
|
||||
(typeof ApiKeyScalarFieldEnum)[keyof typeof ApiKeyScalarFieldEnum];
|
||||
|
||||
export const SubscriptionLevelScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description'
|
||||
} as const
|
||||
|
||||
export type SubscriptionLevelScalarFieldEnum = (typeof SubscriptionLevelScalarFieldEnum)[keyof typeof SubscriptionLevelScalarFieldEnum]
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description'
|
||||
} as const;
|
||||
|
||||
export type SubscriptionLevelScalarFieldEnum =
|
||||
(typeof SubscriptionLevelScalarFieldEnum)[keyof typeof SubscriptionLevelScalarFieldEnum];
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
} as const
|
||||
|
||||
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
} as const;
|
||||
|
||||
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder];
|
||||
|
||||
export const QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
} as const
|
||||
|
||||
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
} as const;
|
||||
|
||||
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode];
|
||||
|
||||
export const NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
} as const
|
||||
|
||||
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
} as const;
|
||||
|
||||
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder];
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This is a barrel export file for all models and their related types.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
export type * from './models/User.ts'
|
||||
export type * from './models/Profile.ts'
|
||||
export type * from './models/Address.ts'
|
||||
export type * from './models/Session.ts'
|
||||
export type * from './models/Account.ts'
|
||||
export type * from './models/UserRole.ts'
|
||||
export type * from './models/Verification.ts'
|
||||
export type * from './models/Proclamation.ts'
|
||||
export type * from './models/UserProclamations.ts'
|
||||
export type * from './models/SolfeggioFrequency.ts'
|
||||
export type * from './models/ProclamationCategory.ts'
|
||||
export type * from './models/LicenseKey.ts'
|
||||
export type * from './models/SubscriptionLevel.ts'
|
||||
export type * from './commonInputTypes.ts'
|
||||
export type * from './models/User.ts';
|
||||
export type * from './models/Profile.ts';
|
||||
export type * from './models/Address.ts';
|
||||
export type * from './models/Session.ts';
|
||||
export type * from './models/Account.ts';
|
||||
export type * from './models/UserRole.ts';
|
||||
export type * from './models/Verification.ts';
|
||||
export type * from './models/Proclamation.ts';
|
||||
export type * from './models/UserProclamations.ts';
|
||||
export type * from './models/SolfeggioFrequency.ts';
|
||||
export type * from './models/ProclamationCategory.ts';
|
||||
export type * from './models/ProclamationCategorySubscriptionLevel.ts';
|
||||
export type * from './models/LicenseKey.ts';
|
||||
export type * from './models/ApiKey.ts';
|
||||
export type * from './models/SubscriptionLevel.ts';
|
||||
export type * from './commonInputTypes.ts';
|
||||
|
||||
+1701
-1422
File diff suppressed because it is too large
Load Diff
+1619
-1210
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1514
-1200
File diff suppressed because it is too large
Load Diff
+1504
-1245
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2299
-1870
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1301
-1058
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+52
-48
@@ -1,71 +1,75 @@
|
||||
// Contains all payment related functions such as making payments,
|
||||
// issueing refunds, storing and deleting credit cards, and more.
|
||||
// A full list of implementation requirements will be kept below.
|
||||
//
|
||||
// issueing refunds, storing and deleting credit cards, and more.
|
||||
// A full list of implementation requirements will be kept below.
|
||||
//
|
||||
// ---- Implementations ----
|
||||
// ✔ Make a payment...
|
||||
// ✔ With a non-saved card
|
||||
// ✔ With a saved card
|
||||
// ✔ Add a card
|
||||
// ✔ Delete a card
|
||||
// ✔ Delete a card
|
||||
// ✔ Issue a refund
|
||||
|
||||
import type { Decimal } from "@prisma/client/runtime/client";
|
||||
import type { Decimal } from '@prisma/client/runtime/client';
|
||||
|
||||
export interface CreditDebitCard {
|
||||
first_name: string,
|
||||
last_name: string,
|
||||
card_number: number,
|
||||
expiration_month: number,
|
||||
expiration_year: number,
|
||||
security_code: number,
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
card_number: number;
|
||||
expiration_month: number;
|
||||
expiration_year: number;
|
||||
security_code: number;
|
||||
}
|
||||
|
||||
export async function chargeCreditDebitCard (card: CreditDebitCard, amount: Decimal): Promise<null> {
|
||||
/**
|
||||
* chargeCreditDebitCard attempts to request payment from a valid card,
|
||||
* for the amount specified.
|
||||
*/
|
||||
console.log(`charged card ${card} for $${amount}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return null;
|
||||
export async function chargeCreditDebitCard(card: CreditDebitCard, amount: Decimal): Promise<null> {
|
||||
/**
|
||||
* chargeCreditDebitCard attempts to request payment from a valid card,
|
||||
* for the amount specified.
|
||||
*/
|
||||
console.log(`charged card ${card} for $${amount}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function chargedStoredCard(cardNumber: number, profileID: string, amount: Decimal): Promise<null> {
|
||||
/**
|
||||
* chargedStoredCard attempts to request payment from a valid card,
|
||||
* for the amount specified.
|
||||
* The card is identified by the last four digits of the card
|
||||
* number, and the profile ID.
|
||||
* NOTE: This should be enough to identify a saved card, but the
|
||||
* function signature may change in the future.
|
||||
*/
|
||||
console.log(`charged ${profileID}'s card ${cardNumber} for ${cardNumber} $${amount}`);
|
||||
return null;
|
||||
export async function chargedStoredCard(
|
||||
cardNumber: number,
|
||||
profileID: string,
|
||||
amount: Decimal
|
||||
): Promise<null> {
|
||||
/**
|
||||
* chargedStoredCard attempts to request payment from a valid card,
|
||||
* for the amount specified.
|
||||
* The card is identified by the last four digits of the card
|
||||
* number, and the profile ID.
|
||||
* NOTE: This should be enough to identify a saved card, but the
|
||||
* function signature may change in the future.
|
||||
*/
|
||||
console.log(`charged ${profileID}'s card ${cardNumber} for ${cardNumber} $${amount}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function deleteCardFromProfile(cardNumber: number, profileID: string): Promise<null> {
|
||||
/**
|
||||
* deleteCardFromProfile deletes a stored card on a user's profile.
|
||||
*/
|
||||
console.log(`Deleted card ${cardNumber} from ${profileID}'s profile.`);
|
||||
return null;
|
||||
/**
|
||||
* deleteCardFromProfile deletes a stored card on a user's profile.
|
||||
*/
|
||||
console.log(`Deleted card ${cardNumber} from ${profileID}'s profile.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function storeCardToProfile(card: CreditDebitCard, profileID: string): Promise<null> {
|
||||
/**
|
||||
* storeCardToProfile stores a new card to a user's profile
|
||||
* for easy re-use later.
|
||||
*/
|
||||
console.log(`Deleted card ${card.card_number} from ${profileID}'s profile.`);
|
||||
return null;
|
||||
/**
|
||||
* storeCardToProfile stores a new card to a user's profile
|
||||
* for easy re-use later.
|
||||
*/
|
||||
console.log(`Deleted card ${card.card_number} from ${profileID}'s profile.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function requestRefund(card: CreditDebitCard, amount: Decimal): Promise<null> {
|
||||
/**
|
||||
* requestRefund attempts to process a refund for the amount to the
|
||||
* provided card.
|
||||
*/
|
||||
console.log(`Refunded ${amount} to ${card.card_number}`);
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* requestRefund attempts to process a refund for the amount to the
|
||||
* provided card.
|
||||
*/
|
||||
console.log(`Refunded ${amount} to ${card.card_number}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { randomBytes, createHash } from 'node:crypto';
|
||||
import { prisma } from '$lib/db';
|
||||
|
||||
export type ApiKeyRecord = {
|
||||
id: string;
|
||||
keyPrefix: string;
|
||||
createdAt: Date;
|
||||
expiresAt: Date | null;
|
||||
revokedAt: Date | null;
|
||||
};
|
||||
|
||||
export type GeneratedKey = {
|
||||
plaintext: string;
|
||||
prefix: string;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
export function generateApiKey(): GeneratedKey {
|
||||
const raw = randomBytes(32);
|
||||
const hex = raw.toString('hex');
|
||||
const plaintext = `sk_${hex}`;
|
||||
const prefix = plaintext.slice(0, 11);
|
||||
const hash = createHash('sha256').update(plaintext).digest('hex');
|
||||
return { plaintext, prefix, hash };
|
||||
}
|
||||
|
||||
export async function validateApiKey(key: string): Promise<ApiKeyRecord | null> {
|
||||
const hash = createHash('sha256').update(key).digest('hex');
|
||||
const record = await prisma.apiKey.findUnique({ where: { keyHash: hash } });
|
||||
if (!record) return null;
|
||||
if (record.revokedAt && record.revokedAt <= new Date()) return null;
|
||||
if (record.expiresAt && record.expiresAt <= new Date()) return null;
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function rotateApiKey(emergency = false): Promise<{
|
||||
plaintext: string;
|
||||
prefix: string;
|
||||
previousExpiresAt: Date | null;
|
||||
previousRevokedAt: Date | null;
|
||||
}> {
|
||||
const now = new Date();
|
||||
|
||||
const activeKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
revokedAt: null,
|
||||
OR: [{ expiresAt: null }, { expiresAt: { gt: now } }]
|
||||
}
|
||||
});
|
||||
|
||||
let previousExpiresAt: Date | null = null;
|
||||
let previousRevokedAt: Date | null = null;
|
||||
|
||||
if (emergency) {
|
||||
previousRevokedAt = now;
|
||||
for (const key of activeKeys) {
|
||||
await prisma.apiKey.update({
|
||||
where: { id: key.id },
|
||||
data: { revokedAt: now }
|
||||
});
|
||||
}
|
||||
previousExpiresAt = null;
|
||||
} else {
|
||||
const graceEnd = new Date(now.getTime() + 48 * 60 * 60 * 1000);
|
||||
previousExpiresAt = graceEnd;
|
||||
for (const key of activeKeys) {
|
||||
await prisma.apiKey.update({
|
||||
where: { id: key.id },
|
||||
data: { expiresAt: graceEnd }
|
||||
});
|
||||
}
|
||||
previousRevokedAt = null;
|
||||
}
|
||||
|
||||
const { plaintext, prefix, hash } = generateApiKey();
|
||||
await prisma.apiKey.create({
|
||||
data: { keyPrefix: prefix, keyHash: hash }
|
||||
});
|
||||
|
||||
return { plaintext, prefix, previousExpiresAt, previousRevokedAt };
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const { sendMail } = vi.hoisted(() => ({
|
||||
sendMail: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$env/static/private', () => ({
|
||||
BETTER_AUTH_URL: 'http://localhost:5173'
|
||||
}));
|
||||
|
||||
vi.mock('./resend', () => ({
|
||||
sendMail
|
||||
}));
|
||||
|
||||
import { sendVerificationEmail } from './email-verification';
|
||||
|
||||
describe('sendVerificationEmail', () => {
|
||||
beforeEach(() => {
|
||||
sendMail.mockReset();
|
||||
});
|
||||
|
||||
it('calls sendMail with correct email and template', async () => {
|
||||
sendMail.mockResolvedValue(undefined);
|
||||
|
||||
await sendVerificationEmail({
|
||||
email: 'jane@example.com',
|
||||
token: 'abc123'
|
||||
});
|
||||
|
||||
expect(sendMail).toHaveBeenCalledWith({
|
||||
to: 'jane@example.com',
|
||||
subject: 'Verify your email address',
|
||||
templateId: 'email-verification-email',
|
||||
data: {
|
||||
target_email_address: 'jane@example.com',
|
||||
verification_link:
|
||||
'http://localhost:5173/api/auth/verify-email?token=abc123&callbackURL=/login?verified=true'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('constructs verification URL with the given token', async () => {
|
||||
sendMail.mockResolvedValue(undefined);
|
||||
|
||||
await sendVerificationEmail({
|
||||
email: 'test@example.com',
|
||||
token: 'xyz789'
|
||||
});
|
||||
|
||||
const call = sendMail.mock.calls[0][0];
|
||||
expect(call.data.verification_link).toContain('xyz789');
|
||||
expect(call.data.verification_link).toContain('/login?verified=true');
|
||||
});
|
||||
|
||||
it('throws when sendMail throws', async () => {
|
||||
sendMail.mockRejectedValue(new Error('Resend error'));
|
||||
|
||||
await expect(
|
||||
sendVerificationEmail({
|
||||
email: 'jane@example.com',
|
||||
token: 'abc123'
|
||||
})
|
||||
).rejects.toThrow('Resend error');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { BETTER_AUTH_URL } from '$env/static/private';
|
||||
import { sendMail } from './resend';
|
||||
|
||||
export async function sendVerificationEmail(options: { email: string; token: string }) {
|
||||
const verificationUrl = `${BETTER_AUTH_URL}/api/auth/verify-email?token=${options.token}&callbackURL=/login?verified=true`;
|
||||
|
||||
await sendMail({
|
||||
to: options.email,
|
||||
subject: 'Verify your email address',
|
||||
templateId: 'email-verification-email',
|
||||
data: {
|
||||
target_email_address: options.email,
|
||||
verification_link: verificationUrl
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/** src/lib/server/resend.ts
|
||||
* AUTHOR: Neil Clack
|
||||
* EMAIL: nclack@radiantwavetech.com
|
||||
* DATE: 07/06/2027
|
||||
*
|
||||
* A server side library and client for Resend
|
||||
* Includes the Resend client, as well as functions
|
||||
* for different email scenarios.
|
||||
*
|
||||
*/
|
||||
import { Resend } from 'resend';
|
||||
import { RESEND_API_KEY, EMAIL_FROM, RESEND_TEMPLATE_ID } from '$env/static/private';
|
||||
|
||||
export const resend = new Resend(RESEND_API_KEY);
|
||||
|
||||
/**
|
||||
* Send a transactional email using a Resend-hosted template.
|
||||
*
|
||||
* @example
|
||||
* await sendMail({
|
||||
* to: 'jane@example.com',
|
||||
* templateId: 'tmpl_abc123', // optional — defaults to RESEND_TEMPLATE_ID
|
||||
* subject: 'Your license key is ready', // optional — defaults to 'Template Email'
|
||||
* data: { // optional — template variables
|
||||
* firstName: 'Jane',
|
||||
* licenseKey: 'RWT-7X9K-2M4Q'
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @returns The Resend API response data on success.
|
||||
* @throws {Error} If the Resend API call fails.
|
||||
*/
|
||||
export async function sendMail(options: {
|
||||
to: string;
|
||||
subject?: string;
|
||||
templateId?: string;
|
||||
data?: Record<string, string | number>;
|
||||
}) {
|
||||
const { to, subject, templateId, data } = options;
|
||||
|
||||
const result = await resend.emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to: [to],
|
||||
subject: subject ?? 'Template Email',
|
||||
template: {
|
||||
id: templateId ?? RESEND_TEMPLATE_ID,
|
||||
variables: data ?? {}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
+130
-109
@@ -1,8 +1,8 @@
|
||||
# svelte-admin
|
||||
|
||||
A config-driven admin panel framework for SvelteKit + Prisma. Define your models once in a
|
||||
registry and get full CRUD UI with dynamic routing, role-based access control, searchable
|
||||
dropdowns, and related-record panels — no per-model route files needed.
|
||||
registry and get full CRUD UI with per-model static pages, role-based access control, searchable
|
||||
tables, FK dropdowns, TipTap editor for rich text, and related-record panels.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -13,27 +13,30 @@ src/lib/svelte-admin/ # Framework core
|
||||
├── roles.ts # Role hierarchy and access helpers
|
||||
├── prisma-delegate.ts # Dynamic Prisma delegate lookup
|
||||
├── introspect.ts # Field label generation and dot-notation resolution
|
||||
├── form-data.ts # FormData → typed object parser
|
||||
├── form-data.ts # FormData -> typed object parser
|
||||
├── crud.ts # CRUD action factory (load, upsert, delete)
|
||||
└── index.ts # Barrel exports
|
||||
|
||||
src/routes/(protected)/admin/ # Dynamic admin routes
|
||||
src/routes/(protected)/admin/ # Per-model admin pages (no dynamic [model] routing)
|
||||
├── +layout.server.ts # Role-based access guard
|
||||
├── +layout.svelte # Layout shell
|
||||
├── +page.server.ts # Admin home (model grid)
|
||||
├── +page.svelte # Admin home UI
|
||||
├── [model]/
|
||||
│ ├── +page.server.ts # List view + create action
|
||||
│ ├── +page.svelte # List view UI
|
||||
│ └── [id]/
|
||||
│ ├── +page.server.ts # Edit view + update/delete actions
|
||||
│ └── +page.svelte # Edit view UI
|
||||
├── users/ # Each model gets its own page directory
|
||||
├── profiles/
|
||||
├── addresses/
|
||||
├── user-roles/
|
||||
├── proclamations/
|
||||
├── solfeggio-frequencies/
|
||||
├── proclamation-categories/
|
||||
├── subscription-levels/
|
||||
└── license-keys/ # listOnly — no create/edit modal
|
||||
|
||||
src/lib/components/admin/ # Reusable admin UI components
|
||||
├── AdminTable.svelte # Sortable, searchable data table
|
||||
├── AdminForm.svelte # Dynamic form with type-aware inputs
|
||||
├── ComboBox.svelte # Searchable dropdown for FK fields
|
||||
├── RelatedHasOne.svelte # Display panel for 1:1 related records
|
||||
└── RelatedHasMany.svelte # Display table for 1:N related records
|
||||
├── AdminModal.svelte # Modal overlay for CRUD forms
|
||||
├── TipTap.svelte # Rich-text editor (in components/tiptap/)
|
||||
```
|
||||
|
||||
## Adding a New Model
|
||||
@@ -74,10 +77,11 @@ src/lib/components/admin/ # Reusable admin UI components
|
||||
}
|
||||
```
|
||||
|
||||
3. **Done.** Visit `/admin/widgets`. The dynamic `[model]` routes handle list, create, edit,
|
||||
and delete automatically.
|
||||
3. **Create the page directory** `src/routes/(protected)/admin/widgets/` with:
|
||||
- `+page.server.ts` — load records + form actions using `crud.ts` helpers
|
||||
- `+page.svelte` — render AdminTable + AdminModal with AdminForm
|
||||
|
||||
No new route files, no new components. The registry is the only thing you touch.
|
||||
4. **Done.** Visit `/admin/widgets`. The page handles list, create, edit via modal.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
@@ -95,25 +99,26 @@ No new route files, no new components. The registry is the only thing you touch.
|
||||
| `searchFields` | `string[]` | Yes | Fields searched by the client-side search filter. |
|
||||
| `formFields` | `string[]` | Yes | Fields rendered in create/edit forms, in display order. |
|
||||
| `readonlyFields` | `string[]` | No | Fields shown as read-only on the edit page. |
|
||||
| `listOnly` | `boolean` | No | If `true`, suppresses edit links in the table. |
|
||||
| `listOnly` | `boolean` | No | If `true`, suppresses edit links in the table and no modal. |
|
||||
| `fieldTypes` | `Record<string, PrismaScalarType>` | Yes | Maps field names to scalar types. Controls input rendering and FormData parsing. |
|
||||
| `relationOptions` | `Record<string, RelationOptionConfig>` | No | FK fields that render as ComboBox dropdowns. |
|
||||
| `relatedRecords` | `RelatedRecordConfig[]` | No | Has-one/has-many panels shown on edit pages. |
|
||||
| `relationOptions` | `Record<string, RelationOptionConfig>` | No | FK fields that render as `<select>` dropdowns. |
|
||||
| `relatedRecords` | `RelatedRecordConfig[]` | No | Has-one/has-many panels shown on edit pages (display only via related records table). |
|
||||
| `accessFilter` | `(viewerRoleId: number) => where \| undefined` | No | Returns a Prisma `where` clause to filter records by viewer role. `undefined` = no filter. |
|
||||
|
||||
### PrismaScalarType
|
||||
|
||||
`'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime'`
|
||||
`'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Tiptap'`
|
||||
|
||||
Controls both form input rendering and FormData parsing:
|
||||
|
||||
| Type | HTML Input | Parse Logic |
|
||||
| ---------- | ----------------------------------- | ------------------------------ |
|
||||
| `String` | `<input type="text">` | `raw.toString()` |
|
||||
| `Int` | `<input type="number" step="1">` | `parseInt()`, empty → `null` |
|
||||
| `Float` | `<input type="number" step="0.01">` | `parseFloat()`, empty → `null` |
|
||||
| `Boolean` | `<input type="checkbox">` | `'on'` or `'true'` → `true` |
|
||||
| `DateTime` | `<input type="datetime-local">` | `new Date()`, empty → `null` |
|
||||
| Type | HTML Input | Parse Logic |
|
||||
| ---------- | -------------------------------------- | ------------------------------- |
|
||||
| `String` | `<input type="text">` | `raw.toString()` |
|
||||
| `Int` | `<input type="number" step="1">` | `parseInt()`, empty -> `null` |
|
||||
| `Float` | `<input type="number" step="0.01">` | `parseFloat()`, empty -> `null` |
|
||||
| `Boolean` | `<input type="checkbox">` | `'on'` or `'true'` -> `true` |
|
||||
| `DateTime` | `<input type="datetime-local">` | `new Date()`, empty -> `null` |
|
||||
| `Tiptap` | TipTap rich-text editor (hidden input) | `raw.toString()` |
|
||||
|
||||
### RelationOptionConfig
|
||||
|
||||
@@ -142,14 +147,18 @@ Controls both form input rendering and FormData parsing:
|
||||
The `fieldTypes` mapping is the central mechanism connecting Prisma scalar types to HTML inputs
|
||||
and form data parsing. Every field in `formFields` must have a corresponding entry in `fieldTypes`.
|
||||
|
||||
When a field also has a `relationOptions` entry, the framework renders a `ComboBox` (searchable
|
||||
dropdown) instead of the default input for that type.
|
||||
When a field also has a `relationOptions` entry, the framework renders a `<select>` dropdown
|
||||
instead of the default input for that type. The options are fetched server-side by `crud.ts`
|
||||
and passed to the client as `relationOptionsData`.
|
||||
|
||||
For `Tiptap` fields, the TipTap rich-text editor is rendered. The editor content is stored in a
|
||||
hidden input and submitted with the form.
|
||||
|
||||
## Relations
|
||||
|
||||
### Foreign Key Dropdowns (relationOptions)
|
||||
|
||||
When a form field is a foreign key, add a `relationOptions` entry to render a searchable ComboBox:
|
||||
When a form field is a foreign key, add a `relationOptions` entry to render a `<select>` dropdown:
|
||||
|
||||
```ts
|
||||
{
|
||||
@@ -165,7 +174,8 @@ When a form field is a foreign key, add a `relationOptions` entry to render a se
|
||||
}
|
||||
```
|
||||
|
||||
The route loader automatically queries the related model and passes options to the ComboBox.
|
||||
The `crud.ts` `loadRecords` function automatically queries the related model and passes options
|
||||
to the page as `relationOptionsData`.
|
||||
|
||||
### Eager Loading (include)
|
||||
|
||||
@@ -184,25 +194,23 @@ Display has-one or has-many related records as panels on the edit page using `re
|
||||
|
||||
```ts
|
||||
relatedRecords: [
|
||||
{
|
||||
relationKey: 'profile',
|
||||
label: 'Profile',
|
||||
type: 'has-one',
|
||||
displayFields: ['firstName', 'lastName'],
|
||||
editSlug: 'profiles' // links to /admin/profiles/{id}
|
||||
},
|
||||
{
|
||||
relationKey: 'licenseKeys',
|
||||
label: 'License Keys',
|
||||
type: 'has-many',
|
||||
displayFields: ['key', 'active', 'level']
|
||||
}
|
||||
{
|
||||
relationKey: 'profile',
|
||||
label: 'Profile',
|
||||
type: 'has-one',
|
||||
displayFields: ['firstName', 'lastName'],
|
||||
editSlug: 'profiles' // links to /admin/profiles/{id}
|
||||
},
|
||||
{
|
||||
relationKey: 'licenseKeys',
|
||||
label: 'License Keys',
|
||||
type: 'has-many',
|
||||
displayFields: ['key', 'active', 'level']
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
The `relationKey` must match a key in `include` so the data is actually loaded. `has-one` renders
|
||||
a key-value panel via `RelatedHasOne`; `has-many` renders a table via `RelatedHasMany`. Both
|
||||
support an optional `editSlug` to link to the related record's edit page.
|
||||
The `relationKey` must match a key in `include` so the data is actually loaded.
|
||||
|
||||
## Access Control
|
||||
|
||||
@@ -228,38 +236,40 @@ Use `accessFilter` to restrict which records a viewer can see:
|
||||
|
||||
```ts
|
||||
accessFilter: (viewerRoleId: number) => {
|
||||
if (viewerRoleId <= ROLES.SUPER) return undefined; // no filter
|
||||
if (viewerRoleId <= ROLES.ADMIN) {
|
||||
return { roleId: { gte: ROLES.STAFF } }; // can't see other admins
|
||||
}
|
||||
return { roleId: { gt: ROLES.STAFF } }; // staff sees only regular users
|
||||
if (viewerRoleId <= ROLES.SUPER) return undefined;
|
||||
if (viewerRoleId <= ROLES.ADMIN) {
|
||||
return { roleId: { gte: ROLES.STAFF } };
|
||||
}
|
||||
return { roleId: { gt: ROLES.STAFF } };
|
||||
};
|
||||
```
|
||||
|
||||
## How Dynamic Routing Works
|
||||
## How Static Pages Work
|
||||
|
||||
1. User visits `/admin/proclamations` (or `/admin/proclamations/abc123`)
|
||||
2. SvelteKit matches the `[model]` (and `[id]`) route params
|
||||
3. `+page.server.ts` calls `getModelConfig(params.model)` to look up the config
|
||||
4. `getDelegate(config)` returns `prisma[lcfirst(config.modelName)]` (e.g., `prisma.proclamation`)
|
||||
5. The delegate runs `findMany` (list), `findUnique` (edit), `create`, `update`, or `delete`
|
||||
6. Data + config are passed to the Svelte page component
|
||||
7. Components (`AdminTable`, `AdminForm`, etc.) render based on config
|
||||
1. User visits `/admin/proclamations`
|
||||
2. SvelteKit matches the static route `/admin/proclamations/+page.server.ts`
|
||||
3. `+page.server.ts` calls `getModelConfig('proclamations')` to look up the config
|
||||
4. `loadRecords(config)` calls `prisma.proclamation.findMany()` with the config's `include` and `accessFilter`
|
||||
5. For each FK field with a `relationOptions`, it also queries the related model for dropdown options
|
||||
6. Data + config are passed to `+page.svelte`
|
||||
7. The page renders `AdminTable` with records and `AdminModal` + `AdminForm` for create/edit
|
||||
|
||||
## Component API
|
||||
|
||||
### AdminTable
|
||||
|
||||
| Prop | Type | Description |
|
||||
| -------------- | --------------------------- | ------------------------------------ |
|
||||
| `records` | `Record<string, unknown>[]` | Data rows |
|
||||
| `listFields` | `string[]` | Column keys (dot-notation supported) |
|
||||
| `searchFields` | `string[]` | Fields for client-side search |
|
||||
| `modelSlug` | `string` | Used for building edit links |
|
||||
| `listOnly` | `boolean?` | Suppress edit links |
|
||||
| Prop | Type | Description |
|
||||
| -------------- | ---------------------------------- | ------------------------------------ |
|
||||
| `records` | `Record<string, unknown>[]` | Data rows |
|
||||
| `listFields` | `string[]` | Column keys (dot-notation supported) |
|
||||
| `searchFields` | `string[]` | Fields for client-side search |
|
||||
| `fieldTypes` | `Record<string, PrismaScalarType>` | Type mapping for cell formatting |
|
||||
| `singular` | `string` | Singular label for UI text |
|
||||
| `onRowClick` | `(record) => void?` | Click handler for edit modal |
|
||||
| `onNew` | `() => void?` | Click handler for create modal |
|
||||
|
||||
Features: client-side search, click-to-sort columns, auto-formatted cells (dates, booleans,
|
||||
decimals), filtered count display.
|
||||
decimals), empty state.
|
||||
|
||||
### AdminForm
|
||||
|
||||
@@ -270,54 +280,65 @@ decimals), filtered count display.
|
||||
| `relationOptions` | `Record<string, RelationOptionConfig>?` | FK dropdown configs |
|
||||
| `relationOptionsData` | `Record<string, { label; value }[]>?` | Loaded dropdown options |
|
||||
| `record` | `Record<string, unknown>?` | Pre-fill values (edit mode) |
|
||||
| `form` | `ActionData?` | Carries validation errors |
|
||||
| `action` | `string` | Form action (e.g., `'?/create'`) |
|
||||
| `readonlyFields` | `string[]?` | Read-only field names |
|
||||
| `action` | `string` | Form action (e.g., `'?/upsert'`) |
|
||||
| `submitLabel` | `string?` | Button text |
|
||||
|
||||
### ComboBox
|
||||
### AdminModal
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------- | -------------------- | ------------------ |
|
||||
| `id` | `string` | Element ID |
|
||||
| `name` | `string` | Form field name |
|
||||
| `options` | `{ label; value }[]` | Dropdown items |
|
||||
| `placeholder` | `string?` | Placeholder text |
|
||||
| `value` | `string \| number?` | Pre-selected value |
|
||||
| Prop | Type | Description |
|
||||
| ---------- | ------------ | ------------------ |
|
||||
| `open` | `boolean` | Show/hide modal |
|
||||
| `title` | `string` | Modal heading |
|
||||
| `onClose` | `() => void` | Close handler |
|
||||
| `children` | `Snippet` | Modal body content |
|
||||
|
||||
Features: type-to-search filtering, keyboard navigation (ArrowUp/Down, Enter, Escape),
|
||||
clear button, scroll-to-highlight.
|
||||
### TipTap
|
||||
|
||||
### RelatedHasOne
|
||||
|
||||
| Prop | Type | Description |
|
||||
| --------------- | --------------------------------- | ------------------------------------ |
|
||||
| `label` | `string` | Section heading |
|
||||
| `record` | `Record<string, unknown> \| null` | Related record |
|
||||
| `displayFields` | `string[]` | Fields to display as key-value pairs |
|
||||
| `editSlug` | `string?` | Links to edit the related record |
|
||||
|
||||
### RelatedHasMany
|
||||
|
||||
| Prop | Type | Description |
|
||||
| --------------- | --------------------------- | ----------------------------- |
|
||||
| `label` | `string` | Section heading (shows count) |
|
||||
| `records` | `Record<string, unknown>[]` | Related records |
|
||||
| `displayFields` | `string[]` | Table columns |
|
||||
| `editSlug` | `string?` | Per-row edit links |
|
||||
| Prop | Type | Description |
|
||||
| ---------------- | -------- | --------------------------------- |
|
||||
| `initialContent` | `string` | Initial HTML content |
|
||||
| `name` | `string` | Hidden input name for form submit |
|
||||
|
||||
## Utility Functions
|
||||
|
||||
| Function | Module | Description |
|
||||
| ------------------------------------------ | -------------------- | ----------------------------------------------------------------------------- |
|
||||
| `fieldNameToLabel(name)` | `introspect.ts` | `'solfeggioFrequencyId'` → `'Solfeggio Frequency'` (strips `Id`, adds spaces) |
|
||||
| `resolveFieldValue(record, path)` | `introspect.ts` | Resolves dot-notation paths like `'category.name'` on nested objects |
|
||||
| `extractFormData(formData, fields, types)` | `form-data.ts` | Parses `FormData` with type-aware conversion per `PrismaScalarType` |
|
||||
| `getDelegate(config)` | `prisma-delegate.ts` | Returns `prisma[lcfirst(modelName)]` delegate |
|
||||
| `parseId(config, raw)` | `prisma-delegate.ts` | Parses string to `number` when `idType` is `'Int'` |
|
||||
| `getModelConfig(slug)` | `registry.ts` | Looks up config by URL slug |
|
||||
| `getAllModelConfigs()` | `registry.ts` | Returns all registered model configs |
|
||||
| `hasRole(roleId, required)` | `roles.ts` | `true` if `roleId <= required` |
|
||||
| `canAccessAdmin(roleId)` | `roles.ts` | `true` if `roleId <= ROLES.STAFF` |
|
||||
| Function | Module | Description |
|
||||
| ------------------------------------------ | -------------------- | ------------------------------------------------------------------------------ |
|
||||
| `fieldNameToLabel(name)` | `introspect.ts` | `'solfeggioFrequencyId'` -> `'Solfeggio Frequency'` (strips `Id`, adds spaces) |
|
||||
| `resolveFieldValue(record, path)` | `introspect.ts` | Resolves dot-notation paths like `'category.name'` on nested objects |
|
||||
| `extractFormData(formData, fields, types)` | `form-data.ts` | Parses `FormData` with type-aware conversion per `PrismaScalarType` |
|
||||
| `getDelegate(config)` | `prisma-delegate.ts` | Returns `prisma[lcfirst(modelName)]` delegate |
|
||||
| `parseId(config, raw)` | `prisma-delegate.ts` | Parses string to `number` when `idType` is `'Int'` |
|
||||
| `getModelConfig(slug)` | `registry.ts` | Looks up config by URL slug |
|
||||
| `getAllModelConfigs()` | `registry.ts` | Returns all registered model configs |
|
||||
| `hasRole(roleId, required)` | `roles.ts` | `true` if `roleId <= required` |
|
||||
| `canAccessAdmin(roleId)` | `roles.ts` | `true` if `roleId <= ROLES.STAFF` |
|
||||
| `loadRecords(locals, config)` | `crud.ts` | Fetches records + FK dropdown options for a model |
|
||||
| `upsertRecord(formData, config, locals)` | `crud.ts` | Creates or updates a record based on presence of `id` in form data |
|
||||
| `deleteRecord(formData, config, locals)` | `crud.ts` | Deletes a record by ID |
|
||||
|
||||
## Models in Admin
|
||||
|
||||
| Registry Slug | Prisma Model | Notes |
|
||||
| ------------------------- | -------------------- | ----------------------------------- |
|
||||
| `users` | User | Role assignment, view-only fields |
|
||||
| `profiles` | Profile | Linked to user FK |
|
||||
| `addresses` | Address | Linked to profile FK |
|
||||
| `user-roles` | UserRole | |
|
||||
| `proclamations` | Proclamation | TipTap rich-text editor for content |
|
||||
| `solfeggio-frequencies` | SolfeggioFrequency | |
|
||||
| `proclamation-categories` | ProclamationCategory | Parent category hierarchy |
|
||||
| `license-keys` | LicenseKey | listOnly — read-only table, no CRUD |
|
||||
| `subscription-levels` | SubscriptionLevel | |
|
||||
|
||||
### Excluded from Admin
|
||||
|
||||
| Model | Reason |
|
||||
| ----------------- | --------------------------------------------- |
|
||||
| Session | Better Auth internal — not for manual editing |
|
||||
| Account | Better Auth internal |
|
||||
| Verification | Better Auth internal |
|
||||
| UserProclamations | Composite key — managed via other models |
|
||||
|
||||
## Complete Example
|
||||
|
||||
@@ -364,7 +385,7 @@ A hypothetical `Product` model showing all configuration features:
|
||||
],
|
||||
accessFilter: (viewerRoleId: number) => {
|
||||
if (viewerRoleId <= 2) return undefined;
|
||||
return { active: true }; // staff only sees active products
|
||||
return { active: true };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createLocals, mockUser } from '$lib/test-utils';
|
||||
import { getModelConfig } from './registry';
|
||||
|
||||
// Ensure superValidate can be imported from server path in Node.js/SSR context
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||
|
||||
vi.mock('$lib/db', () => {
|
||||
const mkdel = () => ({
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn()
|
||||
});
|
||||
return {
|
||||
prisma: {
|
||||
userRole: mkdel(),
|
||||
proclamation: mkdel(),
|
||||
proclamationCategory: mkdel(),
|
||||
solfeggioFrequency: mkdel(),
|
||||
subscriptionLevel: mkdel()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
import { prisma } from '$lib/db';
|
||||
import { loadRecords, upsertRecord, deleteRecord } from './crud';
|
||||
|
||||
const userRoleFindMany = prisma.userRole.findMany as ReturnType<typeof vi.fn>;
|
||||
const create = prisma.userRole.create as ReturnType<typeof vi.fn>;
|
||||
const update = prisma.userRole.update as ReturnType<typeof vi.fn>;
|
||||
const deletes = prisma.userRole.delete as ReturnType<typeof vi.fn>;
|
||||
const findUnique = prisma.userRole.findUnique as ReturnType<typeof vi.fn>;
|
||||
const findFirst = prisma.userRole.findFirst as ReturnType<typeof vi.fn>;
|
||||
const config = getModelConfig('user-roles')!;
|
||||
|
||||
describe('crud loadRecords', () => {
|
||||
beforeEach(() => {
|
||||
userRoleFindMany.mockReset();
|
||||
});
|
||||
|
||||
it('returns records', async () => {
|
||||
userRoleFindMany.mockResolvedValue([{ id: 1, name: 'Admin' }]);
|
||||
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await loadRecords(locals, config);
|
||||
|
||||
expect(result.records).toEqual([{ id: 1, name: 'Admin' }]);
|
||||
});
|
||||
|
||||
it('returns empty records array', async () => {
|
||||
userRoleFindMany.mockResolvedValue([]);
|
||||
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await loadRecords(locals, config);
|
||||
|
||||
expect(result.records).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('crud upsertRecord', () => {
|
||||
beforeEach(() => {
|
||||
create.mockReset();
|
||||
update.mockReset();
|
||||
findUnique.mockReset();
|
||||
findFirst.mockReset();
|
||||
});
|
||||
|
||||
it('creates a new record when no id', async () => {
|
||||
create.mockResolvedValue({ id: 2, name: 'Editor' });
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Editor');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(create).toHaveBeenCalledWith({ data: { name: 'Editor' } });
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('updates an existing record when id is present', async () => {
|
||||
findUnique.mockResolvedValue({ id: 1, name: 'Old Name' });
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('id', '1');
|
||||
fd.set('name', 'New Name');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(update).toHaveBeenCalledWith({ where: { id: 1 }, data: { name: 'New Name' } });
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('returns error when updating non-existent record', async () => {
|
||||
findUnique.mockResolvedValue(null);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('id', '999');
|
||||
fd.set('name', 'Ghost');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({ success: false, error: 'Record not found' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('crud deleteRecord', () => {
|
||||
beforeEach(() => {
|
||||
deletes.mockReset();
|
||||
});
|
||||
|
||||
it('deletes a record by id', async () => {
|
||||
deletes.mockResolvedValue({ id: 1, name: 'Admin' });
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('id', '1');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await deleteRecord(fd, config, locals);
|
||||
|
||||
expect(deletes).toHaveBeenCalledWith({ where: { id: 1 } });
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('returns error when id is missing', async () => {
|
||||
const fd = new FormData();
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await deleteRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({ success: false, error: 'Missing id' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('P2002 unique constraint handling', () => {
|
||||
beforeEach(() => {
|
||||
create.mockReset();
|
||||
});
|
||||
|
||||
it('returns error with field names when meta.target is populated', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { target: ['name'], modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A record with this name already exists.',
|
||||
fieldErrors: { name: 'Already taken' }
|
||||
});
|
||||
});
|
||||
|
||||
it('returns generic message when meta.target is empty', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { target: [], modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A database constraint violation occurred. The record could not be created.'
|
||||
});
|
||||
});
|
||||
|
||||
it('returns generic message when meta.target is missing', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A database constraint violation occurred. The record could not be created.'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sveltekit-superforms import compatibility', () => {
|
||||
it('imports superValidate from server path without pulling in SuperDebug.svelte', async () => {
|
||||
expect(superValidate).toBeDefined();
|
||||
});
|
||||
|
||||
it('calls superValidate with a Request and validates against a Zod schema', async () => {
|
||||
const { z } = await import('zod');
|
||||
const schema = z.object({ name: z.string().min(1) });
|
||||
const body = new URLSearchParams({ name: 'test' });
|
||||
const request = new Request('http://localhost', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' }
|
||||
});
|
||||
const form = await superValidate(request, zod4(schema));
|
||||
expect(form.valid).toBe(true);
|
||||
expect(form.data).toEqual({ name: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('prisma client version consistency', () => {
|
||||
it('generated client references existing query compiler files', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const runtimeDir = path.resolve('node_modules/@prisma/client/runtime');
|
||||
|
||||
const generatedFile = path.resolve('src/lib/generated/prisma/internal/class.ts');
|
||||
const content = fs.readFileSync(generatedFile, 'utf-8');
|
||||
const compilerMatch = content.match(/import\(["']@prisma\/client\/runtime\/([^"']+)["']\)/);
|
||||
expect(compilerMatch).not.toBeNull();
|
||||
const compilerPath = path.join(runtimeDir, compilerMatch![1]);
|
||||
expect(fs.existsSync(compilerPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,190 @@
|
||||
import { getDelegate, parseId } from './prisma-delegate';
|
||||
import { extractFormData } from './form-data';
|
||||
import type { AdminModelConfig } from './types';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||
import * as Schemas from './schemas';
|
||||
import type { z } from 'zod';
|
||||
|
||||
const modelSchemas: Record<string, z.ZodObject<any>> = {
|
||||
User: Schemas.User,
|
||||
Profile: Schemas.Profile,
|
||||
UserRole: Schemas.UserRole,
|
||||
Proclamation: Schemas.Proclamation,
|
||||
ProclamationCategory: Schemas.ProclamationCategory,
|
||||
SolfeggioFrequency: Schemas.SolfeggioFrequency,
|
||||
SubscriptionLevel: Schemas.SubscriptionLevel,
|
||||
LicenseKey: Schemas.LicenseKey
|
||||
};
|
||||
|
||||
export function superFormErrorsToFieldErrors(superErrors: Record<string, unknown>): {
|
||||
error: string;
|
||||
fieldErrors: Record<string, string>;
|
||||
} {
|
||||
const fieldErrors: Record<string, string> = {};
|
||||
let formError = '';
|
||||
for (const [key, value] of Object.entries(superErrors)) {
|
||||
if (key === '_errors' && Array.isArray(value)) {
|
||||
formError = (value[0] as string) ?? '';
|
||||
} else if (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
'_errors' in value &&
|
||||
Array.isArray((value as Record<string, unknown>)._errors)
|
||||
) {
|
||||
const errMsg = ((value as Record<string, unknown>)._errors as string[])[0];
|
||||
fieldErrors[key] = errMsg;
|
||||
}
|
||||
}
|
||||
return { error: formError, fieldErrors };
|
||||
}
|
||||
|
||||
export async function loadRecords(locals: App.Locals, config: AdminModelConfig) {
|
||||
const delegate = getDelegate(config);
|
||||
|
||||
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||
const where = config.accessFilter?.(viewerRoleId);
|
||||
|
||||
const records = await delegate.findMany({
|
||||
where,
|
||||
include: config.include,
|
||||
orderBy: { id: 'asc' }
|
||||
});
|
||||
|
||||
const relationOptionsData: Record<string, { label: string; value: string | number }[]> = {};
|
||||
|
||||
for (const [field, relConfig] of Object.entries(config.relationOptions ?? {})) {
|
||||
const relDelegate = getDelegate({ ...config, modelName: relConfig.model });
|
||||
const selectFields: Record<string, boolean> = {
|
||||
[relConfig.valueField]: true,
|
||||
[relConfig.labelField]: true
|
||||
};
|
||||
const options = await relDelegate.findMany({
|
||||
select: selectFields,
|
||||
orderBy: { [relConfig.labelField]: 'asc' }
|
||||
});
|
||||
const mapped = (options as Record<string, unknown>[]).map((opt: Record<string, unknown>) => ({
|
||||
label: String(opt[relConfig.labelField]),
|
||||
value: opt[relConfig.valueField] as string | number
|
||||
}));
|
||||
relationOptionsData[field] = mapped;
|
||||
}
|
||||
|
||||
return {
|
||||
records: records as Record<string, unknown>[],
|
||||
relationOptionsData
|
||||
};
|
||||
}
|
||||
|
||||
export async function upsertRecord(
|
||||
requestOrFormData: Request | FormData,
|
||||
config: AdminModelConfig,
|
||||
locals: App.Locals
|
||||
) {
|
||||
const formData =
|
||||
requestOrFormData instanceof FormData ? requestOrFormData : await requestOrFormData.formData();
|
||||
|
||||
const schema = modelSchemas[config.modelName];
|
||||
const form = schema ? await superValidate(formData, zod4(schema)) : null;
|
||||
|
||||
if (form && !form.valid) {
|
||||
const { error, fieldErrors } = superFormErrorsToFieldErrors(
|
||||
form.errors as Record<string, unknown>
|
||||
);
|
||||
return { ...form, success: false, error, fieldErrors };
|
||||
}
|
||||
|
||||
const rawId = formData.get('id')?.toString();
|
||||
const data =
|
||||
form?.data ??
|
||||
(extractFormData(formData, config.formFields, config.fieldTypes) as Record<string, unknown>);
|
||||
|
||||
// Strip virtual/UI-only fields from Prisma payload
|
||||
for (const f of config.dataOnlyFields ?? []) {
|
||||
delete data[f];
|
||||
}
|
||||
|
||||
const delegate = getDelegate(config);
|
||||
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||
const where = config.accessFilter?.(viewerRoleId);
|
||||
|
||||
if (rawId) {
|
||||
const id = parseId(config, rawId);
|
||||
|
||||
const existing = await delegate.findUnique({
|
||||
where: { id },
|
||||
...((config.include ? { include: config.include } : {}) as object)
|
||||
});
|
||||
if (!existing) {
|
||||
return { success: false, error: 'Record not found' };
|
||||
}
|
||||
|
||||
if (where) {
|
||||
const match = await delegate.findFirst({
|
||||
where: { id, ...where }
|
||||
});
|
||||
if (!match) {
|
||||
return { success: false, error: 'Access denied' };
|
||||
}
|
||||
}
|
||||
|
||||
await delegate.update({
|
||||
where: { id },
|
||||
data
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} else {
|
||||
try {
|
||||
await delegate.create({ data });
|
||||
} catch (e: unknown) {
|
||||
if (e && typeof e === 'object' && 'code' in e && (e as { code: string }).code === 'P2002') {
|
||||
const meta = (e as { meta?: { target?: string[]; modelName?: string } }).meta;
|
||||
const fields = meta?.target ?? [];
|
||||
console.error(
|
||||
`[crud] P2002 on ${meta?.modelName ?? 'unknown'} create. data=`,
|
||||
data,
|
||||
`meta.target=`,
|
||||
meta?.target
|
||||
);
|
||||
if (fields.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'A database constraint violation occurred. The record could not be created.'
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: `A record with this ${fields.join(', ')} already exists.`,
|
||||
fieldErrors: Object.fromEntries(fields.map((f) => [f, 'Already taken']))
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRecord(
|
||||
requestOrFormData: Request | FormData,
|
||||
config: AdminModelConfig,
|
||||
locals: App.Locals
|
||||
) {
|
||||
const delegate = getDelegate(config);
|
||||
const formData =
|
||||
requestOrFormData instanceof FormData ? requestOrFormData : await requestOrFormData.formData();
|
||||
const rawId = formData.get('id')?.toString();
|
||||
if (!rawId) return { success: false, error: 'Missing id' };
|
||||
|
||||
const id = parseId(config, rawId);
|
||||
|
||||
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||
const where = config.accessFilter?.(viewerRoleId);
|
||||
if (where) {
|
||||
const match = await delegate.findFirst({ where: { id, ...where } });
|
||||
if (!match) return { success: false, error: 'Access denied' };
|
||||
}
|
||||
|
||||
await delegate.delete({ where: { id } });
|
||||
return { success: true };
|
||||
}
|
||||
@@ -31,6 +31,10 @@ export function extractFormData(
|
||||
data[field] = val === '' ? null : new Date(val);
|
||||
break;
|
||||
}
|
||||
case 'Tiptap': {
|
||||
data[field] = raw?.toString() ?? '';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
data[field] = raw?.toString() ?? '';
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@ const models: AdminModelConfig[] = [
|
||||
modelName: 'User',
|
||||
displayName: 'Users',
|
||||
displayNameSingular: 'User',
|
||||
listOnly: true,
|
||||
idType: 'String',
|
||||
include: { role: true, profile: true, licenseKeys: true },
|
||||
listFields: ['name', 'email', 'emailVerified', 'role.name', 'createdAt'],
|
||||
@@ -72,7 +73,7 @@ const models: AdminModelConfig[] = [
|
||||
relationKey: 'addresses',
|
||||
label: 'Addresses',
|
||||
type: 'has-many',
|
||||
displayFields: ['street', 'city', 'state', 'zip']
|
||||
displayFields: ['type', 'street', 'city', 'state', 'postalCode', 'country', 'isDefault']
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -96,17 +97,31 @@ const models: AdminModelConfig[] = [
|
||||
displayName: 'Proclamations',
|
||||
displayNameSingular: 'Proclamation',
|
||||
idType: 'String',
|
||||
include: { solfeggioFrequency: true, category: { include: { parent: true } } },
|
||||
listFields: ['name', 'category.displayName', 'solfeggioFrequency.name', 'price'],
|
||||
include: {
|
||||
solfeggioFrequency: true,
|
||||
category: { include: { parent: true } },
|
||||
subscriptionLevel: true
|
||||
},
|
||||
listFields: ['name', 'category.name', 'solfeggioFrequency.name', 'subscriptionLevel.name'],
|
||||
searchFields: ['name', 'content'],
|
||||
formFields: ['name', 'solfeggioFrequencyId', 'categoryId', 'price', 'content'],
|
||||
formFields: [
|
||||
'name',
|
||||
'solfeggioFrequencyId',
|
||||
'parentCategoryId',
|
||||
'categoryId',
|
||||
'subscriptionLevelId',
|
||||
'content'
|
||||
],
|
||||
readonlyFields: ['id'],
|
||||
dataOnlyFields: ['parentCategoryId'],
|
||||
cascadeFields: { categoryId: { parentField: 'parentCategoryId' } },
|
||||
fieldTypes: {
|
||||
name: 'String',
|
||||
content: 'Tiptap',
|
||||
solfeggioFrequencyId: 'Int',
|
||||
parentCategoryId: 'Int',
|
||||
categoryId: 'Int',
|
||||
price: 'Float'
|
||||
subscriptionLevelId: 'Int'
|
||||
},
|
||||
relationOptions: {
|
||||
solfeggioFrequencyId: {
|
||||
@@ -114,10 +129,20 @@ const models: AdminModelConfig[] = [
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
},
|
||||
parentCategoryId: {
|
||||
model: 'ProclamationCategory',
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
},
|
||||
categoryId: {
|
||||
model: 'ProclamationCategory',
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
},
|
||||
subscriptionLevelId: {
|
||||
model: 'SubscriptionLevel',
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -134,14 +159,39 @@ const models: AdminModelConfig[] = [
|
||||
readonlyFields: ['id'],
|
||||
fieldTypes: {
|
||||
name: 'String',
|
||||
value: 'Float'
|
||||
value: 'Int'
|
||||
},
|
||||
relatedRecords: [
|
||||
{
|
||||
relationKey: 'proclamations',
|
||||
label: 'Proclamations',
|
||||
type: 'has-many',
|
||||
displayFields: ['name', 'price'],
|
||||
displayFields: ['name'],
|
||||
editSlug: 'proclamations'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
slug: 'subscription-levels',
|
||||
modelName: 'SubscriptionLevel',
|
||||
displayName: 'Subscription Levels',
|
||||
displayNameSingular: 'Subscription Level',
|
||||
idType: 'Int',
|
||||
include: { proclamations: true },
|
||||
listFields: ['id', 'name', 'description'],
|
||||
searchFields: ['name', 'description'],
|
||||
formFields: ['name', 'description'],
|
||||
readonlyFields: ['id'],
|
||||
fieldTypes: {
|
||||
name: 'String',
|
||||
description: 'String'
|
||||
},
|
||||
relatedRecords: [
|
||||
{
|
||||
relationKey: 'proclamations',
|
||||
label: 'Proclamations',
|
||||
type: 'has-many',
|
||||
displayFields: ['name'],
|
||||
editSlug: 'proclamations'
|
||||
}
|
||||
]
|
||||
@@ -152,14 +202,15 @@ const models: AdminModelConfig[] = [
|
||||
displayName: 'Proclamation Categories',
|
||||
displayNameSingular: 'Proclamation Category',
|
||||
idType: 'Int',
|
||||
include: { proclamations: true, children: true, parent: true },
|
||||
listFields: ['displayName', 'parent.name'],
|
||||
include: { proclamations: true, children: true },
|
||||
listFields: ['name'],
|
||||
searchFields: ['name'],
|
||||
formFields: ['name', 'parentId'],
|
||||
readonlyFields: ['id'],
|
||||
fieldTypes: {
|
||||
name: 'String',
|
||||
parentId: 'Int'
|
||||
parentId: 'Int',
|
||||
sortOrder: 'Int'
|
||||
},
|
||||
relationOptions: {
|
||||
parentId: { model: 'ProclamationCategory', labelField: 'name', valueField: 'id' }
|
||||
@@ -169,7 +220,7 @@ const models: AdminModelConfig[] = [
|
||||
relationKey: 'proclamations',
|
||||
label: 'Proclamations',
|
||||
type: 'has-many',
|
||||
displayFields: ['name', 'price'],
|
||||
displayFields: ['name'],
|
||||
editSlug: 'proclamations'
|
||||
},
|
||||
{
|
||||
@@ -188,7 +239,6 @@ const models: AdminModelConfig[] = [
|
||||
displayNameSingular: 'License Key',
|
||||
idType: 'String',
|
||||
include: { user: true },
|
||||
listOnly: true,
|
||||
listFields: ['key', 'active', 'level', 'user.name'],
|
||||
searchFields: ['key', 'level'],
|
||||
formFields: ['active', 'level', 'userId'],
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const email = z.string().email('Invalid email address');
|
||||
const phone = z
|
||||
.string()
|
||||
.min(10, 'Phone number must be at least 10 digits')
|
||||
.regex(/^[\d\-\+\(\) ]+$/, 'Invalid phone number format');
|
||||
const name = z.string().min(1, 'Required').max(100, 'Too long');
|
||||
|
||||
export const UserRole = z.object({
|
||||
name: z.string().min(1, 'Required')
|
||||
});
|
||||
|
||||
export const User = z.object({
|
||||
name: z.string().min(1, 'Required'),
|
||||
email,
|
||||
roleId: z.coerce.number().int().min(1)
|
||||
});
|
||||
|
||||
export const Profile = z.object({
|
||||
userId: z.string().min(1, 'Required'),
|
||||
firstName: name,
|
||||
lastName: name,
|
||||
phoneNumber: phone
|
||||
});
|
||||
|
||||
export const Address = z.object({
|
||||
profileId: z.string().min(1),
|
||||
street: z.string().min(1, 'Street is required'),
|
||||
city: z.string().min(1, 'City is required'),
|
||||
state: z.string().min(1, 'State is required'),
|
||||
postalCode: z.string().min(1, 'Postal code is required'),
|
||||
country: z.string().min(1, 'Country is required'),
|
||||
type: z.enum(['Mailing', 'Billing', 'Shipping']).default('Mailing'),
|
||||
isDefaultMailing: z.coerce.boolean().default(false),
|
||||
isDefaultBilling: z.coerce.boolean().default(false),
|
||||
isDefaultShipping: z.coerce.boolean().default(false)
|
||||
});
|
||||
|
||||
export const SolfeggioFrequency = z.object({
|
||||
name: z.string().min(1, 'Required'),
|
||||
value: z.coerce.number().int('Must be a whole number')
|
||||
});
|
||||
|
||||
export const SubscriptionLevel = z.object({
|
||||
name: z.string().min(1, 'Required'),
|
||||
description: z.string().optional().default('')
|
||||
});
|
||||
|
||||
export const ProclamationCategory = z.object({
|
||||
name: z.string().min(1, 'Required'),
|
||||
parentId: z.coerce.number().int().optional()
|
||||
});
|
||||
|
||||
export const Proclamation = z.object({
|
||||
name: z.string().min(1, 'Required'),
|
||||
solfeggioFrequencyId: z.coerce.number().int(),
|
||||
parentCategoryId: z.coerce.number().int(),
|
||||
categoryId: z.coerce.number().int().optional(),
|
||||
subscriptionLevelId: z.coerce.number().int(),
|
||||
content: z.string().optional().default('')
|
||||
});
|
||||
|
||||
export const LicenseKey = z.object({
|
||||
active: z.coerce.boolean(),
|
||||
level: z.string().min(1, 'Required'),
|
||||
userId: z.string().min(1, 'Required')
|
||||
});
|
||||
|
||||
export const UserUpdate = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1, 'Required').optional(),
|
||||
email: z.string().email().optional()
|
||||
});
|
||||
@@ -19,6 +19,12 @@ export type RelatedRecordConfig = {
|
||||
editSlug?: string;
|
||||
};
|
||||
|
||||
export type ActionResult = {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
fieldErrors?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type AdminModelConfig = {
|
||||
slug: string;
|
||||
modelName: string;
|
||||
@@ -35,4 +41,7 @@ export type AdminModelConfig = {
|
||||
relationOptions?: Record<string, RelationOptionConfig>;
|
||||
relatedRecords?: RelatedRecordConfig[];
|
||||
accessFilter?: (viewerRoleId: number) => Record<string, unknown> | undefined;
|
||||
/** Fields that should be excluded from Prisma create/update (virtual/UI-only) */
|
||||
dataOnlyFields?: string[];
|
||||
cascadeFields?: Record<string, { parentField: string }>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user