Compare commits
79
Commits
0753cfaa56
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e86e90137b | ||
|
|
2aa6669007 | ||
|
|
87934dfd42 | ||
|
|
220ff92e78 | ||
|
|
cc4aa8717e | ||
|
|
e9f94b72ee | ||
|
|
04e1f79f37 | ||
|
|
bbf4ac12ee | ||
|
|
bf465eca60 | ||
|
|
6a3ecaf734 | ||
|
|
e6021a6345 | ||
|
|
be303dcd9c | ||
|
|
fcb5db458b | ||
|
|
242be5d241 | ||
|
|
68a979f1a1 | ||
|
|
f6c937b892 | ||
|
|
9a2888af36 | ||
|
|
3bb8e0189b | ||
|
|
138bf03ea9 | ||
|
|
25b5eeb144 | ||
|
|
3c49f425d8 | ||
|
|
8412dfb69c | ||
|
|
1d76e8dff9 | ||
|
|
179df1e252 | ||
|
|
35558b0652 | ||
|
|
6c89a89050 | ||
|
|
dca9c54d83 | ||
|
|
c19c13b192 | ||
|
|
ec826093eb | ||
|
|
96a1342c10 | ||
|
|
468a65a688 | ||
|
|
e934a98f00 | ||
|
|
15bb51582b | ||
|
|
46d7cf66b8 | ||
|
|
177b7bb13a | ||
|
|
750a9286c9 | ||
|
|
c177e7953d | ||
|
|
5f4116b8e8 | ||
|
|
545fb22910 | ||
|
|
4922e941ae | ||
|
|
e6b492a335 | ||
|
|
051520b941 | ||
|
|
64715c91d3 | ||
|
|
960673bbc6 | ||
|
|
a909db39b1 | ||
|
|
020289966d | ||
|
|
fa614b8c37 | ||
|
|
cf8fc7b719 | ||
|
|
848e646880 | ||
|
|
98d9b095d9 | ||
|
|
2bbee98594 | ||
|
|
4d929df604 | ||
|
|
10aad07e6d | ||
|
|
d16965dab6 | ||
|
|
7596937f30 | ||
|
|
0cb4b2148a | ||
|
|
9de0e2e25a | ||
|
|
9cd5397395 | ||
|
|
f6b8cad15e | ||
|
|
29d53af8dd | ||
|
|
809ced6d8a | ||
|
|
84c905c9ff | ||
|
|
ae8d661364 | ||
|
|
5ec3018f8b | ||
|
|
8a3c1587b4 | ||
|
|
e60d61e90a | ||
|
|
f1d80279e9 | ||
|
|
4979d955c2 | ||
|
|
0175cab0b4 | ||
|
|
1aecc51bbd | ||
|
|
f50eb34769 | ||
|
|
4a1777de87 | ||
|
|
d89fc6837c | ||
|
|
5e8851e070 | ||
|
|
91d162c746 | ||
|
|
f4e736c92d | ||
|
|
5096118ef5 | ||
|
|
86b6720676 | ||
|
|
94c9877eab |
+4
-1
@@ -5,5 +5,8 @@ GOOGLE_CLIENT_ID=6
|
|||||||
GOOGLE_CLIENT_SECRET=
|
GOOGLE_CLIENT_SECRET=
|
||||||
RESEND_API_KEY=
|
RESEND_API_KEY=
|
||||||
EMAIL_FROM=
|
EMAIL_FROM=
|
||||||
RESEND_TEMPLATE_ID=
|
# Optional. Resend template for the admin "set your password" invite email.
|
||||||
|
# Defaults to `registration-email`. Expected variables:
|
||||||
|
# `target_email_address`, `registration_link`.
|
||||||
|
RESEND_REGISTRATION_TEMPLATE_ID=
|
||||||
API_V2_ONBOARDING_SECRET=
|
API_V2_ONBOARDING_SECRET=
|
||||||
@@ -6,11 +6,15 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: build-server
|
runs-on: orion-lxc
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
|
git config --global http.extraheader "Authorization: token ${{ github.token }}"
|
||||||
|
git clone --branch "${{ gitea.ref_name }}" \
|
||||||
|
"${{ gitea.server_url }}/${{ gitea.repository }}.git" .
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
@@ -20,7 +24,7 @@ jobs:
|
|||||||
.
|
.
|
||||||
|
|
||||||
- name: Login to DigitalOcean Registry
|
- name: Login to DigitalOcean Registry
|
||||||
uses: docker/login-action@v3
|
uses: https://github.com/docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: registry.digitalocean.com
|
registry: registry.digitalocean.com
|
||||||
username: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
username: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||||
@@ -33,14 +37,18 @@ jobs:
|
|||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
needs: build-and-push
|
needs: build-and-push
|
||||||
runs-on: ubuntu-latest
|
runs-on: orion-lxc
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
|
git config --global http.extraheader "Authorization: token ${{ github.token }}"
|
||||||
|
git clone --branch "${{ gitea.ref_name }}" \
|
||||||
|
"${{ gitea.server_url }}/${{ gitea.repository }}.git" .
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: https://github.com/actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ bun.lockb
|
|||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
/static/
|
/static/
|
||||||
|
|
||||||
|
# Generated code
|
||||||
|
/src/lib/generated/
|
||||||
|
|||||||
Vendored
-27
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Backend (server-side code)",
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "npm",
|
|
||||||
"runtimeArgs": ["run", "dev"],
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"skipFiles": ["<node_internals>/**"],
|
|
||||||
"autoAttachChildProcesses": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Frontend (client-side code)",
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "Start Dev Server",
|
|
||||||
"url": "http://localhost:5173",
|
|
||||||
"webRoot": "${workspaceFolder}",
|
|
||||||
"sourceMapPathOverrides": {
|
|
||||||
"vite:///src/*": "${webRoot}/src/*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
Vendored
-26
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Start Dev Server",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "npm run dev -- --port 5173 --strictPort",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"pattern": [
|
|
||||||
{
|
|
||||||
"regexp": "^.*$",
|
|
||||||
"file": 0,
|
|
||||||
"location": 0,
|
|
||||||
"message": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": "^.*VITE.*v",
|
|
||||||
"endsPattern": "^.*localhost:.*$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -9,11 +9,11 @@ AGENTS.md
|
|||||||
at your project root:
|
at your project root:
|
||||||
|
|
||||||
````text
|
````text
|
||||||
# Rhemawave Portal AI Guide
|
# Radiant Wave Portal AI Guide
|
||||||
|
|
||||||
## Mission
|
## Mission
|
||||||
|
|
||||||
You are working on portal.Rhemawavetech.com.
|
You are working on portal.radiantwavetech.com.
|
||||||
|
|
||||||
Act as a senior engineer making complete, production-ready changes.
|
Act as a senior engineer making complete, production-ready changes.
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ Use narrower commands when appropriate.
|
|||||||
|
|
||||||
If validation fails:
|
If validation fails:
|
||||||
|
|
||||||
* Diagnose the failure.
|
- Diagnose the failure.
|
||||||
* Fix the issue.
|
- Fix the issue.
|
||||||
* Run validation again.
|
- Run validation again.
|
||||||
|
|
||||||
Never finish with known failures.
|
Never finish with known failures.
|
||||||
|
|
||||||
@@ -109,19 +109,19 @@ Never finish with known failures.
|
|||||||
|
|
||||||
Application:
|
Application:
|
||||||
|
|
||||||
portal.Rhemawavetech.com
|
portal.radiantwavetech.com
|
||||||
|
|
||||||
Stack:
|
Stack:
|
||||||
|
|
||||||
* SvelteKit 2
|
- SvelteKit 2
|
||||||
* Svelte 5
|
- Svelte 5
|
||||||
* TypeScript
|
- TypeScript
|
||||||
* Tailwind CSS v4
|
- Tailwind CSS v4
|
||||||
* Prisma 7
|
- Prisma 7
|
||||||
* PostgreSQL
|
- PostgreSQL
|
||||||
* Better Auth
|
- Better Auth
|
||||||
* Vitest
|
- Vitest
|
||||||
* Playwright
|
- Playwright
|
||||||
|
|
||||||
Package manager:
|
Package manager:
|
||||||
|
|
||||||
@@ -266,9 +266,9 @@ AdminPage.svelte
|
|||||||
|
|
||||||
Registry metadata exists for:
|
Registry metadata exists for:
|
||||||
|
|
||||||
* columns
|
- columns
|
||||||
* searches
|
- searches
|
||||||
* relations
|
- relations
|
||||||
|
|
||||||
Forms are manually implemented per route.
|
Forms are manually implemented per route.
|
||||||
|
|
||||||
@@ -309,8 +309,8 @@ Only load that file when modifying Svelte components or Svelte logic.
|
|||||||
|
|
||||||
Testing uses:
|
Testing uses:
|
||||||
|
|
||||||
* Vitest
|
- Vitest
|
||||||
* Playwright browser testing
|
- Playwright browser testing
|
||||||
|
|
||||||
For testing patterns, read:
|
For testing patterns, read:
|
||||||
|
|
||||||
@@ -338,10 +338,10 @@ Only load that file when working on API functionality.
|
|||||||
|
|
||||||
Prettier rules:
|
Prettier rules:
|
||||||
|
|
||||||
* tabs
|
- tabs
|
||||||
* single quotes
|
- single quotes
|
||||||
* no trailing commas
|
- no trailing commas
|
||||||
* 80 character print width
|
- 80 character print width
|
||||||
|
|
||||||
Follow existing formatting.
|
Follow existing formatting.
|
||||||
|
|
||||||
@@ -391,17 +391,14 @@ Confirm:
|
|||||||
2. Relevant tests/checks were run.
|
2. Relevant tests/checks were run.
|
||||||
3. Failures were fixed.
|
3. Failures were fixed.
|
||||||
4. The final response explains:
|
4. The final response explains:
|
||||||
|
- what changed
|
||||||
* what changed
|
- files modified
|
||||||
* files modified
|
- verification performed
|
||||||
* verification performed
|
|
||||||
|
|
||||||
Do not stop at:
|
Do not stop at:
|
||||||
|
|
||||||
* "I found the issue"
|
- "I found the issue"
|
||||||
* "Here is a plan"
|
- "Here is a plan"
|
||||||
* "The next step would be"
|
- "The next step would be"
|
||||||
|
|
||||||
Finish the work.
|
Finish the work.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
# portal.Rhemawavetech.com
|
# portal.radiantwavetech.com
|
||||||
|
|
||||||
SvelteKit 2 + Svelte 5 (runes) + Tailwind CSS v4 + Prisma 7 + PostgreSQL + Better-Auth.
|
SvelteKit 2 + Svelte 5 (runes) + Tailwind CSS v4 + Prisma 7 + PostgreSQL + Better-Auth.
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ The `$effect` resets `dirty = false` on mount (when the record prop changes).
|
|||||||
| Template ID | Variables | Subject (API-level) |
|
| Template ID | Variables | Subject (API-level) |
|
||||||
| ------------------------------ | --------------------------------------------------------------- | ------------------------------ |
|
| ------------------------------ | --------------------------------------------------------------- | ------------------------------ |
|
||||||
| `automated-user-registration` | `firstName`, `tempPassword`, `verifyUrl`, `loginUrl` | "Your account is ready" |
|
| `automated-user-registration` | `firstName`, `tempPassword`, `verifyUrl`, `loginUrl` | "Your account is ready" |
|
||||||
| `license-key-delivery` | `firstName`, `license_key` | "Your Rhemawave License Key" |
|
| `license-key-delivery` | `firstName`, `license_key` | "Your Radiant Wave License Key" |
|
||||||
| `email-verification-email` | `target_email_address`, `verification_link` | "Verify your email address" |
|
| `email-verification-email` | `target_email_address`, `verification_link` | "Verify your email address" |
|
||||||
|
|
||||||
Variables must be declared in the Resend template editor's Variables section. Undeclared variables render as literal `{{{varName}}}` text.
|
Variables must be declared in the Resend template editor's Variables section. Undeclared variables render as literal `{{{varName}}}` text.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Rhema Wave Technologies - User Portal
|
# Radiant Wave Technologies - User Portal
|
||||||
|
|
||||||
A combination application that combines the main Radiant Wave API with a user-facing account management application,
|
A combination application that combines the main Radiant Wave API with a user-facing account management application,
|
||||||
and an admin-facing platform management application. An all-in-one management platform for Radiant Wave and Radiant Wave Technologies.
|
and an admin-facing platform management application. An all-in-one management platform for Radiant Wave and Radiant Wave Technologies.
|
||||||
@@ -36,18 +36,17 @@ podman push registry.digitalocean.com/radiantwave-portal-container-registry/port
|
|||||||
|
|
||||||
## Admin
|
## Admin
|
||||||
|
|
||||||
Admin CRUD pages at `/admin`. 9 models have per-page routes with modals for create/edit:
|
Admin pages live under `/admin` (requires `roleId <= 3`); `/admin` redirects to
|
||||||
|
`/admin/profiles`. Each route owns its own form and modal logic, built on the shared
|
||||||
|
`DataTable.svelte` and `AdminModal.svelte` components. The generic `$lib/svelte-admin`
|
||||||
|
CRUD layer has been removed; see `docs/ADMIN_OVERHAUL.md` for the migration notes.
|
||||||
|
|
||||||
| Included | Excluded |
|
| Route | Notes |
|
||||||
| ------------------------ | --------------------------------- |
|
| -------------------------------- | ------------------------ |
|
||||||
| users | Session (Better Auth) |
|
| `/admin/users` | Name, Email, Role |
|
||||||
| profiles | Account (Better Auth) |
|
| `/admin/profiles` | Reference implementation |
|
||||||
| addresses | Verification (Better Auth) |
|
| `/admin/proclamations` | Super-only content edits |
|
||||||
| user-roles | UserProclamations (composite key) |
|
| `/admin/solfeggio-frequencies` | |
|
||||||
| proclamations | |
|
| `/admin/proclamation-categories` | Draggable reorder |
|
||||||
| solfeggio-frequencies | |
|
| `/admin/proclamation-groups` | Draggable reorder |
|
||||||
| proclamation-categories | |
|
| `/admin/license-keys` | List-only + rotate |
|
||||||
| license-keys (list-only) | |
|
|
||||||
| subscription-levels | |
|
|
||||||
|
|
||||||
See `src/lib/svelte-admin/README.md` for architecture and configuration docs.
|
|
||||||
|
|||||||
+22
-4
@@ -89,14 +89,34 @@ POST:
|
|||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
{
|
{
|
||||||
"license_key": "...",
|
"license_key": "...",
|
||||||
"email_address": "..."
|
"email_address": "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
Requires API key.
|
Requires API key.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Schedule (v2)
|
||||||
|
|
||||||
|
Endpoint:
|
||||||
|
|
||||||
|
GET /api/v2/schedule?email=<email>&licenseKey=<key>
|
||||||
|
|
||||||
|
Authenticates with the paired `email` + `licenseKey` query parameters and
|
||||||
|
validates only that the key belongs to the account (not that the license is
|
||||||
|
active/entitled). Returns the user's Blocks with start/end times and the IDs of
|
||||||
|
the selected Proclamations (content is fetched separately via
|
||||||
|
`/api/v2/proclamations`). Each block also carries `dynamicAudio`; when true, the
|
||||||
|
kiosk enables dynamic audio for the block's duration, overriding any audio
|
||||||
|
selections made on the device while the block is active. Overnight blocks (end
|
||||||
|
clock time before start) are supported and flagged with `overnight: true` plus a
|
||||||
|
wrapping `durationMinutes`. Rate limited to 10 requests per minute per IP.
|
||||||
|
Failure messages follow the existing v2 shape:
|
||||||
|
`not-authorized` or `this key doesn't appear to belong to you`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# GHL Account Creation Webhook
|
# GHL Account Creation Webhook
|
||||||
|
|
||||||
Endpoint:
|
Endpoint:
|
||||||
@@ -153,7 +173,6 @@ Variables:
|
|||||||
- verifyUrl
|
- verifyUrl
|
||||||
- loginUrl
|
- loginUrl
|
||||||
|
|
||||||
|
|
||||||
license-key-delivery
|
license-key-delivery
|
||||||
|
|
||||||
Variables:
|
Variables:
|
||||||
@@ -161,7 +180,6 @@ Variables:
|
|||||||
- firstName
|
- firstName
|
||||||
- license_key
|
- license_key
|
||||||
|
|
||||||
|
|
||||||
email-verification-email
|
email-verification-email
|
||||||
|
|
||||||
Variables:
|
Variables:
|
||||||
|
|||||||
+13
-12
@@ -36,13 +36,13 @@ Always destructure $props() with defaults.
|
|||||||
Preferred pattern:
|
Preferred pattern:
|
||||||
|
|
||||||
let {
|
let {
|
||||||
record = {} as Record<string, unknown>,
|
record = {} as Record<string, unknown>,
|
||||||
fieldErrors = {} as Record<string, string>,
|
fieldErrors = {} as Record<string, string>,
|
||||||
isSuper = false
|
isSuper = false
|
||||||
}: {
|
}: {
|
||||||
record?: Record<string, unknown>;
|
record?: Record<string, unknown>;
|
||||||
fieldErrors?: Record<string, string>;
|
fieldErrors?: Record<string, string>;
|
||||||
isSuper?: boolean;
|
isSuper?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
Do not assume props exist.
|
Do not assume props exist.
|
||||||
@@ -66,7 +66,7 @@ Correct:
|
|||||||
let name = $state('');
|
let name = $state('');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
name = String(record.name ?? '');
|
name = String(record.name ?? '');
|
||||||
});
|
});
|
||||||
|
|
||||||
Use $effect whenever component state needs to follow changing props.
|
Use $effect whenever component state needs to follow changing props.
|
||||||
@@ -88,11 +88,11 @@ Example:
|
|||||||
let dirty = $state(false);
|
let dirty = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
dirty = false;
|
dirty = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
name = String(record.name ?? '');
|
name = String(record.name ?? '');
|
||||||
});
|
});
|
||||||
|
|
||||||
This ensures a new record starts clean.
|
This ensures a new record starts clean.
|
||||||
@@ -116,9 +116,10 @@ This can fail to update correctly.
|
|||||||
Use:
|
Use:
|
||||||
|
|
||||||
<select onchange={(e) => frequency = e.currentTarget.value}>
|
<select onchange={(e) => frequency = e.currentTarget.value}>
|
||||||
<option value="1" selected={frequency === '1'}>
|
|
||||||
One
|
<option value="1" selected={frequency === '1'}>
|
||||||
</option>
|
One
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
Use selected on options.
|
Use selected on options.
|
||||||
|
|||||||
+29
-29
@@ -30,13 +30,13 @@ Tests are colocated with source files.
|
|||||||
|
|
||||||
Client tests:
|
Client tests:
|
||||||
|
|
||||||
*.svelte.test.ts
|
_.svelte.test.ts
|
||||||
*.svelte.spec.ts
|
_.svelte.spec.ts
|
||||||
|
|
||||||
Server tests:
|
Server tests:
|
||||||
|
|
||||||
*.test.ts
|
_.test.ts
|
||||||
*.spec.ts
|
_.spec.ts
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ Node.
|
|||||||
Always reset mocks:
|
Always reset mocks:
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -109,15 +109,15 @@ Mock only the methods used.
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
vi.mock('$lib/db', () => ({
|
vi.mock('$lib/db', () => ({
|
||||||
prisma: {
|
prisma: {
|
||||||
proclamation: {
|
proclamation: {
|
||||||
findMany: vi.fn(),
|
findMany: vi.fn(),
|
||||||
findUnique: vi.fn(),
|
findUnique: vi.fn(),
|
||||||
create: vi.fn(),
|
create: vi.fn(),
|
||||||
update: vi.fn(),
|
update: vi.fn(),
|
||||||
delete: vi.fn()
|
delete: vi.fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Do not create massive unused mocks.
|
Do not create massive unused mocks.
|
||||||
@@ -143,7 +143,7 @@ createLocals(mockUser() as any)
|
|||||||
Specific roles:
|
Specific roles:
|
||||||
|
|
||||||
createLocals(
|
createLocals(
|
||||||
mockUser({ roleId: 4 }) as any
|
mockUser({ roleId: 4 }) as any
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -172,15 +172,15 @@ Always use exact regex names.
|
|||||||
Preferred:
|
Preferred:
|
||||||
|
|
||||||
page.getByRole(
|
page.getByRole(
|
||||||
'textbox',
|
'textbox',
|
||||||
{ name: /^Name$/ }
|
{ name: /^Name$/ }
|
||||||
)
|
)
|
||||||
|
|
||||||
Avoid:
|
Avoid:
|
||||||
|
|
||||||
page.getByRole(
|
page.getByRole(
|
||||||
'textbox',
|
'textbox',
|
||||||
{ name: 'Name' }
|
{ name: 'Name' }
|
||||||
)
|
)
|
||||||
|
|
||||||
because substring matching can create strict-mode failures.
|
because substring matching can create strict-mode failures.
|
||||||
@@ -192,15 +192,15 @@ because substring matching can create strict-mode failures.
|
|||||||
Because selects use selected options instead of bind:value:
|
Because selects use selected options instead of bind:value:
|
||||||
|
|
||||||
const element =
|
const element =
|
||||||
await page
|
await page
|
||||||
.getByRole(
|
.getByRole(
|
||||||
'combobox',
|
'combobox',
|
||||||
{ name: /^Frequency$/ }
|
{ name: /^Frequency$/ }
|
||||||
)
|
)
|
||||||
.element();
|
.element();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
(element as HTMLSelectElement).value
|
(element as HTMLSelectElement).value
|
||||||
).toBe('2');
|
).toBe('2');
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -213,9 +213,9 @@ Use:
|
|||||||
|
|
||||||
vi.mock('$lib/components/example.svelte', () => ({
|
vi.mock('$lib/components/example.svelte', () => ({
|
||||||
default: ($anchor: Element) => {
|
default: ($anchor: Element) => {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
$anchor.replaceWith(div);
|
$anchor.replaceWith(div);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Imports using mocked modules must come after vi.mock().
|
Imports using mocked modules must come after vi.mock().
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-adjacent-0001",
|
||||||
|
"type": "daily",
|
||||||
|
"daily": [
|
||||||
|
{
|
||||||
|
"id": "dd111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "07:00",
|
||||||
|
"endMinutes": 420,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dd222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "08:30",
|
||||||
|
"endMinutes": 510,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 90,
|
||||||
|
"proclamationIds": [
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b",
|
||||||
|
"33333333-3333-4333-8333-333333333333"
|
||||||
|
],
|
||||||
|
"dynamicAudio": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "dd111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "07:00",
|
||||||
|
"endMinutes": 420,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dd222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "08:30",
|
||||||
|
"endMinutes": 510,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 90,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": [
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b",
|
||||||
|
"33333333-3333-4333-8333-333333333333"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-daily-0001",
|
||||||
|
"type": "daily",
|
||||||
|
"daily": [
|
||||||
|
{
|
||||||
|
"id": "11111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"proclamationIds": [
|
||||||
|
"b40fdcb9-703c-420e-af32-a081964c6f08",
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"
|
||||||
|
],
|
||||||
|
"dynamicAudio": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "20:30",
|
||||||
|
"endMinutes": 1230,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 150,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"],
|
||||||
|
"dynamicAudio": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "11111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": [
|
||||||
|
"b40fdcb9-703c-420e-af32-a081964c6f08",
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "20:30",
|
||||||
|
"endMinutes": 1230,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 150,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-dynamic-0001",
|
||||||
|
"type": "daily",
|
||||||
|
"daily": [
|
||||||
|
{
|
||||||
|
"id": "ee111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "08:00",
|
||||||
|
"startMinutes": 480,
|
||||||
|
"endTime": "10:00",
|
||||||
|
"endMinutes": 600,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ee222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "20:00",
|
||||||
|
"startMinutes": 1200,
|
||||||
|
"endTime": "22:00",
|
||||||
|
"endMinutes": 1320,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"proclamationIds": ["7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"],
|
||||||
|
"dynamicAudio": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "ee111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "08:00",
|
||||||
|
"startMinutes": 480,
|
||||||
|
"endTime": "10:00",
|
||||||
|
"endMinutes": 600,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ee222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "20:00",
|
||||||
|
"startMinutes": 1200,
|
||||||
|
"endTime": "22:00",
|
||||||
|
"endMinutes": 1320,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"email": "jane@example.com",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "11111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": [
|
||||||
|
"b40fdcb9-703c-420e-af32-a081964c6f08",
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "20:30",
|
||||||
|
"endMinutes": 1230,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 150,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-midnight-0001",
|
||||||
|
"type": "daily",
|
||||||
|
"daily": [
|
||||||
|
{
|
||||||
|
"id": "cc111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "21:00",
|
||||||
|
"startMinutes": 1260,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": true,
|
||||||
|
"durationMinutes": 720,
|
||||||
|
"proclamationIds": [
|
||||||
|
"b40fdcb9-703c-420e-af32-a081964c6f08",
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"
|
||||||
|
],
|
||||||
|
"dynamicAudio": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cc222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "09:00",
|
||||||
|
"startMinutes": 540,
|
||||||
|
"endTime": "21:00",
|
||||||
|
"endMinutes": 1260,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 720,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"],
|
||||||
|
"dynamicAudio": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cc111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "21:00",
|
||||||
|
"startMinutes": 1260,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": true,
|
||||||
|
"durationMinutes": 720,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": [
|
||||||
|
"b40fdcb9-703c-420e-af32-a081964c6f08",
|
||||||
|
"7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cc222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "09:00",
|
||||||
|
"startMinutes": 540,
|
||||||
|
"endTime": "21:00",
|
||||||
|
"endMinutes": 1260,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 720,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-none-0001",
|
||||||
|
"type": null,
|
||||||
|
"daily": [],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"version": "7f53efde17d291a1ecf604e266d3803d1495b986",
|
||||||
|
"type": "daily",
|
||||||
|
"daily": [
|
||||||
|
{
|
||||||
|
"id": "6a9be0a4-4a1c-4a2c-929d-7b6c6fe56d91",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "07:00",
|
||||||
|
"endMinutes": 420,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["310138f3-adfc-45ce-ae52-07279cd25b48"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "65fecafc-abc8-4796-bb12-ed8e9d99e0fe",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "20:30",
|
||||||
|
"endMinutes": 1230,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 150,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["3805242e-4930-4dd2-afb1-130593b1dbab"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"weekly": {},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "6a9be0a4-4a1c-4a2c-929d-7b6c6fe56d91",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "07:00",
|
||||||
|
"endMinutes": 420,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["310138f3-adfc-45ce-ae52-07279cd25b48"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "65fecafc-abc8-4796-bb12-ed8e9d99e0fe",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "20:30",
|
||||||
|
"endMinutes": 1230,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 150,
|
||||||
|
"dynamicAudio": false,
|
||||||
|
"proclamationIds": ["3805242e-4930-4dd2-afb1-130593b1dbab"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"version": "6de852ba72ce1ab3052150640730ee87a7d460b5",
|
||||||
|
"type": "weekly",
|
||||||
|
"daily": [],
|
||||||
|
"weekly": {
|
||||||
|
"mon": [
|
||||||
|
{
|
||||||
|
"id": "a1054b78-480a-4e87-8465-aac8d677c666",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "07:00",
|
||||||
|
"endMinutes": 420,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["310138f3-adfc-45ce-ae52-07279cd25b48"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "25979ec9-f7da-4aa4-864f-f74b1a2da78b",
|
||||||
|
"startTime": "07:00",
|
||||||
|
"startMinutes": 420,
|
||||||
|
"endTime": "08:30",
|
||||||
|
"endMinutes": 510,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 90,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["3805242e-4930-4dd2-afb1-130593b1dbab"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tue": [],
|
||||||
|
"wed": [],
|
||||||
|
"thu": [],
|
||||||
|
"fri": [],
|
||||||
|
"sat": [],
|
||||||
|
"sun": [
|
||||||
|
{
|
||||||
|
"id": "e2237d98-834c-433b-8979-b56ddad2ef7a",
|
||||||
|
"startTime": "23:00",
|
||||||
|
"startMinutes": 1380,
|
||||||
|
"endTime": "01:00",
|
||||||
|
"endMinutes": 60,
|
||||||
|
"overnight": true,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"dynamicAudio": true,
|
||||||
|
"proclamationIds": ["fcd5a291-3d6e-4678-9d85-9b550e80f1c7"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blocks": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"version": "sched-weekly-0001",
|
||||||
|
"type": "weekly",
|
||||||
|
"daily": [],
|
||||||
|
"weekly": {
|
||||||
|
"mon": [
|
||||||
|
{
|
||||||
|
"id": "a1111111-1111-4111-8111-111111111111",
|
||||||
|
"startTime": "06:00",
|
||||||
|
"startMinutes": 360,
|
||||||
|
"endTime": "08:00",
|
||||||
|
"endMinutes": 480,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tue": [],
|
||||||
|
"wed": [
|
||||||
|
{
|
||||||
|
"id": "a2222222-2222-4222-8222-222222222222",
|
||||||
|
"startTime": "12:00",
|
||||||
|
"startMinutes": 720,
|
||||||
|
"endTime": "13:00",
|
||||||
|
"endMinutes": 780,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"proclamationIds": ["7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thu": [],
|
||||||
|
"fri": [
|
||||||
|
{
|
||||||
|
"id": "a3333333-3333-4333-8333-333333333333",
|
||||||
|
"startTime": "18:00",
|
||||||
|
"startMinutes": 1080,
|
||||||
|
"endTime": "22:00",
|
||||||
|
"endMinutes": 1320,
|
||||||
|
"overnight": false,
|
||||||
|
"durationMinutes": 240,
|
||||||
|
"proclamationIds": ["33333333-3333-4333-8333-333333333333"],
|
||||||
|
"dynamicAudio": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sat": [],
|
||||||
|
"sun": [
|
||||||
|
{
|
||||||
|
"id": "a4444444-4444-4444-8444-444444444444",
|
||||||
|
"startTime": "23:00",
|
||||||
|
"startMinutes": 1380,
|
||||||
|
"endTime": "01:00",
|
||||||
|
"endMinutes": 60,
|
||||||
|
"overnight": true,
|
||||||
|
"durationMinutes": 120,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blocks": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,802 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Radiant Wave Portal API (v2)
|
||||||
|
version: 2.0.0
|
||||||
|
description: |
|
||||||
|
Read API used by the Radiant Wave kiosk plus the automation onboarding
|
||||||
|
webhook.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
The v2 API is **not** API-key gated. The former bearer-token middleware in
|
||||||
|
`src/hooks.server.ts` is present but disabled, so v2 routes are served
|
||||||
|
without a global API key.
|
||||||
|
|
||||||
|
Two endpoints carry their own credential semantics:
|
||||||
|
|
||||||
|
- `GET /api/v2/proclamations` optionally accepts the paired
|
||||||
|
`x-license-key` / `x-email` headers to scope results to an entitled user.
|
||||||
|
Supplying neither returns the full catalogue; supplying only one, or a
|
||||||
|
pair that does not match, is rejected.
|
||||||
|
- `POST /api/v2/automations/create/account` is protected by an HMAC-SHA256
|
||||||
|
request signature (see the operation description).
|
||||||
|
servers:
|
||||||
|
- url: https://portal.radiantwavetech.com
|
||||||
|
description: Production
|
||||||
|
- url: http://localhost:5173
|
||||||
|
description: Local development
|
||||||
|
tags:
|
||||||
|
- name: Catalogue
|
||||||
|
description: Reference data for categories, groups, frequencies and palettes.
|
||||||
|
- name: Proclamations
|
||||||
|
description: Proclamation content, optionally scoped by license entitlement.
|
||||||
|
- name: License
|
||||||
|
description: License key validation.
|
||||||
|
- name: Schedule
|
||||||
|
description: A user's daily listening schedule, consumed by the kiosk.
|
||||||
|
- name: Automations
|
||||||
|
description: Signed machine-to-machine webhooks.
|
||||||
|
paths:
|
||||||
|
/api/v2/categories:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List proclamation categories
|
||||||
|
operationId: listCategories
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: All proclamation categories.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Category'
|
||||||
|
/api/v2/proclamation-categories:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List proclamation categories (dedicated alias)
|
||||||
|
description: Alias of `/api/v2/categories`; returns the identical payload.
|
||||||
|
operationId: listProclamationCategories
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: All proclamation categories.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Category'
|
||||||
|
/api/v2/groups:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List proclamation groups
|
||||||
|
description: Groups are ordered by `sortOrder` ascending.
|
||||||
|
operationId: listGroups
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: All proclamation groups.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Group'
|
||||||
|
/api/v2/proclamation-groups:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List proclamation groups (dedicated alias)
|
||||||
|
description: Alias of `/api/v2/groups`; returns the identical payload.
|
||||||
|
operationId: listProclamationGroups
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: All proclamation groups.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Group'
|
||||||
|
/api/v2/frequencies:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List canonical solfeggio frequencies
|
||||||
|
description: |
|
||||||
|
Returns the nine canonical frequencies (ids 1-9) the kiosk expects.
|
||||||
|
These are static values, not the portal database autoincrement ids.
|
||||||
|
operationId: listFrequencies
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The nine canonical frequencies.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
minItems: 9
|
||||||
|
maxItems: 9
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Frequency'
|
||||||
|
/api/v2/color-palettes:
|
||||||
|
get:
|
||||||
|
tags: [Catalogue]
|
||||||
|
summary: List color palettes per canonical frequency
|
||||||
|
description: |
|
||||||
|
Returns one 7-color palette for each canonical frequency id (1-9).
|
||||||
|
Palette rows stored in the database take precedence; ids without a row
|
||||||
|
fall back to the built-in canonical palette.
|
||||||
|
operationId: listColorPalettes
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Nine palettes, one per canonical frequency id.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
minItems: 9
|
||||||
|
maxItems: 9
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ColorPalette'
|
||||||
|
/api/v2/proclamations:
|
||||||
|
get:
|
||||||
|
tags: [Proclamations]
|
||||||
|
summary: List proclamations
|
||||||
|
description: |
|
||||||
|
Without entitlement headers, returns the full proclamation catalogue.
|
||||||
|
|
||||||
|
With both `x-license-key` and `x-email`, returns only the proclamations
|
||||||
|
assigned to the matching user. Both headers must be present together and
|
||||||
|
the key must belong to the user.
|
||||||
|
|
||||||
|
Proclamations whose frequency has no canonical id (for example 936 Hz)
|
||||||
|
are omitted from the response.
|
||||||
|
operationId: listProclamations
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: x-license-key
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: License key. Must be supplied together with `x-email`.
|
||||||
|
- in: header
|
||||||
|
name: x-email
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
required: false
|
||||||
|
description: Account email. Must be supplied together with `x-license-key`.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Proclamation records with canonical frequency ids and embedded palettes.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Proclamation'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Missing entitlement or the supplied key/email pair is invalid.
|
||||||
|
Message is `not-authorized`, or
|
||||||
|
`this key doesn't appear to belong to you` when the key does not
|
||||||
|
belong to the specified user.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
/api/v2/license:
|
||||||
|
post:
|
||||||
|
tags: [License]
|
||||||
|
summary: Validate a license key
|
||||||
|
description: |
|
||||||
|
Validates that the license key exists, is active, and belongs to the
|
||||||
|
account matching `email_address`. A successful validation records
|
||||||
|
`lastValidatedAt` on the key.
|
||||||
|
operationId: validateLicense
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The validated key and its owner email.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseResponse'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Validation failure or malformed body. Possible messages:
|
||||||
|
`content-type must be application/json`, `invalid JSON body`,
|
||||||
|
`request body is required`, `license_key is required`,
|
||||||
|
`email_address is required`, `license key not found`,
|
||||||
|
`license key is not active`, `user does not exist`,
|
||||||
|
`key does not belong to specified user`.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'413':
|
||||||
|
description: The request body exceeded the 64 KiB limit.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'429':
|
||||||
|
description: Rate limit exceeded (10 requests per minute per IP).
|
||||||
|
headers:
|
||||||
|
Retry-After:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Seconds to wait before retrying.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
/api/v2/schedule:
|
||||||
|
get:
|
||||||
|
tags: [Schedule]
|
||||||
|
summary: Get a user's listening schedule (query-param auth)
|
||||||
|
description: |
|
||||||
|
Machine-to-app endpoint used by the RadiantWave kiosk. Authenticates via
|
||||||
|
the paired `email` + `licenseKey` **query parameters** and validates only
|
||||||
|
that the key belongs to the account for that email. License
|
||||||
|
active/entitlement is deliberately not checked here: an invalid license
|
||||||
|
would already lock the kiosk out, so this endpoint only checks the
|
||||||
|
pairing.
|
||||||
|
|
||||||
|
Returns every Block with its start/end times and the ordered list of
|
||||||
|
selected Proclamation IDs (the legacy daily flattening in `blocks`), and
|
||||||
|
is extended additively with `version`, `type`, `daily`, and `weekly`.
|
||||||
|
Overnight blocks (an end clock time before the start) are supported and
|
||||||
|
flagged with `overnight: true` and a wrapping `durationMinutes`. Each
|
||||||
|
block also carries `dynamicAudio`; when true, the kiosk enables dynamic
|
||||||
|
audio for the block's duration, overriding any audio selections made on
|
||||||
|
the device. Proclamation content is not included; the kiosk fetches it
|
||||||
|
separately via `/api/v2/proclamations`. For `type: "weekly"`, `blocks` is
|
||||||
|
an empty array (the server cannot flatten weekly in the kiosk's
|
||||||
|
timezone). This endpoint emits no ETag and never returns 304.
|
||||||
|
operationId: getSchedule
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: email
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
description: Account email.
|
||||||
|
- in: query
|
||||||
|
name: licenseKey
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
||||||
|
description: License key. The legacy alias `key` is also accepted.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The user's schedule (possibly empty).
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ScheduleResponse'
|
||||||
|
example:
|
||||||
|
email: jane@example.com
|
||||||
|
blocks:
|
||||||
|
- id: 3f1c2b7a-9d4e-4c8a-b2f6-1a2b3c4d5e6f
|
||||||
|
startTime: '21:00'
|
||||||
|
startMinutes: 1260
|
||||||
|
endTime: '09:00'
|
||||||
|
endMinutes: 540
|
||||||
|
overnight: true
|
||||||
|
durationMinutes: 720
|
||||||
|
dynamicAudio: true
|
||||||
|
proclamationIds:
|
||||||
|
- b40fdcb9-703c-420e-af32-a081964c6f08
|
||||||
|
- 7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Missing/invalid parameters, unknown key or user, or the key does not
|
||||||
|
belong to the email's account. Possible messages: `not-authorized`,
|
||||||
|
`this key doesn't appear to belong to you`.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'429':
|
||||||
|
description: Rate limit exceeded (10 requests per minute per IP).
|
||||||
|
headers:
|
||||||
|
Retry-After:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Seconds to wait before retrying.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
/api/v2/licenses/{licenseKey}/schedule:
|
||||||
|
get:
|
||||||
|
tags: [Schedule]
|
||||||
|
summary: Get a License Key's schedule (Kiosk-facing, conditional)
|
||||||
|
description: |
|
||||||
|
Kiosk-facing, License-Key-only schedule read. Returns the target payload
|
||||||
|
(`version`, `type`, `daily`, `weekly`, `blocks`). `version` is a
|
||||||
|
content-derived opaque string and is also the `ETag`; a matching
|
||||||
|
`If-None-Match` returns `304`. An unknown License Key is `404`; a key
|
||||||
|
with no Schedule returns `type: null` with empty lists. No
|
||||||
|
authentication (see the API description) beyond the 10/min/IP limit.
|
||||||
|
operationId: getLicenseSchedule
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: licenseKey
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
||||||
|
- in: header
|
||||||
|
name: If-None-Match
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Prior ETag; when it matches, returns 304.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The target schedule payload.
|
||||||
|
headers:
|
||||||
|
ETag:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The quoted `version`.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SchedulePayload'
|
||||||
|
'304':
|
||||||
|
description: Not modified; the caller keeps its cached payload.
|
||||||
|
headers:
|
||||||
|
ETag:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'404':
|
||||||
|
description: 'Unknown license (`unknown_license`).'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'429':
|
||||||
|
description: Rate limit exceeded (10 requests per minute per IP).
|
||||||
|
headers:
|
||||||
|
Retry-After:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Seconds to wait before retrying.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
/api/v2/licenses/{licenseKey}/system-name:
|
||||||
|
put:
|
||||||
|
tags: [License]
|
||||||
|
summary: Set a License Key's System Name (Kiosk-only)
|
||||||
|
description: |
|
||||||
|
Sets the human-readable System Name for a License Key. The Kiosk is the
|
||||||
|
only writer; the Portal displays the name read-only. The name is unique
|
||||||
|
per account, compared case-insensitively, when non-null. Idempotent:
|
||||||
|
re-sending the current name returns 200 without a write.
|
||||||
|
operationId: setSystemName
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: licenseKey
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [systemName]
|
||||||
|
properties:
|
||||||
|
systemName:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 40
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The stored name.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [systemName, systemNameStatus]
|
||||||
|
properties:
|
||||||
|
systemName:
|
||||||
|
type: string
|
||||||
|
systemNameStatus:
|
||||||
|
type: string
|
||||||
|
enum: [ok]
|
||||||
|
'400':
|
||||||
|
description: 'Invalid name (`invalid_name`); empty/whitespace is invalid.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'404':
|
||||||
|
description: 'Unknown license (`unknown_license`).'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'409':
|
||||||
|
description: 'Name already used on the account (`name_taken`).'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'429':
|
||||||
|
description: Rate limit exceeded (10 requests per minute per IP).
|
||||||
|
headers:
|
||||||
|
Retry-After:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Seconds to wait before retrying.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
/api/v2/automations/create/account:
|
||||||
|
post:
|
||||||
|
tags: [Automations]
|
||||||
|
summary: Create an account and license key (signed webhook)
|
||||||
|
description: |
|
||||||
|
Machine-to-machine onboarding webhook. The request must be signed with
|
||||||
|
HMAC-SHA256 using `API_V2_ONBOARDING_SECRET`.
|
||||||
|
|
||||||
|
The canonical string that is signed is the newline-joined concatenation
|
||||||
|
of:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST
|
||||||
|
<path>
|
||||||
|
<x-timestamp>
|
||||||
|
<x-nonce>
|
||||||
|
<raw request body>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<path>` is `/api/v2/automations/create/account`. The signature is
|
||||||
|
the lowercase hex digest.
|
||||||
|
|
||||||
|
On success, creates the user if they do not already exist, generates a
|
||||||
|
license key, emails it, and returns the user id. Invalid or missing
|
||||||
|
signatures return `401 { "error": "invalid signature" }` (the same
|
||||||
|
response is used for every signature failure, including replay and
|
||||||
|
timestamp skew).
|
||||||
|
operationId: createAutomationAccount
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: x-timestamp
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Unix epoch seconds. Must be within 300 seconds of the server clock.
|
||||||
|
- in: header
|
||||||
|
name: x-nonce
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Unique value per request. Replays within the skew window are rejected.
|
||||||
|
- in: header
|
||||||
|
name: x-signature
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '^[0-9a-f]{64}$'
|
||||||
|
description: Lowercase hex HMAC-SHA256 of the canonical request string.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OnboardingRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Account and license key created.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OnboardingResponse'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Malformed or invalid request. Possible messages:
|
||||||
|
`request body is required`, `invalid JSON body`, or
|
||||||
|
`email is required`.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'401':
|
||||||
|
description: Signature missing, malformed, expired, replayed, or incorrect.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [error]
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
example: invalid signature
|
||||||
|
'413':
|
||||||
|
description: The request body exceeded the 64 KiB limit.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'429':
|
||||||
|
description: |
|
||||||
|
Rate limit exceeded. Limits are 5 requests per minute per IP and
|
||||||
|
3 requests per 24 hours per email address.
|
||||||
|
headers:
|
||||||
|
Retry-After:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Seconds to wait before retrying.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
'500':
|
||||||
|
description: The account could not be created.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorMessage'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
required: [id, name, sortOrder]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
sortOrder:
|
||||||
|
type: integer
|
||||||
|
Group:
|
||||||
|
type: object
|
||||||
|
required: [id, name, sortOrder]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
sortOrder:
|
||||||
|
type: integer
|
||||||
|
Frequency:
|
||||||
|
type: object
|
||||||
|
required: [id, name, value]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 9
|
||||||
|
description: Canonical id used by the kiosk.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
type: integer
|
||||||
|
description: Frequency in Hz.
|
||||||
|
ColorPalette:
|
||||||
|
type: object
|
||||||
|
required: [solfeggioFrequencyId, colors]
|
||||||
|
properties:
|
||||||
|
solfeggioFrequencyId:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 9
|
||||||
|
colors:
|
||||||
|
type: array
|
||||||
|
description: Seven hex colors, ordered light to dark.
|
||||||
|
minItems: 1
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
pattern: '^#[0-9A-Fa-f]{6}$'
|
||||||
|
Proclamation:
|
||||||
|
type: object
|
||||||
|
required: [id, name, content, categoryId, groupId, solfeggioFrequencyId]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: HTML content.
|
||||||
|
categoryId:
|
||||||
|
type: integer
|
||||||
|
groupId:
|
||||||
|
type: integer
|
||||||
|
solfeggioFrequencyId:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 9
|
||||||
|
colorPalette:
|
||||||
|
type: array
|
||||||
|
description: Present only when a palette exists for the canonical frequency id.
|
||||||
|
minItems: 1
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
pattern: '^#[0-9A-Fa-f]{6}$'
|
||||||
|
LicenseRequest:
|
||||||
|
type: object
|
||||||
|
required: [license_key, email_address]
|
||||||
|
properties:
|
||||||
|
license_key:
|
||||||
|
type: string
|
||||||
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
||||||
|
example: ABCD-EFGH-IJKL-MNOP
|
||||||
|
email_address:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
maxLength: 254
|
||||||
|
systemName:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 40
|
||||||
|
description: |
|
||||||
|
Optional. The Kiosk's System Name (trimmed). Omitted means the stored
|
||||||
|
name is left untouched; empty/whitespace is invalid, never a clear.
|
||||||
|
LicenseResponse:
|
||||||
|
type: object
|
||||||
|
required: [key, email_address, systemNameStatus]
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
example: ABCD-EFGH-IJKL-MNOP
|
||||||
|
email_address:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
systemNameStatus:
|
||||||
|
type: string
|
||||||
|
enum: [ok, unchanged, conflict, invalid]
|
||||||
|
description: |
|
||||||
|
Advisory outcome of the optional `systemName`. Validation itself
|
||||||
|
always succeeds. `unchanged` is returned when the field is omitted.
|
||||||
|
ScheduleBlock:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
[
|
||||||
|
id,
|
||||||
|
startTime,
|
||||||
|
startMinutes,
|
||||||
|
endTime,
|
||||||
|
endMinutes,
|
||||||
|
overnight,
|
||||||
|
durationMinutes,
|
||||||
|
dynamicAudio,
|
||||||
|
proclamationIds
|
||||||
|
]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
startTime:
|
||||||
|
type: string
|
||||||
|
example: '06:00'
|
||||||
|
description: Wall-clock start time as `HH:MM`.
|
||||||
|
startMinutes:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 1440
|
||||||
|
description: Minutes from midnight.
|
||||||
|
endTime:
|
||||||
|
type: string
|
||||||
|
example: '08:00'
|
||||||
|
description: Wall-clock end time as `HH:MM`.
|
||||||
|
endMinutes:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 1440
|
||||||
|
description: Minutes from midnight.
|
||||||
|
overnight:
|
||||||
|
type: boolean
|
||||||
|
description: True when the end clock time is at or before the start, i.e. the block crosses midnight.
|
||||||
|
durationMinutes:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
description: Block length in minutes. An overnight block counts the wrap past midnight as the next day.
|
||||||
|
dynamicAudio:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
When true, dynamic audio is enabled for the block's duration, overriding any
|
||||||
|
audio selections made on the device while the block is active.
|
||||||
|
proclamationIds:
|
||||||
|
type: array
|
||||||
|
description: IDs of the selected Proclamations, in play order.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
ScheduleResponse:
|
||||||
|
type: object
|
||||||
|
required: [email, blocks]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
enum: [daily, weekly, null]
|
||||||
|
daily:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
weekly:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
blocks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
SchedulePayload:
|
||||||
|
type: object
|
||||||
|
required: [version, type, daily, weekly, blocks]
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
description: Opaque content hash; doubles as the ETag.
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
enum: [daily, weekly, null]
|
||||||
|
daily:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
weekly:
|
||||||
|
type: object
|
||||||
|
description: Present with all seven keys when `type` is weekly.
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
blocks:
|
||||||
|
type: array
|
||||||
|
description: Legacy daily flattening; empty for weekly.
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ScheduleBlock'
|
||||||
|
OnboardingRequest:
|
||||||
|
type: object
|
||||||
|
required: [email]
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
maxLength: 254
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
maxLength: 120
|
||||||
|
description: Optional display name. Defaults to the email local-part.
|
||||||
|
OnboardingResponse:
|
||||||
|
type: object
|
||||||
|
required: [success, message, user_id]
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: Account and license key created
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
ErrorMessage:
|
||||||
|
type: object
|
||||||
|
required: [message]
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
@@ -0,0 +1,619 @@
|
|||||||
|
# Schedule API & Data Contract
|
||||||
|
|
||||||
|
Status: Phase 0 (contract only — no application code). This document is the
|
||||||
|
authoritative API/data contract for the multi-phase scheduler overhaul. It is
|
||||||
|
kept **byte-identical** in both repositories:
|
||||||
|
|
||||||
|
- `radiantwave` (C++/SDL3 Kiosk app) — `docs/schedule-api-contract.md`
|
||||||
|
- `portal` (SvelteKit/Node) — `docs/schedule-api-contract.md`
|
||||||
|
|
||||||
|
Any change is applied to both copies in the same step, with a dated line
|
||||||
|
appended to the [Changelog](#changelog).
|
||||||
|
|
||||||
|
Glossary terms (Kiosk, Portal, Proclamation, Category, Group, Block, Schedule,
|
||||||
|
License Key, System Name) are used with the meanings defined in the scheduler
|
||||||
|
overhaul brief.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
This section reflects the code as it exists at the start of Phase 0. It is
|
||||||
|
descriptive, not aspirational; the target behaviour is defined in the later
|
||||||
|
sections.
|
||||||
|
|
||||||
|
### Kiosk (`radiantwave`)
|
||||||
|
|
||||||
|
- **License validation.** `POST /api/v2/license` with body
|
||||||
|
`{"license_key": "...", "email_address": "..."}`. Implemented in
|
||||||
|
`src/network/api_client.cpp` (`validateLicense`, `parseLicenseJson`) and
|
||||||
|
consumed by `src/network/license_service.cpp` (`validateStoredLicense`,
|
||||||
|
`markLicenseVerified`, `isLicenseWithinGracePeriod`). A 2xx response is the
|
||||||
|
device's "subscribed" flag; a 7-day offline grace window is stored as
|
||||||
|
`license_last_verified` in `system_kv` (`src/db/db_keys.h`). Response body
|
||||||
|
currently read: `{key, email_address}`; additional fields are ignored.
|
||||||
|
- **Schedule fetch.** `GET /api/v2/schedule?email=<email>&licenseKey=<key>`
|
||||||
|
(`src/network/api_client.cpp`: `fetchSchedule`, `parseScheduleJson`;
|
||||||
|
`PATH_SCHEDULE = "/schedule"`). Response shape read by the Kiosk:
|
||||||
|
`{email, blocks: [{id, startMinutes, endMinutes, overnight, durationMinutes,
|
||||||
|
dynamicAudio, proclamationIds}]}`. `dynamicAudio` is optional on the wire and
|
||||||
|
defaults to `true` in `parseScheduleJson`. Missing/blank credentials cause an
|
||||||
|
early `false`.
|
||||||
|
- **Schedule persistence.** SQLite tables `schedule_blocks` and
|
||||||
|
`schedule_block_proclamations` (`src/db/database_manager.cpp:185`,
|
||||||
|
`replaceScheduleBlocks` at `:1105`, `getScheduleBlocks` at `:1169`). Full
|
||||||
|
replace; block `sort_order` and per-block proclamation `position` preserved.
|
||||||
|
No day-of-week column and no schedule `type`; the model is implicitly daily.
|
||||||
|
- **Schedule sync.** `syncScheduleFromApi` (`src/network/proclamation_sync.cpp:172`)
|
||||||
|
is invoked only as a **best-effort** side effect of
|
||||||
|
`syncProclamationsFromApi` (`:168`). There is no boot sync, no network-up
|
||||||
|
sync, no periodic timer, and no conditional GET (`If-None-Match`/ETag).
|
||||||
|
`syncProclamationsFromApi` is called from `Home::syncProclamationsIfEmpty`
|
||||||
|
(`src/home.cpp:461`) and `ProclamationOptionsPage::onPush`
|
||||||
|
(`src/proclamation_options_page.cpp:49`). There is no explicit atomic
|
||||||
|
schedule cache file; the SQLite database is the durable store.
|
||||||
|
- **Schedule playback.** `src/scroller_page.cpp` selects the active block by
|
||||||
|
comparing `timezone::currentLocalMinutes(cfg.timezone)`
|
||||||
|
(`src/timezone_helper.h`) against each block via
|
||||||
|
`scroller::minuteInScheduleBlock` (`src/scroller_streams.h:31`). Block
|
||||||
|
selection is daily-only (no day-of-week). An active block's ordered
|
||||||
|
`proclamation_ids` replace the local selection
|
||||||
|
(`src/scroller_streams.cpp:57`). `effectiveDynamicAudio()`
|
||||||
|
(`src/scroller_page.cpp:302`) forces dynamic audio when the active block has
|
||||||
|
`dynamic_audio == true`, otherwise the device-wide
|
||||||
|
`config.audio.dynamic_audio` applies. A block change is latched in
|
||||||
|
`scheduleChangePending_` and applied at the next proclamation boundary.
|
||||||
|
- **Settings persistence.** `~/.local/radiantwave/config.yaml` (YAML) via
|
||||||
|
`ConfigManager` (`src/config/config_manager.cpp`, `src/config/yaml_parser.cpp`,
|
||||||
|
schema in `src/config/app_config.h`): `timezone`, `audio.dynamic_audio`,
|
||||||
|
`proclamation.schedule_active` (Kiosk-local on/off toggle, default `false`).
|
||||||
|
Identity lives in SQLite `system_kv` (`email_address`, `license_key`,
|
||||||
|
`license_last_verified`). There is no System Name anywhere.
|
||||||
|
- **Conventions.** `API_BASE_URL = "https://portal.radiantwavetech.com/api/v2"`
|
||||||
|
(`src/network/api_client.h`). Kiosk API knowledge is mirrored in
|
||||||
|
`openapi.yaml` at the repo root.
|
||||||
|
|
||||||
|
### Portal (`portal`)
|
||||||
|
|
||||||
|
- **Framework/tooling.** SvelteKit 2 + Svelte 5 + TypeScript + Tailwind v4;
|
||||||
|
Prisma 7 over PostgreSQL; Vitest. Migrations: Prisma Migrate
|
||||||
|
(`prisma/schema.prisma`, `prisma/migrations/`, `npm run migrate`,
|
||||||
|
`npm run deploy`; config in `prisma.config.ts`). API routes under
|
||||||
|
`src/routes/api/v1/` (API-key gated) and `src/routes/api/v2/`
|
||||||
|
(unauthenticated). v2 helpers: `src/lib/server/v2-helpers.ts`
|
||||||
|
(`jsonOk`, `jsonError`, `readJsonBody`, `rateLimited`, `clientIp`); errors
|
||||||
|
are `{ "message": "<text>" }`.
|
||||||
|
- **License validation.** `POST /api/v2/license`
|
||||||
|
(`src/routes/api/v2/license/+server.ts`): validates `license_key` +
|
||||||
|
`email_address`, requires the key be active and owned by the account, stamps
|
||||||
|
`LicenseKey.lastValidatedAt`, returns `serializeLicense({key, emailAddress})`
|
||||||
|
(`src/lib/server/v2-serializers.ts`). A v1 variant exists
|
||||||
|
(`src/routes/api/v1/license/+server.ts`).
|
||||||
|
- **Schedule storage.** `Schedule` is **per user** (`userId String @unique`,
|
||||||
|
`prisma/schema.prisma:263`), daily-only, `0..4` `Block`s. `Block`
|
||||||
|
(`:275`) stores `startMinutes`/`endMinutes`, `dynamicAudio Boolean
|
||||||
|
@default(false)`, and ordered `BlockProclamations` (`sortOrder`). The schema
|
||||||
|
comment states overnight ranges are disallowed in v1, yet the serializer and
|
||||||
|
Kiosk both support `overnight` — a known inconsistency.
|
||||||
|
- **Schedule service/validation.** `src/lib/server/schedule.ts` (DB
|
||||||
|
orchestration: `getOrCreateSchedule`, `listBlocks`, `createBlock`,
|
||||||
|
`updateBlock`, `deleteBlock`) and the isomorphic
|
||||||
|
`src/lib/schedule-utils.ts` (`MAX_BLOCKS = 4`, `SNAP_MINUTES = 15`,
|
||||||
|
`validateBlock`, `findOverlap`, `isOvernight`, `blockDuration`,
|
||||||
|
`blockSegments`, `formatTime`, `toHm`, `snapMinutes`). Validation enforces a
|
||||||
|
4-block maximum on create, `startMinutes !== endMinutes`, at least one
|
||||||
|
Proclamation, and no overlaps (overnight-aware via linear segments).
|
||||||
|
- **Schedule read API.** `GET /api/v2/schedule`
|
||||||
|
(`src/routes/api/v2/schedule/+server.ts`): auth by paired `email` +
|
||||||
|
`licenseKey` (legacy alias `key`) query params; checks only that the key
|
||||||
|
belongs to the email's account (entitlement deliberately not checked); rate
|
||||||
|
limited 10/min/IP; returns `{email, blocks: [serializeScheduleBlock(...)]}`
|
||||||
|
where `serializeScheduleBlock` (`src/lib/server/v2-serializers.ts:165`) emits
|
||||||
|
`{id, startTime, startMinutes, endTime, endMinutes, overnight,
|
||||||
|
durationMinutes, dynamicAudio, proclamationIds}`.
|
||||||
|
- **Schedule UI.** Protected user route
|
||||||
|
`src/routes/(protected)/(user)/schedule/` (`+page.server.ts`,
|
||||||
|
`+page.svelte`) with `src/lib/components/schedule/DaySchedule.svelte` (single
|
||||||
|
24h day timeline, drag-to-create) and `BlockModal.svelte`. Dashboard
|
||||||
|
(`(protected)/(user)/dashboard/`) lists the user's License Keys. No System
|
||||||
|
Name is displayed or editable.
|
||||||
|
|
||||||
|
### Gaps between current state and target
|
||||||
|
|
||||||
|
- Schedule is per-user, not per-License-Key.
|
||||||
|
- No `daily`/`weekly` typing; no day-of-week storage or UI.
|
||||||
|
- No `version`/ETag, no conditional GET.
|
||||||
|
- Schedule GET requires `email` + `licenseKey`; there is no license-key-only
|
||||||
|
route, no `/api/v2/licenses/...` namespace, and no system-name route.
|
||||||
|
- `dynamicAudio` DB default is `false`; the target default for new Blocks is
|
||||||
|
`true`.
|
||||||
|
- No System Name field, validation, or display.
|
||||||
|
- Sync is opportunistic with no boot/network-up/5-minute cadence and no
|
||||||
|
atomic cache file.
|
||||||
|
- Portal stores no timezone (matches target) and shows no informational
|
||||||
|
geoip-derived timezone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Time semantics
|
||||||
|
|
||||||
|
- Times are **minute precision**, stored as 24-hour `"HH:MM"` strings
|
||||||
|
representing **local wall-clock** time. AM/PM is display-only.
|
||||||
|
- Numerators may also be carried as integer minutes-from-midnight
|
||||||
|
(`startMinutes`/`endMinutes`, `0..1440`) for the Kiosk; the string form is
|
||||||
|
canonical on the wire.
|
||||||
|
- A Block is the **half-open interval `[start, end)`**.
|
||||||
|
- A Block may start on the same minute the previous Block ends (`end 07:00`,
|
||||||
|
next `start 07:00` is valid and has no gap).
|
||||||
|
- A Block whose `end` is earlier than its `start` **crosses midnight** and
|
||||||
|
belongs to its start day. In weekly mode, Sunday's crossing Block spills into
|
||||||
|
Monday. `start == end` is **invalid** (zero- or full-length ambiguity).
|
||||||
|
- **Overlaps are invalid**, including spillover from a midnight-crossing Block
|
||||||
|
into the next day's Blocks (or, in daily mode, into the same list's early
|
||||||
|
Blocks). Touching boundaries are **not** overlaps.
|
||||||
|
- Gaps between Blocks are allowed. During a gap the Kiosk plays whatever
|
||||||
|
Proclamations are selected locally on the Kiosk.
|
||||||
|
- The Kiosk evaluates Schedules in its own Timezone setting
|
||||||
|
(`config.timezone`, IANA code). **The payload carries no timezone.** The
|
||||||
|
Portal may show the user's geoip-derived timezone informationally only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schedule data model (JSON)
|
||||||
|
|
||||||
|
Each License Key has **at most one** Schedule. `type` is `"daily"` or
|
||||||
|
`"weekly"` and determines the mode; there is no separate global mode. **No**
|
||||||
|
Schedule means `type` is `null`.
|
||||||
|
|
||||||
|
Suggested shape from the brief, adapted to existing field names (`start`/`end`
|
||||||
|
→ `startTime`/`endTime`, retaining the existing `startMinutes`/`endMinutes`
|
||||||
|
integers for the Kiosk):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "<opaque string; also used as the ETag>",
|
||||||
|
"type": "daily" | "weekly" | null,
|
||||||
|
"daily": [ Block, ... ],
|
||||||
|
"weekly": { "mon": [Block], "tue": [Block], "wed": [Block],
|
||||||
|
"thu": [Block], "fri": [Block], "sat": [Block],
|
||||||
|
"sun": [Block] },
|
||||||
|
"blocks": [ LegacyBlock, ... ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Block:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "3f1c2b7a-9d4e-4c8a-b2f6-1a2b3c4d5e6f",
|
||||||
|
"startTime": "21:00",
|
||||||
|
"startMinutes": 1260,
|
||||||
|
"endTime": "09:00",
|
||||||
|
"endMinutes": 540,
|
||||||
|
"overnight": true,
|
||||||
|
"durationMinutes": 720,
|
||||||
|
"proclamationIds": ["b40fdcb9-703c-420e-af32-a081964c6f08"],
|
||||||
|
"dynamicAudio": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `version` is an opaque string derived from the Schedule content (see
|
||||||
|
[Endpoints](#endpoints)). It is implementation-defined and must change
|
||||||
|
whenever the serialized schedule changes.
|
||||||
|
- **Only the fields for the active type are populated.** For `type: "daily"`,
|
||||||
|
`daily` is populated and `weekly` is omitted or empty. For `type: "weekly"`,
|
||||||
|
`weekly` is populated with all seven keys and `daily` is omitted or empty.
|
||||||
|
For `type: null`, both are empty/omitted.
|
||||||
|
- `weekly` always contains all seven lowercase keys `mon`..`sun`; a day with no
|
||||||
|
Blocks is an empty array.
|
||||||
|
- `blocks` is the **legacy flattening** retained for older Kiosks (see
|
||||||
|
[Backward compatibility](#backward-compatibility)). It uses the existing
|
||||||
|
`LegacyBlock` shape serialized by the current portal
|
||||||
|
(`{id, startTime, startMinutes, endTime, endMinutes, overnight,
|
||||||
|
durationMinutes, dynamicAudio, proclamationIds}`). For `type: "weekly"`,
|
||||||
|
`blocks` is an **empty array** (the server does not know the Kiosk's
|
||||||
|
timezone and cannot flatten weekly to a single day).
|
||||||
|
- `proclamationIds` is the ordered play list (order is significant);
|
||||||
|
duplicates are not meaningful and are de-duplicated by the Portal.
|
||||||
|
- `dynamicAudio` defaults to `true` for new Blocks created on the Portal.
|
||||||
|
- Proclamation **content** is never included; the Kiosk fetches content
|
||||||
|
separately via `/api/v2/proclamations`. Portal users only ever see
|
||||||
|
title/category/group.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation rules
|
||||||
|
|
||||||
|
These are enforced by the Portal (authoritative) and defended against by the
|
||||||
|
Kiosk.
|
||||||
|
|
||||||
|
1. A Schedule belongs to exactly one License Key; a License Key has at most one
|
||||||
|
Schedule.
|
||||||
|
2. `type` is one of `"daily"`, `"weekly"`, or `null`.
|
||||||
|
3. A Block has `start` and `end` times; `start == end` is invalid.
|
||||||
|
4. `end < start` denotes a midnight-crossing Block belonging to its start day.
|
||||||
|
5. Blocks within a day (and the spillover of a midnight-crossing Block into the
|
||||||
|
following day) must not overlap. Touching boundaries are allowed.
|
||||||
|
6. Weekly modes apply rules 3–5 independently per day; a `sun` Block crossing
|
||||||
|
midnight must not overlap `mon` early Blocks.
|
||||||
|
7. A Block must contain at least one Proclamation. Empty Blocks are rejected
|
||||||
|
server-side. The Kiosk defensively **skips invalid Blocks** and treats them
|
||||||
|
as gaps.
|
||||||
|
8. Gaps are allowed; during a gap local Kiosk selection applies.
|
||||||
|
9. `dynamicAudio` is a boolean; absent means `true` for new Blocks.
|
||||||
|
10. Times are minute-precision `HH:MM` strings; the Portal snaps UI input to
|
||||||
|
`SNAP_MINUTES` (15) with a `MINUTE_STEP` (5) constraint from the existing
|
||||||
|
helpers.
|
||||||
|
11. The existing Portal cap of `MAX_BLOCKS = 4` per day list is **retained**
|
||||||
|
for Phase 0 (see [Open items](#open-items)).
|
||||||
|
|
||||||
|
The Kiosk must not crash on, and must not play, an invalid Block: it skips it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
The API remains **unauthenticated** (no API keys) for v2. This is a known
|
||||||
|
limitation and is recorded under [Open items](#open-items).
|
||||||
|
|
||||||
|
All new endpoints live under the existing versioned namespace
|
||||||
|
`/api/v2/...` to preserve conventions. v1 routes are unchanged.
|
||||||
|
|
||||||
|
### (a) License validation — existing, additive
|
||||||
|
|
||||||
|
`POST /api/v2/license`
|
||||||
|
|
||||||
|
Request (existing fields unchanged; `systemName` additive and optional):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"license_key": "ABCD-EFGH-IJKL-MNOP",
|
||||||
|
"email_address": "jane@example.com",
|
||||||
|
"systemName": "Living Room"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response (existing fields unchanged; `systemNameStatus` additive):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "key": "ABCD-EFGH-IJKL-MNOP", "email_address": "jane@example.com", "systemNameStatus": "ok" }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `systemName` is optional. When present it is trimmed; when absent the stored
|
||||||
|
name is left untouched.
|
||||||
|
- `systemNameStatus` ∈ `{"ok", "unchanged", "conflict", "invalid"}`:
|
||||||
|
- `ok` — name was set or updated.
|
||||||
|
- `unchanged` — omitted, or identical after trim/case-insensitive compare.
|
||||||
|
- `conflict` — another License Key on the same account already holds the name.
|
||||||
|
- `invalid` — trimmed length outside 1–40.
|
||||||
|
- **Validation itself succeeds regardless of name status.** A non-`ok` status
|
||||||
|
never fails the license check; the Kiosk treats it as advisory.
|
||||||
|
- Errors and rate limiting follow the existing endpoint (400 messages,
|
||||||
|
413 body cap, 429 with `Retry-After`).
|
||||||
|
|
||||||
|
### (b) Set System Name — Kiosk-facing
|
||||||
|
|
||||||
|
`PUT /api/v2/licenses/{licenseKey}/system-name`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "systemName": "Living Room" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Responses:
|
||||||
|
|
||||||
|
| Status | Condition | Body |
|
||||||
|
| --------------------- | ----------------------------------------- | ------------------------------------------------- |
|
||||||
|
| 200 | Name set/updated (or unchanged) | `{"systemName": "...", "systemNameStatus": "ok"}` |
|
||||||
|
| 400 `invalid_name` | Trimmed length outside 1–40 | `{"message": "invalid_name"}` |
|
||||||
|
| 404 `unknown_license` | No License Key matches | `{"message": "unknown_license"}` |
|
||||||
|
| 409 `name_taken` | Another key on the account holds the name | `{"message": "name_taken"}` |
|
||||||
|
|
||||||
|
- The Kiosk is the **only** writer. The Portal has no rename capability.
|
||||||
|
- The response body uses the existing v2 error convention
|
||||||
|
(`{"message": "<code>"}`).
|
||||||
|
- Rate limited at the existing 10 requests/min/IP convention.
|
||||||
|
|
||||||
|
### (c) Get Schedule — Kiosk-facing, key-only, conditional
|
||||||
|
|
||||||
|
`GET /api/v2/licenses/{licenseKey}/schedule`
|
||||||
|
|
||||||
|
- 200 with `ETag: "<version>"` and the payload from
|
||||||
|
[Schedule data model](#schedule-data-model-json). A License Key with no
|
||||||
|
Schedule returns 200 with `type: null` and empty lists.
|
||||||
|
- `304 Not Modified` when the request's `If-None-Match` matches the current
|
||||||
|
`ETag`.
|
||||||
|
- `404` (`unknown_license`) when no License Key matches.
|
||||||
|
- `version` doubles as the ETag value; the header value is the quoted form.
|
||||||
|
- The response is the **target** payload (`version`, `type`, `daily`, `weekly`,
|
||||||
|
`blocks`).
|
||||||
|
- Rate limited at the existing 10 requests/min/IP convention.
|
||||||
|
|
||||||
|
### (d) Legacy schedule read — existing, evolved additively
|
||||||
|
|
||||||
|
`GET /api/v2/schedule?email=<email>&licenseKey=<key>` remains supported with
|
||||||
|
its existing auth semantics. Its response is extended **additively** with the
|
||||||
|
same `version`, `type`, `daily`, and `weekly` fields, while continuing to
|
||||||
|
populate `email` and `blocks` exactly as before. Old Kiosks ignore the new
|
||||||
|
fields; new Kiosks may read either endpoint. This endpoint does **not** emit an
|
||||||
|
ETag and does **not** return 304.
|
||||||
|
|
||||||
|
### (e) Portal-internal endpoints (not Kiosk-facing)
|
||||||
|
|
||||||
|
These are session-authenticated Portal actions; they are **not** part of the
|
||||||
|
Kiosk API and are not served by `/api/v2/...`.
|
||||||
|
|
||||||
|
- Schedule CRUD — implemented in Phase 3 as SvelteKit form actions on
|
||||||
|
`(protected)/(user)/schedule` (`?/setType`, `?/createBlock`, `?/updateBlock`,
|
||||||
|
`?/deleteBlock`), backed by `$lib/server/schedule.ts`.
|
||||||
|
- **Copy** — implemented in Phase 4 as the `?/copy` action plus
|
||||||
|
`copySchedule()` in `$lib/server/schedule.ts`. Copies one License Key's
|
||||||
|
Schedule to one or more other License Keys **by value, in a single database
|
||||||
|
transaction** (all targets succeed or none). The target `type` becomes the
|
||||||
|
source's; Block rows get fresh IDs; each block's `dynamicAudio` and ordered
|
||||||
|
Proclamation references are preserved. The source and every target must
|
||||||
|
belong to the caller's account; a target equal to the source, an empty
|
||||||
|
target list, or a source without a schedule are rejected. The resulting
|
||||||
|
schedule is run through the shared validator before commit. Because Block IDs
|
||||||
|
are part of the serialized payload, every target's content-derived
|
||||||
|
`version`/ETag changes, so its Kiosk refetches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sync behavior
|
||||||
|
|
||||||
|
Target Kiosk sync (defaults; adjust only if the code forces it):
|
||||||
|
|
||||||
|
- The Kiosk pulls the Schedule:
|
||||||
|
- on boot,
|
||||||
|
- on network-up,
|
||||||
|
- every 5 minutes.
|
||||||
|
- Pulls use conditional GET (`If-None-Match` / ETag); a `304` leaves the cache
|
||||||
|
untouched.
|
||||||
|
- The last Schedule is cached to disk with an **atomic write** and used offline.
|
||||||
|
- The schedule on/off toggle is **Kiosk-local** (existing
|
||||||
|
`proclamation.schedule_active`) and is not synced.
|
||||||
|
- With no cache and no network, the Kiosk behaves as if schedules are off
|
||||||
|
(local Proclamation selection applies).
|
||||||
|
- During gaps, or when schedules are off, the local audio selection applies.
|
||||||
|
|
||||||
|
Current-state deltas (to be closed by later phases): no boot/network-up/5-minute
|
||||||
|
cadence, no ETag, no explicit atomic cache file. These are recorded under
|
||||||
|
[Touchpoints per phase](#touchpoints-per-phase).
|
||||||
|
|
||||||
|
Identity for sync is the License Key. The legacy schedule endpoint additionally
|
||||||
|
requires the paired email; the new endpoint uses the License Key alone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Name rules
|
||||||
|
|
||||||
|
- A System Name is a human-readable label for a License Key (e.g.
|
||||||
|
"Living Room"). It is a **label only** — never used as a reference or key.
|
||||||
|
- Unique **per account**, compared **case-insensitively**.
|
||||||
|
- Trimmed, **1–40 characters**.
|
||||||
|
- Set or changed **only via the Kiosk**. The Portal displays it **read-only**
|
||||||
|
and has no rename capability.
|
||||||
|
- The Portal falls back to displaying the License Key when no name is set.
|
||||||
|
- A Kiosk that omits `systemName` never clears an existing name.
|
||||||
|
- Wire representations: request field `systemName`; status field
|
||||||
|
`systemNameStatus` ∈ `{"ok","unchanged","conflict","invalid"}` (see
|
||||||
|
[Endpoints](#endpoints)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dynamic audio
|
||||||
|
|
||||||
|
- Each Block has a boolean `dynamicAudio`.
|
||||||
|
- The Portal defaults it to **`true` for new Blocks**.
|
||||||
|
- `true` ⇒ the Kiosk uses dynamic audio for that Block.
|
||||||
|
- `false` ⇒ the Kiosk uses its locally chosen audio.
|
||||||
|
- During gaps, or when schedules are off, the local audio selection applies
|
||||||
|
(existing `config.audio.dynamic_audio` / fixed track).
|
||||||
|
- Kiosk parsing defaults an absent `dynamicAudio` to `true`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backward compatibility
|
||||||
|
|
||||||
|
All existing endpoints and payloads keep working for Kiosks already in the
|
||||||
|
field. New fields are **additive**; no v2 field is removed or renamed, and v1
|
||||||
|
is untouched.
|
||||||
|
|
||||||
|
- **Old Kiosk vs new Portal.** An old Kiosk calls
|
||||||
|
`POST /api/v2/license` (no `systemName`) and
|
||||||
|
`GET /api/v2/schedule?email&licenseKey`, reading `blocks` and ignoring
|
||||||
|
unknown fields. The Portal keeps both, keeps the `blocks` shape byte-
|
||||||
|
compatible, and adds `version`/`type`/`daily`/`weekly` as extra fields. The
|
||||||
|
old Kiosk is unaffected and ignores `systemNameStatus`.
|
||||||
|
- **Daily schedules.** For `type: "daily"`, `blocks` is the flattened daily
|
||||||
|
list; an old Kiosk behaves exactly as today.
|
||||||
|
- **Weekly schedules.** `blocks` is an **empty array**. An old Kiosk therefore
|
||||||
|
sees no schedule and falls back to local Proclamation/audio selection. It
|
||||||
|
cannot interpret weekly schedules; this is accepted and recorded under
|
||||||
|
[Open items](#open-items).
|
||||||
|
- **No schedule.** `type: null`, `blocks: []`; both old and new Kiosks fall
|
||||||
|
back to local selection.
|
||||||
|
- **Omission of `systemName`** by an old Kiosk never clears an existing name;
|
||||||
|
`systemNameStatus` is simply ignored.
|
||||||
|
- The new endpoints are additive; their absence on old Kiosks is irrelevant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Touchpoints per phase
|
||||||
|
|
||||||
|
File/module candidates each later phase will change. Phase numbering is assumed
|
||||||
|
from the brief (Phase 3 = Portal schedule CRUD, Phase 4 = Portal schedule copy)
|
||||||
|
and is recorded under [Open items](#open-items).
|
||||||
|
|
||||||
|
### Kiosk (`radiantwave`)
|
||||||
|
|
||||||
|
- **Phase 1 (Kiosk scheduler UI, storage, sync, playback)**
|
||||||
|
- `src/network/api_client.h` / `src/network/api_client.cpp` — parse
|
||||||
|
`version`/`type`/`daily`/`weekly`, ETag/`If-None-Match`, `PUT`
|
||||||
|
system-name, `systemNameStatus`.
|
||||||
|
- `src/network/proclamation_sync.h` / `.cpp` — boot/network-up/5-minute
|
||||||
|
schedule sync, conditional GET, atomic cache.
|
||||||
|
- `src/network/license_service.h` / `.cpp` — send `systemName`, handle
|
||||||
|
`systemNameStatus`.
|
||||||
|
- `src/db/db_keys.h` — System Name / ETag keys; `src/db/database_manager.h`
|
||||||
|
/ `.cpp` — schedule `type`/day-of-week storage, cache.
|
||||||
|
- `src/scroller_page.h` / `.cpp`, `src/scroller_streams.h` / `.cpp` —
|
||||||
|
day-of-week Block selection, spillover.
|
||||||
|
- `src/timezone_helper.h` — local day-of-week.
|
||||||
|
- `src/config/app_config.h`, `src/config/config_manager.cpp`,
|
||||||
|
`src/config/yaml_parser.cpp` — settings surface.
|
||||||
|
- New page(s) for setting the System Name; tests in `tests/` registered in
|
||||||
|
`CMakeLists.txt`.
|
||||||
|
|
||||||
|
### Portal (`portal`)
|
||||||
|
|
||||||
|
- **Phase 2 (System Name + license validation)**
|
||||||
|
- `prisma/schema.prisma` + migration — `LicenseKey.systemName` and
|
||||||
|
case-insensitive uniqueness.
|
||||||
|
- `src/routes/api/v2/license/+server.ts` — `systemName` /
|
||||||
|
`systemNameStatus`.
|
||||||
|
- `src/routes/api/v2/licenses/[licenseKey]/system-name/+server.ts` — new.
|
||||||
|
- `src/lib/server/v2-serializers.ts`, `src/lib/server/v2-helpers.ts`.
|
||||||
|
- Dashboard read-only display.
|
||||||
|
- **Phase 3 (Schedule CRUD: daily/weekly, per-License-Key)**
|
||||||
|
- `prisma/schema.prisma` + migration — Schedule per License Key, `type`,
|
||||||
|
day-of-week.
|
||||||
|
- `src/lib/server/schedule.ts`, `src/lib/schedule-utils.ts` — weekly,
|
||||||
|
per-license orchestration, cross-day validation.
|
||||||
|
- `src/lib/server/v2-serializers.ts` — `version`/`type`/`daily`/`weekly`.
|
||||||
|
- `src/routes/api/v2/licenses/[licenseKey]/schedule/+server.ts` — new
|
||||||
|
(ETag/304).
|
||||||
|
- `src/routes/api/v2/schedule/+server.ts` — additive evolution.
|
||||||
|
- `src/routes/(protected)/(user)/schedule/*`,
|
||||||
|
`src/lib/components/schedule/*` — weekly UI, per-license selection.
|
||||||
|
- Specs: `schedule.spec.ts`, `page.server.spec.ts`, `schedule-utils.spec.ts`.
|
||||||
|
- **Phase 4 (Schedule copy)**
|
||||||
|
- Copy service in `src/lib/server/schedule.ts`; copy route/action; UI.
|
||||||
|
- **All phases** — `docs/openapi.yaml`, this contract, `docs/fixtures/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open items
|
||||||
|
|
||||||
|
Recorded ambiguities, conservative choices, and remaining known limitations.
|
||||||
|
Items 1–21 were resolved or restated during Phases 1–4; items 22–24 are Phase 5
|
||||||
|
findings. Everything marked "Resolved" is settled for this overhaul.
|
||||||
|
|
||||||
|
1. **Phase numbering.** The brief names Phases 3 (CRUD) and 4 (copy) only;
|
||||||
|
Phase 1 (Kiosk) and Phase 2 (System Name) are assumed here.
|
||||||
|
2. **`blocks` for weekly.** Chosen as an empty array (server cannot know the
|
||||||
|
Kiosk timezone). Consequence: old Kiosks see weekly schedules as "no
|
||||||
|
schedule". Implemented in Phase 3.
|
||||||
|
3. **Block cap.** The existing `MAX_BLOCKS = 4` per day list is retained.
|
||||||
|
Phase 3 applies it **per day list** (each of the seven weekly days gets its
|
||||||
|
own 4-block budget). Whether the cap should be lower is unresolved.
|
||||||
|
4. **Field naming.** The brief's `start`/`end` were mapped to the existing
|
||||||
|
`startTime`/`endTime`; `startMinutes`/`endMinutes` retained for the Kiosk.
|
||||||
|
5. **`version` derivation.** Resolved in Phase 3: a SHA-1 content hash of the
|
||||||
|
serialized `{type, daily, weekly}` shape. Stable for equal content, changes
|
||||||
|
on any mutation.
|
||||||
|
6. **Unauthenticated key-only access.** The new endpoints authenticate by
|
||||||
|
License Key alone (per glossary). Anyone who knows a key could read the
|
||||||
|
schedule or set the name. Mitigated only by the 10/min/IP rate limit. Known
|
||||||
|
limitation.
|
||||||
|
7. **`dynamicAudio` default flip.** DB default changed `false` → `true` for
|
||||||
|
**new** Blocks in Phase 3; existing Blocks keep their stored values.
|
||||||
|
8. **Per-user → per-License-Key migration.** Resolved in Phase 3: each existing
|
||||||
|
per-user Schedule is mapped to that account's **oldest** License Key
|
||||||
|
(`createdAt ASC`), as a daily schedule. Schedules with no owning
|
||||||
|
user/license are retained with `licenseKeyId NULL` and reported via a
|
||||||
|
migration `NOTICE` (not dropped).
|
||||||
|
9. **Cross-day spillover validation.** Implemented in Phase 3: weekly
|
||||||
|
`sun`→`mon` and daily self-wrap spillover are rejected as overlaps; the
|
||||||
|
shared `validateSchedule` is authoritative and mirrored client-side.
|
||||||
|
10. **Account scope for name uniqueness.** "Per account" = the owning User.
|
||||||
|
Legacy License Keys with only an `email` and no `userId` are an edge case
|
||||||
|
(written without a per-account uniqueness check).
|
||||||
|
11. **Geoip timezone display.** Phase 3 shows an informational note that times
|
||||||
|
run in the **Kiosk's** timezone and uses the **browser-resolved** timezone
|
||||||
|
as a fallback; no geoip dependency (paid or external) was added. The
|
||||||
|
Portal still stores no timezone.
|
||||||
|
12. **ETag on the legacy endpoint.** The legacy `GET /api/v2/schedule` emits no
|
||||||
|
ETag and never returns 304, to preserve old behaviour.
|
||||||
|
13. **`systemNameStatus` on v1.** Not added; v1 is unchanged.
|
||||||
|
14. **Trim/case representation.** Names are trimmed; uniqueness is
|
||||||
|
case-insensitive; the stored casing is preserved as provided.
|
||||||
|
15. **Sync cadence.** Resolved in Phase 3: the Kiosk loads its on-disk cache on
|
||||||
|
boot, then pulls on boot, on a 5-minute timer, and on demand.
|
||||||
|
16. **Atomic cache file.** Resolved in Phase 3:
|
||||||
|
`~/.local/radiantwave/schedule_cache.json`, written atomically (temp file +
|
||||||
|
rename); a corrupt cache is ignored.
|
||||||
|
17. **`type` encoding for no schedule.** `type: null`; `daily`/`weekly` absent
|
||||||
|
or empty.
|
||||||
|
18. **Legacy alias.** `key` remains accepted as a `licenseKey` alias on the
|
||||||
|
legacy schedule endpoint.
|
||||||
|
19. **Kiosk local toggle.** `proclamation.schedule_enabled` in `config.yaml`,
|
||||||
|
default **on**, surfaced as System Settings → Schedule. Not synced. When
|
||||||
|
off, the engine plays local selections but the cache keeps updating. The
|
||||||
|
older `proclamation.schedule_active` flag is retained in config but is not
|
||||||
|
consulted by the Phase 3 engine.
|
||||||
|
20. **Copy semantics.** Copies are **independent, by value** snapshots: no link
|
||||||
|
is kept to the source, and editing the source later does not affect targets
|
||||||
|
(and vice versa). A copy replaces any existing target Schedule in full
|
||||||
|
(type and blocks) and requires explicit confirmation in the UI when a target
|
||||||
|
already has one. No "copy to all" shortcut and no scheduled/linked copies
|
||||||
|
(per the Phase 4 brief). The copied schedule is validated before commit; a
|
||||||
|
source schedule that fails validation is rejected rather than copied.
|
||||||
|
21. **Copy and Kiosk code.** Verified against the Phase 3 Kiosk engine/fixture
|
||||||
|
tests that a copied payload (new Block IDs, same type/times/audio/refs) is
|
||||||
|
parsed and evaluated identically to any other; **no Kiosk changes were
|
||||||
|
required**.
|
||||||
|
22. **Phase 3 migration down is lossy for new-model data.** The `up` migration
|
||||||
|
(`20260929130000_move_schedule_to_license_key`) is additive and safe. Its
|
||||||
|
`down` path is **reversible in structure but lossy in data**: the down SQL
|
||||||
|
drops `Schedule.licenseKeyId`/`type` and `Block.day`, then deletes
|
||||||
|
`Schedule` rows whose `userId IS NULL`. Any schedule created under the
|
||||||
|
per-License-Key model (which sets `licenseKeyId` and leaves `userId` NULL)
|
||||||
|
is therefore **deleted** by a down-migration. Consequences:
|
||||||
|
- Back up `Schedule`/`Block`/`BlockProclamations` before any down-migration.
|
||||||
|
- Rolling back past Phase 3 requires restoring that data from backup; it is
|
||||||
|
**not** a data-preserving rollback.
|
||||||
|
- Rollback guidance is in
|
||||||
|
[scheduler-overhaul-release-notes.md](scheduler-overhaul-release-notes.md).
|
||||||
|
23. **Kiosk UI/E2E on real hardware not run here.** The verification ran the
|
||||||
|
Portal and the Kiosk schedule logic (engine, client, cache) via the test
|
||||||
|
harness and real HTTP against a locally running Portal; the SDL Kiosk UI was
|
||||||
|
not driven interactively because the build environment has no display. See
|
||||||
|
the manual checklist in the release notes and the Phase 5 report.
|
||||||
|
24. **Unauthenticated Kiosk endpoints remain a known limitation** (restated from
|
||||||
|
item 6): the key-only schedule and system-name endpoints authenticate by
|
||||||
|
License Key alone, mitigated only by the 10/min/IP rate limit. Authentication
|
||||||
|
is out of scope for this overhaul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- 2026-09-29 — Initial contract created (Phase 0). Documents current state,
|
||||||
|
target time semantics, schedule data model, validation rules, endpoints
|
||||||
|
(`/api/v2/licenses/...`, additive license-validation fields), sync behavior,
|
||||||
|
System Name rules, dynamic audio, backward compatibility, touchpoints,
|
||||||
|
open items, and fixtures.
|
||||||
|
- 2026-09-29 — Phase 3. Schedule is now per License Key with daily/weekly and
|
||||||
|
`Block.day`; the shared `validateSchedule` is authoritative; the key-only
|
||||||
|
`GET /api/v2/licenses/{licenseKey}/schedule` (ETag/304) ships and the legacy
|
||||||
|
endpoint is extended additively; the Portal editor is per-system with a
|
||||||
|
browser-fallback timezone note; the Kiosk adds the conditional client,
|
||||||
|
atomic cache, schedule engine, and a System Settings On/Off toggle. Open
|
||||||
|
items 2, 5, 7, 8, 9, 11, 15, and 16 are updated to reflect the changes.
|
||||||
|
- 2026-09-29 — Phase 4. Schedule copy is implemented as a Portal-internal
|
||||||
|
action (`?/copy`) backed by a single-transaction service that copies by
|
||||||
|
value with fresh Block IDs (so target ETags change), preserving `dynamicAudio`
|
||||||
|
and Proclamation order, scoped to the caller's account, and validated before
|
||||||
|
commit. Endpoint (e) is finalized; open items 20 and 21 record the copy
|
||||||
|
semantics and the Kiosk no-change verification.
|
||||||
|
- 2026-09-29 — Phase 5 (final). End-to-end verification against a locally
|
||||||
|
running Portal on a fresh database: schedule create/type/copy services and
|
||||||
|
the Kiosk-facing HTTP endpoints (validation with/without `systemName`,
|
||||||
|
system-name PUT, key-only schedule GET with ETag/304/404, legacy endpoint
|
||||||
|
parity, cross-account rejection) all pass; old-client compatibility confirmed;
|
||||||
|
migrations verified up/down (the down path is lossy, see open item 22). Adds
|
||||||
|
open items 22–24 (lossy down-migration, unrun UI-on-hardware checks, and the
|
||||||
|
restated unauthenticated-endpoint limitation) and the release notes.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Scheduler Overhaul — Phase 5 Verification Results
|
||||||
|
|
||||||
|
Prepared during Phase 5 against a **locally running Portal on a fresh database**.
|
||||||
|
This records what was actually executed and observed. It is a companion to
|
||||||
|
`scheduler-overhaul-release-notes.md` (rollout steps) and
|
||||||
|
`schedule-api-contract.md` (the contract). Where a scenario could not be run
|
||||||
|
without real Kiosk hardware or an interactive display, the row is marked and the
|
||||||
|
equivalent automated coverage is cited.
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
|
||||||
|
- Portal dev server (`npm run dev`) on `http://localhost:5173`, pointed at a
|
||||||
|
fresh scratch database (`rwportal_p5`) created and migrated for this phase.
|
||||||
|
- Kiosk schedule logic exercised through the C++ test harness
|
||||||
|
(`test_schedule_engine`) and the Kiosk API client's JSON parsing.
|
||||||
|
- No display/Xvfb available, so the SDL Kiosk UI was **not** driven
|
||||||
|
interactively; see the manual checklist in the release notes.
|
||||||
|
|
||||||
|
Legend: **PASS = executed**, **AUTO = verified by automated test** (not
|
||||||
|
interactively), **MANUAL = requires a human on hardware/display**.
|
||||||
|
|
||||||
|
| # | Scenario | Result | Evidence / notes |
|
||||||
|
| --- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| a | Kiosk shows AM/PM everywhere; System Settings contains Email, License Key, System Name, Schedule toggle, WiFi, Timezone, Date Format | AUTO | `test_date_format` (12h conversion), `test_settings_menu` (exactly the 7 entries incl. System Name + Schedule). UI rendering itself is MANUAL. |
|
||||||
|
| b | Validate a License Key, name the Kiosk, see the name read-only on the Portal | PASS + MANUAL | HTTP: validation without name → `200 unchanged`; with name → `200 ok`. Portal render is MANUAL (release-notes checklist). |
|
||||||
|
| c | Two systems in one account: duplicate name in any casing rejected and reverted on the Kiosk | PASS | HTTP: case-insensitive duplicate → validation `200 conflict`; `PUT` duplicate → `409 name_taken`; blank → `400 invalid_name`; unknown → `404 unknown_license`. Kiosk revert covered by `test_license_service`/`test_system_name`. |
|
||||||
|
| d | Rename a system on the Kiosk; its Schedule stays attached (License Key reference) | AUTO | Schedules are keyed by `licenseKeyId`; naming only touches `systemName`. Portal `schedule-copy`/service tests operate purely on License Key IDs. |
|
||||||
|
| e | Create a daily Schedule via the Portal picker; Kiosk fetches and applies it | PASS | Opt-in integration spec: daily schedule with 2 blocks created via the service; key-only GET returns it; captured live payload parsed and evaluated by the Kiosk (`SchedulePortalParityTest`). |
|
||||||
|
| f | Weekly: Block boundaries, adjacent (end 7:00 / start 7:00), midnight-crossing, Sunday→Monday spillover | PASS + AUTO | Integration spec: weekly sun crossing + adjacent mon blocks, Sunday spillover collision rejected. Engine tests: adjacency boundary, midnight self-wrap, Sunday→Monday spillover. |
|
||||||
|
| g | Gap plays local Proclamations; `dynamicAudio` true → dynamic, false → local | AUTO | Engine tests: gaps return no block; per-block `dynamicAudio` flag; `ScrollerPage::effectiveDynamicAudio` honored via playback wiring (build passes). |
|
||||||
|
| h | Schedule toggle Off stops schedule effects; On resumes | AUTO | `ScheduleManagerTest.DisabledReturnsNoBlock`; toggle stored in `config.yaml` (`schedule_enabled`, default on). |
|
||||||
|
| i | Schedules assigned to system A never affect system B | PASS | Integration spec: A daily, B weekly simultaneously; independent payloads. |
|
||||||
|
| j | Copy a Schedule to another named system; that Kiosk picks it up | PASS | Integration spec: copy A→second system yields daily with 2 blocks; independence confirmed; target ETag differs. Portal `schedule-copy.spec` covers overwrite/multi-target/rollback/new IDs. |
|
||||||
|
| k | Offline uses cached Schedule; corrupt cache ignored; timezone change re-evaluates | AUTO | `ScheduleCacheTest` (round-trip, corrupt ignored), `ScheduleManagerTest` (offline cache, 304 handled). Timezone is passed as wall-clock minutes (wall-clock means DST-safe). |
|
||||||
|
| l | Portal shows geoip/fallback timezone with kiosk-timezone note; manual minute entry alongside 5-minute options | AUTO | UI note is browser-fallback (no external dep); `TimeInput` tests cover 5-minute options and typed minutes. Visual render MANUAL. |
|
||||||
|
|
||||||
|
## Additional checks
|
||||||
|
|
||||||
|
| Check | Result | Notes |
|
||||||
|
| --------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Old-Kiosk compatibility (validate without `systemName`; legacy schedule endpoint) | PASS | `200 unchanged`; name never cleared; legacy `GET /api/v2/schedule` still returns `email` + `blocks`; modern vs legacy `version` equal. |
|
||||||
|
| Cross-account isolation | PASS | Legacy schedule with another account's key → `400`; copy rejects non-owned source/targets. |
|
||||||
|
| Conditional GET | PASS | Matching `If-None-Match` → `304`; stale ETag → `200`; unknown key → `404`. |
|
||||||
|
| Migration up | PASS | Applied cleanly to a fresh DB (29 migrations); fresh seed succeeded. |
|
||||||
|
| Migration down/up on realistic data | PASS (with caveat) | Phase 2 and Phase 3 down/up round-tripped structurally on a clone; **the Phase 3 down path is lossy** (deletes per-License-Key schedules). Documented in the contract (Open item 22) and release notes. |
|
||||||
|
| Portal `svelte-check` / ESLint / tests | PASS | 0 errors/warnings; 394 passed, 4 skipped (opt-in integration). |
|
||||||
|
| Kiosk build + tests | PASS | Full build; 21 test binaries pass. |
|
||||||
|
| Formatters | PASS | Portal Prettier clean except the pre-existing `.gitea/workflows/deploy.yaml` (unchanged from before this branch). Kiosk follows `.clang-format` (no binary available to run). |
|
||||||
|
|
||||||
|
## Not verified here (requires hardware/display)
|
||||||
|
|
||||||
|
The interactive SDL Kiosk UI (rendering, key handling, onboarding walkthrough)
|
||||||
|
was not driven; all Kiosk behavior above is covered by the engine/client/cache
|
||||||
|
tests and by parsing live Portal payloads. See the manual checklist in
|
||||||
|
`scheduler-overhaul-release-notes.md`.
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# Scheduler Overhaul — Release Notes
|
||||||
|
|
||||||
|
Status: prepared for rollout. These are **instructions only**; nothing here has
|
||||||
|
been executed. The contract lives at `docs/schedule-api-contract.md` (kept
|
||||||
|
byte-identical in both repos).
|
||||||
|
|
||||||
|
This document is kept **byte-identical** in both repositories:
|
||||||
|
|
||||||
|
- `radiantwave` (C++/SDL3 Kiosk app) — `docs/scheduler-overhaul-release-notes.md`
|
||||||
|
- `portal` (SvelteKit/Node) — `docs/scheduler-overhaul-release-notes.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### Portal
|
||||||
|
|
||||||
|
- **Schedules are assigned to a License Key**, not a user. `Schedule` gained
|
||||||
|
`licenseKeyId` (unique, nullable) and `type` (`daily` | `weekly` | null);
|
||||||
|
`Block` gained `day` (null for daily, `mon`..`sun` for weekly) and its
|
||||||
|
`dynamicAudio` default became `true`.
|
||||||
|
- **New Kiosk endpoint** `GET /api/v2/licenses/{licenseKey}/schedule` returns the
|
||||||
|
target payload (`version`, `type`, `daily`, `weekly`, `blocks`) with an `ETag`
|
||||||
|
derived from `version`, supports conditional GET (`304`), and is `404` for an
|
||||||
|
unknown key.
|
||||||
|
- **License validation** (`POST /api/v2/license`) accepts an optional
|
||||||
|
`systemName` and returns an additive `systemNameStatus`
|
||||||
|
(`ok` | `unchanged` | `conflict` | `invalid`). Validation always succeeds; a
|
||||||
|
non-`ok` name status is advisory.
|
||||||
|
- **New Kiosk endpoint** `PUT /api/v2/licenses/{licenseKey}/system-name`
|
||||||
|
(`200` / `400 invalid_name` / `404 unknown_license` / `409 name_taken`,
|
||||||
|
idempotent).
|
||||||
|
- **System Name** is a human-readable label for a License Key. It is set only
|
||||||
|
by the Kiosk and shown read-only in the Portal (dashboard and admin lists),
|
||||||
|
falling back to displaying the License Key. There is no Portal rename path.
|
||||||
|
Names are unique per account, compared case-insensitively, trimmed, 1–40
|
||||||
|
characters.
|
||||||
|
- **Schedule editor** (protected user route `(protected)/(user)/schedule`):
|
||||||
|
a per-system picker labelled by System Name, a Daily/Weekly type selector with
|
||||||
|
confirmation when switching a populated schedule (daily→weekly prefills all
|
||||||
|
seven days; weekly→daily and clearing start empty), per-day weekly editing,
|
||||||
|
AM/PM `TimeInput` (5-minute suggestions plus manual minute entry), midnight
|
||||||
|
crossing labelled "ends next day", an on-by-default dynamic-audio toggle, gap
|
||||||
|
helper text, and an informational note that times run in the Kiosk's own
|
||||||
|
timezone (browser timezone fallback; no external GeoIP service).
|
||||||
|
- **Copy to…** copies one system's Schedule to one or more other systems in the
|
||||||
|
same account, by value, in a single database transaction (all or none). Target
|
||||||
|
Blocks get fresh IDs so target `version`/ETag changes; `dynamicAudio` and
|
||||||
|
Proclamation order are preserved. Overwriting a target that already has a
|
||||||
|
schedule requires explicit confirmation. Copies are independent.
|
||||||
|
- **OpenAPI** (`docs/openapi.yaml`) documents the new endpoints and payloads.
|
||||||
|
|
||||||
|
### Kiosk (`radiantwave`)
|
||||||
|
|
||||||
|
- **12-hour AM/PM** time display everywhere the Kiosk shows a time; storage and
|
||||||
|
parsing remain 24-hour `HH:MM`. The Date Format setting is unchanged.
|
||||||
|
- **System Settings** menu contains Email Address, License Key, **System Name**,
|
||||||
|
**Schedule** (on/off), WiFi Setup, Time Zone Setup, and Date Format. The list
|
||||||
|
is data-driven.
|
||||||
|
- **System Name** entry (trimmed, 1–40), sent on license validation and via
|
||||||
|
`PUT /api/v2/licenses/{key}/system-name`. Conflicts/invalid names are reverted
|
||||||
|
locally with a user-facing message; network failures keep the new name locally
|
||||||
|
and retry (pending-sync flag) on boot, on validation, and on a timer.
|
||||||
|
- **Schedule client**: conditional GET (`If-None-Match`) of
|
||||||
|
`GET /api/v2/licenses/{key}/schedule`, on boot, on network-up, and every
|
||||||
|
5 minutes. The last payload is cached to
|
||||||
|
`~/.local/radiantwave/schedule_cache.json` with an **atomic write** (temp file
|
||||||
|
- rename); a corrupt cache is ignored. Offline uses the cache; no cache and no
|
||||||
|
network behaves as schedules-off.
|
||||||
|
- **Schedule engine**: given the payload, the local wall-clock time in the
|
||||||
|
Kiosk's Timezone setting, and the weekday, picks the active Block. Half-open
|
||||||
|
`[start, end)` boundaries; midnight-crossing Blocks belong to their start day
|
||||||
|
(weekly Sunday spills into Monday); invalid Blocks are skipped with a warning
|
||||||
|
and treated as gaps. Active Block → its Proclamations; `dynamicAudio` true
|
||||||
|
forces dynamic audio, false uses the locally chosen audio; gaps / no schedule
|
||||||
|
/ toggle off use local selection exactly as before.
|
||||||
|
- **Schedule toggle** in System Settings (`proclamation.schedule_enabled`,
|
||||||
|
default on, Kiosk-local, not synced). Off takes no action but the cache keeps
|
||||||
|
updating.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration notes
|
||||||
|
|
||||||
|
- **Portal migrations are additive `up` migrations** and are safe to deploy
|
||||||
|
while old Kiosks keep running:
|
||||||
|
- `20260929120000_add_license_key_system_name` — adds
|
||||||
|
`LicenseKey.systemName` / `systemNameLower` and a unique index on
|
||||||
|
`(userId, systemNameLower)`.
|
||||||
|
- `20260929130000_move_schedule_to_license_key` — adds `Schedule.licenseKeyId`
|
||||||
|
/ `type` and `Block.day`, changes the `dynamicAudio` default to `true`,
|
||||||
|
makes the legacy `Schedule.userId` optional/non-unique, and backfills each
|
||||||
|
existing per-user Schedule to that account's **oldest** License Key as a
|
||||||
|
**daily** schedule. Schedules that cannot be mapped (no owning user/license)
|
||||||
|
are retained with `licenseKeyId NULL` and reported via a migration `NOTICE`
|
||||||
|
— they are never silently dropped.
|
||||||
|
- **Existing (per-user) schedules survive the `up` migration** and are mapped to
|
||||||
|
a License Key on the same account.
|
||||||
|
- **⚠ The `down` path is lossy for data created under the new model.** Back up
|
||||||
|
`Schedule`, `Block`, and `BlockProclamations` before any down-migration.
|
||||||
|
Rolling back past the Phase 3 migration deletes per-License-Key schedules (see
|
||||||
|
the contract's Open item 22).
|
||||||
|
- No Portal schema change affects Kiosks (the Kiosk stores nothing new on the
|
||||||
|
server beyond the System Name label and its schedule mapping).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended rollout order
|
||||||
|
|
||||||
|
Each step is independent; do not skip the verification gates. Do **not** tag,
|
||||||
|
push, or deploy as part of reading this document — these are instructions.
|
||||||
|
|
||||||
|
### Step 1 — Run Portal migrations and deploy the Portal
|
||||||
|
|
||||||
|
1. Back up the Portal database (at minimum the `Schedule`, `Block`,
|
||||||
|
`BlockProclamations`, and `LicenseKey` tables).
|
||||||
|
2. Apply migrations: `npm run deploy` (which runs `npx prisma migrate deploy`).
|
||||||
|
3. Confirm `npx prisma migrate status` reports the database is up to date and
|
||||||
|
review the migration logs for the unmappable-schedule `NOTICE`, if any.
|
||||||
|
4. Deploy the Portal build (the existing CI/deploy path).
|
||||||
|
|
||||||
|
**Rollback (Step 1):** redeploy the previous Portal build. Because the `up`
|
||||||
|
migrations are additive, the old Portal continues to work against the migrated
|
||||||
|
schema; no schema rollback is required to redeploy the old Portal. If a schema
|
||||||
|
rollback is unavoidable, restore from the Step-1 backup (the `down` path is
|
||||||
|
lossy — see Migration notes).
|
||||||
|
|
||||||
|
### Step 2 — Verify old Kiosks still work
|
||||||
|
|
||||||
|
1. Against the deployed Portal, confirm an **old Kiosk** (no `systemName`, using
|
||||||
|
the legacy endpoints) still:
|
||||||
|
- validates via `POST /api/v2/license` (no `systemName`) and receives `200`;
|
||||||
|
- reads `GET /api/v2/schedule?email=…&licenseKey=…` and still finds its
|
||||||
|
`blocks`;
|
||||||
|
- is unaffected by the new fields, and its stored System Name (if any) is
|
||||||
|
never cleared.
|
||||||
|
2. Confirm the Portal dashboard/admin show System Names read-only.
|
||||||
|
|
||||||
|
**Rollback (Step 2):** none needed — this step is read-only verification. If old
|
||||||
|
Kiosks misbehave, roll back the Portal (Step 1 rollback) before proceeding.
|
||||||
|
|
||||||
|
### Step 3 — Release the Kiosk build
|
||||||
|
|
||||||
|
1. Build and validate the Kiosk through the normal path: tag → CI → the
|
||||||
|
auto-update channel. (Tagging triggers production CI; do it only when ready.)
|
||||||
|
2. Confirm the released build reports the expected version and updates in place.
|
||||||
|
3. Spot-check a device after update: 12-hour times, System Settings contents,
|
||||||
|
schedule on/off, and that an assigned Schedule is fetched and applied.
|
||||||
|
|
||||||
|
**Rollback (Step 3):** use the existing auto-update rollback (re-publish or
|
||||||
|
pin the previous known-good Kiosk version). The Kiosk tolerates both old and new
|
||||||
|
Portal payloads, so a Portal rollback alone does not require a Kiosk rollback,
|
||||||
|
and vice versa.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known limitations
|
||||||
|
|
||||||
|
- Kiosk API endpoints are **unauthenticated** (key-only), mitigated by a
|
||||||
|
10 requests/min/IP rate limit. Authentication is out of scope for this
|
||||||
|
overhaul.
|
||||||
|
- The Phase 3 migration's `down` path is lossy for new-model schedules
|
||||||
|
(see Migration notes and contract Open item 22).
|
||||||
|
- The Portal does not offer a rename path for System Names by design.
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<title>Verify your email address</title>
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- Preheader (hidden) -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0; color: transparent">
|
||||||
|
Confirm your email address to activate your Radiant Wave account.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="background-color: #f1f5f9; padding: 32px 12px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 32px 0 32px">
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4f46e5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Radiant Wave
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 0 32px">
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 700;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Confirm your email address
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 0 0 8px 0; color: #334155; font-size: 16px; line-height: 1.6">
|
||||||
|
Thanks for signing up for Radiant Wave. Verify your email address to activate your
|
||||||
|
account.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 10px; background-color: #4f46e5">
|
||||||
|
<a
|
||||||
|
href="{{{verification_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
padding: 14px 28px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Verify Email Address
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<p style="margin: 0; color: #64748b; font-size: 13px; line-height: 1.6">
|
||||||
|
If the button doesn't work, copy and paste this link into your browser:
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{{verification_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="color: #4f46e5; text-decoration: underline"
|
||||||
|
>{{{verification_link}}}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 32px 32px">
|
||||||
|
<hr style="border: none; border-top: 1px solid #e2e8f0; margin: 0 0 16px 0" />
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
This link expires in 1 hour. If you didn't create an account, you can safely
|
||||||
|
ignore this email.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="width: 100%; max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 8px; text-align: center">
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
Sent to {{{target_email_address}}}<br />
|
||||||
|
© Radiant Wave Technologies
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<title>Your Radiant Wave License Key</title>
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- Preheader (hidden) -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0; color: transparent">
|
||||||
|
Here's your Radiant Wave license key.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="background-color: #f1f5f9; padding: 32px 12px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 32px 0 32px">
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4f46e5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Radiant Wave
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 0 32px">
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 700;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Your license key
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 0 0 8px 0; color: #334155; font-size: 16px; line-height: 1.6">
|
||||||
|
Hi {{{firstName}}}, your Radiant Wave account is ready. Here's your license key —
|
||||||
|
you'll need it to activate the app.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 32px 0 32px">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="background-color: #f8fafc; border: 1px dashed #cbd5e1; border-radius: 12px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 18px 20px; text-align: center">
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Your License Key
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
word-break: break-all;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{{license_key}}}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 32px 0 32px">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 10px; background-color: #4f46e5">
|
||||||
|
<a
|
||||||
|
href="https://portal.radiantwavetech.com/dashboard"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
padding: 14px 28px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Go to Your Dashboard
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 32px 32px">
|
||||||
|
<hr style="border: none; border-top: 1px solid #e2e8f0; margin: 0 0 16px 0" />
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
Keep this key safe — anyone who has it can activate the app. If you didn't request
|
||||||
|
this, contact support.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="width: 100%; max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 8px; text-align: center">
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
© Radiant Wave Technologies
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<title>Reset your Radiant Wave password</title>
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- Preheader (hidden) -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0; color: transparent">
|
||||||
|
Choose a new password for your Radiant Wave account.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="background-color: #f1f5f9; padding: 32px 12px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 32px 0 32px">
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4f46e5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Radiant Wave
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 0 32px">
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 700;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Reset your password
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 0 0 8px 0; color: #334155; font-size: 16px; line-height: 1.6">
|
||||||
|
We received a request to reset your Radiant Wave password. Click the button below
|
||||||
|
to choose a new one.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 10px; background-color: #4f46e5">
|
||||||
|
<a
|
||||||
|
href="{{{reset_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
padding: 14px 28px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<p style="margin: 0; color: #64748b; font-size: 13px; line-height: 1.6">
|
||||||
|
If the button doesn't work, copy and paste this link into your browser:
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{{reset_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="color: #4f46e5; text-decoration: underline"
|
||||||
|
>{{{reset_link}}}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 32px 32px">
|
||||||
|
<hr style="border: none; border-top: 1px solid #e2e8f0; margin: 0 0 16px 0" />
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
This link expires in 1 hour. If you didn't request a password reset, you can
|
||||||
|
safely ignore this email — your password won't change.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="width: 100%; max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 8px; text-align: center">
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
Sent to {{{target_email_address}}}<br />
|
||||||
|
© Radiant Wave Technologies
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<title>Welcome to Radiant Wave</title>
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- Preheader (hidden) -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0; color: transparent">
|
||||||
|
Set your password to finish setting up your Radiant Wave account.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="background-color: #f1f5f9; padding: 32px 12px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 32px 0 32px">
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4f46e5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Radiant Wave
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 0 32px">
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.25;
|
||||||
|
font-weight: 700;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Welcome to Radiant Wave
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 0 0 8px 0; color: #334155; font-size: 16px; line-height: 1.6">
|
||||||
|
An account has been created for you. Set a password to finish setting up your
|
||||||
|
account and access your dashboard.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 10px; background-color: #4f46e5">
|
||||||
|
<a
|
||||||
|
href="{{{registration_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
display: inline-block;
|
||||||
|
padding: 14px 28px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Set Your Password
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 32px 0 32px">
|
||||||
|
<p style="margin: 0; color: #64748b; font-size: 13px; line-height: 1.6">
|
||||||
|
If the button doesn't work, copy and paste this link into your browser:
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{{registration_link}}}"
|
||||||
|
target="_blank"
|
||||||
|
style="color: #4f46e5; text-decoration: underline"
|
||||||
|
>{{{registration_link}}}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 0 32px">
|
||||||
|
<p style="margin: 0; color: #334155; font-size: 15px; line-height: 1.6">
|
||||||
|
After you set your password, sign in and complete your profile to get started.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px 32px 32px">
|
||||||
|
<hr style="border: none; border-top: 1px solid #e2e8f0; margin: 0 0 16px 0" />
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
This link expires in 1 hour. If you didn't expect this email, you can safely
|
||||||
|
ignore it.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="600"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="width: 100%; max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 8px; text-align: center">
|
||||||
|
<p style="margin: 0; color: #94a3b8; font-size: 12px; line-height: 1.6">
|
||||||
|
Sent to {{{target_email_address}}}<br />
|
||||||
|
© Radiant Wave Technologies
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -37,5 +37,11 @@ export default defineConfig(
|
|||||||
svelteConfig
|
svelteConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.spec.ts', '**/*.spec.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "portal.Rhemawavetech.com",
|
"name": "portal.radiantwavetech.com",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "portal.Rhemawavetech.com",
|
"name": "portal.radiantwavetech.com",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/adapter-pg": "^7.8.0",
|
"@prisma/adapter-pg": "^7.8.0",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "portal.Rhemawavetech.com",
|
"name": "portal.radiantwavetech.com",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,756 +0,0 @@
|
|||||||
# RadiantWave Portal API v2 Schema
|
|
||||||
|
|
||||||
This document defines the **required** API surface for the RadiantWave kiosk
|
|
||||||
application and serves as the implementation contract for the portal
|
|
||||||
(`../portal`).
|
|
||||||
|
|
||||||
It supersedes `payloads.md` and `ApiUpdates.md`, which described a v1 API that
|
|
||||||
does not meet the application's requirements.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Background and constraints
|
|
||||||
|
|
||||||
- The kiosk application (`this repo`) talks to
|
|
||||||
`https://portal.radiantwavetech.com/api/v1`.
|
|
||||||
- The **existing v1 routes must not change**: previously shipped kiosk binaries
|
|
||||||
depend on them. The v1 routes and their exact request/response shapes stay
|
|
||||||
frozen.
|
|
||||||
- To bring the API into compliance with this application's expectations we
|
|
||||||
introduce a **new, versioned API under `/api/v2`**, and migrate the kiosk
|
|
||||||
client to call `/api/v2` instead of `/api/v1`.
|
|
||||||
- The kiosk client currently uses a single base URL
|
|
||||||
(`src/network/api_client.h` → `API_BASE_URL`). All endpoint paths are
|
|
||||||
relative to that base, so migrating the base URL is the only client-side
|
|
||||||
change required.
|
|
||||||
|
|
||||||
Compliance means: every endpoint the kiosk parses must return exactly the
|
|
||||||
fields the kiosk expects, in the shapes documented below. Deviations break
|
|
||||||
provisioning, content sync, or rendering.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Versioning strategy
|
|
||||||
|
|
||||||
| Route space | Status | Consumer |
|
|
||||||
|-------------|---------------------------|-------------------------------------------------------|
|
|
||||||
| `/api/v1/*` | Frozen, never modified | Old/shipped kiosk binaries, GoHighLevel automation |
|
|
||||||
| `/api/v2/*` | New (this document) | Current kiosk application (after base-URL migration) |
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
|
|
||||||
1. Never change the behavior or response shape of an existing `/api/v1/*`
|
|
||||||
route. If a v1 fix is required, fix it in v2.
|
|
||||||
2. New capabilities ship under `/api/v2` only.
|
|
||||||
3. A consumer may pin to the major version it was built against. The kiosk
|
|
||||||
pins to `/api/v2`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## General conventions (v2)
|
|
||||||
|
|
||||||
- Base URL: `https://portal.radiantwavetech.com/api/v2`.
|
|
||||||
- All routes require HTTPS (TLS 1.2+; TLS 1.3 recommended).
|
|
||||||
- Success responses are JSON with `content-type: application/json`.
|
|
||||||
- Error bodies use the shape `{"message": "..."}` for route-handler errors and
|
|
||||||
`{"error": "..."}` for the authentication layer (see Security).
|
|
||||||
- Any response code outside the documented success code is a failure; the
|
|
||||||
kiosk surfaces `message`/`error` text when present.
|
|
||||||
- API keys are sent as `Authorization: Bearer sk_<hex>` and are **required on
|
|
||||||
every v2 route** except the signed onboarding route (below).
|
|
||||||
- Credentials (license keys, emails) must **never** be passed in the query
|
|
||||||
string. v1 accepted `?email=&key=`; v2 must not.
|
|
||||||
- Request body sizes are capped (see Security); oversized bodies are rejected
|
|
||||||
with `413`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Canonical data model: solfeggio frequencies
|
|
||||||
|
|
||||||
The kiosk uses a fixed 9-preset frequency scheme. Frequency ids must be
|
|
||||||
**canonical** — they appear on proclamations and color palettes and must match
|
|
||||||
the kiosk's own `frequencyIdForHz()` mapping:
|
|
||||||
|
|
||||||
| Canonical id | Hz | Frequency (kiosk label) |
|
|
||||||
|--------------|------|------------------------------|
|
|
||||||
| 1 | 174 | Grounding / Relief (285 Hz is an alias of id 1) |
|
|
||||||
| 2 | 396 | Root |
|
|
||||||
| 3 | 417 | Sacral |
|
|
||||||
| 4 | 432 | Clarity / Wellbeing |
|
|
||||||
| 5 | 528 | Solar Plexus (fallback palette) |
|
|
||||||
| 6 | 639 | Heart |
|
|
||||||
| 7 | 741 | Throat |
|
|
||||||
| 8 | 852 | 3rd Eye |
|
|
||||||
| 9 | 963 | Crown |
|
|
||||||
|
|
||||||
**Compliance gap in v1:** the portal's `SolfeggioFrequency` table stores 7
|
|
||||||
rows whose ids do **not** match this scheme (id 1 = 936 Hz, id 5 = 528 Hz,
|
|
||||||
id 7 = 396 Hz, etc.). Because proclamations and color palettes are keyed by
|
|
||||||
frequency id, returning portal ids would make the kiosk look up the wrong
|
|
||||||
palette (e.g. a 396 Hz proclamation would render with the 741 Hz palette).
|
|
||||||
|
|
||||||
**v2 requirement:** every `solfeggioFrequencyId` emitted by v2 must be a
|
|
||||||
canonical id (1–9) whose `value` resolves through the kiosk map above.
|
|
||||||
Frequencies that have no canonical id (e.g. 936 Hz) must not be referenced by
|
|
||||||
any v2 proclamation or palette. See Implementation notes for the reconciling
|
|
||||||
strategy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Endpoints
|
|
||||||
|
|
||||||
### 1. `GET /api/v2/frequencies`
|
|
||||||
|
|
||||||
Lists the canonical solfeggio frequencies.
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/frequencies
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (200):
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "id": 1, "name": "Grounding / Relief", "value": 174 },
|
|
||||||
{ "id": 2, "name": "Root", "value": 396 },
|
|
||||||
{ "id": 3, "name": "Sacral", "value": 417 },
|
|
||||||
{ "id": 4, "name": "Clarity / Wellbeing", "value": 432 },
|
|
||||||
{ "id": 5, "name": "Solar Plexus", "value": 528 },
|
|
||||||
{ "id": 6, "name": "Heart", "value": 639 },
|
|
||||||
{ "id": 7, "name": "Throat", "value": 741 },
|
|
||||||
{ "id": 8, "name": "3rd Eye", "value": 852 },
|
|
||||||
{ "id": 9, "name": "Crown", "value": 963 }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Contract (kiosk `parseFrequenciesJson`):
|
|
||||||
|
|
||||||
- `id`: int, 1–9, **non-zero** (entries with id 0 are skipped).
|
|
||||||
- `name`: string.
|
|
||||||
- `value`: int, Hz. Should be a value the kiosk's `frequencyIdForHz()` map
|
|
||||||
recognizes (174, 285, 396, 417, 432, 528, 639, 741, 852, 963).
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | array of frequency objects | Success |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. `GET /api/v2/categories`
|
|
||||||
|
|
||||||
Lists all proclamation categories (identical shape to v1).
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/categories
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (200):
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "id": 4, "name": "Spiritual", "sortOrder": 0, "parentId": null },
|
|
||||||
{ "id": 5, "name": "General", "sortOrder": 0, "parentId": 4 }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Contract (kiosk `parseCategoriesJson`):
|
|
||||||
|
|
||||||
- `id`: int, **non-zero** (id 0 entries are skipped).
|
|
||||||
- `name`: string.
|
|
||||||
- `parentId`: int for children, **`null` for root** categories (kiosk maps
|
|
||||||
null → 0).
|
|
||||||
- `sortOrder`: int.
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | array of category objects | Success |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. `GET /api/v2/proclamations`
|
|
||||||
|
|
||||||
Returns the proclamation library. This is the kiosk's primary content sync.
|
|
||||||
|
|
||||||
Request (default: full library):
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/proclamations
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional entitlement filter (full records, see below):
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/proclamations
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
X-License-Key: XXXX-XXXX-XXXX-XXXX
|
|
||||||
X-Email: user@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
> Header-based credentials replace the v1 `?email=&key=` query form. Do not
|
|
||||||
> read credentials from the URL.
|
|
||||||
|
|
||||||
Response (200) — every item is a **full proclamation record**:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "b40fdcb9-703c-420e-af32-a081964c6f08",
|
|
||||||
"name": "Peace & Relaxation",
|
|
||||||
"content": "<p>The text content, HTML formatted</p>",
|
|
||||||
"categoryId": 5,
|
|
||||||
"solfeggioFrequencyId": 5,
|
|
||||||
"subscriptionLevelId": 1,
|
|
||||||
"colorPalette": ["#32CD32", "#3CB371", "#90EE90", "#98FB98", "#ADFFA9", "#C8FFD2", "#F0FFF0"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Contract (kiosk `parseProclamationsJson` + `parseProclamationPalettesJson`):
|
|
||||||
|
|
||||||
- `id`: string UUID, **required non-empty** (empty ids are skipped).
|
|
||||||
- `name`: string, used as the display title. Kiosk strips HTML from it.
|
|
||||||
- `content`: string, **HTML** (the kiosk strips tags and unescapes entities
|
|
||||||
before rendering). Plain text is also acceptable but HTML is the convention.
|
|
||||||
- `categoryId`: int, maps to a `/categories` id.
|
|
||||||
- `solfeggioFrequencyId`: int, **must be a canonical id (1–9)** — see the
|
|
||||||
canonical model above.
|
|
||||||
- `subscriptionLevelId`: int or `null`. The kiosk treats missing/null/≤0 as
|
|
||||||
level 1 (Basic).
|
|
||||||
- `colorPalette` (optional): array of exactly 7 `"#RRGGBB"` strings, indexed
|
|
||||||
color 0 (primary) through color 6 (tertiary). When present, the kiosk can
|
|
||||||
color a proclamation directly from this payload (first proclamation per
|
|
||||||
frequency wins for palette derivation). When absent, the kiosk falls back to
|
|
||||||
`/color-palettes`, then to its own 528 Hz seed.
|
|
||||||
|
|
||||||
With entitlement headers (`X-License-Key` + `X-Email`):
|
|
||||||
|
|
||||||
- Validate the key exists, is active, and belongs to the email's user.
|
|
||||||
- Return the **full proclamation records** the user may access (matching the
|
|
||||||
record shape above), **not** raw `UserProclamations` join-table rows.
|
|
||||||
(v1 returned join rows, which the kiosk parser cannot consume — this is the
|
|
||||||
primary v1 compliance defect this endpoint fixes.)
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | array of proclamation records | Success |
|
|
||||||
| 400 | `{"message": "not-authorized"}` | Entitlement headers supplied but key or email does not exist |
|
|
||||||
| 400 | `{"message": "this key doesn't appear to belong to you"}` | Key exists but owned by a different user |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. `POST /api/v2/license`
|
|
||||||
|
|
||||||
Validates a license key / email pair and returns the full subscription level.
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/v2/license
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"license_key": "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
"email_address": "user@example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (200):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"key": "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
"email_address": "user@example.com",
|
|
||||||
"subscription_level": {
|
|
||||||
"id": 2,
|
|
||||||
"name": "Advanced",
|
|
||||||
"description": "General + Specific"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`subscription_level` is **always an object** `{ id, name, description }`. If
|
|
||||||
the license's level name does not match any `SubscriptionLevel` row, fall back
|
|
||||||
to a structurally consistent object:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"key": "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
"email_address": "user@example.com",
|
|
||||||
"subscription_level": { "id": null, "name": "SomeCustomLevel", "description": null }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Contract (kiosk `parseLicenseJson`):
|
|
||||||
|
|
||||||
- `key`: string, **required non-empty**.
|
|
||||||
- `email_address`: string.
|
|
||||||
- `subscription_level.name`: string, **required** for a successful validation
|
|
||||||
(the kiosk persists id/name/description into its local `system_kv` table and
|
|
||||||
uses the level for proclamation gating and the Home footer).
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | success object | License valid and owned by the email |
|
|
||||||
| 400 | `{"message": "license_key is required"}` | Missing `license_key` |
|
|
||||||
| 400 | `{"message": "email_address is required"}` | Missing `email_address` |
|
|
||||||
| 400 | `{"message": "license key not found"}` | No key with that value |
|
|
||||||
| 400 | `{"message": "license key is not active"}` | Key exists but inactive |
|
|
||||||
| 400 | `{"message": "user does not exist"}` | No user with that email |
|
|
||||||
| 400 | `{"message": "key does not belong to specified user"}` | Key owned by a different user |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
> The kiosk client also tolerates the v1 string form and flat
|
|
||||||
> `subscription_level_id` / `subscription_level_description` fields. v2 should
|
|
||||||
> always send the object form; the fallbacks exist only for transitional
|
|
||||||
> compatibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. `GET /api/v2/subscription-levels`
|
|
||||||
|
|
||||||
Lists the subscription levels.
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/subscription-levels
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (200):
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "id": 1, "name": "Basic", "description": "General only" },
|
|
||||||
{ "id": 2, "name": "Advanced", "description": "General + Specific" },
|
|
||||||
{ "id": 3, "name": "Elite", "description": "General + Specific + Precise" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | array of `{id, name, description}` | Success |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. `GET /api/v2/color-palettes`
|
|
||||||
|
|
||||||
Exposes the 7-color palette for every canonical frequency so the portal admin
|
|
||||||
can update colors without a kiosk code release.
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v2/color-palettes
|
|
||||||
Authorization: Bearer sk_<hex>
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (200) — one entry per canonical frequency (1–9):
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"solfeggioFrequencyId": 1,
|
|
||||||
"colors": ["#8B6914", "#B8860B", "#DAA520", "#E8B64C", "#FFD700", "#FFE9A8", "#FFF4CC"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"solfeggioFrequencyId": 5,
|
|
||||||
"colors": ["#32CD32", "#3CB371", "#90EE90", "#98FB98", "#ADFFA9", "#C8FFD2", "#F0FFF0"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Contract (kiosk `parseColorPalettesJson`):
|
|
||||||
|
|
||||||
- `solfeggioFrequencyId`: int, **non-zero**, canonical id.
|
|
||||||
- `colors`: array of `"#RRGGBB"` (or `"RRGGBB"`) strings, exactly 7.
|
|
||||||
|
|
||||||
Kiosk fallback: `db::getPaletteForFrequencyId()` falls back to the 528 Hz
|
|
||||||
palette for any frequency id with no rows; the startup seed backfills missing
|
|
||||||
ids with `INSERT OR IGNORE`, so portal values win after a successful sync.
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | array of `{solfeggioFrequencyId, colors}` | Success |
|
|
||||||
| 401 | `{"error": "..."}` | Missing/invalid API key |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. `POST /api/v2/automations/create/account`
|
|
||||||
|
|
||||||
Creates (or reuses) a user account and issues a brand-new active license key.
|
|
||||||
The key is **not returned** — it is emailed to the user. This is the only v2
|
|
||||||
route that does not require a bearer API key; instead it requires an
|
|
||||||
**HMAC-SHA256 signature** (see Security). It is used for first-run provisioning
|
|
||||||
and by the GoHighLevel automation.
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/v2/automations/create/account
|
|
||||||
Content-Type: application/json
|
|
||||||
X-Timestamp: <unix seconds>
|
|
||||||
X-Nonce: <random uuid>
|
|
||||||
X-Signature: <hex HMAC-SHA256 of canonical request with the onboarding secret>
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"name": "Optional Display Name",
|
|
||||||
"subscription_level": "Basic"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `email` (required, string).
|
|
||||||
- `name` (optional, string; defaults to the local part of `email`).
|
|
||||||
- `subscription_level` (required, string) — one of `Basic`, `Advanced`,
|
|
||||||
`Elite` (must match a `SubscriptionLevel.name`).
|
|
||||||
|
|
||||||
Response (200):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Account and license key created",
|
|
||||||
"user_id": "uuid"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
|
|
||||||
| Code | Body | Meaning |
|
|
||||||
|------|------|---------|
|
|
||||||
| 200 | success object | Account (+ license key) created |
|
|
||||||
| 400 | `{"message": "email is required"}` | Missing/empty `email` |
|
|
||||||
| 400 | `{"message": "subscription_level is required"}` | Missing/empty `subscription_level` |
|
|
||||||
| 400 | `{"message": "subscription_level \"<name>\" not found"}` | Unknown level name |
|
|
||||||
| 401 | `{"error": "invalid signature"}` | Bad/expired/replayed HMAC signature |
|
|
||||||
| 429 | `{"message": "rate limit exceeded"}` | Too many requests from this IP/email |
|
|
||||||
| 500 | `{"message": "Failed to create account"}` | User creation failed |
|
|
||||||
| 500 | `{"message": "Failed to find or create user"}` | User could not be located |
|
|
||||||
|
|
||||||
Behavior (unchanged from v1):
|
|
||||||
|
|
||||||
- If the email already belongs to a user, no new user is created — only a new
|
|
||||||
license key for that user.
|
|
||||||
- A new user receives a temporary-password/verify email and a license-key
|
|
||||||
email. Sending up to two emails, so this endpoint must be rate-limited
|
|
||||||
aggressively.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Compliance contract (kiosk → v2)
|
|
||||||
|
|
||||||
| Kiosk consumer | v2 endpoint | Required fields / shape |
|
|
||||||
|----------------|-------------|-------------------------|
|
|
||||||
| `sync::syncProclamationsFromApi` (Home + ProclamationOptions sync) | `GET /proclamations`, `GET /categories` | Full proclamation records; `id`, `name`, `content`, `categoryId`, `solfeggioFrequencyId` (canonical), `subscriptionLevelId` |
|
|
||||||
| `license::validateStoredLicense` (License Key provisioning page) | `POST /license` | `subscription_level` as `{id, name, description}` object |
|
|
||||||
| `api::fetchColorPalettes` / `sync::syncColorPalettesFromApi` (readiness, to be wired) | `GET /color-palettes` | `[{solfeggioFrequencyId, colors[7]}]` |
|
|
||||||
| `sync::syncProclamationPalettesFromApi` (readiness, to be wired) | `GET /proclamations` | Embedded `colorPalette` array per proclamation |
|
|
||||||
| `api::fetchFrequencies` (readiness) | `GET /frequencies` | Canonical id 1–9 + `value` in Hz |
|
|
||||||
| Future level-picker UI | `GET /subscription-levels` | `{id, name, description}` |
|
|
||||||
| Provisioning / GHL automation | `POST /automations/create/account` | `email`, `subscription_level`, optional `name` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation instructions (portal, `../portal`)
|
|
||||||
|
|
||||||
### Route layout
|
|
||||||
|
|
||||||
Create the v2 route tree alongside the frozen v1 tree:
|
|
||||||
|
|
||||||
```
|
|
||||||
src/routes/api/v2/
|
|
||||||
frequencies/+server.ts
|
|
||||||
categories/+server.ts
|
|
||||||
proclamations/+server.ts
|
|
||||||
license/+server.ts
|
|
||||||
subscription-levels/+server.ts
|
|
||||||
color-palettes/+server.ts
|
|
||||||
automations/create/account/+server.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auth gate (`src/hooks.server.ts`)
|
|
||||||
|
|
||||||
Extend the existing handler so v2 auth is **always enforced** (do not honor
|
|
||||||
`API_KEYS_ACTIVE` for v2):
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const isV2 = event.url.pathname.startsWith('/api/v2/');
|
|
||||||
const isSignedOnboarding =
|
|
||||||
event.url.pathname.startsWith('/api/v2/automations/');
|
|
||||||
|
|
||||||
if (isV2 && !isSignedOnboarding) {
|
|
||||||
// validateApiKey() ALWAYS runs for v2 — no env-var bypass.
|
|
||||||
const authHeader = event.request.headers.get('authorization');
|
|
||||||
if (!authHeader?.startsWith('Bearer ')) {
|
|
||||||
return new Response(JSON.stringify({ error: 'API key required' }), {
|
|
||||||
status: 401,
|
|
||||||
headers: { 'content-type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const record = await validateApiKey(authHeader.slice(7));
|
|
||||||
if (!record) {
|
|
||||||
return new Response(JSON.stringify({ error: 'Invalid or expired API key' }), {
|
|
||||||
status: 401,
|
|
||||||
headers: { 'content-type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resolve(event);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep the existing v1 branch untouched.
|
|
||||||
|
|
||||||
### Shared serializer (`src/lib/server/v2-serializers.ts`)
|
|
||||||
|
|
||||||
Add one module that maps DB rows to the v2 wire format. The critical piece is
|
|
||||||
frequency-id reconciliation:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Canonical id <- Hz (kiosk frequencyIdForHz map).
|
|
||||||
const CANONICAL_ID_FOR_HZ: Record<number, number> = {
|
|
||||||
174: 1, 285: 1, 396: 2, 417: 3, 432: 4,
|
|
||||||
528: 5, 639: 6, 741: 7, 852: 8, 963: 9
|
|
||||||
};
|
|
||||||
|
|
||||||
function canonicalIdForFrequency(freq: { id: number; value: number; name: string }): number | null {
|
|
||||||
const id = CANONICAL_ID_FOR_HZ[freq.value];
|
|
||||||
return id ?? null; // e.g. 936 Hz has no canonical id -> excluded from v2
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeProclamation(p: Proclamation & { solfeggioFrequency: { value: number } }) {
|
|
||||||
const solfeggioFrequencyId = CANONICAL_ID_FOR_HZ[p.solfeggioFrequency.value];
|
|
||||||
if (!solfeggioFrequencyId) return null; // drop proclamations with no canonical frequency
|
|
||||||
return {
|
|
||||||
id: p.id,
|
|
||||||
name: p.name,
|
|
||||||
content: p.content,
|
|
||||||
categoryId: p.categoryId,
|
|
||||||
solfeggioFrequencyId,
|
|
||||||
subscriptionLevelId: p.subscriptionLevelId,
|
|
||||||
// optional; read from the palette source below
|
|
||||||
colorPalette: paletteFor(p.solfeggioFrequency.value) // 7x "#RRGGBB" or omit
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
|
|
||||||
- Derive the emitted `solfeggioFrequencyId` from the joined frequency's
|
|
||||||
**Hz value**, never from the DB id. This keeps v1 rows (and their ids)
|
|
||||||
untouched while v2 emits canonical ids.
|
|
||||||
- Proclamations whose frequency has no canonical id (e.g. 936 Hz) are excluded
|
|
||||||
from v2 responses (or re-assigned a canonical frequency in data).
|
|
||||||
- Add the missing frequencies to the DB so content can be authored for them:
|
|
||||||
`174, 285, 432, 963` (by `value`). Do not delete or renumber the existing
|
|
||||||
rows that v1 depends on.
|
|
||||||
|
|
||||||
### Color palettes
|
|
||||||
|
|
||||||
Add a portal-editable palette source keyed by canonical frequency, for example
|
|
||||||
a `colorPalette` JSON column on `SolfeggioFrequency` or a
|
|
||||||
`ColorPalette`/`ColorPaletteColor` table:
|
|
||||||
|
|
||||||
```prisma
|
|
||||||
model ColorPalette {
|
|
||||||
solfeggioFrequencyId Int @id
|
|
||||||
colors Json // ["#RRGGBB", ... x7]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Seed all 9 canonical palettes to match the kiosk's current seed
|
|
||||||
(`src/db/database_manager.cpp` → `seedColorPalettes()`).
|
|
||||||
- `GET /color-palettes` returns one entry per canonical id.
|
|
||||||
- `GET /proclamations` embeds the palette for the proclamation's canonical
|
|
||||||
frequency.
|
|
||||||
|
|
||||||
### License endpoint
|
|
||||||
|
|
||||||
In `license/+server.ts`, resolve the level object like v1 change #1 suggested:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const level = await prisma.subscriptionLevel.findFirst({
|
|
||||||
where: { name: storedKey.level }
|
|
||||||
});
|
|
||||||
|
|
||||||
subscription_level = level
|
|
||||||
? { id: level.id, name: level.name, description: level.description }
|
|
||||||
: { id: null, name: storedKey.level, description: null };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Proclamations entitlement filter
|
|
||||||
|
|
||||||
When `X-License-Key` / `X-Email` headers are present, validate them exactly as
|
|
||||||
v1 does, but return **full proclamation records** (the user's entitled set via
|
|
||||||
`UserProclamations` + `Proclamation` include), never join-table rows.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- Unit-test the serializers with fixtures matching the kiosk's offline tests
|
|
||||||
in `tests/test_api_client.cpp` (string vs object `subscription_level`,
|
|
||||||
`subscriptionLevelId: null`, `parentId: null`, 7-color palettes, canonical
|
|
||||||
frequency mapping incl. 936 → dropped).
|
|
||||||
- Live smoke tests against `/api/v2` mirroring the existing v1 live tests.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security: restricting v2 to the kiosk application
|
|
||||||
|
|
||||||
These endpoints should be reachable only from enrolled kiosk devices. v1's
|
|
||||||
auth could be disabled with `API_KEYS_ACTIVE=false`; v2 must not offer that
|
|
||||||
bypass. Suggestions below are ordered by impact.
|
|
||||||
|
|
||||||
### 1. Mandatory bearer-key auth on every content route
|
|
||||||
|
|
||||||
- Every v2 route except the signed onboarding route requires
|
|
||||||
`Authorization: Bearer sk_<hex>`.
|
|
||||||
- Enforce it in `hooks.server.ts` unconditionally (no env toggle).
|
|
||||||
- Keys are generated with `generateApiKey()`, stored **SHA-256 hashed**, and
|
|
||||||
only the `sk_xxxxxxxxxx` prefix is ever displayed. Never log or return the
|
|
||||||
plaintext key.
|
|
||||||
- Use constant-time comparison / lookup by hash (already the case via
|
|
||||||
`validateApiKey`).
|
|
||||||
|
|
||||||
### 2. Per-device credentials (enroll each kiosk)
|
|
||||||
|
|
||||||
One shared API key is a single point of failure. Instead:
|
|
||||||
|
|
||||||
- Add a `Device` record (id, serial, model, owner, enrolledAt, apiKeyId) and
|
|
||||||
issue **one unique key per kiosk** at first boot (or at manufacturing).
|
|
||||||
- Bind the key to the device: include the device id in a custom header
|
|
||||||
(`X-Device-Id`) and verify the key belongs to that device.
|
|
||||||
- A compromised key is then revoked per-device without a global rotation, and
|
|
||||||
the portal can tell which device made each request.
|
|
||||||
|
|
||||||
### 3. Least privilege / scoped keys
|
|
||||||
|
|
||||||
- Kiosk keys are read-only for content (`/frequencies`, `/categories`,
|
|
||||||
`/proclamations`, `/color-palettes`, `/subscription-levels`) and may POST to
|
|
||||||
`/license`.
|
|
||||||
- Reject any other verb/route combination with 403. Admin and data-mutation
|
|
||||||
keys are issued separately to portal staff only.
|
|
||||||
|
|
||||||
### 4. Rate limiting per key and per IP
|
|
||||||
|
|
||||||
- Content GETs: ~60/min per key/IP.
|
|
||||||
- `/license`: ~10/min per key/IP.
|
|
||||||
- `/automations/create/account`: ~5/min per IP and ~3/day per email.
|
|
||||||
- Return `429` with `Retry-After`; the kiosk client should back off and retry.
|
|
||||||
- Slippery handling for repeated failures (rate-limit on failures too).
|
|
||||||
|
|
||||||
### 5. Replay protection / request signing
|
|
||||||
|
|
||||||
- The bearer key alone is replayable if captured. Add to the high-risk routes
|
|
||||||
(and the onboarding route) an HMAC-SHA256 signature over the canonical
|
|
||||||
request (method, path, `X-Timestamp`, `X-Nonce`, raw body) using a secret
|
|
||||||
known only to the device and the server.
|
|
||||||
- Reject when `|now − X-Timestamp| > 300s` or when a nonce has been seen
|
|
||||||
before. This defeats capture-and-replay attacks over the network.
|
|
||||||
- Implement the same scheme for the onboarding route using a shared secret
|
|
||||||
configured on the portal and the GoHighLevel automation (replacing the
|
|
||||||
current entirely unauthenticated `/automations/*` exemption).
|
|
||||||
|
|
||||||
### 6. Never put credentials in URLs
|
|
||||||
|
|
||||||
- v1 accepted license keys and emails in the query string, which land in
|
|
||||||
access logs, proxies, and referrers. v2 reads credentials from the
|
|
||||||
`Authorization` header and JSON body only.
|
|
||||||
- Redact `Authorization` values, license keys, and emails in application and
|
|
||||||
proxy logs.
|
|
||||||
|
|
||||||
### 7. Client fingerprinting and bot rejection
|
|
||||||
|
|
||||||
- The kiosk sends `User-Agent: RadiantWave-Kiosk/0.1`. Require it (optionally
|
|
||||||
with a signed `X-Client-Id`) and return `403` for generic browsers, `curl`,
|
|
||||||
and headless scanners.
|
|
||||||
- Reject request bodies that are not `application/json` where JSON is
|
|
||||||
expected; enforce request-size limits (reject with `413`).
|
|
||||||
- At the proxy layer: require TLS 1.2+, prefer 1.3, set HSTS; consider
|
|
||||||
restricting v2 to expected deployment geographies and blocking TOR/exit
|
|
||||||
nodes and known hosting/bot ASNs.
|
|
||||||
|
|
||||||
### 8. Input validation and uniform errors
|
|
||||||
|
|
||||||
- Validate each body against a strict schema: email ≤ 254 chars and a valid
|
|
||||||
shape, `license_key` must match `^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$`,
|
|
||||||
`subscription_level` must equal a known level name, `name` ≤ 120 chars.
|
|
||||||
- Reject embedded HTML/script in string fields.
|
|
||||||
- Return uniform `{"message": ...}` bodies; never leak stack traces or SQL
|
|
||||||
errors. Avoid verbose error differences that enable credential enumeration
|
|
||||||
(see below).
|
|
||||||
|
|
||||||
### 9. License / onboarding abuse resistance
|
|
||||||
|
|
||||||
- `/license` success/failure is enumerable by design (a fixed key format). At
|
|
||||||
minimum rate-limit aggressively. Consider returning a generic
|
|
||||||
`{"message": "invalid credentials"}` (possibly plus a structured `code`
|
|
||||||
field for the kiosk) so attackers cannot distinguish "user exists" from
|
|
||||||
"key exists". Keep the specific messages only behind authenticated admin
|
|
||||||
tooling if they are needed for support.
|
|
||||||
- `/automations/create/account` mints real license keys and sends email, so
|
|
||||||
protect it hardest: HMAC signature (above), per-IP and per-email rate
|
|
||||||
limits, an email-domain/typo check, and a honeypot field. Never expose it
|
|
||||||
without signature as an unauthenticated "create account" funnel.
|
|
||||||
|
|
||||||
### 10. Monitoring, logging, alerting
|
|
||||||
|
|
||||||
- Log every v2 request as structured data: timestamp, endpoint, method, status,
|
|
||||||
key prefix, `X-Device-Id`, IP, latency. Never log full keys, emails, or
|
|
||||||
bodies.
|
|
||||||
- Alert on: error-rate spikes, repeated 401s, unexpected `/license` volume,
|
|
||||||
signup bursts, single-device request floods, and revoked-key usage.
|
|
||||||
- Automatically suspend a device that shows abusive patterns; require
|
|
||||||
re-enrollment.
|
|
||||||
|
|
||||||
### 11. Key lifecycle
|
|
||||||
|
|
||||||
- Rotate keys on a schedule; support the existing `rotateApiKey` 48-hour grace
|
|
||||||
and emergency revocation per device.
|
|
||||||
- Auto-expire keys that have not authenticated in N days.
|
|
||||||
- Consider an authenticated `/api/v2/auth/refresh` (or device re-enrollment)
|
|
||||||
so a kiosk can obtain a fresh key, turning a leaked key into a self-healing
|
|
||||||
problem.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollout checklist
|
|
||||||
|
|
||||||
1. Implement the v2 routes and serializers; add unit + live smoke tests.
|
|
||||||
2. Ship v2 alongside v1 (both live; v1 frozen).
|
|
||||||
3. Flip the kiosk base URL: `src/network/api_client.h` →
|
|
||||||
`https://portal.radiantwavetech.com/api/v2` (already done by this change).
|
|
||||||
4. Run the kiosk live tests with `RW_LIVE_API_KEY` set:
|
|
||||||
- `./build/test_api_client`
|
|
||||||
- `./build/test_license_service`
|
|
||||||
5. Update portal-admin color palettes and verify a fresh kiosk sync picks them
|
|
||||||
up (now wired via `/color-palettes`).
|
|
||||||
6. Keep `/api/v1` online until all deployed kiosks are on the v2 build;
|
|
||||||
monitor v1 traffic and sunset it only after the fleet is migrated.
|
|
||||||
@@ -5,6 +5,15 @@ import { defineConfig } from 'prisma/config';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: 'prisma/schema.prisma',
|
schema: 'prisma/schema.prisma',
|
||||||
|
// Legacy subscription tables are retained in the database but are no longer
|
||||||
|
// managed by Prisma Migrate. They must never be dropped (production may not
|
||||||
|
// own them and the data is kept for recovery).
|
||||||
|
experimental: {
|
||||||
|
externalTables: true
|
||||||
|
},
|
||||||
|
tables: {
|
||||||
|
external: ['public.SubscriptionLevel', 'public.ProclamationCategorySubscriptionLevel']
|
||||||
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
path: 'prisma/migrations',
|
path: 'prisma/migrations',
|
||||||
seed: 'tsx prisma/seed.ts'
|
seed: 'tsx prisma/seed.ts'
|
||||||
|
|||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
-- Flatten ProclamationCategory hierarchy into a flat list and introduce
|
||||||
|
-- ProclamationGroup (formerly child sub-categories).
|
||||||
|
--
|
||||||
|
-- This migration is intentionally NON-DESTRUCTIVE:
|
||||||
|
-- * ProclamationGroup / Proclamation.groupId are added additively.
|
||||||
|
-- * Former child categories are ARCHIVED to ProclamationCategoryLegacy
|
||||||
|
-- before the hierarchy (parentId) is removed, so no category data is lost.
|
||||||
|
-- * Subscription-level resources (SubscriptionLevel,
|
||||||
|
-- ProclamationCategorySubscriptionLevel, Proclamation.subscriptionLevelId,
|
||||||
|
-- LicenseKey.level) are LEFT IN PLACE as unmanaged legacy data. They are no
|
||||||
|
-- longer modeled by the Prisma schema and are ignored by the application,
|
||||||
|
-- but their data is preserved and no DROP is attempted on tables the deploy
|
||||||
|
-- role may not own.
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProclamationGroup" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
CONSTRAINT "ProclamationGroup_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ProclamationGroup_name_key" ON "ProclamationGroup"("name");
|
||||||
|
|
||||||
|
-- Seed groups from every distinct former sub-category name
|
||||||
|
INSERT INTO "ProclamationGroup" ("name", "sortOrder")
|
||||||
|
SELECT "name",
|
||||||
|
ROW_NUMBER() OVER (ORDER BY "name") - 1
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT "name" FROM "ProclamationCategory" WHERE "parentId" IS NOT NULL
|
||||||
|
) AS d;
|
||||||
|
|
||||||
|
-- AddColumn (nullable during backfill)
|
||||||
|
ALTER TABLE "Proclamation" ADD COLUMN "groupId" INTEGER;
|
||||||
|
|
||||||
|
-- Backfill: former child categories become the proclamation's group; the
|
||||||
|
-- parent becomes its flat category.
|
||||||
|
UPDATE "Proclamation" AS p
|
||||||
|
SET "groupId" = g."id",
|
||||||
|
"categoryId" = c."parentId"
|
||||||
|
FROM "ProclamationCategory" AS c
|
||||||
|
JOIN "ProclamationGroup" AS g ON g."name" = c."name"
|
||||||
|
WHERE p."categoryId" = c."id" AND c."parentId" IS NOT NULL;
|
||||||
|
|
||||||
|
-- Backfill: proclamations that had no group (e.g. 'Custom (Client Specific)')
|
||||||
|
-- default to the General group, if it exists.
|
||||||
|
UPDATE "Proclamation"
|
||||||
|
SET "groupId" = (SELECT "id" FROM "ProclamationGroup" WHERE "name" = 'General')
|
||||||
|
WHERE "groupId" IS NULL;
|
||||||
|
|
||||||
|
-- Safety net: any proclamation still unmapped takes the first group.
|
||||||
|
UPDATE "Proclamation"
|
||||||
|
SET "groupId" = (SELECT "id" FROM "ProclamationGroup" ORDER BY "sortOrder" LIMIT 1)
|
||||||
|
WHERE "groupId" IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE "Proclamation" ALTER COLUMN "groupId" SET NOT NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Proclamation" ADD CONSTRAINT "Proclamation_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "ProclamationGroup"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- Archive former child categories before removing them, so their data is
|
||||||
|
-- preserved and recoverable. The legacy table also records each child's
|
||||||
|
-- parent for audit/recovery.
|
||||||
|
CREATE TABLE "ProclamationCategoryLegacy" (
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"parentId" INTEGER,
|
||||||
|
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
CONSTRAINT "ProclamationCategoryLegacy_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "ProclamationCategoryLegacy" ("id", "name", "parentId", "sortOrder")
|
||||||
|
SELECT "id", "name", "parentId", "sortOrder"
|
||||||
|
FROM "ProclamationCategory"
|
||||||
|
WHERE "parentId" IS NOT NULL;
|
||||||
|
|
||||||
|
-- Archive category->subscription mappings for the child categories being
|
||||||
|
-- removed, so the cascade delete below does not destroy this (deprecated)
|
||||||
|
-- mapping data. Requires read access to the join table, but never ownership.
|
||||||
|
CREATE TABLE "ProclamationCategorySubscriptionLevelLegacy" (
|
||||||
|
"categoryId" INTEGER NOT NULL,
|
||||||
|
"subscriptionLevelId" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "ProclamationCategorySubscriptionLevelLegacy_pkey" PRIMARY KEY ("categoryId", "subscriptionLevelId")
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "ProclamationCategorySubscriptionLevelLegacy" ("categoryId", "subscriptionLevelId")
|
||||||
|
SELECT j."categoryId", j."subscriptionLevelId"
|
||||||
|
FROM "ProclamationCategorySubscriptionLevel" AS j
|
||||||
|
JOIN "ProclamationCategory" AS c ON c."id" = j."categoryId" AND c."parentId" IS NOT NULL;
|
||||||
|
|
||||||
|
-- Remove former child categories (their data is archived above; the
|
||||||
|
-- subscription-level join rows for them are archived above and the
|
||||||
|
-- SubscriptionLevel table itself is left untouched).
|
||||||
|
DELETE FROM "ProclamationCategory" WHERE "parentId" IS NOT NULL;
|
||||||
|
|
||||||
|
-- Drop self-relation
|
||||||
|
ALTER TABLE "ProclamationCategory" DROP CONSTRAINT "ProclamationCategory_parentId_fkey";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ProclamationCategory_parentId_name_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ProclamationCategory" DROP COLUMN "parentId";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ProclamationCategory_name_key" ON "ProclamationCategory"("name");
|
||||||
|
|
||||||
|
-- Subscription-level resources are intentionally left in place (unmanaged).
|
||||||
|
-- No DROP TABLE / DROP COLUMN / DROP CONSTRAINT is performed here, so this
|
||||||
|
-- migration never requires ownership of pre-existing subscription tables and
|
||||||
|
-- never destroys subscription data.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "LicenseKey" DROP CONSTRAINT "LicenseKey_userId_fkey";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Profile_userId_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LicenseKey" ADD COLUMN "email" TEXT,
|
||||||
|
ALTER COLUMN "userId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Profile" ADD COLUMN "email" TEXT,
|
||||||
|
ALTER COLUMN "userId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LicenseKey_userId_idx" ON "LicenseKey"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LicenseKey_email_idx" ON "LicenseKey"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Profile_userId_idx" ON "Profile"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Profile_email_idx" ON "Profile"("email");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LicenseKey" ADD CONSTRAINT "LicenseKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "user" ALTER COLUMN "roleId" SET DEFAULT 4;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- Retain the legacy subscription artifacts as externally managed data.
|
||||||
|
--
|
||||||
|
-- SubscriptionLevel and ProclamationCategorySubscriptionLevel are no longer
|
||||||
|
-- modeled by the application. They are declared as external tables in
|
||||||
|
-- prisma.config.ts so Prisma Migrate ignores them entirely and never drops
|
||||||
|
-- them (production may not own these tables, and their data is kept for
|
||||||
|
-- recovery).
|
||||||
|
--
|
||||||
|
-- This migration only touches Prisma-managed tables to decouple them from the
|
||||||
|
-- legacy FK. It is intentionally idempotent so it applies cleanly to
|
||||||
|
-- production (columns present, NOT NULL, FK present), a fresh database built
|
||||||
|
-- from migration history, and development databases that already removed the
|
||||||
|
-- legacy objects.
|
||||||
|
|
||||||
|
-- Ensure the legacy columns exist. On databases built from migration history
|
||||||
|
-- these already exist; this re-adds them on environments that dropped them.
|
||||||
|
ALTER TABLE "Proclamation" ADD COLUMN IF NOT EXISTS "subscriptionLevelId" INTEGER;
|
||||||
|
ALTER TABLE "LicenseKey" ADD COLUMN IF NOT EXISTS "level" TEXT;
|
||||||
|
|
||||||
|
-- Decouple from SubscriptionLevel: drop the FK and relax NOT NULL so the
|
||||||
|
-- application can keep inserting rows without a legacy subscription value.
|
||||||
|
ALTER TABLE "Proclamation" DROP CONSTRAINT IF EXISTS "Proclamation_subscriptionLevelId_fkey";
|
||||||
|
ALTER TABLE "Proclamation" ALTER COLUMN "subscriptionLevelId" DROP NOT NULL;
|
||||||
|
ALTER TABLE "LicenseKey" ALTER COLUMN "level" DROP NOT NULL;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LicenseKey" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LicenseKey" ADD COLUMN "lastValidatedAt" TIMESTAMP(3);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AuditLog" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"userId" TEXT,
|
||||||
|
"email" TEXT,
|
||||||
|
"roleId" INTEGER,
|
||||||
|
"roleName" TEXT,
|
||||||
|
"page" TEXT NOT NULL,
|
||||||
|
"action" TEXT NOT NULL,
|
||||||
|
"entity" TEXT,
|
||||||
|
"entityId" TEXT,
|
||||||
|
"before" JSONB,
|
||||||
|
"after" JSONB,
|
||||||
|
"ip" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AuditLog_roleId_idx" ON "AuditLog"("roleId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AuditLog_page_idx" ON "AuditLog"("page");
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Schedule" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Schedule_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Block" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scheduleId" TEXT NOT NULL,
|
||||||
|
"startMinutes" INTEGER NOT NULL,
|
||||||
|
"endMinutes" INTEGER NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Block_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "BlockProclamations" (
|
||||||
|
"blockId" TEXT NOT NULL,
|
||||||
|
"proclamationId" TEXT NOT NULL,
|
||||||
|
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "BlockProclamations_pkey" PRIMARY KEY ("blockId","proclamationId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Schedule_userId_key" ON "Schedule"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Block_scheduleId_idx" ON "Block"("scheduleId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "BlockProclamations_proclamationId_idx" ON "BlockProclamations"("proclamationId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Schedule" ADD CONSTRAINT "Schedule_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Block" ADD CONSTRAINT "Block_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "Schedule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BlockProclamations" ADD CONSTRAINT "BlockProclamations_blockId_fkey" FOREIGN KEY ("blockId") REFERENCES "Block"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BlockProclamations" ADD CONSTRAINT "BlockProclamations_proclamationId_fkey" FOREIGN KEY ("proclamationId") REFERENCES "Proclamation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Block" ADD COLUMN "dynamicAudio" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LicenseKey" ADD COLUMN "systemName" TEXT,
|
||||||
|
ADD COLUMN "systemNameLower" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LicenseKey_userId_systemNameLower_key" ON "LicenseKey"("userId", "systemNameLower");
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
-- Phase 3: move Schedule from per-user to per-License-Key, add weekly support.
|
||||||
|
|
||||||
|
-- AlterTable: Schedule gains licenseKeyId + type, userId becomes optional.
|
||||||
|
ALTER TABLE "Schedule" ADD COLUMN "licenseKeyId" TEXT,
|
||||||
|
ADD COLUMN "type" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable: Block gains day; new Blocks default dynamicAudio true.
|
||||||
|
ALTER TABLE "Block" ADD COLUMN "day" TEXT;
|
||||||
|
ALTER TABLE "Block" ALTER COLUMN "dynamicAudio" SET DEFAULT true;
|
||||||
|
|
||||||
|
-- DropIndex: userId is no longer the schedule identity.
|
||||||
|
DROP INDEX "Schedule_userId_key";
|
||||||
|
|
||||||
|
-- AlterTable: legacy owner becomes optional and non-unique.
|
||||||
|
ALTER TABLE "Schedule" ALTER COLUMN "userId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Schedule_licenseKeyId_key" ON "Schedule"("licenseKeyId");
|
||||||
|
CREATE INDEX "Schedule_userId_idx" ON "Schedule"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Schedule" ADD CONSTRAINT "Schedule_licenseKeyId_fkey" FOREIGN KEY ("licenseKeyId") REFERENCES "LicenseKey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- Backfill: map each existing per-user Schedule to the account's oldest
|
||||||
|
-- License Key. Existing content was daily-only.
|
||||||
|
UPDATE "Schedule" s
|
||||||
|
SET "licenseKeyId" = lk."id",
|
||||||
|
"type" = COALESCE(s."type", 'daily')
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT ON ("userId") "id", "userId"
|
||||||
|
FROM "LicenseKey"
|
||||||
|
WHERE "userId" IS NOT NULL
|
||||||
|
ORDER BY "userId", "createdAt" ASC, "id" ASC
|
||||||
|
) lk
|
||||||
|
WHERE s."userId" = lk."userId"
|
||||||
|
AND s."licenseKeyId" IS NULL;
|
||||||
|
|
||||||
|
-- Report (do not silently drop) schedules that cannot be mapped to a key.
|
||||||
|
DO $$
|
||||||
|
DECLARE unmapped integer;
|
||||||
|
BEGIN
|
||||||
|
SELECT count(*) INTO unmapped FROM "Schedule" WHERE "licenseKeyId" IS NULL;
|
||||||
|
IF unmapped > 0 THEN
|
||||||
|
RAISE NOTICE 'Phase 3 migration: % Schedule row(s) could not be mapped to a License Key (no owning user/license); retained with licenseKeyId NULL.', unmapped;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
+146
-28
@@ -21,13 +21,14 @@ model User {
|
|||||||
image String?
|
image String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
roleId Int?
|
roleId Int? @default(4) // New users default to "User"
|
||||||
role UserRole? @relation(fields: [roleId], references: [id])
|
role UserRole? @relation(fields: [roleId], references: [id])
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
profile Profile?
|
profiles Profile[]
|
||||||
licenseKeys LicenseKey[]
|
licenseKeys LicenseKey[]
|
||||||
userProclamations UserProclamations[]
|
userProclamations UserProclamations[]
|
||||||
|
schedules Schedule[]
|
||||||
|
|
||||||
@@unique([email])
|
@@unique([email])
|
||||||
@@map("user")
|
@@map("user")
|
||||||
@@ -37,13 +38,17 @@ model User {
|
|||||||
// a bunch of time trying to figure out how to customize the default Better-Auth model.
|
// a bunch of time trying to figure out how to customize the default Better-Auth model.
|
||||||
model Profile {
|
model Profile {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String @unique
|
userId String?
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
email String?
|
||||||
firstName String
|
firstName String
|
||||||
lastName String
|
lastName String
|
||||||
phoneNumber String
|
phoneNumber String
|
||||||
ghlContactId String? @map("ghl_contact_id")
|
ghlContactId String? @map("ghl_contact_id")
|
||||||
addresses Address[]
|
addresses Address[]
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@index([email])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Address {
|
model Address {
|
||||||
@@ -123,9 +128,20 @@ model Proclamation {
|
|||||||
solfeggioFrequency SolfeggioFrequency @relation(fields: [solfeggioFrequencyId], references: [id])
|
solfeggioFrequency SolfeggioFrequency @relation(fields: [solfeggioFrequencyId], references: [id])
|
||||||
categoryId Int
|
categoryId Int
|
||||||
category ProclamationCategory @relation(fields: [categoryId], references: [id])
|
category ProclamationCategory @relation(fields: [categoryId], references: [id])
|
||||||
|
groupId Int
|
||||||
|
group ProclamationGroup @relation(fields: [groupId], references: [id])
|
||||||
userProclamations UserProclamations[]
|
userProclamations UserProclamations[]
|
||||||
subscriptionLevelId Int // Keeping old FK for migration stability
|
blockProclamations BlockProclamations[]
|
||||||
subscriptionLevel SubscriptionLevel @relation(fields: [subscriptionLevelId], references: [id])
|
// Legacy subscription-level FK. Retained in the database but unmanaged; the
|
||||||
|
// FK constraint is dropped and the value is left NULL. Hidden from the client.
|
||||||
|
subscriptionLevelId Int? @ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
model ProclamationGroup {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String @unique
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
proclamations Proclamation[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserProclamations {
|
model UserProclamations {
|
||||||
@@ -148,34 +164,61 @@ model SolfeggioFrequency {
|
|||||||
|
|
||||||
model ProclamationCategory {
|
model ProclamationCategory {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String @unique
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
parentId Int?
|
|
||||||
parent ProclamationCategory? @relation("CategoryHierarchy", fields: [parentId], references: [id])
|
|
||||||
children ProclamationCategory[] @relation("CategoryHierarchy")
|
|
||||||
proclamations Proclamation[]
|
proclamations Proclamation[]
|
||||||
subscriptionLevels ProclamationCategorySubscriptionLevel[]
|
|
||||||
|
|
||||||
@@unique([parentId, name])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join table for ProclamationCategory -> SubscriptionLevel defaults
|
// Archive of the former child sub-categories removed when the
|
||||||
model ProclamationCategorySubscriptionLevel {
|
// ProclamationCategory hierarchy was flattened. Kept for recovery/audit.
|
||||||
|
// `@@ignore` keeps it out of the generated client while Prisma remains aware
|
||||||
|
// of the table so `migrate dev` does not report it as an untracked table.
|
||||||
|
model ProclamationCategoryLegacy {
|
||||||
|
id Int @id
|
||||||
|
name String
|
||||||
|
parentId Int?
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
|
||||||
|
@@map("ProclamationCategoryLegacy")
|
||||||
|
@@ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive of the category->subscription-level mappings for the child
|
||||||
|
// categories removed when the category hierarchy was flattened. Kept with
|
||||||
|
// `@@ignore` so Prisma remains aware of the table but it is excluded from the
|
||||||
|
// generated client and Prisma's own migrations.
|
||||||
|
model ProclamationCategorySubscriptionLevelLegacy {
|
||||||
categoryId Int
|
categoryId Int
|
||||||
category ProclamationCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
|
||||||
subscriptionLevelId Int
|
subscriptionLevelId Int
|
||||||
subscriptionLevel SubscriptionLevel @relation(fields: [subscriptionLevelId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@id([categoryId, subscriptionLevelId])
|
@@id([categoryId, subscriptionLevelId])
|
||||||
|
@@map("ProclamationCategorySubscriptionLevelLegacy")
|
||||||
|
@@ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
model LicenseKey {
|
model LicenseKey {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
key String @unique
|
key String @unique
|
||||||
active Boolean
|
active Boolean
|
||||||
level String
|
createdAt DateTime @default(now())
|
||||||
userId String
|
lastValidatedAt DateTime?
|
||||||
user User @relation(fields: [userId], references: [id])
|
userId String?
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||||
|
email String?
|
||||||
|
schedule Schedule?
|
||||||
|
// Human-readable label set only by the Kiosk (e.g. "Living Room"). Trimmed,
|
||||||
|
// 1-40 chars. Never a reference or foreign key; the License Key is the only
|
||||||
|
// identifier. `systemNameLower` is a case-folded copy used to enforce
|
||||||
|
// per-account, case-insensitive uniqueness when non-null.
|
||||||
|
systemName String?
|
||||||
|
systemNameLower String?
|
||||||
|
// Legacy subscription level. Retained in the database but unmanaged; hidden
|
||||||
|
// from the client and left NULL.
|
||||||
|
level String? @ignore
|
||||||
|
|
||||||
|
@@unique([userId, systemNameLower])
|
||||||
|
@@index([userId])
|
||||||
|
@@index([email])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApiKey {
|
model ApiKey {
|
||||||
@@ -187,14 +230,6 @@ model ApiKey {
|
|||||||
revokedAt DateTime?
|
revokedAt DateTime?
|
||||||
}
|
}
|
||||||
|
|
||||||
model SubscriptionLevel {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
name String
|
|
||||||
description String?
|
|
||||||
proclamations Proclamation[]
|
|
||||||
categoryLevels ProclamationCategorySubscriptionLevel[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Portal-editable 7-color palette per canonical solfeggio frequency id (1-9).
|
// Portal-editable 7-color palette per canonical solfeggio frequency id (1-9).
|
||||||
// `solfeggioFrequencyId` is the canonical id the kiosk uses (see
|
// `solfeggioFrequencyId` is the canonical id the kiosk uses (see
|
||||||
// v2-serializers.ts), not the DB autoincrement id of `SolfeggioFrequency`.
|
// v2-serializers.ts), not the DB autoincrement id of `SolfeggioFrequency`.
|
||||||
@@ -204,3 +239,86 @@ model ColorPalette {
|
|||||||
|
|
||||||
@@map("ColorPalette")
|
@@map("ColorPalette")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append-only audit trail of database mutations. Written centrally by the
|
||||||
|
// Prisma extension in src/lib/db.ts; read by the admin Audit Log page.
|
||||||
|
model AuditLog {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
userId String?
|
||||||
|
email String?
|
||||||
|
roleId Int?
|
||||||
|
roleName String?
|
||||||
|
page String
|
||||||
|
action String
|
||||||
|
entity String?
|
||||||
|
entityId String?
|
||||||
|
before Json?
|
||||||
|
after Json?
|
||||||
|
ip String?
|
||||||
|
|
||||||
|
@@index([createdAt])
|
||||||
|
@@index([roleId])
|
||||||
|
@@index([action])
|
||||||
|
@@index([page])
|
||||||
|
@@map("AuditLog")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A listening schedule assigned to one License Key (at most one per key).
|
||||||
|
// `type` is "daily" | "weekly" | null (null = no schedule). Times are
|
||||||
|
// wall-clock minutes from midnight (0-1440), interpreted by the kiosk in its
|
||||||
|
// own configured timezone. The portal never stores a timezone.
|
||||||
|
//
|
||||||
|
// `userId` is a legacy owner retained while per-user schedules are migrated to
|
||||||
|
// per-license-key; new schedules set `licenseKeyId` and leave `userId` null.
|
||||||
|
model Schedule {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
licenseKeyId String? @unique
|
||||||
|
licenseKey LicenseKey? @relation(fields: [licenseKeyId], references: [id], onDelete: Cascade)
|
||||||
|
userId String?
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
type String?
|
||||||
|
blocks Block[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single time range within a Schedule, plus the ordered set of selected
|
||||||
|
// Proclamations to play during it. `day` is null for daily schedules and
|
||||||
|
// "mon".."sun" for weekly schedules. Overnight (midnight-crossing) ranges are
|
||||||
|
// valid: an end clock time before the start belongs to the start day and spills
|
||||||
|
// into the next day.
|
||||||
|
model Block {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
scheduleId String
|
||||||
|
schedule Schedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
|
||||||
|
day String?
|
||||||
|
startMinutes Int
|
||||||
|
endMinutes Int
|
||||||
|
// When true, the kiosk enables dynamic audio for the duration of this block,
|
||||||
|
// overriding any audio selections made on the device. Portal default: true.
|
||||||
|
dynamicAudio Boolean @default(true)
|
||||||
|
proclamations BlockProclamations[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([scheduleId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join table: the Proclamations selected for a Block. Carries the Proclamation
|
||||||
|
// content for playback via the API; the portal UI only ever reads
|
||||||
|
// title/category/group.
|
||||||
|
model BlockProclamations {
|
||||||
|
blockId String
|
||||||
|
block Block @relation(fields: [blockId], references: [id], onDelete: Cascade)
|
||||||
|
proclamationId String
|
||||||
|
proclamation Proclamation @relation(fields: [proclamationId], references: [id])
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@id([blockId, proclamationId])
|
||||||
|
@@index([proclamationId])
|
||||||
|
}
|
||||||
|
|||||||
+26
-43
@@ -72,43 +72,26 @@ const colorPalettes: { solfeggioFrequencyId: number; colors: string[] }[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const subscriptionLevels = [
|
const proclamationGroups = [
|
||||||
{ id: 1, name: 'Basic', description: 'General only' },
|
{ name: 'General', sortOrder: 0 },
|
||||||
{ id: 2, name: 'Advanced', description: 'General + Specific' },
|
{ name: 'Specific', sortOrder: 1 },
|
||||||
{ id: 3, name: 'Elite', description: 'General + Specific + Precise' }
|
{ name: 'Precise', sortOrder: 2 },
|
||||||
|
{ name: 'Prayers', sortOrder: 3 },
|
||||||
|
{ name: 'Scripture', sortOrder: 4 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Flat category list (formerly hierarchical parents).
|
||||||
const proclamationCategories = [
|
const proclamationCategories = [
|
||||||
{ id: 4, name: 'Spiritual', parentId: null },
|
{ id: 4, name: 'Spiritual' },
|
||||||
{ id: 5, name: 'General', parentId: 4 },
|
{ id: 2, name: 'Physical' },
|
||||||
{ id: 9, name: 'Specific', parentId: 4 },
|
{ id: 16, name: 'Mental' },
|
||||||
{ id: 40, name: 'Precise', parentId: 4 },
|
{ id: 17, name: 'Custom (Client Specific)' },
|
||||||
{ id: 2, name: 'Physical', parentId: null },
|
{ id: 19, name: 'Financial' },
|
||||||
{ id: 3, name: 'General', parentId: 2 },
|
{ id: 20, name: 'Emotional' },
|
||||||
{ id: 10, name: 'Specific', parentId: 2 },
|
{ id: 21, name: 'Social' },
|
||||||
{ id: 32, name: 'Precise', parentId: 2 },
|
{ id: 41, name: 'Biblical' },
|
||||||
{ id: 16, name: 'Mental', parentId: null },
|
{ id: 42, name: 'Personal Growth & Development' },
|
||||||
{ id: 30, name: 'General', parentId: 16 },
|
{ id: 46, name: 'Sports' }
|
||||||
{ id: 31, name: 'Specific', parentId: 16 },
|
|
||||||
{ id: 38, name: 'Precise', parentId: 16 },
|
|
||||||
{ id: 17, name: 'Custom (Client Specific)', parentId: null },
|
|
||||||
{ id: 19, name: 'Financial', parentId: null },
|
|
||||||
{ id: 28, name: 'General', parentId: 19 },
|
|
||||||
{ id: 29, name: 'Specific', parentId: 19 },
|
|
||||||
{ id: 37, name: 'Precise', parentId: 19 },
|
|
||||||
{ id: 20, name: 'Emotional', parentId: null },
|
|
||||||
{ id: 24, name: 'General', parentId: 20 },
|
|
||||||
{ id: 26, name: 'Specific', parentId: 20 },
|
|
||||||
{ id: 45, name: 'Precise', parentId: 20 },
|
|
||||||
{ id: 21, name: 'Social', parentId: null },
|
|
||||||
{ id: 22, name: 'General', parentId: 21 },
|
|
||||||
{ id: 23, name: 'Specific', parentId: 21 },
|
|
||||||
{ id: 39, name: 'Precise', parentId: 21 },
|
|
||||||
{ id: 41, name: 'Biblical', parentId: null },
|
|
||||||
{ id: 42, name: 'Personal Growth & Development', parentId: null },
|
|
||||||
{ id: 43, name: 'Prayers', parentId: 41 },
|
|
||||||
{ id: 44, name: 'Scripture', parentId: 41 },
|
|
||||||
{ id: 46, name: 'Sports', parentId: null }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -130,6 +113,15 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Seeding ProclamationGroup...');
|
||||||
|
for (const group of proclamationGroups) {
|
||||||
|
await prisma.proclamationGroup.upsert({
|
||||||
|
where: { name: group.name },
|
||||||
|
create: group,
|
||||||
|
update: group
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Seeding ProclamationCategory...');
|
console.log('Seeding ProclamationCategory...');
|
||||||
for (const cat of proclamationCategories) {
|
for (const cat of proclamationCategories) {
|
||||||
await prisma.proclamationCategory.upsert({
|
await prisma.proclamationCategory.upsert({
|
||||||
@@ -139,15 +131,6 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Seeding SubscriptionLevel...');
|
|
||||||
for (const level of subscriptionLevels) {
|
|
||||||
await prisma.subscriptionLevel.upsert({
|
|
||||||
where: { id: level.id },
|
|
||||||
create: level,
|
|
||||||
update: level
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Seeding ColorPalette...');
|
console.log('Seeding ColorPalette...');
|
||||||
for (const palette of colorPalettes) {
|
for (const palette of colorPalettes) {
|
||||||
await prisma.colorPalette.upsert({
|
await prisma.colorPalette.upsert({
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
// Exercises the Kiosk-facing HTTP API exactly as the Kiosk client does.
|
||||||
|
// Scenarios: b (validate + name), c (duplicate name conflict), k (ETag/304),
|
||||||
|
// old-client backward compatibility.
|
||||||
|
const BASE = process.env.P5_BASE ?? 'http://localhost:5173';
|
||||||
|
|
||||||
|
const KA1 = 'ALCE-AAAA-AAAA-0001';
|
||||||
|
const KA2 = 'ALCE-AAAA-AAAA-0002';
|
||||||
|
const KB1 = 'BOBB-BBBB-BBBB-0001';
|
||||||
|
const EMAIL = 'p5-alice@example.com';
|
||||||
|
|
||||||
|
const results: { id: string; pass: boolean; note: string }[] = [];
|
||||||
|
function rec(id: string, pass: boolean, note: string) {
|
||||||
|
results.push({ id, pass, note });
|
||||||
|
console.log(`${pass ? 'PASS' : 'FAIL'} ${id} ${note}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function post(path: string, body: unknown) {
|
||||||
|
const res = await fetch(BASE + path, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
return { status: res.status, body: await res.json().catch(() => null), headers: res.headers };
|
||||||
|
}
|
||||||
|
async function put(path: string, body: unknown) {
|
||||||
|
const res = await fetch(BASE + path, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
return { status: res.status, body: await res.json().catch(() => null) };
|
||||||
|
}
|
||||||
|
async function get(path: string, headers: Record<string, string> = {}) {
|
||||||
|
const res = await fetch(BASE + path, { headers });
|
||||||
|
return { status: res.status, body: await res.json().catch(() => null), headers: res.headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// (b) validate without a name first (old-client behavior), then set a name.
|
||||||
|
const validateNoName = await post('/api/v2/license', {
|
||||||
|
license_key: KA1,
|
||||||
|
email_address: EMAIL
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'b1',
|
||||||
|
validateNoName.status === 200 && validateNoName.body?.systemNameStatus === 'unchanged',
|
||||||
|
`validation without systemName -> ${validateNoName.status}, status=${validateNoName.body?.systemNameStatus}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const setName = await post('/api/v2/license', {
|
||||||
|
license_key: KA1,
|
||||||
|
email_address: EMAIL,
|
||||||
|
systemName: 'Living Room'
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'b2',
|
||||||
|
setName.status === 200 && setName.body?.systemNameStatus === 'ok',
|
||||||
|
`name a Kiosk -> ${setName.status}, status=${setName.body?.systemNameStatus}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// (c) duplicate name, different casing, same account -> conflict + not cleared.
|
||||||
|
const dup = await post('/api/v2/license', {
|
||||||
|
license_key: KA2,
|
||||||
|
email_address: EMAIL,
|
||||||
|
systemName: 'living room'
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'c1',
|
||||||
|
dup.status === 200 && dup.body?.systemNameStatus === 'conflict',
|
||||||
|
`case-insensitive duplicate -> status=${dup.body?.systemNameStatus} (validation still ${dup.status})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Confirm the original name survived and KA2 has no name.
|
||||||
|
const after = await get('/api/v2/licenses/' + KA1 + '/schedule');
|
||||||
|
rec('c2', after.status === 200, `KA1 schedule GET after conflict -> ${after.status}`);
|
||||||
|
|
||||||
|
// (c) PUT system-name endpoint: 409 for a taken name.
|
||||||
|
const putDup = await put('/api/v2/licenses/' + KA2 + '/system-name', {
|
||||||
|
systemName: 'LIVING ROOM'
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'c3',
|
||||||
|
putDup.status === 409 && putDup.body?.message === 'name_taken',
|
||||||
|
`PUT duplicate -> ${putDup.status} ${putDup.body?.message}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const putOk = await put('/api/v2/licenses/' + KA2 + '/system-name', { systemName: 'Kitchen' });
|
||||||
|
rec('c4', putOk.status === 200, `PUT unique name -> ${putOk.status}`);
|
||||||
|
|
||||||
|
const putInvalid = await put('/api/v2/licenses/' + KA2 + '/system-name', { systemName: ' ' });
|
||||||
|
rec(
|
||||||
|
'c5',
|
||||||
|
putInvalid.status === 400 && putInvalid.body?.message === 'invalid_name',
|
||||||
|
`PUT blank -> ${putInvalid.status} ${putInvalid.body?.message}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const putUnknown = await put('/api/v2/licenses/ZZZZ-ZZZZ-ZZZZ-ZZZZ/system-name', {
|
||||||
|
systemName: 'X'
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'c6',
|
||||||
|
putUnknown.status === 404 && putUnknown.body?.message === 'unknown_license',
|
||||||
|
`PUT unknown license -> ${putUnknown.status} ${putUnknown.body?.message}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// (k) schedule GET parity between the legacy endpoint and the new key-only endpoint.
|
||||||
|
const modern = await get('/api/v2/licenses/' + KA1 + '/schedule');
|
||||||
|
const legacy = await get(
|
||||||
|
'/api/v2/schedule?email=' + encodeURIComponent(EMAIL) + '&licenseKey=' + KA1
|
||||||
|
);
|
||||||
|
rec(
|
||||||
|
'k1',
|
||||||
|
modern.status === 200 && legacy.status === 200 && modern.body?.version === legacy.body?.version,
|
||||||
|
`modern vs legacy version equal=${modern.body?.version === legacy.body?.version}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// (k) conditional GET: matching If-None-Match -> 304.
|
||||||
|
const etag = modern.headers.get('etag') ?? '';
|
||||||
|
const notModified = await get('/api/v2/licenses/' + KA1 + '/schedule', { 'if-none-match': etag });
|
||||||
|
rec('k2', notModified.status === 304, `conditional GET with ETag -> ${notModified.status}`);
|
||||||
|
|
||||||
|
const stale = await get('/api/v2/licenses/' + KA1 + '/schedule', { 'if-none-match': '"stale"' });
|
||||||
|
rec('k3', stale.status === 200, `conditional GET with stale ETag -> ${stale.status}`);
|
||||||
|
|
||||||
|
// Unknown license -> 404.
|
||||||
|
const missing = await get('/api/v2/licenses/ZZZZ-ZZZZ-ZZZZ-ZZZZ/schedule');
|
||||||
|
rec('k4', missing.status === 404, `unknown license schedule GET -> ${missing.status}`);
|
||||||
|
|
||||||
|
// Old-client validation (no systemName field at all) never clears the name.
|
||||||
|
const validateAgain = await post('/api/v2/license', { license_key: KA1, email_address: EMAIL });
|
||||||
|
const checkName = await put('/api/v2/licenses/' + KA1 + '/system-name', {
|
||||||
|
systemName: 'Living Room'
|
||||||
|
});
|
||||||
|
rec(
|
||||||
|
'old1',
|
||||||
|
validateAgain.status === 200 &&
|
||||||
|
validateAgain.body?.systemNameStatus === 'unchanged' &&
|
||||||
|
checkName.status === 200,
|
||||||
|
`old-client validate stays unchanged; name still present`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Old-client list endpoint shape: blocks array still present.
|
||||||
|
rec(
|
||||||
|
'old2',
|
||||||
|
legacy.status === 200 && Array.isArray(legacy.body?.blocks),
|
||||||
|
`legacy endpoint still returns email+blocks (${legacy.body?.blocks?.length} blocks)`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cross-account: Bob's key with Alice's email must fail.
|
||||||
|
const cross = await get(
|
||||||
|
'/api/v2/schedule?email=' + encodeURIComponent(EMAIL) + '&licenseKey=' + KB1
|
||||||
|
);
|
||||||
|
rec('auth1', cross.status === 400, `cross-account legacy schedule -> ${cross.status}`);
|
||||||
|
|
||||||
|
console.log('\nP5_HTTP_RESULTS ' + JSON.stringify(results));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error('RUNNER ERROR', e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import { PrismaClient } from '../src/lib/generated/prisma/client';
|
||||||
|
|
||||||
|
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
|
||||||
|
|
||||||
|
// A handful of real Proclamations so schedule Blocks have valid references.
|
||||||
|
async function main() {
|
||||||
|
const category = await prisma.proclamationCategory.findFirst({ orderBy: { id: 'asc' } });
|
||||||
|
const group = await prisma.proclamationGroup.findFirst({ orderBy: { id: 'asc' } });
|
||||||
|
const freq = await prisma.solfeggioFrequency.findFirst({ orderBy: { id: 'asc' } });
|
||||||
|
if (!category || !group || !freq) throw new Error('seed missing catalog');
|
||||||
|
|
||||||
|
const names = ['P5 Dawn', 'P5 Dusk', 'P5 Noon', 'P5 Night'];
|
||||||
|
const ids: string[] = [];
|
||||||
|
for (const name of names) {
|
||||||
|
const existing = await prisma.proclamation.findUnique({ where: { name } });
|
||||||
|
if (existing) {
|
||||||
|
ids.push(existing.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const created = await prisma.proclamation.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
content: `<p>${name} content</p>`,
|
||||||
|
categoryId: category.id,
|
||||||
|
groupId: group.id,
|
||||||
|
solfeggioFrequencyId: freq.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ids.push(created.id);
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify(ids));
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(async (e) => {
|
||||||
|
console.error(e);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import { PrismaClient } from '../src/lib/generated/prisma/client';
|
||||||
|
|
||||||
|
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const adapter = new PrismaPg(pool);
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
|
// Two accounts, each with two License Keys, for scenario coverage:
|
||||||
|
// - acct A: keys KA1, KA2 (same account -> name uniqueness + copy)
|
||||||
|
// - acct B: key KB1 (cross-account isolation)
|
||||||
|
// Names are left NULL; the Kiosk sets them via the API.
|
||||||
|
const accounts = [
|
||||||
|
{ email: 'p5-alice@example.com', key: 'ALCE-AAAA-AAAA-0001' },
|
||||||
|
{ email: 'p5-alice@example.com', key: 'ALCE-AAAA-AAAA-0002' },
|
||||||
|
{ email: 'p5-bob@example.com', key: 'BOBB-BBBB-BBBB-0001' }
|
||||||
|
];
|
||||||
|
|
||||||
|
async function ensureUser(email) {
|
||||||
|
let user = await prisma.user.findUnique({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
user = await prisma.user.create({
|
||||||
|
data: { id: crypto.randomUUID(), name: email.split('@')[0], email, emailVerified: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
for (const { email, key } of accounts) {
|
||||||
|
const user = await ensureUser(email);
|
||||||
|
const existing = await prisma.licenseKey.findUnique({ where: { key } });
|
||||||
|
if (!existing) {
|
||||||
|
await prisma.licenseKey.create({ data: { key, active: true, userId: user.id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('P5 test data ready:');
|
||||||
|
for (const { email, key } of accounts) console.log(` ${email} -> ${key}`);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(async (e) => {
|
||||||
|
console.error(e);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import { PrismaClient } from '../src/lib/generated/prisma/client';
|
||||||
|
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
|
||||||
|
const schedules = await prisma.schedule.findMany({ include: { blocks: true } });
|
||||||
|
const keys = await prisma.licenseKey.findMany({ select: { key: true, systemName: true } });
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
scheduleCount: schedules.length,
|
||||||
|
blockCount: schedules.reduce((n, s) => n + s.blocks.length, 0),
|
||||||
|
types: schedules.map((s) => s.type),
|
||||||
|
names: keys
|
||||||
|
.filter((k) => k.systemName)
|
||||||
|
.map((k) => k.systemName)
|
||||||
|
.sort()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
Vendored
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
import type { Session, User } from 'better-auth';
|
import type { auth } from '$lib/auth';
|
||||||
import type { ApiKeyRecord } from '$lib/server/api-key';
|
import type { ApiKeyRecord } from '$lib/server/api-key';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
session: Session | null;
|
session: typeof auth.$Infer.Session.session | null;
|
||||||
user: User | null;
|
user: typeof auth.$Infer.Session.user | null;
|
||||||
apiKey?: ApiKeyRecord;
|
apiKey?: ApiKeyRecord;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -5,21 +5,21 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
try {
|
||||||
const theme = localStorage.getItem('theme');
|
const theme = localStorage.getItem('theme');
|
||||||
if (
|
const dark =
|
||||||
theme === 'dark' ||
|
theme === 'dark' ||
|
||||||
(!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
(!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
) {
|
document.documentElement.classList.toggle('dark', dark);
|
||||||
document.documentElement.classList.add('dark');
|
} catch (e) {
|
||||||
} else {
|
// ignore
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="m-0" data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div class="flex h-dvh w-dvw flex-col">%sveltekit.body%</div>
|
<div>%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,8 +3,24 @@ import { svelteKitHandler } from 'better-auth/svelte-kit';
|
|||||||
import { building } from '$app/environment';
|
import { building } from '$app/environment';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import { validateApiKey } from '$lib/server/api-key';
|
import { validateApiKey } from '$lib/server/api-key';
|
||||||
|
import { runWithAuditContext, type AuditContext } from '$lib/server/audit-context';
|
||||||
|
import { clientIp } from '$lib/server/v2-helpers';
|
||||||
|
|
||||||
|
function contextFor(
|
||||||
|
event: { url: URL; request: Request },
|
||||||
|
session?: App.Locals['user']
|
||||||
|
): AuditContext {
|
||||||
|
return {
|
||||||
|
userId: session?.id ?? null,
|
||||||
|
email: session?.email ?? null,
|
||||||
|
roleId: session?.roleId ?? null,
|
||||||
|
page: event.url.pathname,
|
||||||
|
ip: clientIp(event.request)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function handle({ event, resolve }) {
|
export async function handle({ event, resolve }) {
|
||||||
|
return runWithAuditContext(contextFor(event), async () => {
|
||||||
// v2 auth is ALWAYS enforced — no API_KEYS_ACTIVE bypass. The only
|
// v2 auth is ALWAYS enforced — no API_KEYS_ACTIVE bypass. The only
|
||||||
// exceptions are the signed onboarding routes (HMAC-protected).
|
// exceptions are the signed onboarding routes (HMAC-protected).
|
||||||
// TODO: re-enable when v2 API key auth is ready.
|
// TODO: re-enable when v2 API key auth is ready.
|
||||||
@@ -67,7 +83,12 @@ export async function handle({ event, resolve }) {
|
|||||||
if (session) {
|
if (session) {
|
||||||
event.locals.session = session.session;
|
event.locals.session = session.session;
|
||||||
event.locals.user = session.user;
|
event.locals.user = session.user;
|
||||||
|
|
||||||
|
return runWithAuditContext(contextFor(event, session.user), () =>
|
||||||
|
svelteKitHandler({ event, resolve, auth, building })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return svelteKitHandler({ event, resolve, auth, building });
|
return svelteKitHandler({ event, resolve, auth, building });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-1
@@ -5,13 +5,40 @@ import { sveltekitCookies } from 'better-auth/svelte-kit';
|
|||||||
import { getRequestEvent } from '$app/server';
|
import { getRequestEvent } from '$app/server';
|
||||||
import { sendMail } from './server/resend';
|
import { sendMail } from './server/resend';
|
||||||
import { sendVerificationEmail } from './server/email-verification';
|
import { sendVerificationEmail } from './server/email-verification';
|
||||||
|
import { sendRegistrationEmail } from './server/registration-email';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An admin-created user is invited with a set-password link. That reuses the
|
||||||
|
* password-reset token flow but marks the request with `invite=1` so we send a
|
||||||
|
* welcome/registration email instead of a "you requested a reset" email.
|
||||||
|
*/
|
||||||
|
function isRegistrationInvite(url: string): boolean {
|
||||||
|
try {
|
||||||
|
const callbackURL = new URL(url).searchParams.get('callbackURL') ?? '';
|
||||||
|
return callbackURL.includes('invite=1');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
baseURL: env.BETTER_AUTH_URL,
|
baseURL: env.BETTER_AUTH_URL,
|
||||||
database: prismaAdapter(prisma, {
|
database: prismaAdapter(prisma, {
|
||||||
provider: 'postgresql'
|
provider: 'postgresql'
|
||||||
}),
|
}),
|
||||||
|
user: {
|
||||||
|
// Expose our custom role column on the session user so role checks can
|
||||||
|
// read `locals.user.roleId` without a database round-trip.
|
||||||
|
additionalFields: {
|
||||||
|
roleId: {
|
||||||
|
type: 'number',
|
||||||
|
required: false,
|
||||||
|
defaultValue: 4,
|
||||||
|
input: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
emailVerification: {
|
emailVerification: {
|
||||||
sendOnSignUp: true,
|
sendOnSignUp: true,
|
||||||
sendVerificationEmail: async ({ user, token }) => {
|
sendVerificationEmail: async ({ user, token }) => {
|
||||||
@@ -27,8 +54,16 @@ export const auth = betterAuth({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
requireEmailVerification: true,
|
requireEmailVerification: true,
|
||||||
sendResetPassword: async ({ user, url }) => {
|
sendResetPassword: async ({ user, url }) => {
|
||||||
|
if (isRegistrationInvite(url)) {
|
||||||
|
void sendRegistrationEmail({ email: user.email, url }).catch((e) => {
|
||||||
|
console.error('sendRegistrationEmail template email failed:', e);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void sendMail({
|
void sendMail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
|
subject: 'Reset your Radiant Wave password',
|
||||||
templateId: 'password-reset-email',
|
templateId: 'password-reset-email',
|
||||||
data: {
|
data: {
|
||||||
target_email_address: user.email,
|
target_email_address: user.email,
|
||||||
@@ -51,6 +86,6 @@ export const auth = betterAuth({
|
|||||||
window: 60,
|
window: 60,
|
||||||
max: 100
|
max: 100
|
||||||
},
|
},
|
||||||
trustedOrigins: ['http://localhost:5173', 'https://portal.rhemawavetech.com'],
|
trustedOrigins: ['http://localhost:5173', 'https://portal.radiantwavetech.com'],
|
||||||
plugins: [sveltekitCookies(getRequestEvent)]
|
plugins: [sveltekitCookies(getRequestEvent)]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
let {
|
||||||
|
title,
|
||||||
|
description = '',
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
children: Snippet;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="mt-8">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">{title}</h2>
|
||||||
|
{#if description}
|
||||||
|
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">{description}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export type DataTableColumn = {
|
||||||
|
label: string;
|
||||||
|
value: (record: Record<string, unknown>) => unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
title = '',
|
||||||
|
columns = [] as DataTableColumn[],
|
||||||
|
records = [] as Record<string, unknown>[],
|
||||||
|
newLabel = 'New',
|
||||||
|
searchPlaceholder = 'Search...',
|
||||||
|
copyableFields = [] as string[],
|
||||||
|
draggable = false,
|
||||||
|
onRowClick,
|
||||||
|
onNew,
|
||||||
|
onReorder
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
columns?: DataTableColumn[];
|
||||||
|
records?: Record<string, unknown>[];
|
||||||
|
newLabel?: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
copyableFields?: string[];
|
||||||
|
draggable?: boolean;
|
||||||
|
onRowClick?: (record: Record<string, unknown>) => void;
|
||||||
|
onNew?: () => void;
|
||||||
|
onReorder?: (ids: (string | number)[]) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let search = $state('');
|
||||||
|
|
||||||
|
const filteredRecords = $derived(
|
||||||
|
search
|
||||||
|
? records.filter((record) =>
|
||||||
|
columns.some((column) =>
|
||||||
|
String(column.value(record) ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(search.toLowerCase())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: records
|
||||||
|
);
|
||||||
|
|
||||||
|
let dragOverride = $state<Record<string, unknown>[] | null>(null);
|
||||||
|
let dragIndex = $state<number | null>(null);
|
||||||
|
let hoverIndex = $state<number | null>(null);
|
||||||
|
|
||||||
|
const rows = $derived(dragOverride ?? filteredRecords);
|
||||||
|
|
||||||
|
function rowKey(record: Record<string, unknown>, index: number): string {
|
||||||
|
return String(record.id ?? record.key ?? index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyValue(value: unknown) {
|
||||||
|
navigator.clipboard.writeText(String(value ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart(e: DragEvent, i: number) {
|
||||||
|
dragOverride = filteredRecords;
|
||||||
|
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);
|
||||||
|
dragIndex = null;
|
||||||
|
hoverIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd() {
|
||||||
|
dragOverride = null;
|
||||||
|
dragIndex = null;
|
||||||
|
hoverIndex = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">{title}</h2>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-3 sm:w-auto sm:flex-row sm:items-center">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
bind:value={search}
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none sm:w-64 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||||
|
/>
|
||||||
|
{#if onNew}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={onNew}
|
||||||
|
class="inline-flex h-10 items-center justify-center gap-1.5 rounded-md bg-indigo-600 px-4 text-sm font-medium text-white shadow-sm transition hover:bg-indigo-700"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"><path d="M12 5v14" /><path d="M5 12h14" /></svg
|
||||||
|
>
|
||||||
|
{newLabel}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<table class="w-full min-w-max text-left text-sm text-slate-700 dark:text-slate-300">
|
||||||
|
<thead>
|
||||||
|
<tr
|
||||||
|
class="border-b border-slate-200 bg-slate-100 text-xs font-semibold tracking-wide text-slate-500 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
{#if draggable}
|
||||||
|
<th scope="col" class="w-10 px-4 py-3"></th>
|
||||||
|
{/if}
|
||||||
|
{#each columns as column (column.label)}
|
||||||
|
<th scope="col" class="px-4 py-3">{column.label}</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each rows as record, i (rowKey(record, i))}
|
||||||
|
<tr
|
||||||
|
class="transition {i % 2 === 0
|
||||||
|
? 'bg-white dark:bg-slate-900'
|
||||||
|
: 'bg-slate-50 dark:bg-slate-950'} {onRowClick
|
||||||
|
? 'cursor-pointer hover:bg-indigo-50 dark:hover:bg-indigo-950'
|
||||||
|
: ''}"
|
||||||
|
{draggable}
|
||||||
|
ondragstart={(e) => handleDragStart(e, i)}
|
||||||
|
ondragover={(e) => handleDragOver(e, i)}
|
||||||
|
ondrop={handleDrop}
|
||||||
|
ondragend={handleDragEnd}
|
||||||
|
onclick={() => onRowClick?.(record)}
|
||||||
|
>
|
||||||
|
{#if draggable}
|
||||||
|
<td class="px-4 py-3" onclick={(e) => e.stopPropagation()} aria-hidden="true">
|
||||||
|
<span
|
||||||
|
class="cursor-grab text-slate-400 select-none active:cursor-grabbing dark:text-slate-500"
|
||||||
|
title="Drag to reorder">⠿</span
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
{#each columns as column (column.label)}
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
{#if copyableFields.includes(column.label) && column.value(record) != null}
|
||||||
|
<span>
|
||||||
|
{String(column.value(record))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyValue(column.value(record));
|
||||||
|
}}
|
||||||
|
title="Copy to clipboard"
|
||||||
|
class="ml-1 inline-flex text-slate-400 transition hover:text-indigo-600 dark:text-slate-500 dark:hover:text-indigo-400"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
{String(column.value(record) ?? '')}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{:else}
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colspan={columns.length + (draggable ? 1 : 0)}
|
||||||
|
class="bg-white dark:bg-slate-900 px-4 py-10 text-center text-slate-500 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
No results found.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
fiveMinuteOptions,
|
||||||
|
formatTime,
|
||||||
|
hmToMinutes,
|
||||||
|
parseTimeInput,
|
||||||
|
toHm
|
||||||
|
} from '$lib/schedule-utils';
|
||||||
|
|
||||||
|
// A reusable 12-hour clock input. Displays and accepts AM/PM times, offers
|
||||||
|
// the 5-minute options as suggestions, and allows typing any minute 00-59.
|
||||||
|
// Its value model is a 24-hour "HH:MM" string.
|
||||||
|
let {
|
||||||
|
value = '00:00',
|
||||||
|
label,
|
||||||
|
onchange,
|
||||||
|
onvalidity,
|
||||||
|
required = false,
|
||||||
|
disabled = false
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
label?: string;
|
||||||
|
onchange?: (value: string) => void;
|
||||||
|
onvalidity?: (valid: boolean) => void;
|
||||||
|
required?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const inputId = $props.id();
|
||||||
|
const options = fiveMinuteOptions();
|
||||||
|
|
||||||
|
// While the user is typing we show their raw text (`editing`); otherwise we
|
||||||
|
// show the canonical 12-hour label derived from the 24-hour value.
|
||||||
|
let editing = $state<string | null>(null);
|
||||||
|
const valueMinutes = $derived(hmToMinutes(value) ?? 0);
|
||||||
|
const display = $derived(editing ?? formatTime(valueMinutes));
|
||||||
|
const error = $derived.by(() => {
|
||||||
|
if (editing === null) return '';
|
||||||
|
const result = parseTimeInput(editing);
|
||||||
|
return result.ok ? '' : result.error;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleInput(event: Event) {
|
||||||
|
const raw = (event.currentTarget as HTMLInputElement).value;
|
||||||
|
editing = raw;
|
||||||
|
const result = parseTimeInput(raw);
|
||||||
|
if (result.ok) {
|
||||||
|
onvalidity?.(true);
|
||||||
|
onchange?.(toHm(result.minutes));
|
||||||
|
} else {
|
||||||
|
onvalidity?.(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlur() {
|
||||||
|
if (editing === null) return;
|
||||||
|
const result = parseTimeInput(editing);
|
||||||
|
if (result.ok) {
|
||||||
|
// Drop back to the canonical label derived from the value.
|
||||||
|
editing = null;
|
||||||
|
onvalidity?.(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocus(event: FocusEvent) {
|
||||||
|
editing = (event.currentTarget as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
{#if label}
|
||||||
|
<label for={inputId} class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
id={inputId}
|
||||||
|
type="text"
|
||||||
|
inputmode="text"
|
||||||
|
autocomplete="off"
|
||||||
|
class="rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none disabled:opacity-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||||
|
list={`${inputId}-options`}
|
||||||
|
{required}
|
||||||
|
{disabled}
|
||||||
|
aria-label={label}
|
||||||
|
aria-invalid={error ? 'true' : undefined}
|
||||||
|
aria-describedby={error ? `${inputId}-error` : undefined}
|
||||||
|
value={display}
|
||||||
|
oninput={handleInput}
|
||||||
|
onfocus={handleFocus}
|
||||||
|
onblur={handleBlur}
|
||||||
|
/>
|
||||||
|
<datalist id={`${inputId}-options`}>
|
||||||
|
{#each options as minutes (minutes)}
|
||||||
|
<option value={formatTime(minutes)}></option>
|
||||||
|
{/each}
|
||||||
|
</datalist>
|
||||||
|
{#if error}
|
||||||
|
<p id={`${inputId}-error`} role="alert" class="text-sm text-red-600 dark:text-red-400">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import TimeInput from './TimeInput.svelte';
|
||||||
|
|
||||||
|
function mount(props: Record<string, unknown> = {}) {
|
||||||
|
return render(TimeInput, { value: '07:05', label: 'Time', ...props });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function type(text: string) {
|
||||||
|
const input = (await page.getByRole('combobox', { name: 'Time' }).element()) as HTMLInputElement;
|
||||||
|
input.value = text;
|
||||||
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TimeInput', () => {
|
||||||
|
it('displays the 24-hour value as a 12-hour label', async () => {
|
||||||
|
mount({ value: '21:05' });
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'Time' })).toHaveValue('9:05 PM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onchange with a 24-hour value for tolerant input', async () => {
|
||||||
|
const onchange = vi.fn();
|
||||||
|
mount({ onchange });
|
||||||
|
|
||||||
|
await type('7:05 pm');
|
||||||
|
|
||||||
|
expect(onchange).toHaveBeenCalledWith('19:05');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts typed minutes that are not 5-minute steps', async () => {
|
||||||
|
const onchange = vi.fn();
|
||||||
|
mount({ value: '00:00', onchange });
|
||||||
|
|
||||||
|
await type('7:07');
|
||||||
|
|
||||||
|
expect(onchange).toHaveBeenCalledWith('07:07');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts an hour with no minutes', async () => {
|
||||||
|
const onchange = vi.fn();
|
||||||
|
mount({ onchange });
|
||||||
|
|
||||||
|
await type('7pm');
|
||||||
|
|
||||||
|
expect(onchange).toHaveBeenCalledWith('19:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows clear feedback and reports invalid input', async () => {
|
||||||
|
const onvalidity = vi.fn();
|
||||||
|
mount({ onvalidity });
|
||||||
|
|
||||||
|
await type('13:00 PM');
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('alert')).toBeInTheDocument();
|
||||||
|
expect(onvalidity).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offers the 5-minute clock options', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const options = document.querySelectorAll('datalist option');
|
||||||
|
expect(options).toHaveLength((24 * 60) / 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
deleteAction?: string;
|
deleteAction?: string;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
function handleBackdrop(e: MouseEvent) {
|
function handleBackdrop() {
|
||||||
if (e.target === e.currentTarget) onClose?.();
|
onClose?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
@@ -44,24 +44,27 @@
|
|||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
role="presentation"
|
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 p-4"
|
||||||
class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/50 pt-12"
|
|
||||||
onclick={handleBackdrop}
|
|
||||||
onkeydown={handleKeydown}
|
onkeydown={handleKeydown}
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div class="absolute inset-0" onclick={handleBackdrop} aria-hidden="true"></div>
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label={title}
|
||||||
|
class="relative z-10 flex max-h-[90vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl dark:border-slate-800 dark:bg-slate-900"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="my-8 w-full max-w-2xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900"
|
class="flex shrink-0 items-center justify-between gap-3 border-b border-slate-200 px-6 py-4 dark:border-slate-800"
|
||||||
>
|
>
|
||||||
<div
|
<h2 class="text-lg font-bold text-slate-900 dark:text-slate-100">{title}</h2>
|
||||||
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">
|
<div class="flex items-center gap-2">
|
||||||
{#if canDelete && recordId}
|
{#if canDelete && recordId}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={confirmDelete}
|
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"
|
class="rounded-md bg-red-50 px-3 py-1.5 text-sm font-medium text-red-600 ring-1 ring-red-200 transition ring-inset hover:bg-red-600 hover:text-white dark:bg-red-950/50 dark:text-red-400 dark:ring-red-900"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -69,12 +72,14 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={onClose}
|
onclick={onClose}
|
||||||
class="px-2 py-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
aria-label="Close"
|
||||||
>×</button
|
class="rounded-md p-1.5 text-2xl leading-none text-slate-400 transition hover:bg-slate-100 hover:text-red-600 dark:text-slate-500 dark:hover:bg-slate-800"
|
||||||
>
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6 py-4">
|
<div class="overflow-y-auto px-6 py-5">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
<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,11 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let {
|
let { message = '' }: { message?: string } = $props();
|
||||||
message = '',
|
|
||||||
type = 'success' as 'success' | 'error'
|
|
||||||
}: {
|
|
||||||
message?: string;
|
|
||||||
type?: 'success' | 'error';
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
let remaining = $state(5);
|
let remaining = $state(5);
|
||||||
let visible = $state(true);
|
let visible = $state(true);
|
||||||
@@ -32,15 +26,17 @@
|
|||||||
{#if visible}
|
{#if visible}
|
||||||
<div
|
<div
|
||||||
role="alert"
|
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 ===
|
class="fixed right-4 bottom-4 z-50 flex items-center gap-3 rounded-lg border border-slate-700 bg-slate-900 px-4 py-3 text-sm text-white shadow-lg dark:border-slate-300 dark:bg-slate-950"
|
||||||
'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>{message}</span>
|
||||||
<span class="text-xs tabular-nums opacity-70">{remaining}s</span>
|
<span class="text-xs text-slate-400 dark:text-slate-500">{remaining}s</span>
|
||||||
<button type="button" onclick={dismiss} class="ml-1 leading-none font-bold hover:opacity-70"
|
<button
|
||||||
>×</button
|
type="button"
|
||||||
|
onclick={dismiss}
|
||||||
|
aria-label="Dismiss"
|
||||||
|
class="rounded p-1 text-slate-400 transition hover:text-white dark:text-slate-500"
|
||||||
>
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-0 flex-1 items-center justify-center overflow-auto">
|
<div class="mx-auto w-full max-w-md px-4 py-8 sm:py-12">
|
||||||
|
<div
|
||||||
|
class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm sm:p-8 dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import type { SubmitFunction } from '@sveltejs/kit';
|
||||||
|
import ProclamationBrowser from './ProclamationBrowser.svelte';
|
||||||
|
import TimeInput from '$lib/components/TimeInput.svelte';
|
||||||
|
import {
|
||||||
|
formatTime,
|
||||||
|
hmToMinutes,
|
||||||
|
isOvernight,
|
||||||
|
toHm,
|
||||||
|
validateBlockAgainstSchedule,
|
||||||
|
type BlockSibling
|
||||||
|
} from '$lib/schedule-utils';
|
||||||
|
|
||||||
|
type BrowserCategory = { id: number; name: string };
|
||||||
|
type BrowserGroup = { id: number; name: string };
|
||||||
|
type BrowserProclamation = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
categoryId: number;
|
||||||
|
groupId: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
open = false,
|
||||||
|
mode = 'create',
|
||||||
|
blockId,
|
||||||
|
day = null,
|
||||||
|
licenseKeyId = '',
|
||||||
|
initialStartMinutes = 9 * 60,
|
||||||
|
initialEndMinutes = 10 * 60,
|
||||||
|
initialProclamationIds = [],
|
||||||
|
initialDynamicAudio = true,
|
||||||
|
scheduleType = 'daily' as 'daily' | 'weekly' | null,
|
||||||
|
categories = [],
|
||||||
|
groups = [],
|
||||||
|
proclamations = [],
|
||||||
|
existingBlocks = [],
|
||||||
|
createAction = '?/createBlock',
|
||||||
|
updateAction = '?/updateBlock',
|
||||||
|
deleteAction = '?/deleteBlock',
|
||||||
|
onClose,
|
||||||
|
onSaved
|
||||||
|
}: {
|
||||||
|
open?: boolean;
|
||||||
|
mode?: 'create' | 'edit';
|
||||||
|
blockId?: string;
|
||||||
|
day?: string | null;
|
||||||
|
licenseKeyId?: string;
|
||||||
|
initialStartMinutes?: number;
|
||||||
|
initialEndMinutes?: number;
|
||||||
|
initialProclamationIds?: string[];
|
||||||
|
initialDynamicAudio?: boolean;
|
||||||
|
scheduleType?: 'daily' | 'weekly' | null;
|
||||||
|
categories?: BrowserCategory[];
|
||||||
|
groups?: BrowserGroup[];
|
||||||
|
proclamations?: BrowserProclamation[];
|
||||||
|
existingBlocks?: BlockSibling[];
|
||||||
|
createAction?: string;
|
||||||
|
updateAction?: string;
|
||||||
|
deleteAction?: string;
|
||||||
|
onClose?: () => void;
|
||||||
|
onSaved?: () => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let step = $state<'details' | 'browse'>('details');
|
||||||
|
// Seeded from the props in the effect below whenever the modal opens.
|
||||||
|
let startMinutes = $state(9 * 60);
|
||||||
|
let endMinutes = $state(10 * 60);
|
||||||
|
let startValid = $state(true);
|
||||||
|
let endValid = $state(true);
|
||||||
|
let selectedIds = $state<string[]>([]);
|
||||||
|
let dynamicAudio = $state(true);
|
||||||
|
let error = $state('');
|
||||||
|
|
||||||
|
function setStart(hm: string) {
|
||||||
|
const minutes = hmToMinutes(hm);
|
||||||
|
if (minutes !== null) startMinutes = minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEnd(hm: string) {
|
||||||
|
const minutes = hmToMinutes(hm);
|
||||||
|
if (minutes !== null) endMinutes = minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-seed the fields whenever the modal is (re)opened.
|
||||||
|
$effect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
startMinutes = initialStartMinutes;
|
||||||
|
endMinutes = initialEndMinutes;
|
||||||
|
startValid = true;
|
||||||
|
endValid = true;
|
||||||
|
selectedIds = [...initialProclamationIds];
|
||||||
|
dynamicAudio = initialDynamicAudio;
|
||||||
|
error = '';
|
||||||
|
step = 'details';
|
||||||
|
});
|
||||||
|
|
||||||
|
const overnight = $derived(
|
||||||
|
startMinutes !== endMinutes && isOvernight({ startMinutes, endMinutes })
|
||||||
|
);
|
||||||
|
|
||||||
|
const siblings = $derived(existingBlocks.filter((block) => block.id !== blockId));
|
||||||
|
|
||||||
|
const nameById = $derived(new Map(proclamations.map((p) => [p.id, p.name])));
|
||||||
|
const selectedTitles = $derived(
|
||||||
|
selectedIds
|
||||||
|
.map((id) => nameById.get(id))
|
||||||
|
.filter((name): name is string => typeof name === 'string')
|
||||||
|
);
|
||||||
|
|
||||||
|
function validate(): string | null {
|
||||||
|
if (!startValid || !endValid) return 'Enter a valid start and end time.';
|
||||||
|
if (scheduleType === null) return 'Set a schedule type first.';
|
||||||
|
const result = validateBlockAgainstSchedule(
|
||||||
|
scheduleType,
|
||||||
|
{ day, startMinutes, endMinutes, proclamationIds: selectedIds },
|
||||||
|
siblings
|
||||||
|
);
|
||||||
|
return result.ok ? null : result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit: SubmitFunction = ({ formData, cancel }) => {
|
||||||
|
const intent = formData.get('intent');
|
||||||
|
if (intent !== 'delete') {
|
||||||
|
const validationError = validate();
|
||||||
|
if (validationError) {
|
||||||
|
error = validationError;
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error = '';
|
||||||
|
|
||||||
|
return async ({ result, update }) => {
|
||||||
|
if (result.type === 'failure') {
|
||||||
|
const data = result.data as { error?: string } | undefined;
|
||||||
|
error = data?.error ?? 'Unable to save block.';
|
||||||
|
await update({ reset: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.type === 'error') {
|
||||||
|
error = 'Unable to save block.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await update();
|
||||||
|
onSaved?.();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Escape') onClose?.();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 p-4"
|
||||||
|
onkeydown={handleKeydown}
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="absolute inset-0 h-full w-full cursor-default"
|
||||||
|
onclick={() => onClose?.()}
|
||||||
|
></button>
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label={mode === 'edit' ? 'Edit block' : 'Create block'}
|
||||||
|
class="relative z-10 flex max-h-[90vh] w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex shrink-0 items-center justify-between gap-3 border-b border-slate-200 px-6 py-4 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<h2 class="text-lg font-bold text-slate-900 dark:text-slate-100">
|
||||||
|
{step === 'browse'
|
||||||
|
? 'Select Proclamations'
|
||||||
|
: mode === 'edit'
|
||||||
|
? 'Edit Block'
|
||||||
|
: 'Create Block'}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => onClose?.()}
|
||||||
|
aria-label="Close"
|
||||||
|
class="rounded-md p-1.5 text-2xl leading-none text-slate-400 transition hover:bg-slate-100 hover:text-red-600 dark:text-slate-500 dark:hover:bg-slate-800"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action={mode === 'edit' ? updateAction : createAction}
|
||||||
|
use:enhance={handleSubmit}
|
||||||
|
class="flex min-h-0 flex-1 flex-col"
|
||||||
|
>
|
||||||
|
{#if mode === 'edit' && blockId}
|
||||||
|
<input type="hidden" name="id" value={blockId} />
|
||||||
|
{/if}
|
||||||
|
<input type="hidden" name="licenseKeyId" value={licenseKeyId} />
|
||||||
|
{#if day}
|
||||||
|
<input type="hidden" name="day" value={day} />
|
||||||
|
{/if}
|
||||||
|
<input type="hidden" name="startMinutes" value={startMinutes} />
|
||||||
|
<input type="hidden" name="endMinutes" value={endMinutes} />
|
||||||
|
<input type="hidden" name="proclamationIds" value={JSON.stringify(selectedIds)} />
|
||||||
|
<input type="hidden" name="dynamicAudio" value={dynamicAudio ? 'true' : 'false'} />
|
||||||
|
|
||||||
|
<div class="min-h-0 flex-1 overflow-y-auto px-6 py-5">
|
||||||
|
{#if error}
|
||||||
|
<div
|
||||||
|
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step === 'details'}
|
||||||
|
<div class="grid gap-6 md:grid-cols-[minmax(0,1fr)_16rem]">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<TimeInput
|
||||||
|
label="Start time"
|
||||||
|
value={toHm(startMinutes)}
|
||||||
|
onchange={setStart}
|
||||||
|
onvalidity={(valid) => (startValid = valid)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimeInput
|
||||||
|
label="End time"
|
||||||
|
value={toHm(endMinutes)}
|
||||||
|
onchange={setEnd}
|
||||||
|
onvalidity={(valid) => (endValid = valid)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
Proclamations ({selectedTitles.length})
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (step = 'browse')}
|
||||||
|
class="rounded-md border border-indigo-200 bg-indigo-50 px-3 py-1.5 text-sm font-medium text-indigo-700 transition hover:bg-indigo-100 dark:border-indigo-900 dark:bg-indigo-950/50 dark:text-indigo-300"
|
||||||
|
>
|
||||||
|
Browse proclamations
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-2 text-sm font-medium text-slate-700 dark:text-slate-300"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={dynamicAudio}
|
||||||
|
onchange={(e) => (dynamicAudio = e.currentTarget.checked)}
|
||||||
|
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 dark:border-slate-600"
|
||||||
|
/>
|
||||||
|
Enable Dynamic Audio
|
||||||
|
</label>
|
||||||
|
<span class="group relative inline-flex">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="What is Dynamic Audio?"
|
||||||
|
aria-describedby="dynamic-audio-help"
|
||||||
|
class="flex h-5 w-5 items-center justify-center rounded-full border border-slate-300 text-xs font-semibold text-slate-500 transition hover:border-indigo-400 hover:text-indigo-600 focus:border-indigo-500 focus:outline-none dark:border-slate-600 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</button>
|
||||||
|
<span
|
||||||
|
id="dynamic-audio-help"
|
||||||
|
role="tooltip"
|
||||||
|
class="pointer-events-none absolute bottom-full left-1/2 z-20 mb-2 w-64 -translate-x-1/2 rounded-md bg-slate-900 px-3 py-2 text-xs font-normal text-white opacity-0 shadow-lg transition-opacity group-focus-within:opacity-100 group-hover:opacity-100 dark:bg-slate-700"
|
||||||
|
>
|
||||||
|
Dynamic audio will be enabled for this block, while this block is playing.
|
||||||
|
This will override any audio selections you have made on your device while
|
||||||
|
this block is active.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-950"
|
||||||
|
>
|
||||||
|
<h3 class="text-sm font-semibold text-slate-800 dark:text-slate-200">Review</h3>
|
||||||
|
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
{formatTime(startMinutes)} – {formatTime(endMinutes)}{overnight
|
||||||
|
? ' (ends next day)'
|
||||||
|
: ''}
|
||||||
|
</p>
|
||||||
|
{#if selectedTitles.length === 0}
|
||||||
|
<p class="mt-2 text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
No proclamations selected.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<ul class="mt-2 space-y-1">
|
||||||
|
{#each selectedTitles as title (title)}
|
||||||
|
<li class="text-sm text-slate-700 dark:text-slate-300">{title}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<ProclamationBrowser
|
||||||
|
{categories}
|
||||||
|
{groups}
|
||||||
|
{proclamations}
|
||||||
|
{selectedIds}
|
||||||
|
onChange={(ids) => (selectedIds = ids)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex shrink-0 items-center justify-between gap-3 border-t border-slate-200 px-6 py-4 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
{#if step === 'details' && mode === 'edit' && blockId}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="intent"
|
||||||
|
value="delete"
|
||||||
|
formaction={deleteAction}
|
||||||
|
class="rounded-md bg-red-50 px-3 py-1.5 text-sm font-medium text-red-600 ring-1 ring-red-200 transition ring-inset hover:bg-red-600 hover:text-white dark:bg-red-950/50 dark:text-red-400 dark:ring-red-900"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => onClose?.()}
|
||||||
|
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
{#if step === 'browse'}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (step = 'details')}
|
||||||
|
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-indigo-700"
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="intent"
|
||||||
|
value="save"
|
||||||
|
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-indigo-700"
|
||||||
|
>
|
||||||
|
{mode === 'edit' ? 'Save Block' : 'Create Block'}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
|
||||||
|
const { submitRef } = vi.hoisted(() => ({
|
||||||
|
submitRef: {
|
||||||
|
current: null as unknown as (opts: { formData: FormData; cancel?: () => void }) => unknown
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('$app/forms', () => ({
|
||||||
|
enhance: (_node: unknown, submit: unknown) => {
|
||||||
|
submitRef.current = submit as typeof submitRef.current;
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
import BlockModal from './BlockModal.svelte';
|
||||||
|
|
||||||
|
const categories = [{ id: 1, name: 'Physical' }];
|
||||||
|
const groups = [{ id: 1, name: 'Prayers' }];
|
||||||
|
const proclamations = [
|
||||||
|
{ id: 'p1', name: 'Peace', categoryId: 1, groupId: 1 },
|
||||||
|
{ id: 'p2', name: 'Strength', categoryId: 1, groupId: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
function mount(overrides: Record<string, unknown> = {}) {
|
||||||
|
return render(BlockModal, {
|
||||||
|
open: true,
|
||||||
|
mode: 'create',
|
||||||
|
categories,
|
||||||
|
groups,
|
||||||
|
proclamations,
|
||||||
|
existingBlocks: [],
|
||||||
|
initialStartMinutes: 6 * 60,
|
||||||
|
initialEndMinutes: 8 * 60,
|
||||||
|
initialProclamationIds: ['p1'],
|
||||||
|
onClose: () => {},
|
||||||
|
onSaved: () => {},
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
const form = document.querySelector('form') as HTMLFormElement;
|
||||||
|
return submitRef.current({ formData: new FormData(form), cancel: () => {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function typeTime(name: string, text: string) {
|
||||||
|
const input = (await page.getByRole('combobox', { name }).element()) as HTMLInputElement;
|
||||||
|
input.value = text;
|
||||||
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BlockModal', () => {
|
||||||
|
it('uses a 12-hour TimeInput for start and end', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'Start time' })).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'End time' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pre-fills the fields from the initial times', async () => {
|
||||||
|
mount({ initialStartMinutes: 7 * 60 + 30, initialEndMinutes: 9 * 60 + 15 });
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'Start time' })).toHaveValue('7:30 AM');
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'End time' })).toHaveValue('9:15 AM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a 12-hour clock with AM/PM', async () => {
|
||||||
|
mount({ initialStartMinutes: 21 * 60, initialEndMinutes: 0 });
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'Start time' })).toHaveValue('9:00 PM');
|
||||||
|
await expect.element(page.getByRole('combobox', { name: 'End time' })).toHaveValue('12:00 AM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recomputes the time from typed input', async () => {
|
||||||
|
mount({ initialStartMinutes: 9 * 60, initialEndMinutes: 10 * 60 });
|
||||||
|
|
||||||
|
await typeTime('End time', '10:00 PM');
|
||||||
|
|
||||||
|
await expect.element(page.getByText('9:00 AM – 10:00 PM')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows validation feedback for unparseable input', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await typeTime('Start time', '25:00');
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('alert')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the review times and selected proclamation titles', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await expect.element(page.getByText('6:00 AM – 8:00 AM')).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByText('Peace')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks an overnight block in the review', async () => {
|
||||||
|
mount({ initialStartMinutes: 21 * 60, initialEndMinutes: 9 * 60 });
|
||||||
|
|
||||||
|
await expect.element(page.getByText('9:00 PM – 9:00 AM (ends next day)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an overlapping time range with the specific error', async () => {
|
||||||
|
mount({
|
||||||
|
existingBlocks: [{ id: 'b1', startMinutes: 6 * 60, endMinutes: 8 * 60 }],
|
||||||
|
initialStartMinutes: 7 * 60,
|
||||||
|
initialEndMinutes: 9 * 60
|
||||||
|
});
|
||||||
|
|
||||||
|
submit();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByText(/This overlaps with your 6:00 AM–8:00 AM block\./))
|
||||||
|
.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires at least one proclamation', async () => {
|
||||||
|
mount({ initialProclamationIds: [] });
|
||||||
|
|
||||||
|
submit();
|
||||||
|
|
||||||
|
await expect.element(page.getByText('Select at least one proclamation.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show an error for a valid selection', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const result = submit();
|
||||||
|
|
||||||
|
expect(typeof result).toBe('function');
|
||||||
|
await expect.element(page.getByRole('alert')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows Cancel with Accept in the browse step, and Accept returns to the details', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const browse = page.getByRole('button', { name: 'Browse proclamations' });
|
||||||
|
await expect.element(browse).toBeInTheDocument();
|
||||||
|
(browse.element() as HTMLElement).click();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||||
|
const accept = page.getByRole('button', { name: 'Accept' });
|
||||||
|
await expect.element(accept).toBeInTheDocument();
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: 'Create Block' }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
|
||||||
|
(accept.element() as HTMLElement).click();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: 'Create Block' })).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByRole('button', { name: 'Accept' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offers an Enable Dynamic Audio checkbox with a help tooltip', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const checkbox = page.getByRole('checkbox', { name: /Enable Dynamic Audio/ });
|
||||||
|
await expect.element(checkbox).toBeInTheDocument();
|
||||||
|
await expect
|
||||||
|
.element(page.getByText(/Dynamic audio will be enabled for this block/))
|
||||||
|
.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults dynamic audio on for new blocks', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const checkbox = page.getByRole('checkbox', { name: /Enable Dynamic Audio/ });
|
||||||
|
expect((checkbox.element() as HTMLInputElement).checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pre-checks dynamic audio when editing a block that has it enabled', async () => {
|
||||||
|
mount({ initialDynamicAudio: true });
|
||||||
|
|
||||||
|
const checkbox = page.getByRole('checkbox', { name: /Enable Dynamic Audio/ });
|
||||||
|
expect((checkbox.element() as HTMLInputElement).checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves dynamic audio off when editing a block that has it disabled', async () => {
|
||||||
|
mount({ initialDynamicAudio: false });
|
||||||
|
|
||||||
|
const checkbox = page.getByRole('checkbox', { name: /Enable Dynamic Audio/ });
|
||||||
|
expect((checkbox.element() as HTMLInputElement).checked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles the dynamic audio flag in the submitted payload', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
const checkbox = page.getByRole('checkbox', { name: /Enable Dynamic Audio/ });
|
||||||
|
await expect.element(checkbox).toBeInTheDocument();
|
||||||
|
(checkbox.element() as HTMLInputElement).click();
|
||||||
|
|
||||||
|
const hidden = document.querySelector('input[name="dynamicAudio"]') as HTMLInputElement;
|
||||||
|
await expect.poll(() => hidden.value).toBe('false');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
type BrowserCategory = { id: number; name: string };
|
||||||
|
type BrowserGroup = { id: number; name: string };
|
||||||
|
type BrowserProclamation = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
categoryId: number;
|
||||||
|
groupId: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
categories = [],
|
||||||
|
groups = [],
|
||||||
|
proclamations = [],
|
||||||
|
selectedIds = [],
|
||||||
|
onChange
|
||||||
|
}: {
|
||||||
|
categories: BrowserCategory[];
|
||||||
|
groups: BrowserGroup[];
|
||||||
|
proclamations: BrowserProclamation[];
|
||||||
|
selectedIds: string[];
|
||||||
|
onChange?: (ids: string[]) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
// Categories start collapsed. Groups are pre-expanded, so we track the
|
||||||
|
// smaller set of groups the user has explicitly collapsed.
|
||||||
|
let openCategoryIds = $state<number[]>([]);
|
||||||
|
let closedGroupIds = $state<number[]>([]);
|
||||||
|
|
||||||
|
const selectedSet = $derived(new Set(selectedIds));
|
||||||
|
const nameById = $derived(new Map(proclamations.map((p) => [p.id, p.name])));
|
||||||
|
const selectedTitles = $derived(
|
||||||
|
selectedIds
|
||||||
|
.map((id) => nameById.get(id))
|
||||||
|
.filter((name): name is string => typeof name === 'string')
|
||||||
|
);
|
||||||
|
|
||||||
|
type GroupView = { group: BrowserGroup; items: BrowserProclamation[] };
|
||||||
|
type CategoryView = {
|
||||||
|
category: BrowserCategory;
|
||||||
|
groups: GroupView[];
|
||||||
|
ungrouped: BrowserProclamation[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const view = $derived.by<CategoryView[]>(() =>
|
||||||
|
categories.map((category) => {
|
||||||
|
const inCategory = proclamations.filter((p) => p.categoryId === category.id);
|
||||||
|
const groupViews = groups
|
||||||
|
.map((group) => ({
|
||||||
|
group,
|
||||||
|
items: inCategory.filter((p) => p.groupId === group.id)
|
||||||
|
}))
|
||||||
|
.filter((groupView) => groupView.items.length > 0);
|
||||||
|
const ungrouped = inCategory.filter((p) => p.groupId === null || p.groupId === undefined);
|
||||||
|
return { category, groups: groupViews, ungrouped };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
function toggleCategory(id: number) {
|
||||||
|
openCategoryIds = openCategoryIds.includes(id)
|
||||||
|
? openCategoryIds.filter((value) => value !== id)
|
||||||
|
: [...openCategoryIds, id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGroup(id: number) {
|
||||||
|
closedGroupIds = closedGroupIds.includes(id)
|
||||||
|
? closedGroupIds.filter((value) => value !== id)
|
||||||
|
: [...closedGroupIds, id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleProclamation(id: string) {
|
||||||
|
const next = selectedSet.has(id)
|
||||||
|
? selectedIds.filter((value) => value !== id)
|
||||||
|
: [...selectedIds, id];
|
||||||
|
onChange?.(next);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-[minmax(0,1fr)_16rem]">
|
||||||
|
<div
|
||||||
|
class="max-h-96 overflow-y-auto rounded-lg border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
{#if categories.length === 0}
|
||||||
|
<p class="p-4 text-sm text-slate-500 dark:text-slate-400">No categories available.</p>
|
||||||
|
{:else}
|
||||||
|
<ul class="divide-y divide-slate-100 dark:divide-slate-800">
|
||||||
|
{#each view as categoryView (categoryView.category.id)}
|
||||||
|
{@const isCategoryOpen = openCategoryIds.includes(categoryView.category.id)}
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-expanded={isCategoryOpen}
|
||||||
|
onclick={() => toggleCategory(categoryView.category.id)}
|
||||||
|
class="flex w-full items-center justify-between gap-2 px-4 py-3 text-left text-sm font-semibold text-slate-800 transition hover:bg-slate-50 dark:text-slate-200 dark:hover:bg-slate-800"
|
||||||
|
>
|
||||||
|
<span>{categoryView.category.name}</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="shrink-0 transition-transform {isCategoryOpen ? 'rotate-90' : ''}"
|
||||||
|
aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isCategoryOpen}
|
||||||
|
<div class="px-4 pb-3">
|
||||||
|
{#if categoryView.groups.length === 0 && categoryView.ungrouped.length === 0}
|
||||||
|
<p class="py-2 text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
No proclamations in this category.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each categoryView.groups as groupView (groupView.group.id)}
|
||||||
|
{@const isGroupOpen = !closedGroupIds.includes(groupView.group.id)}
|
||||||
|
<div class="mt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-expanded={isGroupOpen}
|
||||||
|
onclick={() => toggleGroup(groupView.group.id)}
|
||||||
|
class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-xs font-semibold tracking-wide text-slate-500 uppercase transition hover:bg-slate-50 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||||
|
>
|
||||||
|
<span>{groupView.group.name}</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="shrink-0 transition-transform {isGroupOpen ? 'rotate-180' : ''}"
|
||||||
|
aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
{#if isGroupOpen}
|
||||||
|
<ul class="mt-1 space-y-0.5">
|
||||||
|
{#each groupView.items as proclamation (proclamation.id)}
|
||||||
|
<li>
|
||||||
|
<label
|
||||||
|
class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-slate-700 transition hover:bg-indigo-50 dark:text-slate-300 dark:hover:bg-indigo-950"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedSet.has(proclamation.id)}
|
||||||
|
onchange={() => toggleProclamation(proclamation.id)}
|
||||||
|
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 dark:border-slate-600"
|
||||||
|
/>
|
||||||
|
<span>{proclamation.name}</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if categoryView.ungrouped.length > 0}
|
||||||
|
<div class="mt-3 border-t border-slate-100 pt-2 dark:border-slate-800">
|
||||||
|
<ul class="space-y-0.5">
|
||||||
|
{#each categoryView.ungrouped as proclamation (proclamation.id)}
|
||||||
|
<li>
|
||||||
|
<label
|
||||||
|
class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-slate-700 transition hover:bg-indigo-50 dark:text-slate-300 dark:hover:bg-indigo-950"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedSet.has(proclamation.id)}
|
||||||
|
onchange={() => toggleProclamation(proclamation.id)}
|
||||||
|
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 dark:border-slate-600"
|
||||||
|
/>
|
||||||
|
<span>{proclamation.name}</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-950"
|
||||||
|
>
|
||||||
|
<h3 class="text-sm font-semibold text-slate-800 dark:text-slate-200">
|
||||||
|
Selected ({selectedTitles.length})
|
||||||
|
</h3>
|
||||||
|
{#if selectedTitles.length === 0}
|
||||||
|
<p class="mt-2 text-sm text-slate-500 dark:text-slate-400">No proclamations selected.</p>
|
||||||
|
{:else}
|
||||||
|
<ul class="mt-2 space-y-1">
|
||||||
|
{#each selectedTitles as title, index (selectedIds[index])}
|
||||||
|
<li class="text-sm text-slate-700 dark:text-slate-300">{title}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import ProclamationBrowser from './ProclamationBrowser.svelte';
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{ id: 1, name: 'Physical' },
|
||||||
|
{ id: 2, name: 'Mental' },
|
||||||
|
{ id: 3, name: 'Empty' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const groups = [
|
||||||
|
{ id: 1, name: 'Prayers' },
|
||||||
|
{ id: 2, name: 'Scripture' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const proclamations = [
|
||||||
|
{ id: 'p1', name: 'Healing', categoryId: 1, groupId: 1 },
|
||||||
|
{ id: 'p2', name: 'Strength', categoryId: 1, groupId: 1 },
|
||||||
|
{ id: 'p3', name: 'Focus', categoryId: 1, groupId: null },
|
||||||
|
{ id: 'p4', name: 'Calm', categoryId: 2, groupId: 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
function mount(overrides: Record<string, unknown> = {}) {
|
||||||
|
return render(ProclamationBrowser, {
|
||||||
|
categories,
|
||||||
|
groups,
|
||||||
|
proclamations,
|
||||||
|
selectedIds: [],
|
||||||
|
onChange: () => {},
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function click(locator: { element: () => HTMLElement | SVGElement }) {
|
||||||
|
const el = locator.element() as HTMLElement;
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ProclamationBrowser', () => {
|
||||||
|
it('collapses categories by default', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: 'Physical' })).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByRole('checkbox', { name: 'Healing' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reveals pre-expanded groups and their proclamations when a category opens', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
|
||||||
|
await expect.element(page.getByText('Prayers')).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByRole('checkbox', { name: 'Healing' })).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByRole('checkbox', { name: 'Strength' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides groups that have no proclamations in the category', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
|
||||||
|
await expect.element(page.getByText('Scripture')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lists ungrouped proclamations below the groups', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('checkbox', { name: 'Focus' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports toggled selections through onChange', async () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
mount({ selectedIds: ['p1'], onChange });
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
await click(page.getByRole('checkbox', { name: 'Strength' }));
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith(['p1', 'p2']);
|
||||||
|
|
||||||
|
await click(page.getByRole('checkbox', { name: 'Healing' }));
|
||||||
|
expect(onChange).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows selected titles in the summary and keeps selection across collapse', async () => {
|
||||||
|
mount({ selectedIds: ['p1'] });
|
||||||
|
|
||||||
|
await expect.element(page.getByText('Selected (1)')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
const checkbox = (await page
|
||||||
|
.getByRole('checkbox', { name: 'Healing' })
|
||||||
|
.element()) as HTMLInputElement;
|
||||||
|
expect(checkbox.checked).toBe(true);
|
||||||
|
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
await expect.element(page.getByRole('checkbox', { name: 'Healing' })).not.toBeInTheDocument();
|
||||||
|
await click(page.getByRole('button', { name: 'Physical' }));
|
||||||
|
|
||||||
|
const reopened = (await page
|
||||||
|
.getByRole('checkbox', { name: 'Healing' })
|
||||||
|
.element()) as HTMLInputElement;
|
||||||
|
expect(reopened.checked).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
MINUTES_PER_DAY,
|
||||||
|
SNAP_MINUTES,
|
||||||
|
blockSegments,
|
||||||
|
formatTime,
|
||||||
|
isOvernight,
|
||||||
|
snapMinutes
|
||||||
|
} from '$lib/schedule-utils';
|
||||||
|
|
||||||
|
type ScheduleBlockView = {
|
||||||
|
id: string;
|
||||||
|
day?: string | null;
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
dynamicAudio: boolean;
|
||||||
|
proclamations: { id: string; name: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type OpenBlock = {
|
||||||
|
id: string;
|
||||||
|
day: string | null;
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
dynamicAudio: boolean;
|
||||||
|
proclamations: { id: string; name: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
days,
|
||||||
|
blocks = [],
|
||||||
|
onCreate,
|
||||||
|
onEdit
|
||||||
|
}: {
|
||||||
|
days: { key: string; label: string; short?: string }[];
|
||||||
|
blocks?: ScheduleBlockView[];
|
||||||
|
onCreate?: (day: string, startMinutes: number, endMinutes: number) => void;
|
||||||
|
onEdit?: (block: OpenBlock) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const hourMarks = Array.from({ length: 24 }, (_, i) => i);
|
||||||
|
const defaultDuration = 60;
|
||||||
|
|
||||||
|
// Which grid the pointer is currently over, so the live preview shows up in
|
||||||
|
// the right day column (Google-Calendar style).
|
||||||
|
let drag = $state<{
|
||||||
|
day: string;
|
||||||
|
anchor: number;
|
||||||
|
current: number;
|
||||||
|
pointerId: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
function dayLabel(key: string): string {
|
||||||
|
return days.find((d) => d.key === key)?.label ?? key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minutesFromEvent(event: PointerEvent, column: HTMLElement): number {
|
||||||
|
const rect = column.getBoundingClientRect();
|
||||||
|
// Guard against an unmeasured/zero-height column (e.g. before layout) so
|
||||||
|
// a tap still yields a valid, finite start minute instead of NaN.
|
||||||
|
if (!rect.height) return 0;
|
||||||
|
const ratio = (event.clientY - rect.top) / rect.height;
|
||||||
|
return Math.max(0, Math.min(MINUTES_PER_DAY, snapMinutes(ratio * MINUTES_PER_DAY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer capture happens on the column element, so moves/ups land there even
|
||||||
|
// when the pointer leaves the small cell.
|
||||||
|
function startDrag(event: PointerEvent, day: string) {
|
||||||
|
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||||
|
const column = event.currentTarget as HTMLElement;
|
||||||
|
const minutes = minutesFromEvent(event, column);
|
||||||
|
drag = { day, anchor: minutes, current: minutes, pointerId: event.pointerId };
|
||||||
|
column.setPointerCapture(event.pointerId);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDrag(event: PointerEvent) {
|
||||||
|
if (!drag || event.pointerId !== drag.pointerId) return;
|
||||||
|
const column = event.currentTarget as HTMLElement;
|
||||||
|
drag = { ...drag, current: minutesFromEvent(event, column) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function endDrag(event: PointerEvent) {
|
||||||
|
if (!drag || event.pointerId !== drag.pointerId) return;
|
||||||
|
const { day, anchor, current } = drag;
|
||||||
|
const column = event.currentTarget as HTMLElement;
|
||||||
|
column.releasePointerCapture?.(event.pointerId);
|
||||||
|
drag = null;
|
||||||
|
|
||||||
|
const start = Math.min(anchor, current);
|
||||||
|
const end = Math.max(anchor, current);
|
||||||
|
if (end - start >= SNAP_MINUTES) {
|
||||||
|
onCreate?.(day, start, end);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A plain click creates a default-length block starting at that slot.
|
||||||
|
const clickStart = Math.min(start, MINUTES_PER_DAY - defaultDuration);
|
||||||
|
const clickEnd = Math.min(MINUTES_PER_DAY, clickStart + defaultDuration);
|
||||||
|
onCreate?.(day, Math.max(0, clickStart), clickEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
const preview = $derived.by(() => {
|
||||||
|
if (!drag) return null;
|
||||||
|
const start = Math.min(drag.anchor, drag.current);
|
||||||
|
const end = Math.max(drag.anchor, drag.current);
|
||||||
|
return {
|
||||||
|
day: drag.day,
|
||||||
|
top: (start / MINUTES_PER_DAY) * 100,
|
||||||
|
height: ((end - start) / MINUTES_PER_DAY) * 100
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function blocksForDay(day: string): ScheduleBlockView[] {
|
||||||
|
// Daily schedules use a single "daily" column while their Blocks carry
|
||||||
|
// day=null; weekly columns match the `mon`..`sun` keys directly.
|
||||||
|
return blocks.filter((b) => (day === 'daily' ? b.day == null : b.day === day));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The column before each day, wrapping across the schedule (mon's previous
|
||||||
|
// day is sun; the single daily column is its own previous day). Used to spill
|
||||||
|
// an overnight block's post-midnight tail into the next day's column.
|
||||||
|
const previousDayKey: Record<string, string> = $derived.by(() => {
|
||||||
|
const map: Record<string, string> = {};
|
||||||
|
days.forEach((day, index) => {
|
||||||
|
map[day.key] = days[(index - 1 + days.length) % days.length].key;
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
|
type DayView = {
|
||||||
|
block: ScheduleBlockView;
|
||||||
|
// A same-day block, the pre-midnight start of an overnight block, or the
|
||||||
|
// post-midnight tail of the previous day's overnight block.
|
||||||
|
part: 'whole' | 'head' | 'tail';
|
||||||
|
top: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function toView(
|
||||||
|
block: ScheduleBlockView,
|
||||||
|
part: DayView['part'],
|
||||||
|
[start, end]: [number, number]
|
||||||
|
): DayView {
|
||||||
|
return {
|
||||||
|
block,
|
||||||
|
part,
|
||||||
|
top: (start / MINUTES_PER_DAY) * 100,
|
||||||
|
height: ((end - start) / MINUTES_PER_DAY) * 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A column shows its own blocks plus the tails of the previous day's
|
||||||
|
// overnight blocks. An overnight block contributes only its pre-midnight
|
||||||
|
// head to its start day; its tail is drawn at the top of the next day
|
||||||
|
// (weekly sun 7PM→1AM spills into mon). For daily, the previous day is the
|
||||||
|
// same single column, so both parts render together as before.
|
||||||
|
function viewsForDay(day: string): DayView[] {
|
||||||
|
const views: DayView[] = [];
|
||||||
|
|
||||||
|
for (const block of blocksForDay(day)) {
|
||||||
|
const head = blockSegments(block)[0];
|
||||||
|
if (!head || head[1] <= head[0]) continue;
|
||||||
|
views.push(toView(block, isOvernight(block) ? 'head' : 'whole', head));
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = previousDayKey[day];
|
||||||
|
if (previous) {
|
||||||
|
for (const block of blocksForDay(previous)) {
|
||||||
|
if (!isOvernight(block)) continue;
|
||||||
|
const tail = blockSegments(block)[1];
|
||||||
|
if (!tail || tail[1] <= tail[0]) continue;
|
||||||
|
views.push(toView(block, 'tail', tail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return views;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Schedule as a Google-Calendar-style grid: day columns side by side with a
|
||||||
|
shared time gutter. Scrolls horizontally on narrow screens (the gutter stays
|
||||||
|
pinned); tap/click or drag an empty slot to create a block at that time, tap a
|
||||||
|
block to edit it.
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
class="isolate overflow-hidden rounded-lg border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<div class="flex overflow-x-auto" style="overscroll-behavior-x: contain;">
|
||||||
|
<!-- Time gutter (pinned while the day columns scroll horizontally). -->
|
||||||
|
<div
|
||||||
|
class="sticky left-0 z-30 w-20 shrink-0 border-r border-slate-200 bg-white sm:w-24 dark:border-slate-800 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<!-- Spacer aligning with the day header row. -->
|
||||||
|
<div class="h-12 border-b border-slate-200 dark:border-slate-800"></div>
|
||||||
|
<div class="relative h-[720px]" aria-hidden="true">
|
||||||
|
{#each hourMarks as hour (hour)}
|
||||||
|
<div
|
||||||
|
class="absolute right-2 -translate-y-1/2 text-xs whitespace-nowrap text-slate-400 tabular-nums dark:text-slate-500"
|
||||||
|
style={`top:${(hour / 24) * 100}%`}
|
||||||
|
>
|
||||||
|
{formatTime(hour * 60)}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Day columns share the available width equally so a weekly view fits
|
||||||
|
without scrolling on desktop. Each keeps a minimum width, so when the
|
||||||
|
seven days cannot fit (phones) the row scrolls horizontally. -->
|
||||||
|
{#each days as day (day.key)}
|
||||||
|
<div
|
||||||
|
class="min-w-24 flex-1 basis-0 border-r border-slate-200 last:border-r-0 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex h-12 items-center justify-center border-b border-slate-200 px-1 text-xs font-semibold text-slate-700 sm:text-sm dark:border-slate-800 dark:text-slate-200"
|
||||||
|
>
|
||||||
|
{#if day.short}
|
||||||
|
<span class="sm:hidden">{day.short}</span>
|
||||||
|
<span class="hidden sm:inline">{day.label}</span>
|
||||||
|
{:else}
|
||||||
|
{day.label}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="relative h-[720px] cursor-pointer select-none"
|
||||||
|
style="touch-action: pan-x pan-y;"
|
||||||
|
aria-label={`Schedule grid ${dayLabel(day.key)}`}
|
||||||
|
onpointerdown={(e) => startDrag(e, day.key)}
|
||||||
|
onpointermove={moveDrag}
|
||||||
|
onpointerup={endDrag}
|
||||||
|
onpointercancel={(e) => {
|
||||||
|
if (drag && e.pointerId === drag.pointerId) drag = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#each hourMarks as hour (hour)}
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-x-0 border-t border-slate-100 dark:border-slate-800"
|
||||||
|
style={`top:${(hour / 24) * 100}%`}
|
||||||
|
></div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if preview && preview.day === day.key}
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-x-1 z-10 rounded-md bg-indigo-200/70 dark:bg-indigo-700/50"
|
||||||
|
style={`top:${preview.top}%;height:${preview.height}%`}
|
||||||
|
></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each viewsForDay(day.key) as view (`${view.block.id}-${view.part}`)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onpointerdown={(event) => event.stopPropagation()}
|
||||||
|
onclick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onEdit?.({ ...view.block, day: view.block.day ?? null });
|
||||||
|
}}
|
||||||
|
aria-label={`Block ${formatTime(view.block.startMinutes)} to ${formatTime(view.block.endMinutes)}`}
|
||||||
|
class="absolute inset-x-0.5 z-20 overflow-hidden rounded-md border border-indigo-300 bg-indigo-50 px-1.5 py-1 text-left text-xs text-indigo-900 shadow-sm transition hover:bg-indigo-100 dark:border-indigo-800 dark:bg-indigo-950/70 dark:text-indigo-200 dark:hover:bg-indigo-900"
|
||||||
|
style={`top:${view.top}%;height:${view.height}%`}
|
||||||
|
>
|
||||||
|
{#if view.part === 'tail'}
|
||||||
|
<span class="block truncate font-semibold tabular-nums">
|
||||||
|
{formatTime(0)}–{formatTime(view.block.endMinutes)}
|
||||||
|
</span>
|
||||||
|
<span class="block truncate text-[11px] opacity-80">↳ continues</span>
|
||||||
|
{:else}
|
||||||
|
<span class="block truncate font-semibold tabular-nums">
|
||||||
|
{formatTime(view.block.startMinutes)}–{formatTime(view.block.endMinutes)}
|
||||||
|
</span>
|
||||||
|
{#if view.part === 'head'}
|
||||||
|
<span class="block truncate text-[11px] opacity-80">ends next day</span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<span class="block truncate">
|
||||||
|
{view.block.proclamations.map((p) => p.name).join(', ') || 'No proclamations'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import WeekSchedule from './WeekSchedule.svelte';
|
||||||
|
|
||||||
|
const WEEK_DAYS = [
|
||||||
|
{ key: 'mon', label: 'Monday' },
|
||||||
|
{ key: 'tue', label: 'Tuesday' },
|
||||||
|
{ key: 'wed', label: 'Wednesday' },
|
||||||
|
{ key: 'thu', label: 'Thursday' },
|
||||||
|
{ key: 'fri', label: 'Friday' },
|
||||||
|
{ key: 'sat', label: 'Saturday' },
|
||||||
|
{ key: 'sun', label: 'Sunday' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const block = (overrides: Record<string, unknown> = {}) => ({
|
||||||
|
id: 'b1',
|
||||||
|
day: 'mon',
|
||||||
|
startMinutes: 6 * 60,
|
||||||
|
endMinutes: 8 * 60,
|
||||||
|
dynamicAudio: true,
|
||||||
|
proclamations: [{ id: 'p1', name: 'Peace' }],
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
|
||||||
|
function mount(props: Record<string, unknown> = {}) {
|
||||||
|
return render(WeekSchedule, { days: WEEK_DAYS, blocks: [], ...props });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click at a fraction of the way down a day column.
|
||||||
|
async function clickColumn(label: string, fraction: number) {
|
||||||
|
const column = document.querySelector(
|
||||||
|
`[aria-label="Schedule grid ${label}"]`
|
||||||
|
) as HTMLElement | null;
|
||||||
|
if (!column) throw new Error(`no column ${label}`);
|
||||||
|
const rect = column.getBoundingClientRect();
|
||||||
|
const x = rect.left + rect.width / 2;
|
||||||
|
const y = rect.top + rect.height * fraction;
|
||||||
|
column.dispatchEvent(
|
||||||
|
new PointerEvent('pointerdown', { clientX: x, clientY: y, bubbles: true, pointerId: 1 })
|
||||||
|
);
|
||||||
|
column.dispatchEvent(
|
||||||
|
new PointerEvent('pointerup', { clientX: x, clientY: y, bubbles: true, pointerId: 1 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WeekSchedule', () => {
|
||||||
|
it('renders all seven day columns side by side', async () => {
|
||||||
|
mount();
|
||||||
|
for (const day of WEEK_DAYS) {
|
||||||
|
await expect.element(page.getByText(day.label)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a single column for a daily schedule', async () => {
|
||||||
|
mount({ days: [{ key: 'daily', label: 'Every day' }] });
|
||||||
|
await expect.element(page.getByText('Every day')).toBeInTheDocument();
|
||||||
|
expect(document.querySelectorAll('[aria-label^="Schedule grid"]')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is horizontally scrollable with a pinned time gutter (mobile-friendly)', async () => {
|
||||||
|
mount();
|
||||||
|
|
||||||
|
// The day columns live in a horizontally scrollable row.
|
||||||
|
const scroller = document.querySelector('.overflow-x-auto') as HTMLElement | null;
|
||||||
|
expect(scroller).not.toBeNull();
|
||||||
|
|
||||||
|
// The time gutter is sticky so it stays visible while days scroll.
|
||||||
|
const gutter = scroller?.querySelector('[class*="sticky"][class*="left-0"]');
|
||||||
|
expect(gutter).not.toBeNull();
|
||||||
|
|
||||||
|
// Each day column has a minimum basis width so it stays legible on a phone.
|
||||||
|
const columns = scroller?.querySelectorAll('[aria-label^="Schedule grid"]') ?? [];
|
||||||
|
expect(columns.length).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows short day labels on small screens', async () => {
|
||||||
|
mount({
|
||||||
|
days: [
|
||||||
|
{ key: 'mon', label: 'Monday', short: 'Mon' },
|
||||||
|
{ key: 'tue', label: 'Tuesday', short: 'Tue' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
// Both the short (small-screen) and long (large-screen) labels exist in
|
||||||
|
// the DOM; CSS decides which is visible at a given width.
|
||||||
|
const text = document.body.textContent ?? '';
|
||||||
|
expect(text).toContain('Mon');
|
||||||
|
expect(text).toContain('Monday');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows blocks as events', async () => {
|
||||||
|
mount({ blocks: [block()] });
|
||||||
|
await expect.element(page.getByText('Peace')).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByLabelText('Block 6:00 AM to 8:00 AM')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders daily blocks (day null) in the single daily column', async () => {
|
||||||
|
mount({
|
||||||
|
days: [{ key: 'daily', label: 'Every day' }],
|
||||||
|
blocks: [block({ day: null })]
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(page.getByText('Peace')).toBeInTheDocument();
|
||||||
|
await expect.element(page.getByLabelText('Block 6:00 AM to 8:00 AM')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
function column(label: string): HTMLElement {
|
||||||
|
return document.querySelector(`[aria-label="Schedule grid ${label}"]`) as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('spills an overnight weekly block into the next day column', async () => {
|
||||||
|
mount({ blocks: [block({ day: 'sun', startMinutes: 19 * 60, endMinutes: 60 })] });
|
||||||
|
|
||||||
|
// The start day keeps only the pre-midnight head.
|
||||||
|
const sunday = column('Sunday');
|
||||||
|
expect(sunday.textContent).toContain('7:00 PM–1:00 AM');
|
||||||
|
expect(sunday.textContent).toContain('ends next day');
|
||||||
|
expect(sunday.textContent).not.toContain('continues');
|
||||||
|
|
||||||
|
// The post-midnight tail is drawn on the following day.
|
||||||
|
const monday = column('Monday');
|
||||||
|
expect(monday.textContent).toContain('12:00 AM–1:00 AM');
|
||||||
|
expect(monday.textContent).toContain('↳ continues');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spills a Saturday overnight block into Sunday', async () => {
|
||||||
|
mount({ blocks: [block({ day: 'sat', startMinutes: 23 * 60, endMinutes: 30 })] });
|
||||||
|
|
||||||
|
const sunday = column('Sunday');
|
||||||
|
expect(sunday.textContent).toContain('12:00 AM–12:30 AM');
|
||||||
|
expect(sunday.textContent).toContain('↳ continues');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps both parts of a daily overnight block in its single column', async () => {
|
||||||
|
mount({
|
||||||
|
days: [{ key: 'daily', label: 'Every day' }],
|
||||||
|
blocks: [block({ day: null, startMinutes: 21 * 60, endMinutes: 9 * 60 })]
|
||||||
|
});
|
||||||
|
|
||||||
|
const daily = column('Every day');
|
||||||
|
expect(daily.textContent).toContain('9:00 PM–9:00 AM');
|
||||||
|
expect(daily.textContent).toContain('ends next day');
|
||||||
|
expect(daily.textContent).toContain('12:00 AM–9:00 AM');
|
||||||
|
expect(daily.textContent).toContain('↳ continues');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens create on the clicked day', async () => {
|
||||||
|
const onCreate = vi.fn();
|
||||||
|
mount({ onCreate });
|
||||||
|
|
||||||
|
await clickColumn('Monday', 0.25);
|
||||||
|
|
||||||
|
expect(onCreate).toHaveBeenCalledTimes(1);
|
||||||
|
const [day, start, end] = onCreate.mock.calls[0] as [string, number, number];
|
||||||
|
expect(day).toBe('mon');
|
||||||
|
// The handler always yields a sensible minute range (a default-length
|
||||||
|
// block when the click distance is below the snap threshold).
|
||||||
|
expect(Number.isFinite(start)).toBe(true);
|
||||||
|
expect(Number.isFinite(end)).toBe(true);
|
||||||
|
expect(end - start).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens edit when a block is clicked', async () => {
|
||||||
|
const onEdit = vi.fn();
|
||||||
|
mount({ blocks: [block()], onEdit });
|
||||||
|
|
||||||
|
await page.getByLabelText('Block 6:00 AM to 8:00 AM').click();
|
||||||
|
|
||||||
|
expect(onEdit).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onEdit.mock.calls[0][0]).toMatchObject({ id: 'b1', day: 'mon' });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
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' }: { initialContent?: string; name?: string } =
|
|
||||||
$props();
|
|
||||||
|
|
||||||
let element: HTMLElement;
|
|
||||||
let editor: Editor;
|
|
||||||
let content = $state('');
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
content = initialContent;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 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} />
|
|
||||||
+160
-6
@@ -2,18 +2,172 @@ import pg from 'pg';
|
|||||||
import { PrismaPg } from '@prisma/adapter-pg';
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
import { PrismaClient } from '$lib/generated/prisma/client';
|
import { PrismaClient } from '$lib/generated/prisma/client';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
import { getAuditContext } from '$lib/server/audit-context';
|
||||||
|
import { describeAction, entityIdFor, sanitizeAuditValue, shouldAudit } from '$lib/server/audit';
|
||||||
|
|
||||||
let _prisma: PrismaClient | undefined;
|
let _prisma: PrismaClient | undefined;
|
||||||
|
let _base: PrismaClient | undefined;
|
||||||
|
let _pool: pg.Pool | undefined;
|
||||||
|
|
||||||
|
function getBase(): { base: PrismaClient; pool: pg.Pool } {
|
||||||
|
if (!_base || !_pool) {
|
||||||
|
_pool = new pg.Pool({ connectionString: env.DATABASE_URL });
|
||||||
|
const adapter = new PrismaPg(_pool);
|
||||||
|
_base = new PrismaClient({ adapter });
|
||||||
|
}
|
||||||
|
return { base: _base, pool: _pool };
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleNameCache = new Map<number, string>();
|
||||||
|
|
||||||
|
async function resolveRoleName(base: PrismaClient, roleId: number | null): Promise<string | null> {
|
||||||
|
if (roleId === null) return null;
|
||||||
|
const cached = roleNameCache.get(roleId);
|
||||||
|
if (cached) return cached;
|
||||||
|
try {
|
||||||
|
const role = await base.userRole.findUnique({
|
||||||
|
where: { id: roleId },
|
||||||
|
select: { name: true }
|
||||||
|
});
|
||||||
|
if (role?.name) {
|
||||||
|
roleNameCache.set(roleId, role.name);
|
||||||
|
return role.name;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to resolve role name for audit log:', e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Delegate = {
|
||||||
|
findFirst?: (args: unknown) => Promise<unknown>;
|
||||||
|
findMany?: (args: unknown) => Promise<unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function delegateFor(base: PrismaClient, model: string): Delegate | undefined {
|
||||||
|
const key = model.charAt(0).toLowerCase() + model.slice(1);
|
||||||
|
return (base as unknown as Record<string, unknown>)[key] as Delegate | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readBefore(
|
||||||
|
base: PrismaClient,
|
||||||
|
model: string,
|
||||||
|
operation: string,
|
||||||
|
args: unknown
|
||||||
|
): Promise<unknown> {
|
||||||
|
if (operation.startsWith('create')) return null;
|
||||||
|
const delegate = delegateFor(base, model);
|
||||||
|
if (!delegate) return null;
|
||||||
|
const where = (args as { where?: unknown } | null)?.where;
|
||||||
|
try {
|
||||||
|
if (operation === 'updateMany' || operation === 'deleteMany') {
|
||||||
|
return delegate.findMany ? await delegate.findMany({ where, take: 50 }) : null;
|
||||||
|
}
|
||||||
|
return delegate.findFirst ? await delegate.findFirst({ where }) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeAuditRow(
|
||||||
|
pool: pg.Pool,
|
||||||
|
base: PrismaClient,
|
||||||
|
entry: {
|
||||||
|
userId: string | null;
|
||||||
|
email: string | null;
|
||||||
|
roleId: number | null;
|
||||||
|
page: string;
|
||||||
|
action: string;
|
||||||
|
entity: string;
|
||||||
|
entityId: string | null;
|
||||||
|
before: unknown;
|
||||||
|
after: unknown;
|
||||||
|
ip: string | null;
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
const roleName = await resolveRoleName(base, entry.roleId);
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO "AuditLog"
|
||||||
|
("id", "createdAt", "userId", "email", "roleId", "roleName", "page", "action", "entity", "entityId", "before", "after", "ip")
|
||||||
|
VALUES ($1, now(), $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)`,
|
||||||
|
[
|
||||||
|
crypto.randomUUID(),
|
||||||
|
entry.userId,
|
||||||
|
entry.email,
|
||||||
|
entry.roleId,
|
||||||
|
roleName,
|
||||||
|
entry.page,
|
||||||
|
entry.action,
|
||||||
|
entry.entity,
|
||||||
|
entry.entityId,
|
||||||
|
entry.before === null ? null : JSON.stringify(entry.before),
|
||||||
|
entry.after === null ? null : JSON.stringify(entry.after),
|
||||||
|
entry.ip
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getPrisma(): PrismaClient {
|
function getPrisma(): PrismaClient {
|
||||||
if (!_prisma) {
|
if (_prisma) return _prisma;
|
||||||
const pool = new pg.Pool({
|
|
||||||
connectionString: env.DATABASE_URL
|
const { base, pool } = getBase();
|
||||||
|
|
||||||
|
const extended = base.$extends({
|
||||||
|
query: {
|
||||||
|
$allModels: {
|
||||||
|
async $allOperations({ model, operation, args, query }) {
|
||||||
|
if (!shouldAudit(model, operation)) {
|
||||||
|
return query(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = await readBefore(base, model, operation, args);
|
||||||
|
const result = await query(args);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = getAuditContext();
|
||||||
|
const res = result as { id?: unknown; email?: string; roleId?: number | null } | null;
|
||||||
|
|
||||||
|
let userId = ctx?.userId ?? null;
|
||||||
|
let email = ctx?.email ?? null;
|
||||||
|
let roleId = ctx?.roleId ?? null;
|
||||||
|
|
||||||
|
// Self-signup: attribute the new user to itself since no session exists yet.
|
||||||
|
if (!userId && model === 'User' && operation === 'create' && res) {
|
||||||
|
userId = (res.id as string | undefined) ?? null;
|
||||||
|
email = res.email ?? null;
|
||||||
|
roleId = res.roleId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const after =
|
||||||
|
operation === 'updateMany' || operation === 'deleteMany'
|
||||||
|
? { count: (result as { count?: number } | null)?.count ?? null }
|
||||||
|
: operation.startsWith('delete')
|
||||||
|
? null
|
||||||
|
: result;
|
||||||
|
|
||||||
|
await writeAuditRow(pool, base, {
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
roleId,
|
||||||
|
page: ctx?.page ?? '/system',
|
||||||
|
action: describeAction(operation),
|
||||||
|
entity: model,
|
||||||
|
entityId: entityIdFor(args, result, before),
|
||||||
|
before: sanitizeAuditValue(before),
|
||||||
|
after: sanitizeAuditValue(after),
|
||||||
|
ip: ctx?.ip ?? null
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to write audit log:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const adapter = new PrismaPg(pool);
|
_prisma = extended as unknown as PrismaClient;
|
||||||
_prisma = new PrismaClient({ adapter });
|
|
||||||
}
|
|
||||||
return _prisma;
|
return _prisma;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ export type Verification = Prisma.VerificationModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type Proclamation = Prisma.ProclamationModel
|
export type Proclamation = Prisma.ProclamationModel
|
||||||
|
/**
|
||||||
|
* Model ProclamationGroup
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type ProclamationGroup = Prisma.ProclamationGroupModel
|
||||||
/**
|
/**
|
||||||
* Model UserProclamations
|
* Model UserProclamations
|
||||||
*
|
*
|
||||||
@@ -72,11 +77,6 @@ export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
||||||
/**
|
|
||||||
* Model ProclamationCategorySubscriptionLevel
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type ProclamationCategorySubscriptionLevel = Prisma.ProclamationCategorySubscriptionLevelModel
|
|
||||||
/**
|
/**
|
||||||
* Model LicenseKey
|
* Model LicenseKey
|
||||||
*
|
*
|
||||||
@@ -87,13 +87,28 @@ export type LicenseKey = Prisma.LicenseKeyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ApiKey = Prisma.ApiKeyModel
|
export type ApiKey = Prisma.ApiKeyModel
|
||||||
/**
|
|
||||||
* Model SubscriptionLevel
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel
|
|
||||||
/**
|
/**
|
||||||
* Model ColorPalette
|
* Model ColorPalette
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ColorPalette = Prisma.ColorPaletteModel
|
export type ColorPalette = Prisma.ColorPaletteModel
|
||||||
|
/**
|
||||||
|
* Model AuditLog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type AuditLog = Prisma.AuditLogModel
|
||||||
|
/**
|
||||||
|
* Model Schedule
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Schedule = Prisma.ScheduleModel
|
||||||
|
/**
|
||||||
|
* Model Block
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Block = Prisma.BlockModel
|
||||||
|
/**
|
||||||
|
* Model BlockProclamations
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type BlockProclamations = Prisma.BlockProclamationsModel
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ export type Verification = Prisma.VerificationModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type Proclamation = Prisma.ProclamationModel
|
export type Proclamation = Prisma.ProclamationModel
|
||||||
|
/**
|
||||||
|
* Model ProclamationGroup
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type ProclamationGroup = Prisma.ProclamationGroupModel
|
||||||
/**
|
/**
|
||||||
* Model UserProclamations
|
* Model UserProclamations
|
||||||
*
|
*
|
||||||
@@ -96,11 +101,6 @@ export type SolfeggioFrequency = Prisma.SolfeggioFrequencyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
export type ProclamationCategory = Prisma.ProclamationCategoryModel
|
||||||
/**
|
|
||||||
* Model ProclamationCategorySubscriptionLevel
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type ProclamationCategorySubscriptionLevel = Prisma.ProclamationCategorySubscriptionLevelModel
|
|
||||||
/**
|
/**
|
||||||
* Model LicenseKey
|
* Model LicenseKey
|
||||||
*
|
*
|
||||||
@@ -111,13 +111,28 @@ export type LicenseKey = Prisma.LicenseKeyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ApiKey = Prisma.ApiKeyModel
|
export type ApiKey = Prisma.ApiKeyModel
|
||||||
/**
|
|
||||||
* Model SubscriptionLevel
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type SubscriptionLevel = Prisma.SubscriptionLevelModel
|
|
||||||
/**
|
/**
|
||||||
* Model ColorPalette
|
* Model ColorPalette
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type ColorPalette = Prisma.ColorPaletteModel
|
export type ColorPalette = Prisma.ColorPaletteModel
|
||||||
|
/**
|
||||||
|
* Model AuditLog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type AuditLog = Prisma.AuditLogModel
|
||||||
|
/**
|
||||||
|
* Model Schedule
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Schedule = Prisma.ScheduleModel
|
||||||
|
/**
|
||||||
|
* Model Block
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Block = Prisma.BlockModel
|
||||||
|
/**
|
||||||
|
* Model BlockProclamations
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type BlockProclamations = Prisma.BlockProclamationsModel
|
||||||
|
|||||||
@@ -253,6 +253,57 @@ export type JsonWithAggregatesFilterBase<$PrismaModel = never> = {
|
|||||||
_max?: Prisma.NestedJsonFilter<$PrismaModel>
|
_max?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type JsonNullableFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonNullableFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonNullableFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonNullableWithAggregatesFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedJsonNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedJsonNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
export type NestedStringFilter<$PrismaModel = never> = {
|
export type NestedStringFilter<$PrismaModel = never> = {
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
@@ -478,4 +529,28 @@ export type NestedJsonFilterBase<$PrismaModel = never> = {
|
|||||||
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NestedJsonNullableFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<NestedJsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<NestedJsonNullableFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type NestedJsonNullableFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -392,14 +392,17 @@ export const ModelName = {
|
|||||||
UserRole: 'UserRole',
|
UserRole: 'UserRole',
|
||||||
Verification: 'Verification',
|
Verification: 'Verification',
|
||||||
Proclamation: 'Proclamation',
|
Proclamation: 'Proclamation',
|
||||||
|
ProclamationGroup: 'ProclamationGroup',
|
||||||
UserProclamations: 'UserProclamations',
|
UserProclamations: 'UserProclamations',
|
||||||
SolfeggioFrequency: 'SolfeggioFrequency',
|
SolfeggioFrequency: 'SolfeggioFrequency',
|
||||||
ProclamationCategory: 'ProclamationCategory',
|
ProclamationCategory: 'ProclamationCategory',
|
||||||
ProclamationCategorySubscriptionLevel: 'ProclamationCategorySubscriptionLevel',
|
|
||||||
LicenseKey: 'LicenseKey',
|
LicenseKey: 'LicenseKey',
|
||||||
ApiKey: 'ApiKey',
|
ApiKey: 'ApiKey',
|
||||||
SubscriptionLevel: 'SubscriptionLevel',
|
ColorPalette: 'ColorPalette',
|
||||||
ColorPalette: 'ColorPalette'
|
AuditLog: 'AuditLog',
|
||||||
|
Schedule: 'Schedule',
|
||||||
|
Block: 'Block',
|
||||||
|
BlockProclamations: 'BlockProclamations'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
@@ -415,7 +418,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "user" | "profile" | "address" | "session" | "account" | "userRole" | "verification" | "proclamation" | "userProclamations" | "solfeggioFrequency" | "proclamationCategory" | "proclamationCategorySubscriptionLevel" | "licenseKey" | "apiKey" | "subscriptionLevel" | "colorPalette"
|
modelProps: "user" | "profile" | "address" | "session" | "account" | "userRole" | "verification" | "proclamation" | "proclamationGroup" | "userProclamations" | "solfeggioFrequency" | "proclamationCategory" | "licenseKey" | "apiKey" | "colorPalette" | "auditLog" | "schedule" | "block" | "blockProclamations"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
@@ -1011,6 +1014,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ProclamationGroup: {
|
||||||
|
payload: Prisma.$ProclamationGroupPayload<ExtArgs>
|
||||||
|
fields: Prisma.ProclamationGroupFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.ProclamationGroupFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.ProclamationGroupFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.ProclamationGroupFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.ProclamationGroupFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.ProclamationGroupFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.ProclamationGroupCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.ProclamationGroupCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.ProclamationGroupCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.ProclamationGroupDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.ProclamationGroupUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.ProclamationGroupDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.ProclamationGroupUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.ProclamationGroupUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.ProclamationGroupUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationGroupPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.ProclamationGroupAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateProclamationGroup>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.ProclamationGroupGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.ProclamationGroupGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.ProclamationGroupCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.ProclamationGroupCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
UserProclamations: {
|
UserProclamations: {
|
||||||
payload: Prisma.$UserProclamationsPayload<ExtArgs>
|
payload: Prisma.$UserProclamationsPayload<ExtArgs>
|
||||||
fields: Prisma.UserProclamationsFieldRefs
|
fields: Prisma.UserProclamationsFieldRefs
|
||||||
@@ -1233,80 +1310,6 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProclamationCategorySubscriptionLevel: {
|
|
||||||
payload: Prisma.$ProclamationCategorySubscriptionLevelPayload<ExtArgs>
|
|
||||||
fields: Prisma.ProclamationCategorySubscriptionLevelFieldRefs
|
|
||||||
operations: {
|
|
||||||
findUnique: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelFindUniqueArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload> | null
|
|
||||||
}
|
|
||||||
findUniqueOrThrow: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelFindUniqueOrThrowArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
findFirst: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelFindFirstArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload> | null
|
|
||||||
}
|
|
||||||
findFirstOrThrow: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelFindFirstOrThrowArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
findMany: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelFindManyArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
create: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelCreateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
createMany: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelCreateManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
createManyAndReturn: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelCreateManyAndReturnArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
delete: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelDeleteArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
update: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelUpdateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
deleteMany: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelDeleteManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
updateMany: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelUpdateManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
updateManyAndReturn: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelUpdateManyAndReturnArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
upsert: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelUpsertArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$ProclamationCategorySubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
aggregate: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelAggregateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.AggregateProclamationCategorySubscriptionLevel>
|
|
||||||
}
|
|
||||||
groupBy: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelGroupByArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.ProclamationCategorySubscriptionLevelGroupByOutputType>[]
|
|
||||||
}
|
|
||||||
count: {
|
|
||||||
args: Prisma.ProclamationCategorySubscriptionLevelCountArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.ProclamationCategorySubscriptionLevelCountAggregateOutputType> | number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LicenseKey: {
|
LicenseKey: {
|
||||||
payload: Prisma.$LicenseKeyPayload<ExtArgs>
|
payload: Prisma.$LicenseKeyPayload<ExtArgs>
|
||||||
fields: Prisma.LicenseKeyFieldRefs
|
fields: Prisma.LicenseKeyFieldRefs
|
||||||
@@ -1455,80 +1458,6 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubscriptionLevel: {
|
|
||||||
payload: Prisma.$SubscriptionLevelPayload<ExtArgs>
|
|
||||||
fields: Prisma.SubscriptionLevelFieldRefs
|
|
||||||
operations: {
|
|
||||||
findUnique: {
|
|
||||||
args: Prisma.SubscriptionLevelFindUniqueArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload> | null
|
|
||||||
}
|
|
||||||
findUniqueOrThrow: {
|
|
||||||
args: Prisma.SubscriptionLevelFindUniqueOrThrowArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
findFirst: {
|
|
||||||
args: Prisma.SubscriptionLevelFindFirstArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload> | null
|
|
||||||
}
|
|
||||||
findFirstOrThrow: {
|
|
||||||
args: Prisma.SubscriptionLevelFindFirstOrThrowArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
findMany: {
|
|
||||||
args: Prisma.SubscriptionLevelFindManyArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
create: {
|
|
||||||
args: Prisma.SubscriptionLevelCreateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
createMany: {
|
|
||||||
args: Prisma.SubscriptionLevelCreateManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
createManyAndReturn: {
|
|
||||||
args: Prisma.SubscriptionLevelCreateManyAndReturnArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
delete: {
|
|
||||||
args: Prisma.SubscriptionLevelDeleteArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
update: {
|
|
||||||
args: Prisma.SubscriptionLevelUpdateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
deleteMany: {
|
|
||||||
args: Prisma.SubscriptionLevelDeleteManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
updateMany: {
|
|
||||||
args: Prisma.SubscriptionLevelUpdateManyArgs<ExtArgs>
|
|
||||||
result: BatchPayload
|
|
||||||
}
|
|
||||||
updateManyAndReturn: {
|
|
||||||
args: Prisma.SubscriptionLevelUpdateManyAndReturnArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>[]
|
|
||||||
}
|
|
||||||
upsert: {
|
|
||||||
args: Prisma.SubscriptionLevelUpsertArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$SubscriptionLevelPayload>
|
|
||||||
}
|
|
||||||
aggregate: {
|
|
||||||
args: Prisma.SubscriptionLevelAggregateArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.AggregateSubscriptionLevel>
|
|
||||||
}
|
|
||||||
groupBy: {
|
|
||||||
args: Prisma.SubscriptionLevelGroupByArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.SubscriptionLevelGroupByOutputType>[]
|
|
||||||
}
|
|
||||||
count: {
|
|
||||||
args: Prisma.SubscriptionLevelCountArgs<ExtArgs>
|
|
||||||
result: runtime.Types.Utils.Optional<Prisma.SubscriptionLevelCountAggregateOutputType> | number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColorPalette: {
|
ColorPalette: {
|
||||||
payload: Prisma.$ColorPalettePayload<ExtArgs>
|
payload: Prisma.$ColorPalettePayload<ExtArgs>
|
||||||
fields: Prisma.ColorPaletteFieldRefs
|
fields: Prisma.ColorPaletteFieldRefs
|
||||||
@@ -1603,6 +1532,302 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AuditLog: {
|
||||||
|
payload: Prisma.$AuditLogPayload<ExtArgs>
|
||||||
|
fields: Prisma.AuditLogFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.AuditLogFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.AuditLogFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.AuditLogFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.AuditLogFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.AuditLogFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.AuditLogCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.AuditLogCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.AuditLogCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.AuditLogDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.AuditLogUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.AuditLogDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.AuditLogUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.AuditLogUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.AuditLogUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$AuditLogPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.AuditLogAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateAuditLog>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.AuditLogGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AuditLogGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.AuditLogCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AuditLogCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Schedule: {
|
||||||
|
payload: Prisma.$SchedulePayload<ExtArgs>
|
||||||
|
fields: Prisma.ScheduleFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.ScheduleFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.ScheduleFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.ScheduleFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.ScheduleFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.ScheduleFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.ScheduleCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.ScheduleCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.ScheduleCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.ScheduleDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.ScheduleUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.ScheduleDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.ScheduleUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.ScheduleUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.ScheduleUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SchedulePayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.ScheduleAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateSchedule>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.ScheduleGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.ScheduleGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.ScheduleCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.ScheduleCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Block: {
|
||||||
|
payload: Prisma.$BlockPayload<ExtArgs>
|
||||||
|
fields: Prisma.BlockFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.BlockFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.BlockFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.BlockFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.BlockFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.BlockFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.BlockCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.BlockCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.BlockCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.BlockDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.BlockUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.BlockDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.BlockUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.BlockUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.BlockUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.BlockAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateBlock>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.BlockGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.BlockGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.BlockCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.BlockCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockProclamations: {
|
||||||
|
payload: Prisma.$BlockProclamationsPayload<ExtArgs>
|
||||||
|
fields: Prisma.BlockProclamationsFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.BlockProclamationsFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.BlockProclamationsFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.BlockProclamationsFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.BlockProclamationsFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.BlockProclamationsFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.BlockProclamationsCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.BlockProclamationsCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.BlockProclamationsCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.BlockProclamationsDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.BlockProclamationsUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.BlockProclamationsDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.BlockProclamationsUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.BlockProclamationsUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.BlockProclamationsUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$BlockProclamationsPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.BlockProclamationsAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateBlockProclamations>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.BlockProclamationsGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.BlockProclamationsGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.BlockProclamationsCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.BlockProclamationsCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} & {
|
} & {
|
||||||
other: {
|
other: {
|
||||||
@@ -1659,6 +1884,7 @@ export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof User
|
|||||||
export const ProfileScalarFieldEnum = {
|
export const ProfileScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
firstName: 'firstName',
|
firstName: 'firstName',
|
||||||
lastName: 'lastName',
|
lastName: 'lastName',
|
||||||
phoneNumber: 'phoneNumber',
|
phoneNumber: 'phoneNumber',
|
||||||
@@ -1744,12 +1970,21 @@ export const ProclamationScalarFieldEnum = {
|
|||||||
content: 'content',
|
content: 'content',
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
categoryId: 'categoryId',
|
categoryId: 'categoryId',
|
||||||
subscriptionLevelId: 'subscriptionLevelId'
|
groupId: 'groupId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ProclamationScalarFieldEnum = (typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum]
|
export type ProclamationScalarFieldEnum = (typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ProclamationGroupScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
sortOrder: 'sortOrder'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ProclamationGroupScalarFieldEnum = (typeof ProclamationGroupScalarFieldEnum)[keyof typeof ProclamationGroupScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const UserProclamationsScalarFieldEnum = {
|
export const UserProclamationsScalarFieldEnum = {
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
proclamationId: 'proclamationId',
|
proclamationId: 'proclamationId',
|
||||||
@@ -1772,27 +2007,22 @@ export type SolfeggioFrequencyScalarFieldEnum = (typeof SolfeggioFrequencyScalar
|
|||||||
export const ProclamationCategoryScalarFieldEnum = {
|
export const ProclamationCategoryScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
sortOrder: 'sortOrder',
|
sortOrder: 'sortOrder'
|
||||||
parentId: 'parentId'
|
|
||||||
} as const
|
} 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 = {
|
export const LicenseKeyScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
key: 'key',
|
key: 'key',
|
||||||
active: 'active',
|
active: 'active',
|
||||||
level: 'level',
|
createdAt: 'createdAt',
|
||||||
userId: 'userId'
|
lastValidatedAt: 'lastValidatedAt',
|
||||||
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
|
systemName: 'systemName',
|
||||||
|
systemNameLower: 'systemNameLower'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type LicenseKeyScalarFieldEnum = (typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum]
|
export type LicenseKeyScalarFieldEnum = (typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum]
|
||||||
@@ -1810,15 +2040,6 @@ export const ApiKeyScalarFieldEnum = {
|
|||||||
export type ApiKeyScalarFieldEnum = (typeof ApiKeyScalarFieldEnum)[keyof typeof ApiKeyScalarFieldEnum]
|
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]
|
|
||||||
|
|
||||||
|
|
||||||
export const ColorPaletteScalarFieldEnum = {
|
export const ColorPaletteScalarFieldEnum = {
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
colors: 'colors'
|
colors: 'colors'
|
||||||
@@ -1827,6 +2048,62 @@ export const ColorPaletteScalarFieldEnum = {
|
|||||||
export type ColorPaletteScalarFieldEnum = (typeof ColorPaletteScalarFieldEnum)[keyof typeof ColorPaletteScalarFieldEnum]
|
export type ColorPaletteScalarFieldEnum = (typeof ColorPaletteScalarFieldEnum)[keyof typeof ColorPaletteScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const AuditLogScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
|
roleId: 'roleId',
|
||||||
|
roleName: 'roleName',
|
||||||
|
page: 'page',
|
||||||
|
action: 'action',
|
||||||
|
entity: 'entity',
|
||||||
|
entityId: 'entityId',
|
||||||
|
before: 'before',
|
||||||
|
after: 'after',
|
||||||
|
ip: 'ip'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type AuditLogScalarFieldEnum = (typeof AuditLogScalarFieldEnum)[keyof typeof AuditLogScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ScheduleScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
licenseKeyId: 'licenseKeyId',
|
||||||
|
userId: 'userId',
|
||||||
|
type: 'type',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ScheduleScalarFieldEnum = (typeof ScheduleScalarFieldEnum)[keyof typeof ScheduleScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const BlockScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
scheduleId: 'scheduleId',
|
||||||
|
day: 'day',
|
||||||
|
startMinutes: 'startMinutes',
|
||||||
|
endMinutes: 'endMinutes',
|
||||||
|
dynamicAudio: 'dynamicAudio',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type BlockScalarFieldEnum = (typeof BlockScalarFieldEnum)[keyof typeof BlockScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const BlockProclamationsScalarFieldEnum = {
|
||||||
|
blockId: 'blockId',
|
||||||
|
proclamationId: 'proclamationId',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type BlockProclamationsScalarFieldEnum = (typeof BlockProclamationsScalarFieldEnum)[keyof typeof BlockProclamationsScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const SortOrder = {
|
export const SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
@@ -1842,6 +2119,14 @@ export const JsonNullValueInput = {
|
|||||||
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullableJsonNullValueInput = {
|
||||||
|
DbNull: DbNull,
|
||||||
|
JsonNull: JsonNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
export const QueryMode = {
|
export const QueryMode = {
|
||||||
default: 'default',
|
default: 'default',
|
||||||
insensitive: 'insensitive'
|
insensitive: 'insensitive'
|
||||||
@@ -2067,14 +2352,17 @@ export type GlobalOmitConfig = {
|
|||||||
userRole?: Prisma.UserRoleOmit
|
userRole?: Prisma.UserRoleOmit
|
||||||
verification?: Prisma.VerificationOmit
|
verification?: Prisma.VerificationOmit
|
||||||
proclamation?: Prisma.ProclamationOmit
|
proclamation?: Prisma.ProclamationOmit
|
||||||
|
proclamationGroup?: Prisma.ProclamationGroupOmit
|
||||||
userProclamations?: Prisma.UserProclamationsOmit
|
userProclamations?: Prisma.UserProclamationsOmit
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyOmit
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyOmit
|
||||||
proclamationCategory?: Prisma.ProclamationCategoryOmit
|
proclamationCategory?: Prisma.ProclamationCategoryOmit
|
||||||
proclamationCategorySubscriptionLevel?: Prisma.ProclamationCategorySubscriptionLevelOmit
|
|
||||||
licenseKey?: Prisma.LicenseKeyOmit
|
licenseKey?: Prisma.LicenseKeyOmit
|
||||||
apiKey?: Prisma.ApiKeyOmit
|
apiKey?: Prisma.ApiKeyOmit
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelOmit
|
|
||||||
colorPalette?: Prisma.ColorPaletteOmit
|
colorPalette?: Prisma.ColorPaletteOmit
|
||||||
|
auditLog?: Prisma.AuditLogOmit
|
||||||
|
schedule?: Prisma.ScheduleOmit
|
||||||
|
block?: Prisma.BlockOmit
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsOmit
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Types for Logging */
|
/* Types for Logging */
|
||||||
|
|||||||
@@ -59,14 +59,17 @@ export const ModelName = {
|
|||||||
UserRole: 'UserRole',
|
UserRole: 'UserRole',
|
||||||
Verification: 'Verification',
|
Verification: 'Verification',
|
||||||
Proclamation: 'Proclamation',
|
Proclamation: 'Proclamation',
|
||||||
|
ProclamationGroup: 'ProclamationGroup',
|
||||||
UserProclamations: 'UserProclamations',
|
UserProclamations: 'UserProclamations',
|
||||||
SolfeggioFrequency: 'SolfeggioFrequency',
|
SolfeggioFrequency: 'SolfeggioFrequency',
|
||||||
ProclamationCategory: 'ProclamationCategory',
|
ProclamationCategory: 'ProclamationCategory',
|
||||||
ProclamationCategorySubscriptionLevel: 'ProclamationCategorySubscriptionLevel',
|
|
||||||
LicenseKey: 'LicenseKey',
|
LicenseKey: 'LicenseKey',
|
||||||
ApiKey: 'ApiKey',
|
ApiKey: 'ApiKey',
|
||||||
SubscriptionLevel: 'SubscriptionLevel',
|
ColorPalette: 'ColorPalette',
|
||||||
ColorPalette: 'ColorPalette'
|
AuditLog: 'AuditLog',
|
||||||
|
Schedule: 'Schedule',
|
||||||
|
Block: 'Block',
|
||||||
|
BlockProclamations: 'BlockProclamations'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
@@ -102,6 +105,7 @@ export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof User
|
|||||||
export const ProfileScalarFieldEnum = {
|
export const ProfileScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
firstName: 'firstName',
|
firstName: 'firstName',
|
||||||
lastName: 'lastName',
|
lastName: 'lastName',
|
||||||
phoneNumber: 'phoneNumber',
|
phoneNumber: 'phoneNumber',
|
||||||
@@ -187,12 +191,21 @@ export const ProclamationScalarFieldEnum = {
|
|||||||
content: 'content',
|
content: 'content',
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
categoryId: 'categoryId',
|
categoryId: 'categoryId',
|
||||||
subscriptionLevelId: 'subscriptionLevelId'
|
groupId: 'groupId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ProclamationScalarFieldEnum = (typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum]
|
export type ProclamationScalarFieldEnum = (typeof ProclamationScalarFieldEnum)[keyof typeof ProclamationScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ProclamationGroupScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
sortOrder: 'sortOrder'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ProclamationGroupScalarFieldEnum = (typeof ProclamationGroupScalarFieldEnum)[keyof typeof ProclamationGroupScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const UserProclamationsScalarFieldEnum = {
|
export const UserProclamationsScalarFieldEnum = {
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
proclamationId: 'proclamationId',
|
proclamationId: 'proclamationId',
|
||||||
@@ -215,27 +228,22 @@ export type SolfeggioFrequencyScalarFieldEnum = (typeof SolfeggioFrequencyScalar
|
|||||||
export const ProclamationCategoryScalarFieldEnum = {
|
export const ProclamationCategoryScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
sortOrder: 'sortOrder',
|
sortOrder: 'sortOrder'
|
||||||
parentId: 'parentId'
|
|
||||||
} as const
|
} 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 = {
|
export const LicenseKeyScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
key: 'key',
|
key: 'key',
|
||||||
active: 'active',
|
active: 'active',
|
||||||
level: 'level',
|
createdAt: 'createdAt',
|
||||||
userId: 'userId'
|
lastValidatedAt: 'lastValidatedAt',
|
||||||
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
|
systemName: 'systemName',
|
||||||
|
systemNameLower: 'systemNameLower'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type LicenseKeyScalarFieldEnum = (typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum]
|
export type LicenseKeyScalarFieldEnum = (typeof LicenseKeyScalarFieldEnum)[keyof typeof LicenseKeyScalarFieldEnum]
|
||||||
@@ -253,15 +261,6 @@ export const ApiKeyScalarFieldEnum = {
|
|||||||
export type ApiKeyScalarFieldEnum = (typeof ApiKeyScalarFieldEnum)[keyof typeof ApiKeyScalarFieldEnum]
|
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]
|
|
||||||
|
|
||||||
|
|
||||||
export const ColorPaletteScalarFieldEnum = {
|
export const ColorPaletteScalarFieldEnum = {
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
colors: 'colors'
|
colors: 'colors'
|
||||||
@@ -270,6 +269,62 @@ export const ColorPaletteScalarFieldEnum = {
|
|||||||
export type ColorPaletteScalarFieldEnum = (typeof ColorPaletteScalarFieldEnum)[keyof typeof ColorPaletteScalarFieldEnum]
|
export type ColorPaletteScalarFieldEnum = (typeof ColorPaletteScalarFieldEnum)[keyof typeof ColorPaletteScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const AuditLogScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
userId: 'userId',
|
||||||
|
email: 'email',
|
||||||
|
roleId: 'roleId',
|
||||||
|
roleName: 'roleName',
|
||||||
|
page: 'page',
|
||||||
|
action: 'action',
|
||||||
|
entity: 'entity',
|
||||||
|
entityId: 'entityId',
|
||||||
|
before: 'before',
|
||||||
|
after: 'after',
|
||||||
|
ip: 'ip'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type AuditLogScalarFieldEnum = (typeof AuditLogScalarFieldEnum)[keyof typeof AuditLogScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ScheduleScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
licenseKeyId: 'licenseKeyId',
|
||||||
|
userId: 'userId',
|
||||||
|
type: 'type',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ScheduleScalarFieldEnum = (typeof ScheduleScalarFieldEnum)[keyof typeof ScheduleScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const BlockScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
scheduleId: 'scheduleId',
|
||||||
|
day: 'day',
|
||||||
|
startMinutes: 'startMinutes',
|
||||||
|
endMinutes: 'endMinutes',
|
||||||
|
dynamicAudio: 'dynamicAudio',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type BlockScalarFieldEnum = (typeof BlockScalarFieldEnum)[keyof typeof BlockScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const BlockProclamationsScalarFieldEnum = {
|
||||||
|
blockId: 'blockId',
|
||||||
|
proclamationId: 'proclamationId',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type BlockProclamationsScalarFieldEnum = (typeof BlockProclamationsScalarFieldEnum)[keyof typeof BlockProclamationsScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const SortOrder = {
|
export const SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
@@ -285,6 +340,14 @@ export const JsonNullValueInput = {
|
|||||||
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullableJsonNullValueInput = {
|
||||||
|
DbNull: DbNull,
|
||||||
|
JsonNull: JsonNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
export const QueryMode = {
|
export const QueryMode = {
|
||||||
default: 'default',
|
default: 'default',
|
||||||
insensitive: 'insensitive'
|
insensitive: 'insensitive'
|
||||||
|
|||||||
@@ -16,12 +16,15 @@ export type * from './models/Account.ts'
|
|||||||
export type * from './models/UserRole.ts'
|
export type * from './models/UserRole.ts'
|
||||||
export type * from './models/Verification.ts'
|
export type * from './models/Verification.ts'
|
||||||
export type * from './models/Proclamation.ts'
|
export type * from './models/Proclamation.ts'
|
||||||
|
export type * from './models/ProclamationGroup.ts'
|
||||||
export type * from './models/UserProclamations.ts'
|
export type * from './models/UserProclamations.ts'
|
||||||
export type * from './models/SolfeggioFrequency.ts'
|
export type * from './models/SolfeggioFrequency.ts'
|
||||||
export type * from './models/ProclamationCategory.ts'
|
export type * from './models/ProclamationCategory.ts'
|
||||||
export type * from './models/ProclamationCategorySubscriptionLevel.ts'
|
|
||||||
export type * from './models/LicenseKey.ts'
|
export type * from './models/LicenseKey.ts'
|
||||||
export type * from './models/ApiKey.ts'
|
export type * from './models/ApiKey.ts'
|
||||||
export type * from './models/SubscriptionLevel.ts'
|
|
||||||
export type * from './models/ColorPalette.ts'
|
export type * from './models/ColorPalette.ts'
|
||||||
|
export type * from './models/AuditLog.ts'
|
||||||
|
export type * from './models/Schedule.ts'
|
||||||
|
export type * from './models/Block.ts'
|
||||||
|
export type * from './models/BlockProclamations.ts'
|
||||||
export type * from './commonInputTypes.ts'
|
export type * from './commonInputTypes.ts'
|
||||||
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
@@ -28,24 +28,36 @@ export type LicenseKeyMinAggregateOutputType = {
|
|||||||
id: string | null
|
id: string | null
|
||||||
key: string | null
|
key: string | null
|
||||||
active: boolean | null
|
active: boolean | null
|
||||||
level: string | null
|
createdAt: Date | null
|
||||||
|
lastValidatedAt: Date | null
|
||||||
userId: string | null
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
|
systemName: string | null
|
||||||
|
systemNameLower: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyMaxAggregateOutputType = {
|
export type LicenseKeyMaxAggregateOutputType = {
|
||||||
id: string | null
|
id: string | null
|
||||||
key: string | null
|
key: string | null
|
||||||
active: boolean | null
|
active: boolean | null
|
||||||
level: string | null
|
createdAt: Date | null
|
||||||
|
lastValidatedAt: Date | null
|
||||||
userId: string | null
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
|
systemName: string | null
|
||||||
|
systemNameLower: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCountAggregateOutputType = {
|
export type LicenseKeyCountAggregateOutputType = {
|
||||||
id: number
|
id: number
|
||||||
key: number
|
key: number
|
||||||
active: number
|
active: number
|
||||||
level: number
|
createdAt: number
|
||||||
|
lastValidatedAt: number
|
||||||
userId: number
|
userId: number
|
||||||
|
email: number
|
||||||
|
systemName: number
|
||||||
|
systemNameLower: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,24 +66,36 @@ export type LicenseKeyMinAggregateInputType = {
|
|||||||
id?: true
|
id?: true
|
||||||
key?: true
|
key?: true
|
||||||
active?: true
|
active?: true
|
||||||
level?: true
|
createdAt?: true
|
||||||
|
lastValidatedAt?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
|
systemName?: true
|
||||||
|
systemNameLower?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyMaxAggregateInputType = {
|
export type LicenseKeyMaxAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
key?: true
|
key?: true
|
||||||
active?: true
|
active?: true
|
||||||
level?: true
|
createdAt?: true
|
||||||
|
lastValidatedAt?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
|
systemName?: true
|
||||||
|
systemNameLower?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCountAggregateInputType = {
|
export type LicenseKeyCountAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
key?: true
|
key?: true
|
||||||
active?: true
|
active?: true
|
||||||
level?: true
|
createdAt?: true
|
||||||
|
lastValidatedAt?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
|
systemName?: true
|
||||||
|
systemNameLower?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +175,12 @@ export type LicenseKeyGroupByOutputType = {
|
|||||||
id: string
|
id: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt: Date
|
||||||
userId: string
|
lastValidatedAt: Date | null
|
||||||
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
|
systemName: string | null
|
||||||
|
systemNameLower: string | null
|
||||||
_count: LicenseKeyCountAggregateOutputType | null
|
_count: LicenseKeyCountAggregateOutputType | null
|
||||||
_min: LicenseKeyMinAggregateOutputType | null
|
_min: LicenseKeyMinAggregateOutputType | null
|
||||||
_max: LicenseKeyMaxAggregateOutputType | null
|
_max: LicenseKeyMaxAggregateOutputType | null
|
||||||
@@ -180,38 +208,58 @@ export type LicenseKeyWhereInput = {
|
|||||||
id?: Prisma.StringFilter<"LicenseKey"> | string
|
id?: Prisma.StringFilter<"LicenseKey"> | string
|
||||||
key?: Prisma.StringFilter<"LicenseKey"> | string
|
key?: Prisma.StringFilter<"LicenseKey"> | string
|
||||||
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
||||||
level?: Prisma.StringFilter<"LicenseKey"> | string
|
createdAt?: Prisma.DateTimeFilter<"LicenseKey"> | Date | string
|
||||||
userId?: Prisma.StringFilter<"LicenseKey"> | string
|
lastValidatedAt?: Prisma.DateTimeNullableFilter<"LicenseKey"> | Date | string | null
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
userId?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
email?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemName?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemNameLower?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
user?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||||
|
schedule?: Prisma.XOR<Prisma.ScheduleNullableScalarRelationFilter, Prisma.ScheduleWhereInput> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyOrderByWithRelationInput = {
|
export type LicenseKeyOrderByWithRelationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
key?: Prisma.SortOrder
|
key?: Prisma.SortOrder
|
||||||
active?: Prisma.SortOrder
|
active?: Prisma.SortOrder
|
||||||
level?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
lastValidatedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
userId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
systemName?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
systemNameLower?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
user?: Prisma.UserOrderByWithRelationInput
|
user?: Prisma.UserOrderByWithRelationInput
|
||||||
|
schedule?: Prisma.ScheduleOrderByWithRelationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyWhereUniqueInput = Prisma.AtLeast<{
|
export type LicenseKeyWhereUniqueInput = Prisma.AtLeast<{
|
||||||
id?: string
|
id?: string
|
||||||
key?: string
|
key?: string
|
||||||
|
userId_systemNameLower?: Prisma.LicenseKeyUserIdSystemNameLowerCompoundUniqueInput
|
||||||
AND?: Prisma.LicenseKeyWhereInput | Prisma.LicenseKeyWhereInput[]
|
AND?: Prisma.LicenseKeyWhereInput | Prisma.LicenseKeyWhereInput[]
|
||||||
OR?: Prisma.LicenseKeyWhereInput[]
|
OR?: Prisma.LicenseKeyWhereInput[]
|
||||||
NOT?: Prisma.LicenseKeyWhereInput | Prisma.LicenseKeyWhereInput[]
|
NOT?: Prisma.LicenseKeyWhereInput | Prisma.LicenseKeyWhereInput[]
|
||||||
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
||||||
level?: Prisma.StringFilter<"LicenseKey"> | string
|
createdAt?: Prisma.DateTimeFilter<"LicenseKey"> | Date | string
|
||||||
userId?: Prisma.StringFilter<"LicenseKey"> | string
|
lastValidatedAt?: Prisma.DateTimeNullableFilter<"LicenseKey"> | Date | string | null
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
userId?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
}, "id" | "key">
|
email?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemName?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemNameLower?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
user?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||||
|
schedule?: Prisma.XOR<Prisma.ScheduleNullableScalarRelationFilter, Prisma.ScheduleWhereInput> | null
|
||||||
|
}, "id" | "key" | "userId_systemNameLower">
|
||||||
|
|
||||||
export type LicenseKeyOrderByWithAggregationInput = {
|
export type LicenseKeyOrderByWithAggregationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
key?: Prisma.SortOrder
|
key?: Prisma.SortOrder
|
||||||
active?: Prisma.SortOrder
|
active?: Prisma.SortOrder
|
||||||
level?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
lastValidatedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
userId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
systemName?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
systemNameLower?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
_count?: Prisma.LicenseKeyCountOrderByAggregateInput
|
_count?: Prisma.LicenseKeyCountOrderByAggregateInput
|
||||||
_max?: Prisma.LicenseKeyMaxOrderByAggregateInput
|
_max?: Prisma.LicenseKeyMaxOrderByAggregateInput
|
||||||
_min?: Prisma.LicenseKeyMinOrderByAggregateInput
|
_min?: Prisma.LicenseKeyMinOrderByAggregateInput
|
||||||
@@ -224,63 +272,99 @@ export type LicenseKeyScalarWhereWithAggregatesInput = {
|
|||||||
id?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
id?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
||||||
key?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
key?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
||||||
active?: Prisma.BoolWithAggregatesFilter<"LicenseKey"> | boolean
|
active?: Prisma.BoolWithAggregatesFilter<"LicenseKey"> | boolean
|
||||||
level?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
createdAt?: Prisma.DateTimeWithAggregatesFilter<"LicenseKey"> | Date | string
|
||||||
userId?: Prisma.StringWithAggregatesFilter<"LicenseKey"> | string
|
lastValidatedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"LicenseKey"> | Date | string | null
|
||||||
|
userId?: Prisma.StringNullableWithAggregatesFilter<"LicenseKey"> | string | null
|
||||||
|
email?: Prisma.StringNullableWithAggregatesFilter<"LicenseKey"> | string | null
|
||||||
|
systemName?: Prisma.StringNullableWithAggregatesFilter<"LicenseKey"> | string | null
|
||||||
|
systemNameLower?: Prisma.StringNullableWithAggregatesFilter<"LicenseKey"> | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateInput = {
|
export type LicenseKeyCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
user: Prisma.UserCreateNestedOneWithoutLicenseKeysInput
|
lastValidatedAt?: Date | string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
user?: Prisma.UserCreateNestedOneWithoutLicenseKeysInput
|
||||||
|
schedule?: Prisma.ScheduleCreateNestedOneWithoutLicenseKeyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedCreateInput = {
|
export type LicenseKeyUncheckedCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
userId: string
|
lastValidatedAt?: Date | string | null
|
||||||
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
schedule?: Prisma.ScheduleUncheckedCreateNestedOneWithoutLicenseKeyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUpdateInput = {
|
export type LicenseKeyUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: Prisma.UserUpdateOneRequiredWithoutLicenseKeysNestedInput
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
user?: Prisma.UserUpdateOneWithoutLicenseKeysNestedInput
|
||||||
|
schedule?: Prisma.ScheduleUpdateOneWithoutLicenseKeyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedUpdateInput = {
|
export type LicenseKeyUncheckedUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
userId?: Prisma.StringFieldUpdateOperationsInput | string
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
schedule?: Prisma.ScheduleUncheckedUpdateOneWithoutLicenseKeyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateManyInput = {
|
export type LicenseKeyCreateManyInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
userId: string
|
lastValidatedAt?: Date | string | null
|
||||||
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUpdateManyMutationInput = {
|
export type LicenseKeyUpdateManyMutationInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedUpdateManyInput = {
|
export type LicenseKeyUncheckedUpdateManyInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
userId?: Prisma.StringFieldUpdateOperationsInput | string
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyListRelationFilter = {
|
export type LicenseKeyListRelationFilter = {
|
||||||
@@ -293,28 +377,50 @@ export type LicenseKeyOrderByRelationAggregateInput = {
|
|||||||
_count?: Prisma.SortOrder
|
_count?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUserIdSystemNameLowerCompoundUniqueInput = {
|
||||||
|
userId: string
|
||||||
|
systemNameLower: string
|
||||||
|
}
|
||||||
|
|
||||||
export type LicenseKeyCountOrderByAggregateInput = {
|
export type LicenseKeyCountOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
key?: Prisma.SortOrder
|
key?: Prisma.SortOrder
|
||||||
active?: Prisma.SortOrder
|
active?: Prisma.SortOrder
|
||||||
level?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
|
lastValidatedAt?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
|
systemName?: Prisma.SortOrder
|
||||||
|
systemNameLower?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyMaxOrderByAggregateInput = {
|
export type LicenseKeyMaxOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
key?: Prisma.SortOrder
|
key?: Prisma.SortOrder
|
||||||
active?: Prisma.SortOrder
|
active?: Prisma.SortOrder
|
||||||
level?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
|
lastValidatedAt?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
|
systemName?: Prisma.SortOrder
|
||||||
|
systemNameLower?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyMinOrderByAggregateInput = {
|
export type LicenseKeyMinOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
key?: Prisma.SortOrder
|
key?: Prisma.SortOrder
|
||||||
active?: Prisma.SortOrder
|
active?: Prisma.SortOrder
|
||||||
level?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
|
lastValidatedAt?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
|
systemName?: Prisma.SortOrder
|
||||||
|
systemNameLower?: Prisma.SortOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyNullableScalarRelationFilter = {
|
||||||
|
is?: Prisma.LicenseKeyWhereInput | null
|
||||||
|
isNot?: Prisma.LicenseKeyWhereInput | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateNestedManyWithoutUserInput = {
|
export type LicenseKeyCreateNestedManyWithoutUserInput = {
|
||||||
@@ -359,18 +465,44 @@ export type LicenseKeyUncheckedUpdateManyWithoutUserNestedInput = {
|
|||||||
deleteMany?: Prisma.LicenseKeyScalarWhereInput | Prisma.LicenseKeyScalarWhereInput[]
|
deleteMany?: Prisma.LicenseKeyScalarWhereInput | Prisma.LicenseKeyScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyCreateNestedOneWithoutScheduleInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.LicenseKeyCreateWithoutScheduleInput, Prisma.LicenseKeyUncheckedCreateWithoutScheduleInput>
|
||||||
|
connectOrCreate?: Prisma.LicenseKeyCreateOrConnectWithoutScheduleInput
|
||||||
|
connect?: Prisma.LicenseKeyWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUpdateOneWithoutScheduleNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.LicenseKeyCreateWithoutScheduleInput, Prisma.LicenseKeyUncheckedCreateWithoutScheduleInput>
|
||||||
|
connectOrCreate?: Prisma.LicenseKeyCreateOrConnectWithoutScheduleInput
|
||||||
|
upsert?: Prisma.LicenseKeyUpsertWithoutScheduleInput
|
||||||
|
disconnect?: Prisma.LicenseKeyWhereInput | boolean
|
||||||
|
delete?: Prisma.LicenseKeyWhereInput | boolean
|
||||||
|
connect?: Prisma.LicenseKeyWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.LicenseKeyUpdateToOneWithWhereWithoutScheduleInput, Prisma.LicenseKeyUpdateWithoutScheduleInput>, Prisma.LicenseKeyUncheckedUpdateWithoutScheduleInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateWithoutUserInput = {
|
export type LicenseKeyCreateWithoutUserInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
|
lastValidatedAt?: Date | string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
schedule?: Prisma.ScheduleCreateNestedOneWithoutLicenseKeyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedCreateWithoutUserInput = {
|
export type LicenseKeyUncheckedCreateWithoutUserInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
|
lastValidatedAt?: Date | string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
schedule?: Prisma.ScheduleUncheckedCreateNestedOneWithoutLicenseKeyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateOrConnectWithoutUserInput = {
|
export type LicenseKeyCreateOrConnectWithoutUserInput = {
|
||||||
@@ -406,36 +538,122 @@ export type LicenseKeyScalarWhereInput = {
|
|||||||
id?: Prisma.StringFilter<"LicenseKey"> | string
|
id?: Prisma.StringFilter<"LicenseKey"> | string
|
||||||
key?: Prisma.StringFilter<"LicenseKey"> | string
|
key?: Prisma.StringFilter<"LicenseKey"> | string
|
||||||
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
active?: Prisma.BoolFilter<"LicenseKey"> | boolean
|
||||||
level?: Prisma.StringFilter<"LicenseKey"> | string
|
createdAt?: Prisma.DateTimeFilter<"LicenseKey"> | Date | string
|
||||||
userId?: Prisma.StringFilter<"LicenseKey"> | string
|
lastValidatedAt?: Prisma.DateTimeNullableFilter<"LicenseKey"> | Date | string | null
|
||||||
|
userId?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
email?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemName?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
systemNameLower?: Prisma.StringNullableFilter<"LicenseKey"> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyCreateWithoutScheduleInput = {
|
||||||
|
id?: string
|
||||||
|
key: string
|
||||||
|
active: boolean
|
||||||
|
createdAt?: Date | string
|
||||||
|
lastValidatedAt?: Date | string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
user?: Prisma.UserCreateNestedOneWithoutLicenseKeysInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUncheckedCreateWithoutScheduleInput = {
|
||||||
|
id?: string
|
||||||
|
key: string
|
||||||
|
active: boolean
|
||||||
|
createdAt?: Date | string
|
||||||
|
lastValidatedAt?: Date | string | null
|
||||||
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyCreateOrConnectWithoutScheduleInput = {
|
||||||
|
where: Prisma.LicenseKeyWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.LicenseKeyCreateWithoutScheduleInput, Prisma.LicenseKeyUncheckedCreateWithoutScheduleInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUpsertWithoutScheduleInput = {
|
||||||
|
update: Prisma.XOR<Prisma.LicenseKeyUpdateWithoutScheduleInput, Prisma.LicenseKeyUncheckedUpdateWithoutScheduleInput>
|
||||||
|
create: Prisma.XOR<Prisma.LicenseKeyCreateWithoutScheduleInput, Prisma.LicenseKeyUncheckedCreateWithoutScheduleInput>
|
||||||
|
where?: Prisma.LicenseKeyWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUpdateToOneWithWhereWithoutScheduleInput = {
|
||||||
|
where?: Prisma.LicenseKeyWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.LicenseKeyUpdateWithoutScheduleInput, Prisma.LicenseKeyUncheckedUpdateWithoutScheduleInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUpdateWithoutScheduleInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
user?: Prisma.UserUpdateOneWithoutLicenseKeysNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LicenseKeyUncheckedUpdateWithoutScheduleInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyCreateManyUserInput = {
|
export type LicenseKeyCreateManyUserInput = {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt?: Date | string
|
||||||
|
lastValidatedAt?: Date | string | null
|
||||||
|
email?: string | null
|
||||||
|
systemName?: string | null
|
||||||
|
systemNameLower?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUpdateWithoutUserInput = {
|
export type LicenseKeyUpdateWithoutUserInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
schedule?: Prisma.ScheduleUpdateOneWithoutLicenseKeyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedUpdateWithoutUserInput = {
|
export type LicenseKeyUncheckedUpdateWithoutUserInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
schedule?: Prisma.ScheduleUncheckedUpdateOneWithoutLicenseKeyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyUncheckedUpdateManyWithoutUserInput = {
|
export type LicenseKeyUncheckedUpdateManyWithoutUserInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
key?: Prisma.StringFieldUpdateOperationsInput | string
|
key?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
active?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
level?: Prisma.StringFieldUpdateOperationsInput | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
lastValidatedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
systemNameLower?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -444,59 +662,82 @@ export type LicenseKeySelect<ExtArgs extends runtime.Types.Extensions.InternalAr
|
|||||||
id?: boolean
|
id?: boolean
|
||||||
key?: boolean
|
key?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
level?: boolean
|
createdAt?: boolean
|
||||||
|
lastValidatedAt?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
email?: boolean
|
||||||
|
systemName?: boolean
|
||||||
|
systemNameLower?: boolean
|
||||||
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
|
schedule?: boolean | Prisma.LicenseKey$scheduleArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["licenseKey"]>
|
}, ExtArgs["result"]["licenseKey"]>
|
||||||
|
|
||||||
export type LicenseKeySelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type LicenseKeySelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
key?: boolean
|
key?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
level?: boolean
|
createdAt?: boolean
|
||||||
|
lastValidatedAt?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
email?: boolean
|
||||||
|
systemName?: boolean
|
||||||
|
systemNameLower?: boolean
|
||||||
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["licenseKey"]>
|
}, ExtArgs["result"]["licenseKey"]>
|
||||||
|
|
||||||
export type LicenseKeySelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type LicenseKeySelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
key?: boolean
|
key?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
level?: boolean
|
createdAt?: boolean
|
||||||
|
lastValidatedAt?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
email?: boolean
|
||||||
|
systemName?: boolean
|
||||||
|
systemNameLower?: boolean
|
||||||
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["licenseKey"]>
|
}, ExtArgs["result"]["licenseKey"]>
|
||||||
|
|
||||||
export type LicenseKeySelectScalar = {
|
export type LicenseKeySelectScalar = {
|
||||||
id?: boolean
|
id?: boolean
|
||||||
key?: boolean
|
key?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
level?: boolean
|
createdAt?: boolean
|
||||||
|
lastValidatedAt?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
|
email?: boolean
|
||||||
|
systemName?: boolean
|
||||||
|
systemNameLower?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicenseKeyOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "key" | "active" | "level" | "userId", ExtArgs["result"]["licenseKey"]>
|
export type LicenseKeyOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "key" | "active" | "createdAt" | "lastValidatedAt" | "userId" | "email" | "systemName" | "systemNameLower", ExtArgs["result"]["licenseKey"]>
|
||||||
export type LicenseKeyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type LicenseKeyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
|
schedule?: boolean | Prisma.LicenseKey$scheduleArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type LicenseKeyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type LicenseKeyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type LicenseKeyIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type LicenseKeyIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.LicenseKey$userArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $LicenseKeyPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type $LicenseKeyPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
name: "LicenseKey"
|
name: "LicenseKey"
|
||||||
objects: {
|
objects: {
|
||||||
user: Prisma.$UserPayload<ExtArgs>
|
user: Prisma.$UserPayload<ExtArgs> | null
|
||||||
|
schedule: Prisma.$SchedulePayload<ExtArgs> | null
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
level: string
|
createdAt: Date
|
||||||
userId: string
|
lastValidatedAt: Date | null
|
||||||
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
|
systemName: string | null
|
||||||
|
systemNameLower: string | null
|
||||||
}, ExtArgs["result"]["licenseKey"]>
|
}, ExtArgs["result"]["licenseKey"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
}
|
}
|
||||||
@@ -891,7 +1132,8 @@ readonly fields: LicenseKeyFieldRefs;
|
|||||||
*/
|
*/
|
||||||
export interface Prisma__LicenseKeyClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
export interface Prisma__LicenseKeyClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
user<T extends Prisma.UserDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.UserDefaultArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
user<T extends Prisma.LicenseKey$userArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.LicenseKey$userArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
|
schedule<T extends Prisma.LicenseKey$scheduleArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.LicenseKey$scheduleArgs<ExtArgs>>): Prisma.Prisma__ScheduleClient<runtime.Types.Result.GetResult<Prisma.$SchedulePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -924,8 +1166,12 @@ export interface LicenseKeyFieldRefs {
|
|||||||
readonly id: Prisma.FieldRef<"LicenseKey", 'String'>
|
readonly id: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
readonly key: Prisma.FieldRef<"LicenseKey", 'String'>
|
readonly key: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
readonly active: Prisma.FieldRef<"LicenseKey", 'Boolean'>
|
readonly active: Prisma.FieldRef<"LicenseKey", 'Boolean'>
|
||||||
readonly level: Prisma.FieldRef<"LicenseKey", 'String'>
|
readonly createdAt: Prisma.FieldRef<"LicenseKey", 'DateTime'>
|
||||||
|
readonly lastValidatedAt: Prisma.FieldRef<"LicenseKey", 'DateTime'>
|
||||||
readonly userId: Prisma.FieldRef<"LicenseKey", 'String'>
|
readonly userId: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
|
readonly email: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
|
readonly systemName: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
|
readonly systemNameLower: Prisma.FieldRef<"LicenseKey", 'String'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1326,6 +1572,44 @@ export type LicenseKeyDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.In
|
|||||||
limit?: number
|
limit?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LicenseKey.user
|
||||||
|
*/
|
||||||
|
export type LicenseKey$userArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the User
|
||||||
|
*/
|
||||||
|
select?: Prisma.UserSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the User
|
||||||
|
*/
|
||||||
|
omit?: Prisma.UserOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.UserInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LicenseKey.schedule
|
||||||
|
*/
|
||||||
|
export type LicenseKey$scheduleArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the Schedule
|
||||||
|
*/
|
||||||
|
select?: Prisma.ScheduleSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the Schedule
|
||||||
|
*/
|
||||||
|
omit?: Prisma.ScheduleOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.ScheduleInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.ScheduleWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LicenseKey without action
|
* LicenseKey without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ export type AggregateProclamation = {
|
|||||||
export type ProclamationAvgAggregateOutputType = {
|
export type ProclamationAvgAggregateOutputType = {
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
subscriptionLevelId: number | null
|
groupId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumAggregateOutputType = {
|
export type ProclamationSumAggregateOutputType = {
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
subscriptionLevelId: number | null
|
groupId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMinAggregateOutputType = {
|
export type ProclamationMinAggregateOutputType = {
|
||||||
@@ -44,7 +44,7 @@ export type ProclamationMinAggregateOutputType = {
|
|||||||
content: string | null
|
content: string | null
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
subscriptionLevelId: number | null
|
groupId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMaxAggregateOutputType = {
|
export type ProclamationMaxAggregateOutputType = {
|
||||||
@@ -53,7 +53,7 @@ export type ProclamationMaxAggregateOutputType = {
|
|||||||
content: string | null
|
content: string | null
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
subscriptionLevelId: number | null
|
groupId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCountAggregateOutputType = {
|
export type ProclamationCountAggregateOutputType = {
|
||||||
@@ -62,7 +62,7 @@ export type ProclamationCountAggregateOutputType = {
|
|||||||
content: number
|
content: number
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +70,13 @@ export type ProclamationCountAggregateOutputType = {
|
|||||||
export type ProclamationAvgAggregateInputType = {
|
export type ProclamationAvgAggregateInputType = {
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
subscriptionLevelId?: true
|
groupId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumAggregateInputType = {
|
export type ProclamationSumAggregateInputType = {
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
subscriptionLevelId?: true
|
groupId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMinAggregateInputType = {
|
export type ProclamationMinAggregateInputType = {
|
||||||
@@ -85,7 +85,7 @@ export type ProclamationMinAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
subscriptionLevelId?: true
|
groupId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMaxAggregateInputType = {
|
export type ProclamationMaxAggregateInputType = {
|
||||||
@@ -94,7 +94,7 @@ export type ProclamationMaxAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
subscriptionLevelId?: true
|
groupId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCountAggregateInputType = {
|
export type ProclamationCountAggregateInputType = {
|
||||||
@@ -103,7 +103,7 @@ export type ProclamationCountAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
subscriptionLevelId?: true
|
groupId?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ export type ProclamationGroupByOutputType = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
_count: ProclamationCountAggregateOutputType | null
|
_count: ProclamationCountAggregateOutputType | null
|
||||||
_avg: ProclamationAvgAggregateOutputType | null
|
_avg: ProclamationAvgAggregateOutputType | null
|
||||||
_sum: ProclamationSumAggregateOutputType | null
|
_sum: ProclamationSumAggregateOutputType | null
|
||||||
@@ -231,11 +231,12 @@ export type ProclamationWhereInput = {
|
|||||||
content?: Prisma.StringFilter<"Proclamation"> | string
|
content?: Prisma.StringFilter<"Proclamation"> | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
groupId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
solfeggioFrequency?: Prisma.XOR<Prisma.SolfeggioFrequencyScalarRelationFilter, Prisma.SolfeggioFrequencyWhereInput>
|
solfeggioFrequency?: Prisma.XOR<Prisma.SolfeggioFrequencyScalarRelationFilter, Prisma.SolfeggioFrequencyWhereInput>
|
||||||
category?: Prisma.XOR<Prisma.ProclamationCategoryScalarRelationFilter, Prisma.ProclamationCategoryWhereInput>
|
category?: Prisma.XOR<Prisma.ProclamationCategoryScalarRelationFilter, Prisma.ProclamationCategoryWhereInput>
|
||||||
|
group?: Prisma.XOR<Prisma.ProclamationGroupScalarRelationFilter, Prisma.ProclamationGroupWhereInput>
|
||||||
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
||||||
subscriptionLevel?: Prisma.XOR<Prisma.SubscriptionLevelScalarRelationFilter, Prisma.SubscriptionLevelWhereInput>
|
blockProclamations?: Prisma.BlockProclamationsListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationOrderByWithRelationInput = {
|
export type ProclamationOrderByWithRelationInput = {
|
||||||
@@ -244,11 +245,12 @@ export type ProclamationOrderByWithRelationInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyOrderByWithRelationInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyOrderByWithRelationInput
|
||||||
category?: Prisma.ProclamationCategoryOrderByWithRelationInput
|
category?: Prisma.ProclamationCategoryOrderByWithRelationInput
|
||||||
|
group?: Prisma.ProclamationGroupOrderByWithRelationInput
|
||||||
userProclamations?: Prisma.UserProclamationsOrderByRelationAggregateInput
|
userProclamations?: Prisma.UserProclamationsOrderByRelationAggregateInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelOrderByWithRelationInput
|
blockProclamations?: Prisma.BlockProclamationsOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationWhereUniqueInput = Prisma.AtLeast<{
|
export type ProclamationWhereUniqueInput = Prisma.AtLeast<{
|
||||||
@@ -260,11 +262,12 @@ export type ProclamationWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
content?: Prisma.StringFilter<"Proclamation"> | string
|
content?: Prisma.StringFilter<"Proclamation"> | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
groupId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
solfeggioFrequency?: Prisma.XOR<Prisma.SolfeggioFrequencyScalarRelationFilter, Prisma.SolfeggioFrequencyWhereInput>
|
solfeggioFrequency?: Prisma.XOR<Prisma.SolfeggioFrequencyScalarRelationFilter, Prisma.SolfeggioFrequencyWhereInput>
|
||||||
category?: Prisma.XOR<Prisma.ProclamationCategoryScalarRelationFilter, Prisma.ProclamationCategoryWhereInput>
|
category?: Prisma.XOR<Prisma.ProclamationCategoryScalarRelationFilter, Prisma.ProclamationCategoryWhereInput>
|
||||||
|
group?: Prisma.XOR<Prisma.ProclamationGroupScalarRelationFilter, Prisma.ProclamationGroupWhereInput>
|
||||||
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
||||||
subscriptionLevel?: Prisma.XOR<Prisma.SubscriptionLevelScalarRelationFilter, Prisma.SubscriptionLevelWhereInput>
|
blockProclamations?: Prisma.BlockProclamationsListRelationFilter
|
||||||
}, "id" | "name">
|
}, "id" | "name">
|
||||||
|
|
||||||
export type ProclamationOrderByWithAggregationInput = {
|
export type ProclamationOrderByWithAggregationInput = {
|
||||||
@@ -273,7 +276,7 @@ export type ProclamationOrderByWithAggregationInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
_count?: Prisma.ProclamationCountOrderByAggregateInput
|
_count?: Prisma.ProclamationCountOrderByAggregateInput
|
||||||
_avg?: Prisma.ProclamationAvgOrderByAggregateInput
|
_avg?: Prisma.ProclamationAvgOrderByAggregateInput
|
||||||
_max?: Prisma.ProclamationMaxOrderByAggregateInput
|
_max?: Prisma.ProclamationMaxOrderByAggregateInput
|
||||||
@@ -290,7 +293,7 @@ export type ProclamationScalarWhereWithAggregatesInput = {
|
|||||||
content?: Prisma.StringWithAggregatesFilter<"Proclamation"> | string
|
content?: Prisma.StringWithAggregatesFilter<"Proclamation"> | string
|
||||||
solfeggioFrequencyId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
solfeggioFrequencyId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
||||||
categoryId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
categoryId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
||||||
subscriptionLevelId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
groupId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateInput = {
|
export type ProclamationCreateInput = {
|
||||||
@@ -299,8 +302,9 @@ export type ProclamationCreateInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
|
group: Prisma.ProclamationGroupCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
subscriptionLevel: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
blockProclamations?: Prisma.BlockProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateInput = {
|
export type ProclamationUncheckedCreateInput = {
|
||||||
@@ -309,8 +313,9 @@ export type ProclamationUncheckedCreateInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateInput = {
|
export type ProclamationUpdateInput = {
|
||||||
@@ -319,8 +324,9 @@ export type ProclamationUpdateInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
group?: Prisma.ProclamationGroupUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
blockProclamations?: Prisma.BlockProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateInput = {
|
export type ProclamationUncheckedUpdateInput = {
|
||||||
@@ -329,8 +335,9 @@ export type ProclamationUncheckedUpdateInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateManyInput = {
|
export type ProclamationCreateManyInput = {
|
||||||
@@ -339,7 +346,7 @@ export type ProclamationCreateManyInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateManyMutationInput = {
|
export type ProclamationUpdateManyMutationInput = {
|
||||||
@@ -354,7 +361,7 @@ export type ProclamationUncheckedUpdateManyInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCountOrderByAggregateInput = {
|
export type ProclamationCountOrderByAggregateInput = {
|
||||||
@@ -363,13 +370,13 @@ export type ProclamationCountOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationAvgOrderByAggregateInput = {
|
export type ProclamationAvgOrderByAggregateInput = {
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMaxOrderByAggregateInput = {
|
export type ProclamationMaxOrderByAggregateInput = {
|
||||||
@@ -378,7 +385,7 @@ export type ProclamationMaxOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationMinOrderByAggregateInput = {
|
export type ProclamationMinOrderByAggregateInput = {
|
||||||
@@ -387,18 +394,13 @@ export type ProclamationMinOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumOrderByAggregateInput = {
|
export type ProclamationSumOrderByAggregateInput = {
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
groupId?: Prisma.SortOrder
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationScalarRelationFilter = {
|
|
||||||
is?: Prisma.ProclamationWhereInput
|
|
||||||
isNot?: Prisma.ProclamationWhereInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationListRelationFilter = {
|
export type ProclamationListRelationFilter = {
|
||||||
@@ -411,6 +413,53 @@ export type ProclamationOrderByRelationAggregateInput = {
|
|||||||
_count?: Prisma.SortOrder
|
_count?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProclamationScalarRelationFilter = {
|
||||||
|
is?: Prisma.ProclamationWhereInput
|
||||||
|
isNot?: Prisma.ProclamationWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationCreateNestedManyWithoutGroupInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput> | Prisma.ProclamationCreateWithoutGroupInput[] | Prisma.ProclamationUncheckedCreateWithoutGroupInput[]
|
||||||
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutGroupInput | Prisma.ProclamationCreateOrConnectWithoutGroupInput[]
|
||||||
|
createMany?: Prisma.ProclamationCreateManyGroupInputEnvelope
|
||||||
|
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUncheckedCreateNestedManyWithoutGroupInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput> | Prisma.ProclamationCreateWithoutGroupInput[] | Prisma.ProclamationUncheckedCreateWithoutGroupInput[]
|
||||||
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutGroupInput | Prisma.ProclamationCreateOrConnectWithoutGroupInput[]
|
||||||
|
createMany?: Prisma.ProclamationCreateManyGroupInputEnvelope
|
||||||
|
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUpdateManyWithoutGroupNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput> | Prisma.ProclamationCreateWithoutGroupInput[] | Prisma.ProclamationUncheckedCreateWithoutGroupInput[]
|
||||||
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutGroupInput | Prisma.ProclamationCreateOrConnectWithoutGroupInput[]
|
||||||
|
upsert?: Prisma.ProclamationUpsertWithWhereUniqueWithoutGroupInput | Prisma.ProclamationUpsertWithWhereUniqueWithoutGroupInput[]
|
||||||
|
createMany?: Prisma.ProclamationCreateManyGroupInputEnvelope
|
||||||
|
set?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
disconnect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
delete?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
update?: Prisma.ProclamationUpdateWithWhereUniqueWithoutGroupInput | Prisma.ProclamationUpdateWithWhereUniqueWithoutGroupInput[]
|
||||||
|
updateMany?: Prisma.ProclamationUpdateManyWithWhereWithoutGroupInput | Prisma.ProclamationUpdateManyWithWhereWithoutGroupInput[]
|
||||||
|
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUncheckedUpdateManyWithoutGroupNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput> | Prisma.ProclamationCreateWithoutGroupInput[] | Prisma.ProclamationUncheckedCreateWithoutGroupInput[]
|
||||||
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutGroupInput | Prisma.ProclamationCreateOrConnectWithoutGroupInput[]
|
||||||
|
upsert?: Prisma.ProclamationUpsertWithWhereUniqueWithoutGroupInput | Prisma.ProclamationUpsertWithWhereUniqueWithoutGroupInput[]
|
||||||
|
createMany?: Prisma.ProclamationCreateManyGroupInputEnvelope
|
||||||
|
set?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
disconnect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
delete?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
||||||
|
update?: Prisma.ProclamationUpdateWithWhereUniqueWithoutGroupInput | Prisma.ProclamationUpdateWithWhereUniqueWithoutGroupInput[]
|
||||||
|
updateMany?: Prisma.ProclamationUpdateManyWithWhereWithoutGroupInput | Prisma.ProclamationUpdateManyWithWhereWithoutGroupInput[]
|
||||||
|
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
||||||
|
}
|
||||||
|
|
||||||
export type ProclamationCreateNestedOneWithoutUserProclamationsInput = {
|
export type ProclamationCreateNestedOneWithoutUserProclamationsInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutUserProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutUserProclamationsInput>
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutUserProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutUserProclamationsInput>
|
||||||
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutUserProclamationsInput
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutUserProclamationsInput
|
||||||
@@ -509,46 +558,76 @@ export type ProclamationUncheckedUpdateManyWithoutCategoryNestedInput = {
|
|||||||
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateNestedManyWithoutSubscriptionLevelInput = {
|
export type ProclamationCreateNestedOneWithoutBlockProclamationsInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput> | Prisma.ProclamationCreateWithoutSubscriptionLevelInput[] | Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput[]
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutBlockProclamationsInput>
|
||||||
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput | Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput[]
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutBlockProclamationsInput
|
||||||
createMany?: Prisma.ProclamationCreateManySubscriptionLevelInputEnvelope
|
connect?: Prisma.ProclamationWhereUniqueInput
|
||||||
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateNestedManyWithoutSubscriptionLevelInput = {
|
export type ProclamationUpdateOneRequiredWithoutBlockProclamationsNestedInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput> | Prisma.ProclamationCreateWithoutSubscriptionLevelInput[] | Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput[]
|
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutBlockProclamationsInput>
|
||||||
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput | Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput[]
|
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutBlockProclamationsInput
|
||||||
createMany?: Prisma.ProclamationCreateManySubscriptionLevelInputEnvelope
|
upsert?: Prisma.ProclamationUpsertWithoutBlockProclamationsInput
|
||||||
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
connect?: Prisma.ProclamationWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.ProclamationUpdateToOneWithWhereWithoutBlockProclamationsInput, Prisma.ProclamationUpdateWithoutBlockProclamationsInput>, Prisma.ProclamationUncheckedUpdateWithoutBlockProclamationsInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateManyWithoutSubscriptionLevelNestedInput = {
|
export type ProclamationCreateWithoutGroupInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput> | Prisma.ProclamationCreateWithoutSubscriptionLevelInput[] | Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput[]
|
id?: string
|
||||||
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput | Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput[]
|
name: string
|
||||||
upsert?: Prisma.ProclamationUpsertWithWhereUniqueWithoutSubscriptionLevelInput | Prisma.ProclamationUpsertWithWhereUniqueWithoutSubscriptionLevelInput[]
|
content: string
|
||||||
createMany?: Prisma.ProclamationCreateManySubscriptionLevelInputEnvelope
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
set?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
disconnect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
delete?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
blockProclamations?: Prisma.BlockProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
|
||||||
update?: Prisma.ProclamationUpdateWithWhereUniqueWithoutSubscriptionLevelInput | Prisma.ProclamationUpdateWithWhereUniqueWithoutSubscriptionLevelInput[]
|
|
||||||
updateMany?: Prisma.ProclamationUpdateManyWithWhereWithoutSubscriptionLevelInput | Prisma.ProclamationUpdateManyWithWhereWithoutSubscriptionLevelInput[]
|
|
||||||
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateManyWithoutSubscriptionLevelNestedInput = {
|
export type ProclamationUncheckedCreateWithoutGroupInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput> | Prisma.ProclamationCreateWithoutSubscriptionLevelInput[] | Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput[]
|
id?: string
|
||||||
connectOrCreate?: Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput | Prisma.ProclamationCreateOrConnectWithoutSubscriptionLevelInput[]
|
name: string
|
||||||
upsert?: Prisma.ProclamationUpsertWithWhereUniqueWithoutSubscriptionLevelInput | Prisma.ProclamationUpsertWithWhereUniqueWithoutSubscriptionLevelInput[]
|
content: string
|
||||||
createMany?: Prisma.ProclamationCreateManySubscriptionLevelInputEnvelope
|
solfeggioFrequencyId: number
|
||||||
set?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
categoryId: number
|
||||||
disconnect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
delete?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
blockProclamations?: Prisma.BlockProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
connect?: Prisma.ProclamationWhereUniqueInput | Prisma.ProclamationWhereUniqueInput[]
|
}
|
||||||
update?: Prisma.ProclamationUpdateWithWhereUniqueWithoutSubscriptionLevelInput | Prisma.ProclamationUpdateWithWhereUniqueWithoutSubscriptionLevelInput[]
|
|
||||||
updateMany?: Prisma.ProclamationUpdateManyWithWhereWithoutSubscriptionLevelInput | Prisma.ProclamationUpdateManyWithWhereWithoutSubscriptionLevelInput[]
|
export type ProclamationCreateOrConnectWithoutGroupInput = {
|
||||||
deleteMany?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
where: Prisma.ProclamationWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationCreateManyGroupInputEnvelope = {
|
||||||
|
data: Prisma.ProclamationCreateManyGroupInput | Prisma.ProclamationCreateManyGroupInput[]
|
||||||
|
skipDuplicates?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUpsertWithWhereUniqueWithoutGroupInput = {
|
||||||
|
where: Prisma.ProclamationWhereUniqueInput
|
||||||
|
update: Prisma.XOR<Prisma.ProclamationUpdateWithoutGroupInput, Prisma.ProclamationUncheckedUpdateWithoutGroupInput>
|
||||||
|
create: Prisma.XOR<Prisma.ProclamationCreateWithoutGroupInput, Prisma.ProclamationUncheckedCreateWithoutGroupInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUpdateWithWhereUniqueWithoutGroupInput = {
|
||||||
|
where: Prisma.ProclamationWhereUniqueInput
|
||||||
|
data: Prisma.XOR<Prisma.ProclamationUpdateWithoutGroupInput, Prisma.ProclamationUncheckedUpdateWithoutGroupInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUpdateManyWithWhereWithoutGroupInput = {
|
||||||
|
where: Prisma.ProclamationScalarWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutGroupInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationScalarWhereInput = {
|
||||||
|
AND?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
||||||
|
OR?: Prisma.ProclamationScalarWhereInput[]
|
||||||
|
NOT?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
||||||
|
id?: Prisma.StringFilter<"Proclamation"> | string
|
||||||
|
name?: Prisma.StringFilter<"Proclamation"> | string
|
||||||
|
content?: Prisma.StringFilter<"Proclamation"> | string
|
||||||
|
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
|
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
|
groupId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateWithoutUserProclamationsInput = {
|
export type ProclamationCreateWithoutUserProclamationsInput = {
|
||||||
@@ -557,7 +636,8 @@ export type ProclamationCreateWithoutUserProclamationsInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
subscriptionLevel: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
group: Prisma.ProclamationGroupCreateNestedOneWithoutProclamationsInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateWithoutUserProclamationsInput = {
|
export type ProclamationUncheckedCreateWithoutUserProclamationsInput = {
|
||||||
@@ -566,7 +646,8 @@ export type ProclamationUncheckedCreateWithoutUserProclamationsInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateOrConnectWithoutUserProclamationsInput = {
|
export type ProclamationCreateOrConnectWithoutUserProclamationsInput = {
|
||||||
@@ -591,7 +672,8 @@ export type ProclamationUpdateWithoutUserProclamationsInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
group?: Prisma.ProclamationGroupUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateWithoutUserProclamationsInput = {
|
export type ProclamationUncheckedUpdateWithoutUserProclamationsInput = {
|
||||||
@@ -600,7 +682,8 @@ export type ProclamationUncheckedUpdateWithoutUserProclamationsInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateWithoutSolfeggioFrequencyInput = {
|
export type ProclamationCreateWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -608,8 +691,9 @@ export type ProclamationCreateWithoutSolfeggioFrequencyInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
|
group: Prisma.ProclamationGroupCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
subscriptionLevel: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
blockProclamations?: Prisma.BlockProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateWithoutSolfeggioFrequencyInput = {
|
export type ProclamationUncheckedCreateWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -617,8 +701,9 @@ export type ProclamationUncheckedCreateWithoutSolfeggioFrequencyInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateOrConnectWithoutSolfeggioFrequencyInput = {
|
export type ProclamationCreateOrConnectWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -647,25 +732,14 @@ export type ProclamationUpdateManyWithWhereWithoutSolfeggioFrequencyInput = {
|
|||||||
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyInput>
|
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationScalarWhereInput = {
|
|
||||||
AND?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
|
||||||
OR?: Prisma.ProclamationScalarWhereInput[]
|
|
||||||
NOT?: Prisma.ProclamationScalarWhereInput | Prisma.ProclamationScalarWhereInput[]
|
|
||||||
id?: Prisma.StringFilter<"Proclamation"> | string
|
|
||||||
name?: Prisma.StringFilter<"Proclamation"> | string
|
|
||||||
content?: Prisma.StringFilter<"Proclamation"> | string
|
|
||||||
solfeggioFrequencyId?: Prisma.IntFilter<"Proclamation"> | number
|
|
||||||
categoryId?: Prisma.IntFilter<"Proclamation"> | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCreateWithoutCategoryInput = {
|
export type ProclamationCreateWithoutCategoryInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
|
group: Prisma.ProclamationGroupCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
subscriptionLevel: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
blockProclamations?: Prisma.BlockProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateWithoutCategoryInput = {
|
export type ProclamationUncheckedCreateWithoutCategoryInput = {
|
||||||
@@ -673,8 +747,9 @@ export type ProclamationUncheckedCreateWithoutCategoryInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateOrConnectWithoutCategoryInput = {
|
export type ProclamationCreateOrConnectWithoutCategoryInput = {
|
||||||
@@ -703,48 +778,96 @@ export type ProclamationUpdateManyWithWhereWithoutCategoryInput = {
|
|||||||
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutCategoryInput>
|
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutCategoryInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateWithoutSubscriptionLevelInput = {
|
export type ProclamationCreateWithoutBlockProclamationsInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
|
group: Prisma.ProclamationGroupCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedCreateWithoutSubscriptionLevelInput = {
|
export type ProclamationUncheckedCreateWithoutBlockProclamationsInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
|
groupId: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateOrConnectWithoutSubscriptionLevelInput = {
|
export type ProclamationCreateOrConnectWithoutBlockProclamationsInput = {
|
||||||
where: Prisma.ProclamationWhereUniqueInput
|
where: Prisma.ProclamationWhereUniqueInput
|
||||||
create: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput>
|
create: Prisma.XOR<Prisma.ProclamationCreateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutBlockProclamationsInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateManySubscriptionLevelInputEnvelope = {
|
export type ProclamationUpsertWithoutBlockProclamationsInput = {
|
||||||
data: Prisma.ProclamationCreateManySubscriptionLevelInput | Prisma.ProclamationCreateManySubscriptionLevelInput[]
|
update: Prisma.XOR<Prisma.ProclamationUpdateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedUpdateWithoutBlockProclamationsInput>
|
||||||
skipDuplicates?: boolean
|
create: Prisma.XOR<Prisma.ProclamationCreateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedCreateWithoutBlockProclamationsInput>
|
||||||
|
where?: Prisma.ProclamationWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpsertWithWhereUniqueWithoutSubscriptionLevelInput = {
|
export type ProclamationUpdateToOneWithWhereWithoutBlockProclamationsInput = {
|
||||||
where: Prisma.ProclamationWhereUniqueInput
|
where?: Prisma.ProclamationWhereInput
|
||||||
update: Prisma.XOR<Prisma.ProclamationUpdateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedUpdateWithoutSubscriptionLevelInput>
|
data: Prisma.XOR<Prisma.ProclamationUpdateWithoutBlockProclamationsInput, Prisma.ProclamationUncheckedUpdateWithoutBlockProclamationsInput>
|
||||||
create: Prisma.XOR<Prisma.ProclamationCreateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedCreateWithoutSubscriptionLevelInput>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateWithWhereUniqueWithoutSubscriptionLevelInput = {
|
export type ProclamationUpdateWithoutBlockProclamationsInput = {
|
||||||
where: Prisma.ProclamationWhereUniqueInput
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
data: Prisma.XOR<Prisma.ProclamationUpdateWithoutSubscriptionLevelInput, Prisma.ProclamationUncheckedUpdateWithoutSubscriptionLevelInput>
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
group?: Prisma.ProclamationGroupUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateManyWithWhereWithoutSubscriptionLevelInput = {
|
export type ProclamationUncheckedUpdateWithoutBlockProclamationsInput = {
|
||||||
where: Prisma.ProclamationScalarWhereInput
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
data: Prisma.XOR<Prisma.ProclamationUpdateManyMutationInput, Prisma.ProclamationUncheckedUpdateManyWithoutSubscriptionLevelInput>
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationCreateManyGroupInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
solfeggioFrequencyId: number
|
||||||
|
categoryId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUpdateWithoutGroupInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUncheckedUpdateWithoutGroupInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProclamationUncheckedUpdateManyWithoutGroupInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateManySolfeggioFrequencyInput = {
|
export type ProclamationCreateManySolfeggioFrequencyInput = {
|
||||||
@@ -752,7 +875,7 @@ export type ProclamationCreateManySolfeggioFrequencyInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateWithoutSolfeggioFrequencyInput = {
|
export type ProclamationUpdateWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -760,8 +883,9 @@ export type ProclamationUpdateWithoutSolfeggioFrequencyInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
group?: Prisma.ProclamationGroupUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
blockProclamations?: Prisma.BlockProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateWithoutSolfeggioFrequencyInput = {
|
export type ProclamationUncheckedUpdateWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -769,8 +893,9 @@ export type ProclamationUncheckedUpdateWithoutSolfeggioFrequencyInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyInput = {
|
export type ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyInput = {
|
||||||
@@ -778,7 +903,7 @@ export type ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCreateManyCategoryInput = {
|
export type ProclamationCreateManyCategoryInput = {
|
||||||
@@ -786,7 +911,7 @@ export type ProclamationCreateManyCategoryInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateWithoutCategoryInput = {
|
export type ProclamationUpdateWithoutCategoryInput = {
|
||||||
@@ -794,8 +919,9 @@ export type ProclamationUpdateWithoutCategoryInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
|
group?: Prisma.ProclamationGroupUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
blockProclamations?: Prisma.BlockProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateWithoutCategoryInput = {
|
export type ProclamationUncheckedUpdateWithoutCategoryInput = {
|
||||||
@@ -803,8 +929,9 @@ export type ProclamationUncheckedUpdateWithoutCategoryInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
|
blockProclamations?: Prisma.BlockProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateManyWithoutCategoryInput = {
|
export type ProclamationUncheckedUpdateManyWithoutCategoryInput = {
|
||||||
@@ -812,41 +939,7 @@ export type ProclamationUncheckedUpdateManyWithoutCategoryInput = {
|
|||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
groupId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCreateManySubscriptionLevelInput = {
|
|
||||||
id?: string
|
|
||||||
name: string
|
|
||||||
content: string
|
|
||||||
solfeggioFrequencyId: number
|
|
||||||
categoryId: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationUpdateWithoutSubscriptionLevelInput = {
|
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateWithoutSubscriptionLevelInput = {
|
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateManyWithoutSubscriptionLevelInput = {
|
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -856,10 +949,12 @@ export type ProclamationUncheckedUpdateManyWithoutSubscriptionLevelInput = {
|
|||||||
|
|
||||||
export type ProclamationCountOutputType = {
|
export type ProclamationCountOutputType = {
|
||||||
userProclamations: number
|
userProclamations: number
|
||||||
|
blockProclamations: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
userProclamations?: boolean | ProclamationCountOutputTypeCountUserProclamationsArgs
|
userProclamations?: boolean | ProclamationCountOutputTypeCountUserProclamationsArgs
|
||||||
|
blockProclamations?: boolean | ProclamationCountOutputTypeCountBlockProclamationsArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -879,6 +974,13 @@ export type ProclamationCountOutputTypeCountUserProclamationsArgs<ExtArgs extend
|
|||||||
where?: Prisma.UserProclamationsWhereInput
|
where?: Prisma.UserProclamationsWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProclamationCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type ProclamationCountOutputTypeCountBlockProclamationsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.BlockProclamationsWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ProclamationSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProclamationSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
@@ -886,11 +988,12 @@ export type ProclamationSelect<ExtArgs extends runtime.Types.Extensions.Internal
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
subscriptionLevelId?: boolean
|
groupId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
userProclamations?: boolean | Prisma.Proclamation$userProclamationsArgs<ExtArgs>
|
userProclamations?: boolean | Prisma.Proclamation$userProclamationsArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
blockProclamations?: boolean | Prisma.Proclamation$blockProclamationsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ProclamationCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProclamationCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["proclamation"]>
|
}, ExtArgs["result"]["proclamation"]>
|
||||||
|
|
||||||
@@ -900,10 +1003,10 @@ export type ProclamationSelectCreateManyAndReturn<ExtArgs extends runtime.Types.
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
subscriptionLevelId?: boolean
|
groupId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["proclamation"]>
|
}, ExtArgs["result"]["proclamation"]>
|
||||||
|
|
||||||
export type ProclamationSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProclamationSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
@@ -912,10 +1015,10 @@ export type ProclamationSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
subscriptionLevelId?: boolean
|
groupId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["proclamation"]>
|
}, ExtArgs["result"]["proclamation"]>
|
||||||
|
|
||||||
export type ProclamationSelectScalar = {
|
export type ProclamationSelectScalar = {
|
||||||
@@ -924,26 +1027,27 @@ export type ProclamationSelectScalar = {
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
subscriptionLevelId?: boolean
|
groupId?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "content" | "solfeggioFrequencyId" | "categoryId" | "subscriptionLevelId", ExtArgs["result"]["proclamation"]>
|
export type ProclamationOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "content" | "solfeggioFrequencyId" | "categoryId" | "groupId", ExtArgs["result"]["proclamation"]>
|
||||||
export type ProclamationInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
userProclamations?: boolean | Prisma.Proclamation$userProclamationsArgs<ExtArgs>
|
userProclamations?: boolean | Prisma.Proclamation$userProclamationsArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
blockProclamations?: boolean | Prisma.Proclamation$blockProclamationsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ProclamationCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProclamationCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ProclamationIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ProclamationIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
subscriptionLevel?: boolean | Prisma.SubscriptionLevelDefaultArgs<ExtArgs>
|
group?: boolean | Prisma.ProclamationGroupDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $ProclamationPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type $ProclamationPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
@@ -951,8 +1055,9 @@ export type $ProclamationPayload<ExtArgs extends runtime.Types.Extensions.Intern
|
|||||||
objects: {
|
objects: {
|
||||||
solfeggioFrequency: Prisma.$SolfeggioFrequencyPayload<ExtArgs>
|
solfeggioFrequency: Prisma.$SolfeggioFrequencyPayload<ExtArgs>
|
||||||
category: Prisma.$ProclamationCategoryPayload<ExtArgs>
|
category: Prisma.$ProclamationCategoryPayload<ExtArgs>
|
||||||
|
group: Prisma.$ProclamationGroupPayload<ExtArgs>
|
||||||
userProclamations: Prisma.$UserProclamationsPayload<ExtArgs>[]
|
userProclamations: Prisma.$UserProclamationsPayload<ExtArgs>[]
|
||||||
subscriptionLevel: Prisma.$SubscriptionLevelPayload<ExtArgs>
|
blockProclamations: Prisma.$BlockProclamationsPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
@@ -960,7 +1065,7 @@ export type $ProclamationPayload<ExtArgs extends runtime.Types.Extensions.Intern
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
subscriptionLevelId: number
|
groupId: number
|
||||||
}, ExtArgs["result"]["proclamation"]>
|
}, ExtArgs["result"]["proclamation"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
}
|
}
|
||||||
@@ -1357,8 +1462,9 @@ export interface Prisma__ProclamationClient<T, Null = never, ExtArgs extends run
|
|||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
solfeggioFrequency<T extends Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>>): Prisma.Prisma__SolfeggioFrequencyClient<runtime.Types.Result.GetResult<Prisma.$SolfeggioFrequencyPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
solfeggioFrequency<T extends Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>>): Prisma.Prisma__SolfeggioFrequencyClient<runtime.Types.Result.GetResult<Prisma.$SolfeggioFrequencyPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||||
category<T extends Prisma.ProclamationCategoryDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategoryDefaultArgs<ExtArgs>>): Prisma.Prisma__ProclamationCategoryClient<runtime.Types.Result.GetResult<Prisma.$ProclamationCategoryPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
category<T extends Prisma.ProclamationCategoryDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategoryDefaultArgs<ExtArgs>>): Prisma.Prisma__ProclamationCategoryClient<runtime.Types.Result.GetResult<Prisma.$ProclamationCategoryPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||||
|
group<T extends Prisma.ProclamationGroupDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationGroupDefaultArgs<ExtArgs>>): Prisma.Prisma__ProclamationGroupClient<runtime.Types.Result.GetResult<Prisma.$ProclamationGroupPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||||
userProclamations<T extends Prisma.Proclamation$userProclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Proclamation$userProclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UserProclamationsPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
userProclamations<T extends Prisma.Proclamation$userProclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Proclamation$userProclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UserProclamationsPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
subscriptionLevel<T extends Prisma.SubscriptionLevelDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.SubscriptionLevelDefaultArgs<ExtArgs>>): Prisma.Prisma__SubscriptionLevelClient<runtime.Types.Result.GetResult<Prisma.$SubscriptionLevelPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
blockProclamations<T extends Prisma.Proclamation$blockProclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Proclamation$blockProclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$BlockProclamationsPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -1393,7 +1499,7 @@ export interface ProclamationFieldRefs {
|
|||||||
readonly content: Prisma.FieldRef<"Proclamation", 'String'>
|
readonly content: Prisma.FieldRef<"Proclamation", 'String'>
|
||||||
readonly solfeggioFrequencyId: Prisma.FieldRef<"Proclamation", 'Int'>
|
readonly solfeggioFrequencyId: Prisma.FieldRef<"Proclamation", 'Int'>
|
||||||
readonly categoryId: Prisma.FieldRef<"Proclamation", 'Int'>
|
readonly categoryId: Prisma.FieldRef<"Proclamation", 'Int'>
|
||||||
readonly subscriptionLevelId: Prisma.FieldRef<"Proclamation", 'Int'>
|
readonly groupId: Prisma.FieldRef<"Proclamation", 'Int'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1818,6 +1924,30 @@ export type Proclamation$userProclamationsArgs<ExtArgs extends runtime.Types.Ext
|
|||||||
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proclamation.blockProclamations
|
||||||
|
*/
|
||||||
|
export type Proclamation$blockProclamationsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the BlockProclamations
|
||||||
|
*/
|
||||||
|
select?: Prisma.BlockProclamationsSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the BlockProclamations
|
||||||
|
*/
|
||||||
|
omit?: Prisma.BlockProclamationsOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.BlockProclamationsInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.BlockProclamationsWhereInput
|
||||||
|
orderBy?: Prisma.BlockProclamationsOrderByWithRelationInput | Prisma.BlockProclamationsOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.BlockProclamationsWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.BlockProclamationsScalarFieldEnum | Prisma.BlockProclamationsScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proclamation without action
|
* Proclamation without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,34 +29,29 @@ export type AggregateProclamationCategory = {
|
|||||||
export type ProclamationCategoryAvgAggregateOutputType = {
|
export type ProclamationCategoryAvgAggregateOutputType = {
|
||||||
id: number | null
|
id: number | null
|
||||||
sortOrder: number | null
|
sortOrder: number | null
|
||||||
parentId: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategorySumAggregateOutputType = {
|
export type ProclamationCategorySumAggregateOutputType = {
|
||||||
id: number | null
|
id: number | null
|
||||||
sortOrder: number | null
|
sortOrder: number | null
|
||||||
parentId: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMinAggregateOutputType = {
|
export type ProclamationCategoryMinAggregateOutputType = {
|
||||||
id: number | null
|
id: number | null
|
||||||
name: string | null
|
name: string | null
|
||||||
sortOrder: number | null
|
sortOrder: number | null
|
||||||
parentId: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMaxAggregateOutputType = {
|
export type ProclamationCategoryMaxAggregateOutputType = {
|
||||||
id: number | null
|
id: number | null
|
||||||
name: string | null
|
name: string | null
|
||||||
sortOrder: number | null
|
sortOrder: number | null
|
||||||
parentId: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCountAggregateOutputType = {
|
export type ProclamationCategoryCountAggregateOutputType = {
|
||||||
id: number
|
id: number
|
||||||
name: number
|
name: number
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
parentId: number
|
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,34 +59,29 @@ export type ProclamationCategoryCountAggregateOutputType = {
|
|||||||
export type ProclamationCategoryAvgAggregateInputType = {
|
export type ProclamationCategoryAvgAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
sortOrder?: true
|
sortOrder?: true
|
||||||
parentId?: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategorySumAggregateInputType = {
|
export type ProclamationCategorySumAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
sortOrder?: true
|
sortOrder?: true
|
||||||
parentId?: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMinAggregateInputType = {
|
export type ProclamationCategoryMinAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
name?: true
|
name?: true
|
||||||
sortOrder?: true
|
sortOrder?: true
|
||||||
parentId?: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMaxAggregateInputType = {
|
export type ProclamationCategoryMaxAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
name?: true
|
name?: true
|
||||||
sortOrder?: true
|
sortOrder?: true
|
||||||
parentId?: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCountAggregateInputType = {
|
export type ProclamationCategoryCountAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
name?: true
|
name?: true
|
||||||
sortOrder?: true
|
sortOrder?: true
|
||||||
parentId?: true
|
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +175,6 @@ export type ProclamationCategoryGroupByOutputType = {
|
|||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
parentId: number | null
|
|
||||||
_count: ProclamationCategoryCountAggregateOutputType | null
|
_count: ProclamationCategoryCountAggregateOutputType | null
|
||||||
_avg: ProclamationCategoryAvgAggregateOutputType | null
|
_avg: ProclamationCategoryAvgAggregateOutputType | null
|
||||||
_sum: ProclamationCategorySumAggregateOutputType | null
|
_sum: ProclamationCategorySumAggregateOutputType | null
|
||||||
@@ -215,44 +204,30 @@ export type ProclamationCategoryWhereInput = {
|
|||||||
id?: Prisma.IntFilter<"ProclamationCategory"> | number
|
id?: Prisma.IntFilter<"ProclamationCategory"> | number
|
||||||
name?: Prisma.StringFilter<"ProclamationCategory"> | string
|
name?: Prisma.StringFilter<"ProclamationCategory"> | string
|
||||||
sortOrder?: Prisma.IntFilter<"ProclamationCategory"> | number
|
sortOrder?: Prisma.IntFilter<"ProclamationCategory"> | number
|
||||||
parentId?: Prisma.IntNullableFilter<"ProclamationCategory"> | number | null
|
|
||||||
parent?: Prisma.XOR<Prisma.ProclamationCategoryNullableScalarRelationFilter, Prisma.ProclamationCategoryWhereInput> | null
|
|
||||||
children?: Prisma.ProclamationCategoryListRelationFilter
|
|
||||||
proclamations?: Prisma.ProclamationListRelationFilter
|
proclamations?: Prisma.ProclamationListRelationFilter
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelListRelationFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryOrderByWithRelationInput = {
|
export type ProclamationCategoryOrderByWithRelationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrderInput | Prisma.SortOrder
|
|
||||||
parent?: Prisma.ProclamationCategoryOrderByWithRelationInput
|
|
||||||
children?: Prisma.ProclamationCategoryOrderByRelationAggregateInput
|
|
||||||
proclamations?: Prisma.ProclamationOrderByRelationAggregateInput
|
proclamations?: Prisma.ProclamationOrderByRelationAggregateInput
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelOrderByRelationAggregateInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryWhereUniqueInput = Prisma.AtLeast<{
|
export type ProclamationCategoryWhereUniqueInput = Prisma.AtLeast<{
|
||||||
id?: number
|
id?: number
|
||||||
parentId_name?: Prisma.ProclamationCategoryParentIdNameCompoundUniqueInput
|
name?: string
|
||||||
AND?: Prisma.ProclamationCategoryWhereInput | Prisma.ProclamationCategoryWhereInput[]
|
AND?: Prisma.ProclamationCategoryWhereInput | Prisma.ProclamationCategoryWhereInput[]
|
||||||
OR?: Prisma.ProclamationCategoryWhereInput[]
|
OR?: Prisma.ProclamationCategoryWhereInput[]
|
||||||
NOT?: Prisma.ProclamationCategoryWhereInput | Prisma.ProclamationCategoryWhereInput[]
|
NOT?: Prisma.ProclamationCategoryWhereInput | Prisma.ProclamationCategoryWhereInput[]
|
||||||
name?: Prisma.StringFilter<"ProclamationCategory"> | string
|
|
||||||
sortOrder?: Prisma.IntFilter<"ProclamationCategory"> | number
|
sortOrder?: Prisma.IntFilter<"ProclamationCategory"> | number
|
||||||
parentId?: Prisma.IntNullableFilter<"ProclamationCategory"> | number | null
|
|
||||||
parent?: Prisma.XOR<Prisma.ProclamationCategoryNullableScalarRelationFilter, Prisma.ProclamationCategoryWhereInput> | null
|
|
||||||
children?: Prisma.ProclamationCategoryListRelationFilter
|
|
||||||
proclamations?: Prisma.ProclamationListRelationFilter
|
proclamations?: Prisma.ProclamationListRelationFilter
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelListRelationFilter
|
}, "id" | "name">
|
||||||
}, "id" | "parentId_name">
|
|
||||||
|
|
||||||
export type ProclamationCategoryOrderByWithAggregationInput = {
|
export type ProclamationCategoryOrderByWithAggregationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrderInput | Prisma.SortOrder
|
|
||||||
_count?: Prisma.ProclamationCategoryCountOrderByAggregateInput
|
_count?: Prisma.ProclamationCategoryCountOrderByAggregateInput
|
||||||
_avg?: Prisma.ProclamationCategoryAvgOrderByAggregateInput
|
_avg?: Prisma.ProclamationCategoryAvgOrderByAggregateInput
|
||||||
_max?: Prisma.ProclamationCategoryMaxOrderByAggregateInput
|
_max?: Prisma.ProclamationCategoryMaxOrderByAggregateInput
|
||||||
@@ -267,52 +242,38 @@ export type ProclamationCategoryScalarWhereWithAggregatesInput = {
|
|||||||
id?: Prisma.IntWithAggregatesFilter<"ProclamationCategory"> | number
|
id?: Prisma.IntWithAggregatesFilter<"ProclamationCategory"> | number
|
||||||
name?: Prisma.StringWithAggregatesFilter<"ProclamationCategory"> | string
|
name?: Prisma.StringWithAggregatesFilter<"ProclamationCategory"> | string
|
||||||
sortOrder?: Prisma.IntWithAggregatesFilter<"ProclamationCategory"> | number
|
sortOrder?: Prisma.IntWithAggregatesFilter<"ProclamationCategory"> | number
|
||||||
parentId?: Prisma.IntNullableWithAggregatesFilter<"ProclamationCategory"> | number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCreateInput = {
|
export type ProclamationCategoryCreateInput = {
|
||||||
name: string
|
name: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
parent?: Prisma.ProclamationCategoryCreateNestedOneWithoutChildrenInput
|
|
||||||
children?: Prisma.ProclamationCategoryCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationCreateNestedManyWithoutCategoryInput
|
proclamations?: Prisma.ProclamationCreateNestedManyWithoutCategoryInput
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateInput = {
|
export type ProclamationCategoryUncheckedCreateInput = {
|
||||||
id?: number
|
id?: number
|
||||||
name: string
|
name: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
parentId?: number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedCreateNestedManyWithoutCategoryInput
|
proclamations?: Prisma.ProclamationUncheckedCreateNestedManyWithoutCategoryInput
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateInput = {
|
export type ProclamationCategoryUpdateInput = {
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
parent?: Prisma.ProclamationCategoryUpdateOneWithoutChildrenNestedInput
|
|
||||||
children?: Prisma.ProclamationCategoryUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUpdateManyWithoutCategoryNestedInput
|
proclamations?: Prisma.ProclamationUpdateManyWithoutCategoryNestedInput
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateInput = {
|
export type ProclamationCategoryUncheckedUpdateInput = {
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutCategoryNestedInput
|
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutCategoryNestedInput
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCreateManyInput = {
|
export type ProclamationCategoryCreateManyInput = {
|
||||||
id?: number
|
id?: number
|
||||||
name: string
|
name: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
parentId?: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateManyMutationInput = {
|
export type ProclamationCategoryUpdateManyMutationInput = {
|
||||||
@@ -324,7 +285,6 @@ export type ProclamationCategoryUncheckedUpdateManyInput = {
|
|||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryScalarRelationFilter = {
|
export type ProclamationCategoryScalarRelationFilter = {
|
||||||
@@ -332,57 +292,32 @@ export type ProclamationCategoryScalarRelationFilter = {
|
|||||||
isNot?: Prisma.ProclamationCategoryWhereInput
|
isNot?: Prisma.ProclamationCategoryWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryNullableScalarRelationFilter = {
|
|
||||||
is?: Prisma.ProclamationCategoryWhereInput | null
|
|
||||||
isNot?: Prisma.ProclamationCategoryWhereInput | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryListRelationFilter = {
|
|
||||||
every?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
some?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
none?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryOrderByRelationAggregateInput = {
|
|
||||||
_count?: Prisma.SortOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryParentIdNameCompoundUniqueInput = {
|
|
||||||
parentId: number
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCountOrderByAggregateInput = {
|
export type ProclamationCategoryCountOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryAvgOrderByAggregateInput = {
|
export type ProclamationCategoryAvgOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMaxOrderByAggregateInput = {
|
export type ProclamationCategoryMaxOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryMinOrderByAggregateInput = {
|
export type ProclamationCategoryMinOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategorySumOrderByAggregateInput = {
|
export type ProclamationCategorySumOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
parentId?: Prisma.SortOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCreateNestedOneWithoutProclamationsInput = {
|
export type ProclamationCategoryCreateNestedOneWithoutProclamationsInput = {
|
||||||
@@ -399,93 +334,15 @@ export type ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
|||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ProclamationCategoryUpdateToOneWithWhereWithoutProclamationsInput, Prisma.ProclamationCategoryUpdateWithoutProclamationsInput>, Prisma.ProclamationCategoryUncheckedUpdateWithoutProclamationsInput>
|
update?: Prisma.XOR<Prisma.XOR<Prisma.ProclamationCategoryUpdateToOneWithWhereWithoutProclamationsInput, Prisma.ProclamationCategoryUpdateWithoutProclamationsInput>, Prisma.ProclamationCategoryUncheckedUpdateWithoutProclamationsInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCreateNestedOneWithoutChildrenInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedCreateWithoutChildrenInput>
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutChildrenInput
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateNestedManyWithoutParentInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput> | Prisma.ProclamationCategoryCreateWithoutParentInput[] | Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput[]
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput | Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput[]
|
|
||||||
createMany?: Prisma.ProclamationCategoryCreateManyParentInputEnvelope
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateNestedManyWithoutParentInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput> | Prisma.ProclamationCategoryCreateWithoutParentInput[] | Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput[]
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput | Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput[]
|
|
||||||
createMany?: Prisma.ProclamationCategoryCreateManyParentInputEnvelope
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateOneWithoutChildrenNestedInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedCreateWithoutChildrenInput>
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutChildrenInput
|
|
||||||
upsert?: Prisma.ProclamationCategoryUpsertWithoutChildrenInput
|
|
||||||
disconnect?: Prisma.ProclamationCategoryWhereInput | boolean
|
|
||||||
delete?: Prisma.ProclamationCategoryWhereInput | boolean
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ProclamationCategoryUpdateToOneWithWhereWithoutChildrenInput, Prisma.ProclamationCategoryUpdateWithoutChildrenInput>, Prisma.ProclamationCategoryUncheckedUpdateWithoutChildrenInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateManyWithoutParentNestedInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput> | Prisma.ProclamationCategoryCreateWithoutParentInput[] | Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput[]
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput | Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput[]
|
|
||||||
upsert?: Prisma.ProclamationCategoryUpsertWithWhereUniqueWithoutParentInput | Prisma.ProclamationCategoryUpsertWithWhereUniqueWithoutParentInput[]
|
|
||||||
createMany?: Prisma.ProclamationCategoryCreateManyParentInputEnvelope
|
|
||||||
set?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
disconnect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
delete?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
update?: Prisma.ProclamationCategoryUpdateWithWhereUniqueWithoutParentInput | Prisma.ProclamationCategoryUpdateWithWhereUniqueWithoutParentInput[]
|
|
||||||
updateMany?: Prisma.ProclamationCategoryUpdateManyWithWhereWithoutParentInput | Prisma.ProclamationCategoryUpdateManyWithWhereWithoutParentInput[]
|
|
||||||
deleteMany?: Prisma.ProclamationCategoryScalarWhereInput | Prisma.ProclamationCategoryScalarWhereInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateManyWithoutParentNestedInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput> | Prisma.ProclamationCategoryCreateWithoutParentInput[] | Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput[]
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput | Prisma.ProclamationCategoryCreateOrConnectWithoutParentInput[]
|
|
||||||
upsert?: Prisma.ProclamationCategoryUpsertWithWhereUniqueWithoutParentInput | Prisma.ProclamationCategoryUpsertWithWhereUniqueWithoutParentInput[]
|
|
||||||
createMany?: Prisma.ProclamationCategoryCreateManyParentInputEnvelope
|
|
||||||
set?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
disconnect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
delete?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput | Prisma.ProclamationCategoryWhereUniqueInput[]
|
|
||||||
update?: Prisma.ProclamationCategoryUpdateWithWhereUniqueWithoutParentInput | Prisma.ProclamationCategoryUpdateWithWhereUniqueWithoutParentInput[]
|
|
||||||
updateMany?: Prisma.ProclamationCategoryUpdateManyWithWhereWithoutParentInput | Prisma.ProclamationCategoryUpdateManyWithWhereWithoutParentInput[]
|
|
||||||
deleteMany?: Prisma.ProclamationCategoryScalarWhereInput | Prisma.ProclamationCategoryScalarWhereInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateNestedOneWithoutSubscriptionLevelsInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedCreateWithoutSubscriptionLevelsInput>
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutSubscriptionLevelsInput
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateOneRequiredWithoutSubscriptionLevelsNestedInput = {
|
|
||||||
create?: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedCreateWithoutSubscriptionLevelsInput>
|
|
||||||
connectOrCreate?: Prisma.ProclamationCategoryCreateOrConnectWithoutSubscriptionLevelsInput
|
|
||||||
upsert?: Prisma.ProclamationCategoryUpsertWithoutSubscriptionLevelsInput
|
|
||||||
connect?: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ProclamationCategoryUpdateToOneWithWhereWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUpdateWithoutSubscriptionLevelsInput>, Prisma.ProclamationCategoryUncheckedUpdateWithoutSubscriptionLevelsInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateWithoutProclamationsInput = {
|
export type ProclamationCategoryCreateWithoutProclamationsInput = {
|
||||||
name: string
|
name: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
parent?: Prisma.ProclamationCategoryCreateNestedOneWithoutChildrenInput
|
|
||||||
children?: Prisma.ProclamationCategoryCreateNestedManyWithoutParentInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateWithoutProclamationsInput = {
|
export type ProclamationCategoryUncheckedCreateWithoutProclamationsInput = {
|
||||||
id?: number
|
id?: number
|
||||||
name: string
|
name: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
parentId?: number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedCreateNestedManyWithoutParentInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCreateOrConnectWithoutProclamationsInput = {
|
export type ProclamationCategoryCreateOrConnectWithoutProclamationsInput = {
|
||||||
@@ -507,200 +364,12 @@ export type ProclamationCategoryUpdateToOneWithWhereWithoutProclamationsInput =
|
|||||||
export type ProclamationCategoryUpdateWithoutProclamationsInput = {
|
export type ProclamationCategoryUpdateWithoutProclamationsInput = {
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
parent?: Prisma.ProclamationCategoryUpdateOneWithoutChildrenNestedInput
|
|
||||||
children?: Prisma.ProclamationCategoryUpdateManyWithoutParentNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateWithoutProclamationsInput = {
|
export type ProclamationCategoryUncheckedUpdateWithoutProclamationsInput = {
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedUpdateManyWithoutParentNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateWithoutChildrenInput = {
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
parent?: Prisma.ProclamationCategoryCreateNestedOneWithoutChildrenInput
|
|
||||||
proclamations?: Prisma.ProclamationCreateNestedManyWithoutCategoryInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateWithoutChildrenInput = {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
parentId?: number | null
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateOrConnectWithoutChildrenInput = {
|
|
||||||
where: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedCreateWithoutChildrenInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateWithoutParentInput = {
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
children?: Prisma.ProclamationCategoryCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationCreateNestedManyWithoutCategoryInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateWithoutParentInput = {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateOrConnectWithoutParentInput = {
|
|
||||||
where: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateManyParentInputEnvelope = {
|
|
||||||
data: Prisma.ProclamationCategoryCreateManyParentInput | Prisma.ProclamationCategoryCreateManyParentInput[]
|
|
||||||
skipDuplicates?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpsertWithoutChildrenInput = {
|
|
||||||
update: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutChildrenInput>
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedCreateWithoutChildrenInput>
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateToOneWithWhereWithoutChildrenInput = {
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
data: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutChildrenInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutChildrenInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateWithoutChildrenInput = {
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
parent?: Prisma.ProclamationCategoryUpdateOneWithoutChildrenNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUpdateManyWithoutCategoryNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateWithoutChildrenInput = {
|
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpsertWithWhereUniqueWithoutParentInput = {
|
|
||||||
where: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
update: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutParentInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutParentInput>
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutParentInput, Prisma.ProclamationCategoryUncheckedCreateWithoutParentInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateWithWhereUniqueWithoutParentInput = {
|
|
||||||
where: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
data: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutParentInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutParentInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateManyWithWhereWithoutParentInput = {
|
|
||||||
where: Prisma.ProclamationCategoryScalarWhereInput
|
|
||||||
data: Prisma.XOR<Prisma.ProclamationCategoryUpdateManyMutationInput, Prisma.ProclamationCategoryUncheckedUpdateManyWithoutParentInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryScalarWhereInput = {
|
|
||||||
AND?: Prisma.ProclamationCategoryScalarWhereInput | Prisma.ProclamationCategoryScalarWhereInput[]
|
|
||||||
OR?: Prisma.ProclamationCategoryScalarWhereInput[]
|
|
||||||
NOT?: Prisma.ProclamationCategoryScalarWhereInput | Prisma.ProclamationCategoryScalarWhereInput[]
|
|
||||||
id?: Prisma.IntFilter<"ProclamationCategory"> | number
|
|
||||||
name?: Prisma.StringFilter<"ProclamationCategory"> | string
|
|
||||||
sortOrder?: Prisma.IntFilter<"ProclamationCategory"> | number
|
|
||||||
parentId?: Prisma.IntNullableFilter<"ProclamationCategory"> | number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateWithoutSubscriptionLevelsInput = {
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
parent?: Prisma.ProclamationCategoryCreateNestedOneWithoutChildrenInput
|
|
||||||
children?: Prisma.ProclamationCategoryCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedCreateWithoutSubscriptionLevelsInput = {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
parentId?: number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedCreateNestedManyWithoutParentInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedCreateNestedManyWithoutCategoryInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateOrConnectWithoutSubscriptionLevelsInput = {
|
|
||||||
where: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedCreateWithoutSubscriptionLevelsInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpsertWithoutSubscriptionLevelsInput = {
|
|
||||||
update: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutSubscriptionLevelsInput>
|
|
||||||
create: Prisma.XOR<Prisma.ProclamationCategoryCreateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedCreateWithoutSubscriptionLevelsInput>
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateToOneWithWhereWithoutSubscriptionLevelsInput = {
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
data: Prisma.XOR<Prisma.ProclamationCategoryUpdateWithoutSubscriptionLevelsInput, Prisma.ProclamationCategoryUncheckedUpdateWithoutSubscriptionLevelsInput>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateWithoutSubscriptionLevelsInput = {
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
parent?: Prisma.ProclamationCategoryUpdateOneWithoutChildrenNestedInput
|
|
||||||
children?: Prisma.ProclamationCategoryUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateWithoutSubscriptionLevelsInput = {
|
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryCreateManyParentInput = {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
sortOrder?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUpdateWithoutParentInput = {
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
children?: Prisma.ProclamationCategoryUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUpdateManyWithoutCategoryNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateWithoutParentInput = {
|
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
children?: Prisma.ProclamationCategoryUncheckedUpdateManyWithoutParentNestedInput
|
|
||||||
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
subscriptionLevels?: Prisma.ProclamationCategorySubscriptionLevelUncheckedUpdateManyWithoutCategoryNestedInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProclamationCategoryUncheckedUpdateManyWithoutParentInput = {
|
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -709,15 +378,11 @@ export type ProclamationCategoryUncheckedUpdateManyWithoutParentInput = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type ProclamationCategoryCountOutputType = {
|
export type ProclamationCategoryCountOutputType = {
|
||||||
children: number
|
|
||||||
proclamations: number
|
proclamations: number
|
||||||
subscriptionLevels: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationCategoryCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
children?: boolean | ProclamationCategoryCountOutputTypeCountChildrenArgs
|
|
||||||
proclamations?: boolean | ProclamationCategoryCountOutputTypeCountProclamationsArgs
|
proclamations?: boolean | ProclamationCategoryCountOutputTypeCountProclamationsArgs
|
||||||
subscriptionLevels?: boolean | ProclamationCategoryCountOutputTypeCountSubscriptionLevelsArgs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -730,13 +395,6 @@ export type ProclamationCategoryCountOutputTypeDefaultArgs<ExtArgs extends runti
|
|||||||
select?: Prisma.ProclamationCategoryCountOutputTypeSelect<ExtArgs> | null
|
select?: Prisma.ProclamationCategoryCountOutputTypeSelect<ExtArgs> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ProclamationCategoryCountOutputType without action
|
|
||||||
*/
|
|
||||||
export type ProclamationCategoryCountOutputTypeCountChildrenArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProclamationCategoryCountOutputType without action
|
* ProclamationCategoryCountOutputType without action
|
||||||
*/
|
*/
|
||||||
@@ -744,23 +402,12 @@ export type ProclamationCategoryCountOutputTypeCountProclamationsArgs<ExtArgs ex
|
|||||||
where?: Prisma.ProclamationWhereInput
|
where?: Prisma.ProclamationWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ProclamationCategoryCountOutputType without action
|
|
||||||
*/
|
|
||||||
export type ProclamationCategoryCountOutputTypeCountSubscriptionLevelsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
where?: Prisma.ProclamationCategorySubscriptionLevelWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type ProclamationCategorySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProclamationCategorySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
sortOrder?: boolean
|
sortOrder?: boolean
|
||||||
parentId?: boolean
|
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
|
||||||
children?: boolean | Prisma.ProclamationCategory$childrenArgs<ExtArgs>
|
|
||||||
proclamations?: boolean | Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>
|
proclamations?: boolean | Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>
|
||||||
subscriptionLevels?: boolean | Prisma.ProclamationCategory$subscriptionLevelsArgs<ExtArgs>
|
|
||||||
_count?: boolean | Prisma.ProclamationCategoryCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProclamationCategoryCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["proclamationCategory"]>
|
}, ExtArgs["result"]["proclamationCategory"]>
|
||||||
|
|
||||||
@@ -768,53 +415,37 @@ export type ProclamationCategorySelectCreateManyAndReturn<ExtArgs extends runtim
|
|||||||
id?: boolean
|
id?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
sortOrder?: boolean
|
sortOrder?: boolean
|
||||||
parentId?: boolean
|
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
|
||||||
}, ExtArgs["result"]["proclamationCategory"]>
|
}, ExtArgs["result"]["proclamationCategory"]>
|
||||||
|
|
||||||
export type ProclamationCategorySelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProclamationCategorySelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
sortOrder?: boolean
|
sortOrder?: boolean
|
||||||
parentId?: boolean
|
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
|
||||||
}, ExtArgs["result"]["proclamationCategory"]>
|
}, ExtArgs["result"]["proclamationCategory"]>
|
||||||
|
|
||||||
export type ProclamationCategorySelectScalar = {
|
export type ProclamationCategorySelectScalar = {
|
||||||
id?: boolean
|
id?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
sortOrder?: boolean
|
sortOrder?: boolean
|
||||||
parentId?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationCategoryOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "sortOrder" | "parentId", ExtArgs["result"]["proclamationCategory"]>
|
export type ProclamationCategoryOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "sortOrder", ExtArgs["result"]["proclamationCategory"]>
|
||||||
export type ProclamationCategoryInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationCategoryInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
|
||||||
children?: boolean | Prisma.ProclamationCategory$childrenArgs<ExtArgs>
|
|
||||||
proclamations?: boolean | Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>
|
proclamations?: boolean | Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>
|
||||||
subscriptionLevels?: boolean | Prisma.ProclamationCategory$subscriptionLevelsArgs<ExtArgs>
|
|
||||||
_count?: boolean | Prisma.ProclamationCategoryCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProclamationCategoryCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ProclamationCategoryIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProclamationCategoryIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
export type ProclamationCategoryIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||||
}
|
|
||||||
export type ProclamationCategoryIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
parent?: boolean | Prisma.ProclamationCategory$parentArgs<ExtArgs>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type $ProclamationCategoryPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type $ProclamationCategoryPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
name: "ProclamationCategory"
|
name: "ProclamationCategory"
|
||||||
objects: {
|
objects: {
|
||||||
parent: Prisma.$ProclamationCategoryPayload<ExtArgs> | null
|
|
||||||
children: Prisma.$ProclamationCategoryPayload<ExtArgs>[]
|
|
||||||
proclamations: Prisma.$ProclamationPayload<ExtArgs>[]
|
proclamations: Prisma.$ProclamationPayload<ExtArgs>[]
|
||||||
subscriptionLevels: Prisma.$ProclamationCategorySubscriptionLevelPayload<ExtArgs>[]
|
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
parentId: number | null
|
|
||||||
}, ExtArgs["result"]["proclamationCategory"]>
|
}, ExtArgs["result"]["proclamationCategory"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
}
|
}
|
||||||
@@ -1209,10 +840,7 @@ readonly fields: ProclamationCategoryFieldRefs;
|
|||||||
*/
|
*/
|
||||||
export interface Prisma__ProclamationCategoryClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
export interface Prisma__ProclamationCategoryClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
parent<T extends Prisma.ProclamationCategory$parentArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategory$parentArgs<ExtArgs>>): Prisma.Prisma__ProclamationCategoryClient<runtime.Types.Result.GetResult<Prisma.$ProclamationCategoryPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
|
||||||
children<T extends Prisma.ProclamationCategory$childrenArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategory$childrenArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ProclamationCategoryPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
|
||||||
proclamations<T extends Prisma.ProclamationCategory$proclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ProclamationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
proclamations<T extends Prisma.ProclamationCategory$proclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategory$proclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ProclamationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
subscriptionLevels<T extends Prisma.ProclamationCategory$subscriptionLevelsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ProclamationCategory$subscriptionLevelsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ProclamationCategorySubscriptionLevelPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -1245,7 +873,6 @@ export interface ProclamationCategoryFieldRefs {
|
|||||||
readonly id: Prisma.FieldRef<"ProclamationCategory", 'Int'>
|
readonly id: Prisma.FieldRef<"ProclamationCategory", 'Int'>
|
||||||
readonly name: Prisma.FieldRef<"ProclamationCategory", 'String'>
|
readonly name: Prisma.FieldRef<"ProclamationCategory", 'String'>
|
||||||
readonly sortOrder: Prisma.FieldRef<"ProclamationCategory", 'Int'>
|
readonly sortOrder: Prisma.FieldRef<"ProclamationCategory", 'Int'>
|
||||||
readonly parentId: Prisma.FieldRef<"ProclamationCategory", 'Int'>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1500,10 +1127,6 @@ export type ProclamationCategoryCreateManyAndReturnArgs<ExtArgs extends runtime.
|
|||||||
*/
|
*/
|
||||||
data: Prisma.ProclamationCategoryCreateManyInput | Prisma.ProclamationCategoryCreateManyInput[]
|
data: Prisma.ProclamationCategoryCreateManyInput | Prisma.ProclamationCategoryCreateManyInput[]
|
||||||
skipDuplicates?: boolean
|
skipDuplicates?: boolean
|
||||||
/**
|
|
||||||
* Choose, which related nodes to fetch as well
|
|
||||||
*/
|
|
||||||
include?: Prisma.ProclamationCategoryIncludeCreateManyAndReturn<ExtArgs> | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1574,10 +1197,6 @@ export type ProclamationCategoryUpdateManyAndReturnArgs<ExtArgs extends runtime.
|
|||||||
* Limit how many ProclamationCategories to update.
|
* Limit how many ProclamationCategories to update.
|
||||||
*/
|
*/
|
||||||
limit?: number
|
limit?: number
|
||||||
/**
|
|
||||||
* Choose, which related nodes to fetch as well
|
|
||||||
*/
|
|
||||||
include?: Prisma.ProclamationCategoryIncludeUpdateManyAndReturn<ExtArgs> | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1646,49 +1265,6 @@ export type ProclamationCategoryDeleteManyArgs<ExtArgs extends runtime.Types.Ext
|
|||||||
limit?: number
|
limit?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ProclamationCategory.parent
|
|
||||||
*/
|
|
||||||
export type ProclamationCategory$parentArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
/**
|
|
||||||
* Select specific fields to fetch from the ProclamationCategory
|
|
||||||
*/
|
|
||||||
select?: Prisma.ProclamationCategorySelect<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Omit specific fields from the ProclamationCategory
|
|
||||||
*/
|
|
||||||
omit?: Prisma.ProclamationCategoryOmit<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Choose, which related nodes to fetch as well
|
|
||||||
*/
|
|
||||||
include?: Prisma.ProclamationCategoryInclude<ExtArgs> | null
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ProclamationCategory.children
|
|
||||||
*/
|
|
||||||
export type ProclamationCategory$childrenArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
/**
|
|
||||||
* Select specific fields to fetch from the ProclamationCategory
|
|
||||||
*/
|
|
||||||
select?: Prisma.ProclamationCategorySelect<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Omit specific fields from the ProclamationCategory
|
|
||||||
*/
|
|
||||||
omit?: Prisma.ProclamationCategoryOmit<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Choose, which related nodes to fetch as well
|
|
||||||
*/
|
|
||||||
include?: Prisma.ProclamationCategoryInclude<ExtArgs> | null
|
|
||||||
where?: Prisma.ProclamationCategoryWhereInput
|
|
||||||
orderBy?: Prisma.ProclamationCategoryOrderByWithRelationInput | Prisma.ProclamationCategoryOrderByWithRelationInput[]
|
|
||||||
cursor?: Prisma.ProclamationCategoryWhereUniqueInput
|
|
||||||
take?: number
|
|
||||||
skip?: number
|
|
||||||
distinct?: Prisma.ProclamationCategoryScalarFieldEnum | Prisma.ProclamationCategoryScalarFieldEnum[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProclamationCategory.proclamations
|
* ProclamationCategory.proclamations
|
||||||
*/
|
*/
|
||||||
@@ -1713,30 +1289,6 @@ export type ProclamationCategory$proclamationsArgs<ExtArgs extends runtime.Types
|
|||||||
distinct?: Prisma.ProclamationScalarFieldEnum | Prisma.ProclamationScalarFieldEnum[]
|
distinct?: Prisma.ProclamationScalarFieldEnum | Prisma.ProclamationScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ProclamationCategory.subscriptionLevels
|
|
||||||
*/
|
|
||||||
export type ProclamationCategory$subscriptionLevelsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
||||||
/**
|
|
||||||
* Select specific fields to fetch from the ProclamationCategorySubscriptionLevel
|
|
||||||
*/
|
|
||||||
select?: Prisma.ProclamationCategorySubscriptionLevelSelect<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Omit specific fields from the ProclamationCategorySubscriptionLevel
|
|
||||||
*/
|
|
||||||
omit?: Prisma.ProclamationCategorySubscriptionLevelOmit<ExtArgs> | null
|
|
||||||
/**
|
|
||||||
* Choose, which related nodes to fetch as well
|
|
||||||
*/
|
|
||||||
include?: Prisma.ProclamationCategorySubscriptionLevelInclude<ExtArgs> | null
|
|
||||||
where?: Prisma.ProclamationCategorySubscriptionLevelWhereInput
|
|
||||||
orderBy?: Prisma.ProclamationCategorySubscriptionLevelOrderByWithRelationInput | Prisma.ProclamationCategorySubscriptionLevelOrderByWithRelationInput[]
|
|
||||||
cursor?: Prisma.ProclamationCategorySubscriptionLevelWhereUniqueInput
|
|
||||||
take?: number
|
|
||||||
skip?: number
|
|
||||||
distinct?: Prisma.ProclamationCategorySubscriptionLevelScalarFieldEnum | Prisma.ProclamationCategorySubscriptionLevelScalarFieldEnum[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProclamationCategory without action
|
* ProclamationCategory without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ export type AggregateProfile = {
|
|||||||
export type ProfileMinAggregateOutputType = {
|
export type ProfileMinAggregateOutputType = {
|
||||||
id: string | null
|
id: string | null
|
||||||
userId: string | null
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
firstName: string | null
|
firstName: string | null
|
||||||
lastName: string | null
|
lastName: string | null
|
||||||
phoneNumber: string | null
|
phoneNumber: string | null
|
||||||
@@ -36,6 +37,7 @@ export type ProfileMinAggregateOutputType = {
|
|||||||
export type ProfileMaxAggregateOutputType = {
|
export type ProfileMaxAggregateOutputType = {
|
||||||
id: string | null
|
id: string | null
|
||||||
userId: string | null
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
firstName: string | null
|
firstName: string | null
|
||||||
lastName: string | null
|
lastName: string | null
|
||||||
phoneNumber: string | null
|
phoneNumber: string | null
|
||||||
@@ -45,6 +47,7 @@ export type ProfileMaxAggregateOutputType = {
|
|||||||
export type ProfileCountAggregateOutputType = {
|
export type ProfileCountAggregateOutputType = {
|
||||||
id: number
|
id: number
|
||||||
userId: number
|
userId: number
|
||||||
|
email: number
|
||||||
firstName: number
|
firstName: number
|
||||||
lastName: number
|
lastName: number
|
||||||
phoneNumber: number
|
phoneNumber: number
|
||||||
@@ -56,6 +59,7 @@ export type ProfileCountAggregateOutputType = {
|
|||||||
export type ProfileMinAggregateInputType = {
|
export type ProfileMinAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
@@ -65,6 +69,7 @@ export type ProfileMinAggregateInputType = {
|
|||||||
export type ProfileMaxAggregateInputType = {
|
export type ProfileMaxAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
@@ -74,6 +79,7 @@ export type ProfileMaxAggregateInputType = {
|
|||||||
export type ProfileCountAggregateInputType = {
|
export type ProfileCountAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
|
email?: true
|
||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
@@ -155,7 +161,8 @@ export type ProfileGroupByArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
|||||||
|
|
||||||
export type ProfileGroupByOutputType = {
|
export type ProfileGroupByOutputType = {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -185,18 +192,20 @@ export type ProfileWhereInput = {
|
|||||||
OR?: Prisma.ProfileWhereInput[]
|
OR?: Prisma.ProfileWhereInput[]
|
||||||
NOT?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
NOT?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
||||||
id?: Prisma.StringFilter<"Profile"> | string
|
id?: Prisma.StringFilter<"Profile"> | string
|
||||||
userId?: Prisma.StringFilter<"Profile"> | string
|
userId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
|
email?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
firstName?: Prisma.StringFilter<"Profile"> | string
|
firstName?: Prisma.StringFilter<"Profile"> | string
|
||||||
lastName?: Prisma.StringFilter<"Profile"> | string
|
lastName?: Prisma.StringFilter<"Profile"> | string
|
||||||
phoneNumber?: Prisma.StringFilter<"Profile"> | string
|
phoneNumber?: Prisma.StringFilter<"Profile"> | string
|
||||||
ghlContactId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
ghlContactId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
user?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||||
addresses?: Prisma.AddressListRelationFilter
|
addresses?: Prisma.AddressListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileOrderByWithRelationInput = {
|
export type ProfileOrderByWithRelationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
@@ -207,21 +216,23 @@ export type ProfileOrderByWithRelationInput = {
|
|||||||
|
|
||||||
export type ProfileWhereUniqueInput = Prisma.AtLeast<{
|
export type ProfileWhereUniqueInput = Prisma.AtLeast<{
|
||||||
id?: string
|
id?: string
|
||||||
userId?: string
|
|
||||||
AND?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
AND?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
||||||
OR?: Prisma.ProfileWhereInput[]
|
OR?: Prisma.ProfileWhereInput[]
|
||||||
NOT?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
NOT?: Prisma.ProfileWhereInput | Prisma.ProfileWhereInput[]
|
||||||
|
userId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
|
email?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
firstName?: Prisma.StringFilter<"Profile"> | string
|
firstName?: Prisma.StringFilter<"Profile"> | string
|
||||||
lastName?: Prisma.StringFilter<"Profile"> | string
|
lastName?: Prisma.StringFilter<"Profile"> | string
|
||||||
phoneNumber?: Prisma.StringFilter<"Profile"> | string
|
phoneNumber?: Prisma.StringFilter<"Profile"> | string
|
||||||
ghlContactId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
ghlContactId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
user?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||||
addresses?: Prisma.AddressListRelationFilter
|
addresses?: Prisma.AddressListRelationFilter
|
||||||
}, "id" | "userId">
|
}, "id">
|
||||||
|
|
||||||
export type ProfileOrderByWithAggregationInput = {
|
export type ProfileOrderByWithAggregationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
@@ -236,7 +247,8 @@ export type ProfileScalarWhereWithAggregatesInput = {
|
|||||||
OR?: Prisma.ProfileScalarWhereWithAggregatesInput[]
|
OR?: Prisma.ProfileScalarWhereWithAggregatesInput[]
|
||||||
NOT?: Prisma.ProfileScalarWhereWithAggregatesInput | Prisma.ProfileScalarWhereWithAggregatesInput[]
|
NOT?: Prisma.ProfileScalarWhereWithAggregatesInput | Prisma.ProfileScalarWhereWithAggregatesInput[]
|
||||||
id?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
id?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
||||||
userId?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
userId?: Prisma.StringNullableWithAggregatesFilter<"Profile"> | string | null
|
||||||
|
email?: Prisma.StringNullableWithAggregatesFilter<"Profile"> | string | null
|
||||||
firstName?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
firstName?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
||||||
lastName?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
lastName?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
||||||
phoneNumber?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
phoneNumber?: Prisma.StringWithAggregatesFilter<"Profile"> | string
|
||||||
@@ -245,17 +257,19 @@ export type ProfileScalarWhereWithAggregatesInput = {
|
|||||||
|
|
||||||
export type ProfileCreateInput = {
|
export type ProfileCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
ghlContactId?: string | null
|
ghlContactId?: string | null
|
||||||
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
user?: Prisma.UserCreateNestedOneWithoutProfilesInput
|
||||||
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedCreateInput = {
|
export type ProfileUncheckedCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
userId: string
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -265,17 +279,19 @@ export type ProfileUncheckedCreateInput = {
|
|||||||
|
|
||||||
export type ProfileUpdateInput = {
|
export type ProfileUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
user?: Prisma.UserUpdateOneWithoutProfilesNestedInput
|
||||||
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedUpdateInput = {
|
export type ProfileUncheckedUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
userId?: Prisma.StringFieldUpdateOperationsInput | string
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -285,7 +301,8 @@ export type ProfileUncheckedUpdateInput = {
|
|||||||
|
|
||||||
export type ProfileCreateManyInput = {
|
export type ProfileCreateManyInput = {
|
||||||
id?: string
|
id?: string
|
||||||
userId: string
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -294,6 +311,7 @@ export type ProfileCreateManyInput = {
|
|||||||
|
|
||||||
export type ProfileUpdateManyMutationInput = {
|
export type ProfileUpdateManyMutationInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -302,21 +320,28 @@ export type ProfileUpdateManyMutationInput = {
|
|||||||
|
|
||||||
export type ProfileUncheckedUpdateManyInput = {
|
export type ProfileUncheckedUpdateManyInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
userId?: Prisma.StringFieldUpdateOperationsInput | string
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileNullableScalarRelationFilter = {
|
export type ProfileListRelationFilter = {
|
||||||
is?: Prisma.ProfileWhereInput | null
|
every?: Prisma.ProfileWhereInput
|
||||||
isNot?: Prisma.ProfileWhereInput | null
|
some?: Prisma.ProfileWhereInput
|
||||||
|
none?: Prisma.ProfileWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileOrderByRelationAggregateInput = {
|
||||||
|
_count?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCountOrderByAggregateInput = {
|
export type ProfileCountOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
@@ -326,6 +351,7 @@ export type ProfileCountOrderByAggregateInput = {
|
|||||||
export type ProfileMaxOrderByAggregateInput = {
|
export type ProfileMaxOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
@@ -335,6 +361,7 @@ export type ProfileMaxOrderByAggregateInput = {
|
|||||||
export type ProfileMinOrderByAggregateInput = {
|
export type ProfileMinOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
userId?: Prisma.SortOrder
|
userId?: Prisma.SortOrder
|
||||||
|
email?: Prisma.SortOrder
|
||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
@@ -346,36 +373,46 @@ export type ProfileScalarRelationFilter = {
|
|||||||
isNot?: Prisma.ProfileWhereInput
|
isNot?: Prisma.ProfileWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCreateNestedOneWithoutUserInput = {
|
export type ProfileCreateNestedManyWithoutUserInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput> | Prisma.ProfileCreateWithoutUserInput[] | Prisma.ProfileUncheckedCreateWithoutUserInput[]
|
||||||
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput
|
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput | Prisma.ProfileCreateOrConnectWithoutUserInput[]
|
||||||
connect?: Prisma.ProfileWhereUniqueInput
|
createMany?: Prisma.ProfileCreateManyUserInputEnvelope
|
||||||
|
connect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedCreateNestedOneWithoutUserInput = {
|
export type ProfileUncheckedCreateNestedManyWithoutUserInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput> | Prisma.ProfileCreateWithoutUserInput[] | Prisma.ProfileUncheckedCreateWithoutUserInput[]
|
||||||
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput
|
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput | Prisma.ProfileCreateOrConnectWithoutUserInput[]
|
||||||
connect?: Prisma.ProfileWhereUniqueInput
|
createMany?: Prisma.ProfileCreateManyUserInputEnvelope
|
||||||
|
connect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUpdateOneWithoutUserNestedInput = {
|
export type ProfileUpdateManyWithoutUserNestedInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput> | Prisma.ProfileCreateWithoutUserInput[] | Prisma.ProfileUncheckedCreateWithoutUserInput[]
|
||||||
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput
|
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput | Prisma.ProfileCreateOrConnectWithoutUserInput[]
|
||||||
upsert?: Prisma.ProfileUpsertWithoutUserInput
|
upsert?: Prisma.ProfileUpsertWithWhereUniqueWithoutUserInput | Prisma.ProfileUpsertWithWhereUniqueWithoutUserInput[]
|
||||||
disconnect?: Prisma.ProfileWhereInput | boolean
|
createMany?: Prisma.ProfileCreateManyUserInputEnvelope
|
||||||
delete?: Prisma.ProfileWhereInput | boolean
|
set?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
connect?: Prisma.ProfileWhereUniqueInput
|
disconnect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ProfileUpdateToOneWithWhereWithoutUserInput, Prisma.ProfileUpdateWithoutUserInput>, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
delete?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
|
connect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
|
update?: Prisma.ProfileUpdateWithWhereUniqueWithoutUserInput | Prisma.ProfileUpdateWithWhereUniqueWithoutUserInput[]
|
||||||
|
updateMany?: Prisma.ProfileUpdateManyWithWhereWithoutUserInput | Prisma.ProfileUpdateManyWithWhereWithoutUserInput[]
|
||||||
|
deleteMany?: Prisma.ProfileScalarWhereInput | Prisma.ProfileScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedUpdateOneWithoutUserNestedInput = {
|
export type ProfileUncheckedUpdateManyWithoutUserNestedInput = {
|
||||||
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
create?: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput> | Prisma.ProfileCreateWithoutUserInput[] | Prisma.ProfileUncheckedCreateWithoutUserInput[]
|
||||||
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput
|
connectOrCreate?: Prisma.ProfileCreateOrConnectWithoutUserInput | Prisma.ProfileCreateOrConnectWithoutUserInput[]
|
||||||
upsert?: Prisma.ProfileUpsertWithoutUserInput
|
upsert?: Prisma.ProfileUpsertWithWhereUniqueWithoutUserInput | Prisma.ProfileUpsertWithWhereUniqueWithoutUserInput[]
|
||||||
disconnect?: Prisma.ProfileWhereInput | boolean
|
createMany?: Prisma.ProfileCreateManyUserInputEnvelope
|
||||||
delete?: Prisma.ProfileWhereInput | boolean
|
set?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
connect?: Prisma.ProfileWhereUniqueInput
|
disconnect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ProfileUpdateToOneWithWhereWithoutUserInput, Prisma.ProfileUpdateWithoutUserInput>, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
delete?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
|
connect?: Prisma.ProfileWhereUniqueInput | Prisma.ProfileWhereUniqueInput[]
|
||||||
|
update?: Prisma.ProfileUpdateWithWhereUniqueWithoutUserInput | Prisma.ProfileUpdateWithWhereUniqueWithoutUserInput[]
|
||||||
|
updateMany?: Prisma.ProfileUpdateManyWithWhereWithoutUserInput | Prisma.ProfileUpdateManyWithWhereWithoutUserInput[]
|
||||||
|
deleteMany?: Prisma.ProfileScalarWhereInput | Prisma.ProfileScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCreateNestedOneWithoutAddressesInput = {
|
export type ProfileCreateNestedOneWithoutAddressesInput = {
|
||||||
@@ -394,6 +431,7 @@ export type ProfileUpdateOneRequiredWithoutAddressesNestedInput = {
|
|||||||
|
|
||||||
export type ProfileCreateWithoutUserInput = {
|
export type ProfileCreateWithoutUserInput = {
|
||||||
id?: string
|
id?: string
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -403,6 +441,7 @@ export type ProfileCreateWithoutUserInput = {
|
|||||||
|
|
||||||
export type ProfileUncheckedCreateWithoutUserInput = {
|
export type ProfileUncheckedCreateWithoutUserInput = {
|
||||||
id?: string
|
id?: string
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -415,47 +454,54 @@ export type ProfileCreateOrConnectWithoutUserInput = {
|
|||||||
create: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
create: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUpsertWithoutUserInput = {
|
export type ProfileCreateManyUserInputEnvelope = {
|
||||||
update: Prisma.XOR<Prisma.ProfileUpdateWithoutUserInput, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
data: Prisma.ProfileCreateManyUserInput | Prisma.ProfileCreateManyUserInput[]
|
||||||
create: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
skipDuplicates?: boolean
|
||||||
where?: Prisma.ProfileWhereInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUpdateToOneWithWhereWithoutUserInput = {
|
export type ProfileUpsertWithWhereUniqueWithoutUserInput = {
|
||||||
where?: Prisma.ProfileWhereInput
|
where: Prisma.ProfileWhereUniqueInput
|
||||||
|
update: Prisma.XOR<Prisma.ProfileUpdateWithoutUserInput, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
||||||
|
create: Prisma.XOR<Prisma.ProfileCreateWithoutUserInput, Prisma.ProfileUncheckedCreateWithoutUserInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileUpdateWithWhereUniqueWithoutUserInput = {
|
||||||
|
where: Prisma.ProfileWhereUniqueInput
|
||||||
data: Prisma.XOR<Prisma.ProfileUpdateWithoutUserInput, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
data: Prisma.XOR<Prisma.ProfileUpdateWithoutUserInput, Prisma.ProfileUncheckedUpdateWithoutUserInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUpdateWithoutUserInput = {
|
export type ProfileUpdateManyWithWhereWithoutUserInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
where: Prisma.ProfileScalarWhereInput
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
data: Prisma.XOR<Prisma.ProfileUpdateManyMutationInput, Prisma.ProfileUncheckedUpdateManyWithoutUserInput>
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
|
||||||
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
|
||||||
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedUpdateWithoutUserInput = {
|
export type ProfileScalarWhereInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
AND?: Prisma.ProfileScalarWhereInput | Prisma.ProfileScalarWhereInput[]
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
OR?: Prisma.ProfileScalarWhereInput[]
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
NOT?: Prisma.ProfileScalarWhereInput | Prisma.ProfileScalarWhereInput[]
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFilter<"Profile"> | string
|
||||||
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
userId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
email?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
|
firstName?: Prisma.StringFilter<"Profile"> | string
|
||||||
|
lastName?: Prisma.StringFilter<"Profile"> | string
|
||||||
|
phoneNumber?: Prisma.StringFilter<"Profile"> | string
|
||||||
|
ghlContactId?: Prisma.StringNullableFilter<"Profile"> | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCreateWithoutAddressesInput = {
|
export type ProfileCreateWithoutAddressesInput = {
|
||||||
id?: string
|
id?: string
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
ghlContactId?: string | null
|
ghlContactId?: string | null
|
||||||
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
user?: Prisma.UserCreateNestedOneWithoutProfilesInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedCreateWithoutAddressesInput = {
|
export type ProfileUncheckedCreateWithoutAddressesInput = {
|
||||||
id?: string
|
id?: string
|
||||||
userId: string
|
userId?: string | null
|
||||||
|
email?: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -480,16 +526,56 @@ export type ProfileUpdateToOneWithWhereWithoutAddressesInput = {
|
|||||||
|
|
||||||
export type ProfileUpdateWithoutAddressesInput = {
|
export type ProfileUpdateWithoutAddressesInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
user?: Prisma.UserUpdateOneWithoutProfilesNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedUpdateWithoutAddressesInput = {
|
export type ProfileUncheckedUpdateWithoutAddressesInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
userId?: Prisma.StringFieldUpdateOperationsInput | string
|
userId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileCreateManyUserInput = {
|
||||||
|
id?: string
|
||||||
|
email?: string | null
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileUpdateWithoutUserInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileUncheckedUpdateWithoutUserInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
ghlContactId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProfileUncheckedUpdateManyWithoutUserInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
phoneNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -530,11 +616,12 @@ export type ProfileCountOutputTypeCountAddressesArgs<ExtArgs extends runtime.Typ
|
|||||||
export type ProfileSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProfileSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
|
email?: boolean
|
||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
ghlContactId?: boolean
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
@@ -542,54 +629,58 @@ export type ProfileSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||||||
export type ProfileSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProfileSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
|
email?: boolean
|
||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
ghlContactId?: boolean
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
|
|
||||||
export type ProfileSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ProfileSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
|
email?: boolean
|
||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
ghlContactId?: boolean
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
|
|
||||||
export type ProfileSelectScalar = {
|
export type ProfileSelectScalar = {
|
||||||
id?: boolean
|
id?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
|
email?: boolean
|
||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
ghlContactId?: boolean
|
ghlContactId?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "firstName" | "lastName" | "phoneNumber" | "ghlContactId", ExtArgs["result"]["profile"]>
|
export type ProfileOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "email" | "firstName" | "lastName" | "phoneNumber" | "ghlContactId", ExtArgs["result"]["profile"]>
|
||||||
export type ProfileInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProfileInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ProfileIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProfileIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ProfileIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ProfileIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.Profile$userArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $ProfilePayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type $ProfilePayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
name: "Profile"
|
name: "Profile"
|
||||||
objects: {
|
objects: {
|
||||||
user: Prisma.$UserPayload<ExtArgs>
|
user: Prisma.$UserPayload<ExtArgs> | null
|
||||||
addresses: Prisma.$AddressPayload<ExtArgs>[]
|
addresses: Prisma.$AddressPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string | null
|
||||||
|
email: string | null
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
@@ -988,7 +1079,7 @@ readonly fields: ProfileFieldRefs;
|
|||||||
*/
|
*/
|
||||||
export interface Prisma__ProfileClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
export interface Prisma__ProfileClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
user<T extends Prisma.UserDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.UserDefaultArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
user<T extends Prisma.Profile$userArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Profile$userArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
addresses<T extends Prisma.Profile$addressesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Profile$addressesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AddressPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
addresses<T extends Prisma.Profile$addressesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Profile$addressesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AddressPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
@@ -1021,6 +1112,7 @@ export interface Prisma__ProfileClient<T, Null = never, ExtArgs extends runtime.
|
|||||||
export interface ProfileFieldRefs {
|
export interface ProfileFieldRefs {
|
||||||
readonly id: Prisma.FieldRef<"Profile", 'String'>
|
readonly id: Prisma.FieldRef<"Profile", 'String'>
|
||||||
readonly userId: Prisma.FieldRef<"Profile", 'String'>
|
readonly userId: Prisma.FieldRef<"Profile", 'String'>
|
||||||
|
readonly email: Prisma.FieldRef<"Profile", 'String'>
|
||||||
readonly firstName: Prisma.FieldRef<"Profile", 'String'>
|
readonly firstName: Prisma.FieldRef<"Profile", 'String'>
|
||||||
readonly lastName: Prisma.FieldRef<"Profile", 'String'>
|
readonly lastName: Prisma.FieldRef<"Profile", 'String'>
|
||||||
readonly phoneNumber: Prisma.FieldRef<"Profile", 'String'>
|
readonly phoneNumber: Prisma.FieldRef<"Profile", 'String'>
|
||||||
@@ -1425,6 +1517,25 @@ export type ProfileDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Inter
|
|||||||
limit?: number
|
limit?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile.user
|
||||||
|
*/
|
||||||
|
export type Profile$userArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the User
|
||||||
|
*/
|
||||||
|
select?: Prisma.UserSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the User
|
||||||
|
*/
|
||||||
|
omit?: Prisma.UserOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.UserInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile.addresses
|
* Profile.addresses
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -243,9 +243,10 @@ export type UserWhereInput = {
|
|||||||
role?: Prisma.XOR<Prisma.UserRoleNullableScalarRelationFilter, Prisma.UserRoleWhereInput> | null
|
role?: Prisma.XOR<Prisma.UserRoleNullableScalarRelationFilter, Prisma.UserRoleWhereInput> | null
|
||||||
sessions?: Prisma.SessionListRelationFilter
|
sessions?: Prisma.SessionListRelationFilter
|
||||||
accounts?: Prisma.AccountListRelationFilter
|
accounts?: Prisma.AccountListRelationFilter
|
||||||
profile?: Prisma.XOR<Prisma.ProfileNullableScalarRelationFilter, Prisma.ProfileWhereInput> | null
|
profiles?: Prisma.ProfileListRelationFilter
|
||||||
licenseKeys?: Prisma.LicenseKeyListRelationFilter
|
licenseKeys?: Prisma.LicenseKeyListRelationFilter
|
||||||
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
||||||
|
schedules?: Prisma.ScheduleListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserOrderByWithRelationInput = {
|
export type UserOrderByWithRelationInput = {
|
||||||
@@ -260,9 +261,10 @@ export type UserOrderByWithRelationInput = {
|
|||||||
role?: Prisma.UserRoleOrderByWithRelationInput
|
role?: Prisma.UserRoleOrderByWithRelationInput
|
||||||
sessions?: Prisma.SessionOrderByRelationAggregateInput
|
sessions?: Prisma.SessionOrderByRelationAggregateInput
|
||||||
accounts?: Prisma.AccountOrderByRelationAggregateInput
|
accounts?: Prisma.AccountOrderByRelationAggregateInput
|
||||||
profile?: Prisma.ProfileOrderByWithRelationInput
|
profiles?: Prisma.ProfileOrderByRelationAggregateInput
|
||||||
licenseKeys?: Prisma.LicenseKeyOrderByRelationAggregateInput
|
licenseKeys?: Prisma.LicenseKeyOrderByRelationAggregateInput
|
||||||
userProclamations?: Prisma.UserProclamationsOrderByRelationAggregateInput
|
userProclamations?: Prisma.UserProclamationsOrderByRelationAggregateInput
|
||||||
|
schedules?: Prisma.ScheduleOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||||
@@ -280,9 +282,10 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
role?: Prisma.XOR<Prisma.UserRoleNullableScalarRelationFilter, Prisma.UserRoleWhereInput> | null
|
role?: Prisma.XOR<Prisma.UserRoleNullableScalarRelationFilter, Prisma.UserRoleWhereInput> | null
|
||||||
sessions?: Prisma.SessionListRelationFilter
|
sessions?: Prisma.SessionListRelationFilter
|
||||||
accounts?: Prisma.AccountListRelationFilter
|
accounts?: Prisma.AccountListRelationFilter
|
||||||
profile?: Prisma.XOR<Prisma.ProfileNullableScalarRelationFilter, Prisma.ProfileWhereInput> | null
|
profiles?: Prisma.ProfileListRelationFilter
|
||||||
licenseKeys?: Prisma.LicenseKeyListRelationFilter
|
licenseKeys?: Prisma.LicenseKeyListRelationFilter
|
||||||
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
userProclamations?: Prisma.UserProclamationsListRelationFilter
|
||||||
|
schedules?: Prisma.ScheduleListRelationFilter
|
||||||
}, "id" | "email">
|
}, "id" | "email">
|
||||||
|
|
||||||
export type UserOrderByWithAggregationInput = {
|
export type UserOrderByWithAggregationInput = {
|
||||||
@@ -326,9 +329,10 @@ export type UserCreateInput = {
|
|||||||
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateInput = {
|
export type UserUncheckedCreateInput = {
|
||||||
@@ -342,9 +346,10 @@ export type UserUncheckedCreateInput = {
|
|||||||
roleId?: number | null
|
roleId?: number | null
|
||||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateInput = {
|
export type UserUpdateInput = {
|
||||||
@@ -358,9 +363,10 @@ export type UserUpdateInput = {
|
|||||||
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateInput = {
|
export type UserUncheckedUpdateInput = {
|
||||||
@@ -374,9 +380,10 @@ export type UserUncheckedUpdateInput = {
|
|||||||
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateManyInput = {
|
export type UserCreateManyInput = {
|
||||||
@@ -452,6 +459,11 @@ export type UserSumOrderByAggregateInput = {
|
|||||||
roleId?: Prisma.SortOrder
|
roleId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserNullableScalarRelationFilter = {
|
||||||
|
is?: Prisma.UserWhereInput | null
|
||||||
|
isNot?: Prisma.UserWhereInput | null
|
||||||
|
}
|
||||||
|
|
||||||
export type UserScalarRelationFilter = {
|
export type UserScalarRelationFilter = {
|
||||||
is?: Prisma.UserWhereInput
|
is?: Prisma.UserWhereInput
|
||||||
isNot?: Prisma.UserWhereInput
|
isNot?: Prisma.UserWhereInput
|
||||||
@@ -491,18 +503,20 @@ export type NullableIntFieldUpdateOperationsInput = {
|
|||||||
divide?: number
|
divide?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateNestedOneWithoutProfileInput = {
|
export type UserCreateNestedOneWithoutProfilesInput = {
|
||||||
create?: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
create?: Prisma.XOR<Prisma.UserCreateWithoutProfilesInput, Prisma.UserUncheckedCreateWithoutProfilesInput>
|
||||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfileInput
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfilesInput
|
||||||
connect?: Prisma.UserWhereUniqueInput
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateOneRequiredWithoutProfileNestedInput = {
|
export type UserUpdateOneWithoutProfilesNestedInput = {
|
||||||
create?: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
create?: Prisma.XOR<Prisma.UserCreateWithoutProfilesInput, Prisma.UserUncheckedCreateWithoutProfilesInput>
|
||||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfileInput
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfilesInput
|
||||||
upsert?: Prisma.UserUpsertWithoutProfileInput
|
upsert?: Prisma.UserUpsertWithoutProfilesInput
|
||||||
|
disconnect?: Prisma.UserWhereInput | boolean
|
||||||
|
delete?: Prisma.UserWhereInput | boolean
|
||||||
connect?: Prisma.UserWhereUniqueInput
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutProfileInput, Prisma.UserUpdateWithoutProfileInput>, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutProfilesInput, Prisma.UserUpdateWithoutProfilesInput>, Prisma.UserUncheckedUpdateWithoutProfilesInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateNestedOneWithoutSessionsInput = {
|
export type UserCreateNestedOneWithoutSessionsInput = {
|
||||||
@@ -595,15 +609,33 @@ export type UserCreateNestedOneWithoutLicenseKeysInput = {
|
|||||||
connect?: Prisma.UserWhereUniqueInput
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateOneRequiredWithoutLicenseKeysNestedInput = {
|
export type UserUpdateOneWithoutLicenseKeysNestedInput = {
|
||||||
create?: Prisma.XOR<Prisma.UserCreateWithoutLicenseKeysInput, Prisma.UserUncheckedCreateWithoutLicenseKeysInput>
|
create?: Prisma.XOR<Prisma.UserCreateWithoutLicenseKeysInput, Prisma.UserUncheckedCreateWithoutLicenseKeysInput>
|
||||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutLicenseKeysInput
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutLicenseKeysInput
|
||||||
upsert?: Prisma.UserUpsertWithoutLicenseKeysInput
|
upsert?: Prisma.UserUpsertWithoutLicenseKeysInput
|
||||||
|
disconnect?: Prisma.UserWhereInput | boolean
|
||||||
|
delete?: Prisma.UserWhereInput | boolean
|
||||||
connect?: Prisma.UserWhereUniqueInput
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutLicenseKeysInput, Prisma.UserUpdateWithoutLicenseKeysInput>, Prisma.UserUncheckedUpdateWithoutLicenseKeysInput>
|
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutLicenseKeysInput, Prisma.UserUpdateWithoutLicenseKeysInput>, Prisma.UserUncheckedUpdateWithoutLicenseKeysInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutProfileInput = {
|
export type UserCreateNestedOneWithoutSchedulesInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.UserCreateWithoutSchedulesInput, Prisma.UserUncheckedCreateWithoutSchedulesInput>
|
||||||
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutSchedulesInput
|
||||||
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateOneWithoutSchedulesNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.UserCreateWithoutSchedulesInput, Prisma.UserUncheckedCreateWithoutSchedulesInput>
|
||||||
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutSchedulesInput
|
||||||
|
upsert?: Prisma.UserUpsertWithoutSchedulesInput
|
||||||
|
disconnect?: Prisma.UserWhereInput | boolean
|
||||||
|
delete?: Prisma.UserWhereInput | boolean
|
||||||
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutSchedulesInput, Prisma.UserUpdateWithoutSchedulesInput>, Prisma.UserUncheckedUpdateWithoutSchedulesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserCreateWithoutProfilesInput = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
@@ -616,9 +648,10 @@ export type UserCreateWithoutProfileInput = {
|
|||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutProfileInput = {
|
export type UserUncheckedCreateWithoutProfilesInput = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
@@ -631,25 +664,26 @@ export type UserUncheckedCreateWithoutProfileInput = {
|
|||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutProfileInput = {
|
export type UserCreateOrConnectWithoutProfilesInput = {
|
||||||
where: Prisma.UserWhereUniqueInput
|
where: Prisma.UserWhereUniqueInput
|
||||||
create: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
create: Prisma.XOR<Prisma.UserCreateWithoutProfilesInput, Prisma.UserUncheckedCreateWithoutProfilesInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpsertWithoutProfileInput = {
|
export type UserUpsertWithoutProfilesInput = {
|
||||||
update: Prisma.XOR<Prisma.UserUpdateWithoutProfileInput, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
update: Prisma.XOR<Prisma.UserUpdateWithoutProfilesInput, Prisma.UserUncheckedUpdateWithoutProfilesInput>
|
||||||
create: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
create: Prisma.XOR<Prisma.UserCreateWithoutProfilesInput, Prisma.UserUncheckedCreateWithoutProfilesInput>
|
||||||
where?: Prisma.UserWhereInput
|
where?: Prisma.UserWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateToOneWithWhereWithoutProfileInput = {
|
export type UserUpdateToOneWithWhereWithoutProfilesInput = {
|
||||||
where?: Prisma.UserWhereInput
|
where?: Prisma.UserWhereInput
|
||||||
data: Prisma.XOR<Prisma.UserUpdateWithoutProfileInput, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
data: Prisma.XOR<Prisma.UserUpdateWithoutProfilesInput, Prisma.UserUncheckedUpdateWithoutProfilesInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateWithoutProfileInput = {
|
export type UserUpdateWithoutProfilesInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -662,9 +696,10 @@ export type UserUpdateWithoutProfileInput = {
|
|||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutProfileInput = {
|
export type UserUncheckedUpdateWithoutProfilesInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -677,6 +712,7 @@ export type UserUncheckedUpdateWithoutProfileInput = {
|
|||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutSessionsInput = {
|
export type UserCreateWithoutSessionsInput = {
|
||||||
@@ -689,9 +725,10 @@ export type UserCreateWithoutSessionsInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutSessionsInput = {
|
export type UserUncheckedCreateWithoutSessionsInput = {
|
||||||
@@ -704,9 +741,10 @@ export type UserUncheckedCreateWithoutSessionsInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
roleId?: number | null
|
roleId?: number | null
|
||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutSessionsInput = {
|
export type UserCreateOrConnectWithoutSessionsInput = {
|
||||||
@@ -735,9 +773,10 @@ export type UserUpdateWithoutSessionsInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutSessionsInput = {
|
export type UserUncheckedUpdateWithoutSessionsInput = {
|
||||||
@@ -750,9 +789,10 @@ export type UserUncheckedUpdateWithoutSessionsInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutAccountsInput = {
|
export type UserCreateWithoutAccountsInput = {
|
||||||
@@ -765,9 +805,10 @@ export type UserCreateWithoutAccountsInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutAccountsInput = {
|
export type UserUncheckedCreateWithoutAccountsInput = {
|
||||||
@@ -780,9 +821,10 @@ export type UserUncheckedCreateWithoutAccountsInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
roleId?: number | null
|
roleId?: number | null
|
||||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutAccountsInput = {
|
export type UserCreateOrConnectWithoutAccountsInput = {
|
||||||
@@ -811,9 +853,10 @@ export type UserUpdateWithoutAccountsInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutAccountsInput = {
|
export type UserUncheckedUpdateWithoutAccountsInput = {
|
||||||
@@ -826,9 +869,10 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutRoleInput = {
|
export type UserCreateWithoutRoleInput = {
|
||||||
@@ -841,9 +885,10 @@ export type UserCreateWithoutRoleInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutRoleInput = {
|
export type UserUncheckedCreateWithoutRoleInput = {
|
||||||
@@ -856,9 +901,10 @@ export type UserUncheckedCreateWithoutRoleInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutRoleInput = {
|
export type UserCreateOrConnectWithoutRoleInput = {
|
||||||
@@ -912,8 +958,9 @@ export type UserCreateWithoutUserProclamationsInput = {
|
|||||||
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutUserProclamationsInput = {
|
export type UserUncheckedCreateWithoutUserProclamationsInput = {
|
||||||
@@ -927,8 +974,9 @@ export type UserUncheckedCreateWithoutUserProclamationsInput = {
|
|||||||
roleId?: number | null
|
roleId?: number | null
|
||||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutUserProclamationsInput = {
|
export type UserCreateOrConnectWithoutUserProclamationsInput = {
|
||||||
@@ -958,8 +1006,9 @@ export type UserUpdateWithoutUserProclamationsInput = {
|
|||||||
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutUserProclamationsInput = {
|
export type UserUncheckedUpdateWithoutUserProclamationsInput = {
|
||||||
@@ -973,8 +1022,9 @@ export type UserUncheckedUpdateWithoutUserProclamationsInput = {
|
|||||||
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutLicenseKeysInput = {
|
export type UserCreateWithoutLicenseKeysInput = {
|
||||||
@@ -988,8 +1038,9 @@ export type UserCreateWithoutLicenseKeysInput = {
|
|||||||
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutLicenseKeysInput = {
|
export type UserUncheckedCreateWithoutLicenseKeysInput = {
|
||||||
@@ -1003,8 +1054,9 @@ export type UserUncheckedCreateWithoutLicenseKeysInput = {
|
|||||||
roleId?: number | null
|
roleId?: number | null
|
||||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
profile?: Prisma.ProfileUncheckedCreateNestedOneWithoutUserInput
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedCreateNestedManyWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutLicenseKeysInput = {
|
export type UserCreateOrConnectWithoutLicenseKeysInput = {
|
||||||
@@ -1034,8 +1086,9 @@ export type UserUpdateWithoutLicenseKeysInput = {
|
|||||||
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutLicenseKeysInput = {
|
export type UserUncheckedUpdateWithoutLicenseKeysInput = {
|
||||||
@@ -1049,7 +1102,88 @@ export type UserUncheckedUpdateWithoutLicenseKeysInput = {
|
|||||||
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserCreateWithoutSchedulesInput = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
emailVerified?: boolean
|
||||||
|
image?: string | null
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
role?: Prisma.UserRoleCreateNestedOneWithoutUsersInput
|
||||||
|
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||||
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
|
profiles?: Prisma.ProfileCreateNestedManyWithoutUserInput
|
||||||
|
licenseKeys?: Prisma.LicenseKeyCreateNestedManyWithoutUserInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutUserInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUncheckedCreateWithoutSchedulesInput = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
emailVerified?: boolean
|
||||||
|
image?: string | null
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
roleId?: number | null
|
||||||
|
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
profiles?: Prisma.ProfileUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
licenseKeys?: Prisma.LicenseKeyUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserCreateOrConnectWithoutSchedulesInput = {
|
||||||
|
where: Prisma.UserWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.UserCreateWithoutSchedulesInput, Prisma.UserUncheckedCreateWithoutSchedulesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpsertWithoutSchedulesInput = {
|
||||||
|
update: Prisma.XOR<Prisma.UserUpdateWithoutSchedulesInput, Prisma.UserUncheckedUpdateWithoutSchedulesInput>
|
||||||
|
create: Prisma.XOR<Prisma.UserCreateWithoutSchedulesInput, Prisma.UserUncheckedCreateWithoutSchedulesInput>
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateToOneWithWhereWithoutSchedulesInput = {
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.UserUpdateWithoutSchedulesInput, Prisma.UserUncheckedUpdateWithoutSchedulesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateWithoutSchedulesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
role?: Prisma.UserRoleUpdateOneWithoutUsersNestedInput
|
||||||
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUncheckedUpdateWithoutSchedulesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
roleId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null
|
||||||
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1073,9 +1207,10 @@ export type UserUpdateWithoutRoleInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutRoleInput = {
|
export type UserUncheckedUpdateWithoutRoleInput = {
|
||||||
@@ -1088,9 +1223,10 @@ export type UserUncheckedUpdateWithoutRoleInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
profile?: Prisma.ProfileUncheckedUpdateOneWithoutUserNestedInput
|
profiles?: Prisma.ProfileUncheckedUpdateManyWithoutUserNestedInput
|
||||||
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
licenseKeys?: Prisma.LicenseKeyUncheckedUpdateManyWithoutUserNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
schedules?: Prisma.ScheduleUncheckedUpdateManyWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateManyWithoutRoleInput = {
|
export type UserUncheckedUpdateManyWithoutRoleInput = {
|
||||||
@@ -1111,15 +1247,19 @@ export type UserUncheckedUpdateManyWithoutRoleInput = {
|
|||||||
export type UserCountOutputType = {
|
export type UserCountOutputType = {
|
||||||
sessions: number
|
sessions: number
|
||||||
accounts: number
|
accounts: number
|
||||||
|
profiles: number
|
||||||
licenseKeys: number
|
licenseKeys: number
|
||||||
userProclamations: number
|
userProclamations: number
|
||||||
|
schedules: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
sessions?: boolean | UserCountOutputTypeCountSessionsArgs
|
sessions?: boolean | UserCountOutputTypeCountSessionsArgs
|
||||||
accounts?: boolean | UserCountOutputTypeCountAccountsArgs
|
accounts?: boolean | UserCountOutputTypeCountAccountsArgs
|
||||||
|
profiles?: boolean | UserCountOutputTypeCountProfilesArgs
|
||||||
licenseKeys?: boolean | UserCountOutputTypeCountLicenseKeysArgs
|
licenseKeys?: boolean | UserCountOutputTypeCountLicenseKeysArgs
|
||||||
userProclamations?: boolean | UserCountOutputTypeCountUserProclamationsArgs
|
userProclamations?: boolean | UserCountOutputTypeCountUserProclamationsArgs
|
||||||
|
schedules?: boolean | UserCountOutputTypeCountSchedulesArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1146,6 +1286,13 @@ export type UserCountOutputTypeCountAccountsArgs<ExtArgs extends runtime.Types.E
|
|||||||
where?: Prisma.AccountWhereInput
|
where?: Prisma.AccountWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type UserCountOutputTypeCountProfilesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.ProfileWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserCountOutputType without action
|
* UserCountOutputType without action
|
||||||
*/
|
*/
|
||||||
@@ -1160,6 +1307,13 @@ export type UserCountOutputTypeCountUserProclamationsArgs<ExtArgs extends runtim
|
|||||||
where?: Prisma.UserProclamationsWhereInput
|
where?: Prisma.UserProclamationsWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type UserCountOutputTypeCountSchedulesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.ScheduleWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
@@ -1173,9 +1327,10 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
|||||||
role?: boolean | Prisma.User$roleArgs<ExtArgs>
|
role?: boolean | Prisma.User$roleArgs<ExtArgs>
|
||||||
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
||||||
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
||||||
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
|
profiles?: boolean | Prisma.User$profilesArgs<ExtArgs>
|
||||||
licenseKeys?: boolean | Prisma.User$licenseKeysArgs<ExtArgs>
|
licenseKeys?: boolean | Prisma.User$licenseKeysArgs<ExtArgs>
|
||||||
userProclamations?: boolean | Prisma.User$userProclamationsArgs<ExtArgs>
|
userProclamations?: boolean | Prisma.User$userProclamationsArgs<ExtArgs>
|
||||||
|
schedules?: boolean | Prisma.User$schedulesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["user"]>
|
}, ExtArgs["result"]["user"]>
|
||||||
|
|
||||||
@@ -1219,9 +1374,10 @@ export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
role?: boolean | Prisma.User$roleArgs<ExtArgs>
|
role?: boolean | Prisma.User$roleArgs<ExtArgs>
|
||||||
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
||||||
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
||||||
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
|
profiles?: boolean | Prisma.User$profilesArgs<ExtArgs>
|
||||||
licenseKeys?: boolean | Prisma.User$licenseKeysArgs<ExtArgs>
|
licenseKeys?: boolean | Prisma.User$licenseKeysArgs<ExtArgs>
|
||||||
userProclamations?: boolean | Prisma.User$userProclamationsArgs<ExtArgs>
|
userProclamations?: boolean | Prisma.User$userProclamationsArgs<ExtArgs>
|
||||||
|
schedules?: boolean | Prisma.User$schedulesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type UserIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type UserIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
@@ -1237,9 +1393,10 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
role: Prisma.$UserRolePayload<ExtArgs> | null
|
role: Prisma.$UserRolePayload<ExtArgs> | null
|
||||||
sessions: Prisma.$SessionPayload<ExtArgs>[]
|
sessions: Prisma.$SessionPayload<ExtArgs>[]
|
||||||
accounts: Prisma.$AccountPayload<ExtArgs>[]
|
accounts: Prisma.$AccountPayload<ExtArgs>[]
|
||||||
profile: Prisma.$ProfilePayload<ExtArgs> | null
|
profiles: Prisma.$ProfilePayload<ExtArgs>[]
|
||||||
licenseKeys: Prisma.$LicenseKeyPayload<ExtArgs>[]
|
licenseKeys: Prisma.$LicenseKeyPayload<ExtArgs>[]
|
||||||
userProclamations: Prisma.$UserProclamationsPayload<ExtArgs>[]
|
userProclamations: Prisma.$UserProclamationsPayload<ExtArgs>[]
|
||||||
|
schedules: Prisma.$SchedulePayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
@@ -1647,9 +1804,10 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
|
|||||||
role<T extends Prisma.User$roleArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$roleArgs<ExtArgs>>): Prisma.Prisma__UserRoleClient<runtime.Types.Result.GetResult<Prisma.$UserRolePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
role<T extends Prisma.User$roleArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$roleArgs<ExtArgs>>): Prisma.Prisma__UserRoleClient<runtime.Types.Result.GetResult<Prisma.$UserRolePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
sessions<T extends Prisma.User$sessionsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$sessionsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SessionPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
sessions<T extends Prisma.User$sessionsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$sessionsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SessionPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
accounts<T extends Prisma.User$accountsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$accountsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AccountPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
accounts<T extends Prisma.User$accountsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$accountsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AccountPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
profile<T extends Prisma.User$profileArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$profileArgs<ExtArgs>>): Prisma.Prisma__ProfileClient<runtime.Types.Result.GetResult<Prisma.$ProfilePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
profiles<T extends Prisma.User$profilesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$profilesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ProfilePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
licenseKeys<T extends Prisma.User$licenseKeysArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$licenseKeysArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$LicenseKeyPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
licenseKeys<T extends Prisma.User$licenseKeysArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$licenseKeysArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$LicenseKeyPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
userProclamations<T extends Prisma.User$userProclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$userProclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UserProclamationsPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
userProclamations<T extends Prisma.User$userProclamationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$userProclamationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UserProclamationsPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
|
schedules<T extends Prisma.User$schedulesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$schedulesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SchedulePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -2155,9 +2313,9 @@ export type User$accountsArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User.profile
|
* User.profiles
|
||||||
*/
|
*/
|
||||||
export type User$profileArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type User$profilesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
/**
|
/**
|
||||||
* Select specific fields to fetch from the Profile
|
* Select specific fields to fetch from the Profile
|
||||||
*/
|
*/
|
||||||
@@ -2171,6 +2329,11 @@ export type User$profileArgs<ExtArgs extends runtime.Types.Extensions.InternalAr
|
|||||||
*/
|
*/
|
||||||
include?: Prisma.ProfileInclude<ExtArgs> | null
|
include?: Prisma.ProfileInclude<ExtArgs> | null
|
||||||
where?: Prisma.ProfileWhereInput
|
where?: Prisma.ProfileWhereInput
|
||||||
|
orderBy?: Prisma.ProfileOrderByWithRelationInput | Prisma.ProfileOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.ProfileWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.ProfileScalarFieldEnum | Prisma.ProfileScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2221,6 +2384,30 @@ export type User$userProclamationsArgs<ExtArgs extends runtime.Types.Extensions.
|
|||||||
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User.schedules
|
||||||
|
*/
|
||||||
|
export type User$schedulesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the Schedule
|
||||||
|
*/
|
||||||
|
select?: Prisma.ScheduleSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the Schedule
|
||||||
|
*/
|
||||||
|
omit?: Prisma.ScheduleOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.ScheduleInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.ScheduleWhereInput
|
||||||
|
orderBy?: Prisma.ScheduleOrderByWithRelationInput | Prisma.ScheduleOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.ScheduleWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.ScheduleScalarFieldEnum | Prisma.ScheduleScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User without action
|
* User without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
blockDuration,
|
||||||
|
blockSegments,
|
||||||
|
findOverlap,
|
||||||
|
findScheduleOverlap,
|
||||||
|
fiveMinuteOptions,
|
||||||
|
formatTime,
|
||||||
|
hmToMinutes,
|
||||||
|
isOvernight,
|
||||||
|
parseTimeInput,
|
||||||
|
snapMinutes,
|
||||||
|
toHm,
|
||||||
|
validateBlock,
|
||||||
|
validateBlockShape,
|
||||||
|
validateSchedule,
|
||||||
|
type BlockInput,
|
||||||
|
type BlockSibling
|
||||||
|
} from './schedule-utils';
|
||||||
|
|
||||||
|
const input = (overrides: Partial<BlockInput> = {}): BlockInput => ({
|
||||||
|
startMinutes: 6 * 60,
|
||||||
|
endMinutes: 8 * 60,
|
||||||
|
proclamationIds: ['p1'],
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
|
||||||
|
const sibling = (startMinutes: number, endMinutes: number, id = 'b1'): BlockSibling => ({
|
||||||
|
id,
|
||||||
|
startMinutes,
|
||||||
|
endMinutes
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatTime', () => {
|
||||||
|
it('formats 12-hour clock labels', () => {
|
||||||
|
expect(formatTime(0)).toBe('12:00 AM');
|
||||||
|
expect(formatTime(6 * 60)).toBe('6:00 AM');
|
||||||
|
expect(formatTime(8 * 60 + 5)).toBe('8:05 AM');
|
||||||
|
expect(formatTime(12 * 60)).toBe('12:00 PM');
|
||||||
|
expect(formatTime(13 * 60 + 30)).toBe('1:30 PM');
|
||||||
|
expect(formatTime(23 * 60 + 59)).toBe('11:59 PM');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toHm', () => {
|
||||||
|
it('formats 24-hour HH:MM strings', () => {
|
||||||
|
expect(toHm(0)).toBe('00:00');
|
||||||
|
expect(toHm(6 * 60)).toBe('06:00');
|
||||||
|
expect(toHm(23 * 60 + 59)).toBe('23:59');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hmToMinutes', () => {
|
||||||
|
it('parses 24-hour HH:MM strings', () => {
|
||||||
|
expect(hmToMinutes('00:00')).toBe(0);
|
||||||
|
expect(hmToMinutes('07:05')).toBe(7 * 60 + 5);
|
||||||
|
expect(hmToMinutes('23:59')).toBe(23 * 60 + 59);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects malformed or out-of-range values', () => {
|
||||||
|
expect(hmToMinutes('7:05')).toBeNull();
|
||||||
|
expect(hmToMinutes('24:00')).toBeNull();
|
||||||
|
expect(hmToMinutes('12:60')).toBeNull();
|
||||||
|
expect(hmToMinutes('')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseTimeInput', () => {
|
||||||
|
it('parses tolerant 12-hour input', () => {
|
||||||
|
expect(parseTimeInput('7:05 pm')).toEqual({ ok: true, minutes: 19 * 60 + 5 });
|
||||||
|
expect(parseTimeInput('7:05PM')).toEqual({ ok: true, minutes: 19 * 60 + 5 });
|
||||||
|
expect(parseTimeInput('7pm')).toEqual({ ok: true, minutes: 19 * 60 });
|
||||||
|
expect(parseTimeInput('7:7')).toEqual({ ok: true, minutes: 7 * 60 + 7 });
|
||||||
|
expect(parseTimeInput('12:00 am')).toEqual({ ok: true, minutes: 0 });
|
||||||
|
expect(parseTimeInput('12:30 PM')).toEqual({ ok: true, minutes: 12 * 60 + 30 });
|
||||||
|
expect(parseTimeInput('11:59 pm')).toEqual({ ok: true, minutes: 23 * 60 + 59 });
|
||||||
|
expect(parseTimeInput('0:30')).toEqual({ ok: true, minutes: 30 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects ambiguous or out-of-range input', () => {
|
||||||
|
expect(parseTimeInput('13:00 PM').ok).toBe(false);
|
||||||
|
expect(parseTimeInput('7:70').ok).toBe(false);
|
||||||
|
expect(parseTimeInput('25:00').ok).toBe(false);
|
||||||
|
expect(parseTimeInput('').ok).toBe(false);
|
||||||
|
expect(parseTimeInput('noon').ok).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fiveMinuteOptions', () => {
|
||||||
|
it('lists every 5-minute step across the day', () => {
|
||||||
|
const options = fiveMinuteOptions();
|
||||||
|
expect(options[0]).toBe(0);
|
||||||
|
expect(options[1]).toBe(5);
|
||||||
|
expect(options[options.length - 1]).toBe(23 * 60 + 55);
|
||||||
|
expect(options).toHaveLength((24 * 60) / 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('snapMinutes', () => {
|
||||||
|
it('rounds to the nearest 15 minutes and clamps to the day', () => {
|
||||||
|
expect(snapMinutes(0)).toBe(0);
|
||||||
|
expect(snapMinutes(7)).toBe(0);
|
||||||
|
expect(snapMinutes(8)).toBe(15);
|
||||||
|
expect(snapMinutes(370)).toBe(375);
|
||||||
|
expect(snapMinutes(-10)).toBe(0);
|
||||||
|
expect(snapMinutes(2000)).toBe(1440);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isOvernight', () => {
|
||||||
|
it('is true when the end clock time is before the start', () => {
|
||||||
|
expect(isOvernight({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toBe(true);
|
||||||
|
expect(isOvernight({ startMinutes: 22 * 60, endMinutes: 0 })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is false for same-day ranges and a full-day range', () => {
|
||||||
|
expect(isOvernight({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toBe(false);
|
||||||
|
expect(isOvernight({ startMinutes: 0, endMinutes: 1440 })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('blockDuration', () => {
|
||||||
|
it('counts a same-day range directly', () => {
|
||||||
|
expect(blockDuration({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toBe(120);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts an overnight range as wrapping to the next day', () => {
|
||||||
|
expect(blockDuration({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toBe(720);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats an end of midnight as wrapping from the start', () => {
|
||||||
|
expect(blockDuration({ startMinutes: 22 * 60, endMinutes: 0 })).toBe(120);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('blockSegments', () => {
|
||||||
|
it('returns a single segment for a same-day range', () => {
|
||||||
|
expect(blockSegments({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toEqual([[6 * 60, 8 * 60]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('splits an overnight range at midnight', () => {
|
||||||
|
expect(blockSegments({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toEqual([
|
||||||
|
[21 * 60, 1440],
|
||||||
|
[0, 9 * 60]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no segments for a zero-length range', () => {
|
||||||
|
expect(blockSegments({ startMinutes: 360, endMinutes: 360 })).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOverlap', () => {
|
||||||
|
it('returns null for adjacent ranges', () => {
|
||||||
|
expect(
|
||||||
|
findOverlap({ startMinutes: 8 * 60, endMinutes: 9 * 60 }, [sibling(6 * 60, 8 * 60)])
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the sibling for a partial overlap', () => {
|
||||||
|
const other = sibling(7 * 60, 9 * 60);
|
||||||
|
expect(findOverlap({ startMinutes: 8 * 60, endMinutes: 10 * 60 }, [other])).toBe(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects an overnight block overlapping an early-morning block', () => {
|
||||||
|
const other = sibling(8 * 60, 10 * 60);
|
||||||
|
expect(findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [other])).toBe(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects an overnight block overlapping a late-evening block', () => {
|
||||||
|
const other = sibling(20 * 60, 22 * 60);
|
||||||
|
expect(findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [other])).toBe(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows an overnight block and its exact complement (adjacent at both ends)', () => {
|
||||||
|
expect(
|
||||||
|
findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [sibling(9 * 60, 21 * 60)])
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows an overnight block and a midday block it does not touch', () => {
|
||||||
|
expect(
|
||||||
|
findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [sibling(10 * 60, 20 * 60)])
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateBlock', () => {
|
||||||
|
it('accepts a valid block with no siblings', () => {
|
||||||
|
expect(validateBlock(input(), [], { isCreate: true })).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects equal start and end times', () => {
|
||||||
|
const result = validateBlock(input({ endMinutes: 6 * 60 }), []);
|
||||||
|
expect(result).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: "Start and end time can't be the same."
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a time outside the 24-hour range', () => {
|
||||||
|
expect(validateBlock(input({ startMinutes: 23 * 60, endMinutes: 25 * 60 }), [])).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'Enter a valid start and end time.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a negative start time', () => {
|
||||||
|
expect(validateBlock(input({ startMinutes: -1 }), [])).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'Enter a valid start and end time.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts an overnight block', () => {
|
||||||
|
expect(
|
||||||
|
validateBlock(input({ startMinutes: 21 * 60, endMinutes: 9 * 60 }), [], {
|
||||||
|
isCreate: true
|
||||||
|
})
|
||||||
|
).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an overnight block that overlaps another and names it', () => {
|
||||||
|
const result = validateBlock(input({ startMinutes: 21 * 60, endMinutes: 9 * 60 }), [
|
||||||
|
sibling(8 * 60, 10 * 60)
|
||||||
|
]);
|
||||||
|
expect(result).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'This overlaps with your 8:00 AM–10:00 AM block.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires at least one proclamation', () => {
|
||||||
|
expect(validateBlock(input({ proclamationIds: [] }), [])).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'Select at least one proclamation.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces the four-block maximum on create', () => {
|
||||||
|
const siblings = [
|
||||||
|
sibling(0, 60, 'a'),
|
||||||
|
sibling(60, 120, 'b'),
|
||||||
|
sibling(120, 180, 'c'),
|
||||||
|
sibling(180, 240, 'd')
|
||||||
|
];
|
||||||
|
const result = validateBlock(input({ startMinutes: 300, endMinutes: 360 }), siblings, {
|
||||||
|
isCreate: true
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ ok: false, error: 'You can have at most 4 blocks.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows adjacent blocks (end exactly equals next start)', () => {
|
||||||
|
expect(
|
||||||
|
validateBlock(input({ startMinutes: 8 * 60, endMinutes: 9 * 60 }), [sibling(6 * 60, 8 * 60)])
|
||||||
|
).toEqual({
|
||||||
|
ok: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an exact duplicate range and names the conflicting block', () => {
|
||||||
|
const result = validateBlock(input(), [sibling(6 * 60, 8 * 60)]);
|
||||||
|
expect(result).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'This overlaps with your 6:00 AM–8:00 AM block.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a partial overlap', () => {
|
||||||
|
const result = validateBlock(input({ startMinutes: 7 * 60, endMinutes: 9 * 60 }), [
|
||||||
|
sibling(8 * 60, 10 * 60)
|
||||||
|
]);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a block that contains another block', () => {
|
||||||
|
const result = validateBlock(input({ startMinutes: 5 * 60, endMinutes: 11 * 60 }), [
|
||||||
|
sibling(6 * 60, 8 * 60)
|
||||||
|
]);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not apply the maximum when updating (isCreate false)', () => {
|
||||||
|
const siblings = [sibling(0, 60, 'a'), sibling(60, 120, 'b'), sibling(120, 180, 'c')];
|
||||||
|
expect(validateBlock(input({ startMinutes: 300, endMinutes: 360 }), siblings).ok).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Schedule-wide validation (daily + weekly) ─────────────────────────
|
||||||
|
|
||||||
|
const block = (
|
||||||
|
startMinutes: number,
|
||||||
|
endMinutes: number,
|
||||||
|
day: string | null = null,
|
||||||
|
proclamationIds: string[] = ['p1']
|
||||||
|
) => ({ day, startMinutes, endMinutes, proclamationIds });
|
||||||
|
|
||||||
|
describe('validateBlockShape', () => {
|
||||||
|
it('rejects start == end', () => {
|
||||||
|
expect(validateBlockShape(block(360, 360))).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: "Start and end time can't be the same."
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an empty block', () => {
|
||||||
|
expect(validateBlockShape(block(360, 420, null, []))).toEqual({
|
||||||
|
ok: false,
|
||||||
|
error: 'Select at least one proclamation.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts a single-minute block', () => {
|
||||||
|
expect(validateBlockShape(block(360, 361))).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateSchedule daily', () => {
|
||||||
|
it('accepts adjacent blocks (touching boundaries)', () => {
|
||||||
|
expect(validateSchedule('daily', [block(360, 420), block(420, 510)])).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts a single-minute block', () => {
|
||||||
|
expect(validateSchedule('daily', [block(360, 361)])).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects overlapping blocks', () => {
|
||||||
|
expect(validateSchedule('daily', [block(360, 480), block(420, 540)]).ok).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a midnight-crossing block that spills into an early block', () => {
|
||||||
|
// 21:00→09:00 covers [0,540) on the next day; 08:00→08:30 overlaps.
|
||||||
|
expect(validateSchedule('daily', [block(1260, 540, null), block(480, 510, null)]).ok).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts a crossing block and its exact complement', () => {
|
||||||
|
expect(validateSchedule('daily', [block(1260, 540, null), block(540, 1260, null)])).toEqual({
|
||||||
|
ok: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateSchedule weekly', () => {
|
||||||
|
it('applies rules per day', () => {
|
||||||
|
expect(validateSchedule('weekly', [block(360, 420, 'mon'), block(360, 420, 'tue')])).toEqual({
|
||||||
|
ok: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects overlaps within the same day', () => {
|
||||||
|
expect(validateSchedule('weekly', [block(360, 480, 'wed'), block(420, 540, 'wed')]).ok).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects Sunday midnight-crossing spilling into Monday', () => {
|
||||||
|
expect(validateSchedule('weekly', [block(1380, 60, 'sun'), block(0, 120, 'mon')]).ok).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts a Sunday crossing block adjacent to a Monday block', () => {
|
||||||
|
expect(validateSchedule('weekly', [block(1380, 60, 'sun'), block(60, 120, 'mon')])).toEqual({
|
||||||
|
ok: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a Saturday crossing spilling into Sunday', () => {
|
||||||
|
expect(validateSchedule('weekly', [block(1380, 60, 'sat'), block(30, 90, 'sun')]).ok).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findScheduleOverlap', () => {
|
||||||
|
it('returns null for gaps', () => {
|
||||||
|
expect(findScheduleOverlap('daily', [block(360, 420), block(600, 660)])).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
// Isomorphic, dependency-free schedule helpers. Used by the server service
|
||||||
|
// layer, the v2 serializers and the client-side Block modal (for live
|
||||||
|
// pre-validation), so it must not import Prisma or any server-only code.
|
||||||
|
|
||||||
|
export const MAX_BLOCKS = 4;
|
||||||
|
export const MINUTES_PER_DAY = 24 * 60;
|
||||||
|
export const SNAP_MINUTES = 15;
|
||||||
|
export const MINUTE_STEP = 5;
|
||||||
|
|
||||||
|
export type BlockSibling = {
|
||||||
|
id?: string;
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BlockInput = {
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
proclamationIds: string[];
|
||||||
|
/** When true the kiosk enables dynamic audio for the block's duration. */
|
||||||
|
dynamicAudio?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ValidationResult = { ok: true } | { ok: false; error: string };
|
||||||
|
|
||||||
|
// ─── Schedule-wide validation (daily + weekly, midnight spillover) ─────
|
||||||
|
|
||||||
|
export type ScheduleType = 'daily' | 'weekly';
|
||||||
|
|
||||||
|
export const WEEK_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] as const;
|
||||||
|
export type WeekDay = (typeof WEEK_DAYS)[number];
|
||||||
|
|
||||||
|
/** A Block as seen by the validator. `day` is null for daily schedules. */
|
||||||
|
export type ValidationBlock = {
|
||||||
|
id?: string;
|
||||||
|
day?: string | null;
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
proclamationIds?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Block length in minutes, counting a wrap past midnight as the next day. */
|
||||||
|
function blockLinearLength(block: { startMinutes: number; endMinutes: number }): number {
|
||||||
|
return block.endMinutes > block.startMinutes
|
||||||
|
? block.endMinutes - block.startMinutes
|
||||||
|
: block.endMinutes - block.startMinutes + MINUTES_PER_DAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Linear half-open interval `[start, end)` relative to `dayIndex`. */
|
||||||
|
function dayInterval(
|
||||||
|
block: { startMinutes: number; endMinutes: number },
|
||||||
|
dayIndex: number
|
||||||
|
): [number, number] {
|
||||||
|
const base = dayIndex * MINUTES_PER_DAY;
|
||||||
|
const start = base + block.startMinutes;
|
||||||
|
return [start, start + blockLinearLength(block)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCrossing(block: { startMinutes: number; endMinutes: number }): boolean {
|
||||||
|
return block.endMinutes < block.startMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function blocksForDay(blocks: ValidationBlock[], dayIndex: number): ValidationBlock[] {
|
||||||
|
const day = WEEK_DAYS[dayIndex];
|
||||||
|
return blocks.filter((b) => (b.day ?? null) === day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First overlapping pair of Blocks in a schedule, or null. Implements the
|
||||||
|
* contract's overlap rules: touching boundaries are fine; a midnight-crossing
|
||||||
|
* Block spills into the next day (weekly Sunday→Monday) and must not overlap the
|
||||||
|
* next day's early Blocks.
|
||||||
|
*/
|
||||||
|
export function findScheduleOverlap(
|
||||||
|
type: ScheduleType,
|
||||||
|
blocks: ValidationBlock[]
|
||||||
|
): { a: ValidationBlock; b: ValidationBlock } | null {
|
||||||
|
if (type === 'daily') {
|
||||||
|
// Daily is a single day list that wraps onto itself: a crossing Block
|
||||||
|
// spills into the next day's copy of the same list. First check the
|
||||||
|
// same-day intervals, then every crossing Block against the next day.
|
||||||
|
const intervals = blocks.map((b) => ({ block: b, interval: dayInterval(b, 0) }));
|
||||||
|
const sameDay = firstPairOverlap(intervals);
|
||||||
|
if (sameDay) return sameDay;
|
||||||
|
|
||||||
|
for (const crossing of blocks.filter(isCrossing)) {
|
||||||
|
// The crossing Block's post-midnight tail, expressed in this day's
|
||||||
|
// 0-based frame (it spans [0, endMinutes)).
|
||||||
|
const spill: IntervalEntry = {
|
||||||
|
block: crossing,
|
||||||
|
interval: [0, crossing.endMinutes]
|
||||||
|
};
|
||||||
|
for (const { block, interval } of intervals) {
|
||||||
|
if (block === crossing) continue;
|
||||||
|
if (segmentsIntersect(spill.interval, interval)) {
|
||||||
|
return { a: crossing, b: block };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let d = 0; d < WEEK_DAYS.length; d++) {
|
||||||
|
const dayBlocks = blocksForDay(blocks, d);
|
||||||
|
const intervals = dayBlocks.map((b) => ({ block: b, interval: dayInterval(b, d) }));
|
||||||
|
|
||||||
|
// Same-day overlaps (a crossing Block's late segment vs the same day).
|
||||||
|
const pair = firstPairOverlap(intervals);
|
||||||
|
if (pair) return pair;
|
||||||
|
|
||||||
|
// Spillover from the previous day's crossing Blocks into this day's early
|
||||||
|
// Blocks. All spills start at midnight, so their union is [0, max(end)).
|
||||||
|
const prevIndex = (d - 1 + WEEK_DAYS.length) % WEEK_DAYS.length;
|
||||||
|
const prevCrossing = blocksForDay(blocks, prevIndex).filter(isCrossing);
|
||||||
|
for (const prev of prevCrossing) {
|
||||||
|
const spill: [number, number] = [d * MINUTES_PER_DAY, d * MINUTES_PER_DAY + prev.endMinutes];
|
||||||
|
for (const { block, interval } of intervals) {
|
||||||
|
if (segmentsIntersect(spill, interval)) {
|
||||||
|
return { a: prev, b: block };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntervalEntry = { block: ValidationBlock; interval: [number, number] };
|
||||||
|
|
||||||
|
function firstPairOverlap(
|
||||||
|
entries: IntervalEntry[]
|
||||||
|
): { a: ValidationBlock; b: ValidationBlock } | null {
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
for (let j = i + 1; j < entries.length; j++) {
|
||||||
|
if (segmentsIntersect(entries[i].interval, entries[j].interval)) {
|
||||||
|
return { a: entries[i].block, b: entries[j].block };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validate one Block's shape (time range + at least one Proclamation). */
|
||||||
|
export function validateBlockShape(block: ValidationBlock): ValidationResult {
|
||||||
|
const { startMinutes, endMinutes, proclamationIds } = block;
|
||||||
|
if (
|
||||||
|
!Number.isInteger(startMinutes) ||
|
||||||
|
!Number.isInteger(endMinutes) ||
|
||||||
|
startMinutes < 0 ||
|
||||||
|
startMinutes > MINUTES_PER_DAY ||
|
||||||
|
endMinutes < 0 ||
|
||||||
|
endMinutes > MINUTES_PER_DAY
|
||||||
|
) {
|
||||||
|
return { ok: false, error: 'Enter a valid start and end time.' };
|
||||||
|
}
|
||||||
|
if (startMinutes === endMinutes) {
|
||||||
|
return { ok: false, error: "Start and end time can't be the same." };
|
||||||
|
}
|
||||||
|
if (block.day != null && !WEEK_DAYS.includes(block.day as WeekDay)) {
|
||||||
|
return { ok: false, error: 'Pick a valid day of the week.' };
|
||||||
|
}
|
||||||
|
if (proclamationIds !== undefined) {
|
||||||
|
if (!Array.isArray(proclamationIds) || proclamationIds.length === 0) {
|
||||||
|
return { ok: false, error: 'Select at least one proclamation.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a whole schedule. The server is authoritative; the client calls the
|
||||||
|
* same function for instant feedback.
|
||||||
|
*/
|
||||||
|
export function validateSchedule(type: ScheduleType, blocks: ValidationBlock[]): ValidationResult {
|
||||||
|
for (const block of blocks) {
|
||||||
|
const shape = validateBlockShape(block);
|
||||||
|
if (!shape.ok) return shape;
|
||||||
|
}
|
||||||
|
const overlap = findScheduleOverlap(type, blocks);
|
||||||
|
if (overlap) {
|
||||||
|
return { ok: false, error: overlapError(overlap.a) };
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a candidate Block against the schedule's other Blocks (which must
|
||||||
|
* exclude the candidate when editing).
|
||||||
|
*/
|
||||||
|
export function validateBlockAgainstSchedule(
|
||||||
|
type: ScheduleType,
|
||||||
|
candidate: ValidationBlock,
|
||||||
|
siblings: ValidationBlock[]
|
||||||
|
): ValidationResult {
|
||||||
|
const shape = validateBlockShape(candidate);
|
||||||
|
if (!shape.ok) return shape;
|
||||||
|
const overlap = findScheduleOverlap(type, [...siblings, candidate]);
|
||||||
|
if (overlap) {
|
||||||
|
return { ok: false, error: overlapError(overlap.a) };
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function overlapError(a: ValidationBlock): string {
|
||||||
|
return `This overlaps with your ${formatTime(a.startMinutes)}–${formatTime(a.endMinutes)} block.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMinutes(minutes: number): number {
|
||||||
|
return (((Math.trunc(minutes) % MINUTES_PER_DAY) + MINUTES_PER_DAY) % MINUTES_PER_DAY) | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format minutes-from-midnight as a 12-hour clock label, e.g. `6:00 AM`. */
|
||||||
|
export function formatTime(minutes: number): string {
|
||||||
|
const normalized = normalizeMinutes(minutes);
|
||||||
|
const hours24 = Math.floor(normalized / 60);
|
||||||
|
const mins = normalized % 60;
|
||||||
|
const period = hours24 < 12 ? 'AM' : 'PM';
|
||||||
|
const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
|
||||||
|
return `${hours12}:${String(mins).padStart(2, '0')} ${period}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format minutes-from-midnight as a 24-hour `HH:MM` string. */
|
||||||
|
export function toHm(minutes: number): string {
|
||||||
|
const normalized = normalizeMinutes(minutes);
|
||||||
|
const hours = Math.floor(normalized / 60);
|
||||||
|
const mins = normalized % 60;
|
||||||
|
return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a 24-hour `HH:MM` string into minutes from midnight. Returns null when
|
||||||
|
* the input is not a valid 24-hour time.
|
||||||
|
*/
|
||||||
|
export function hmToMinutes(hm: string): number | null {
|
||||||
|
const match = /^(\d{2}):(\d{2})$/.exec(hm.trim());
|
||||||
|
if (!match) return null;
|
||||||
|
const hours = Number(match[1]);
|
||||||
|
const minutes = Number(match[2]);
|
||||||
|
if (hours > 23 || minutes > 59) return null;
|
||||||
|
return hours * 60 + minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TimeInputResult = { ok: true; minutes: number } | { ok: false; error: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a user-typed 12-hour clock time into minutes from midnight. Tolerant of
|
||||||
|
* "7:05 pm", "7:05PM", "7pm", "7:7", "12:00 am". A period is optional; when
|
||||||
|
* present the hour must be 1-12, so "13:00 PM" is rejected. Minutes may be any
|
||||||
|
* value 00-59, not just the 5-minute steps.
|
||||||
|
*/
|
||||||
|
export function parseTimeInput(raw: string): TimeInputResult {
|
||||||
|
const text = raw.trim().toLowerCase();
|
||||||
|
if (!text) return { ok: false, error: 'Enter a time.' };
|
||||||
|
|
||||||
|
const match = /^(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|a|p)?$/.exec(text);
|
||||||
|
if (!match) return { ok: false, error: 'Enter a time like 7:05 PM.' };
|
||||||
|
|
||||||
|
const hour = Number(match[1]);
|
||||||
|
const minute = match[2] === undefined ? 0 : Number(match[2]);
|
||||||
|
const period = match[3];
|
||||||
|
|
||||||
|
if (minute > 59) return { ok: false, error: 'Minutes must be 00-59.' };
|
||||||
|
|
||||||
|
if (period) {
|
||||||
|
if (hour < 1 || hour > 12) {
|
||||||
|
return { ok: false, error: 'Hour must be 1-12 when AM/PM is given.' };
|
||||||
|
}
|
||||||
|
const isPm = period === 'pm' || period === 'p';
|
||||||
|
const hours24 = (hour % 12) + (isPm ? 12 : 0);
|
||||||
|
return { ok: true, minutes: hours24 * 60 + minute };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hour > 23) return { ok: false, error: 'Hour must be 0-23.' };
|
||||||
|
return { ok: true, minutes: hour * 60 + minute };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The 5-minute clock options (00:00, 00:05, ..., 23:55) in play order. */
|
||||||
|
export function fiveMinuteOptions(): number[] {
|
||||||
|
const options: number[] = [];
|
||||||
|
for (let minutes = 0; minutes < MINUTES_PER_DAY; minutes += 5) {
|
||||||
|
options.push(minutes);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Snap a minute value to the nearest `SNAP_MINUTES` interval, clamped to 24h. */
|
||||||
|
export function snapMinutes(minutes: number): number {
|
||||||
|
const snapped = Math.round(minutes / SNAP_MINUTES) * SNAP_MINUTES;
|
||||||
|
return Math.max(0, Math.min(MINUTES_PER_DAY, snapped));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A Block whose end clock time is at or before its start wraps past midnight. */
|
||||||
|
export function isOvernight(block: { startMinutes: number; endMinutes: number }): boolean {
|
||||||
|
return block.endMinutes <= block.startMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Block length in minutes, counting a wrap past midnight as the next day. */
|
||||||
|
export function blockDuration(block: { startMinutes: number; endMinutes: number }): number {
|
||||||
|
if (block.endMinutes > block.startMinutes) {
|
||||||
|
return block.endMinutes - block.startMinutes;
|
||||||
|
}
|
||||||
|
return block.endMinutes - block.startMinutes + MINUTES_PER_DAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a Block into the linear half-open `[start, end)` segments it occupies
|
||||||
|
* within the 24h day. An overnight block produces two segments (start→midnight
|
||||||
|
* and midnight→end); a zero-length block produces none.
|
||||||
|
*/
|
||||||
|
export function blockSegments(block: {
|
||||||
|
startMinutes: number;
|
||||||
|
endMinutes: number;
|
||||||
|
}): [number, number][] {
|
||||||
|
if (block.startMinutes < block.endMinutes) {
|
||||||
|
return [[block.startMinutes, block.endMinutes]];
|
||||||
|
}
|
||||||
|
if (block.startMinutes > block.endMinutes) {
|
||||||
|
return [
|
||||||
|
[block.startMinutes, MINUTES_PER_DAY],
|
||||||
|
[0, block.endMinutes]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function segmentsIntersect(a: [number, number], b: [number, number]): boolean {
|
||||||
|
return a[0] < b[1] && b[0] < a[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First sibling whose occupied time intersects the input. Works for overnight
|
||||||
|
* (midnight-crossing) blocks by comparing their linear segments; ranges that
|
||||||
|
* only touch at an endpoint (e.g. 9PM–9AM vs 9AM–9PM) do not overlap.
|
||||||
|
*/
|
||||||
|
export function findOverlap(
|
||||||
|
input: { startMinutes: number; endMinutes: number },
|
||||||
|
siblings: BlockSibling[]
|
||||||
|
): BlockSibling | null {
|
||||||
|
const inputSegments = blockSegments(input);
|
||||||
|
for (const sibling of siblings) {
|
||||||
|
const siblingSegments = blockSegments(sibling);
|
||||||
|
for (const segment of inputSegments) {
|
||||||
|
for (const other of siblingSegments) {
|
||||||
|
if (segmentsIntersect(segment, other)) return sibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a Block against the schedule's other Blocks. `siblings` must exclude
|
||||||
|
* the Block being edited. `isCreate` enables the 4-block maximum (an update
|
||||||
|
* never increases the count).
|
||||||
|
*/
|
||||||
|
export function validateBlock(
|
||||||
|
input: BlockInput,
|
||||||
|
siblings: BlockSibling[],
|
||||||
|
opts: { isCreate?: boolean } = {}
|
||||||
|
): ValidationResult {
|
||||||
|
const { startMinutes, endMinutes, proclamationIds } = input;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Number.isInteger(startMinutes) ||
|
||||||
|
!Number.isInteger(endMinutes) ||
|
||||||
|
startMinutes < 0 ||
|
||||||
|
startMinutes > MINUTES_PER_DAY ||
|
||||||
|
endMinutes < 0 ||
|
||||||
|
endMinutes > MINUTES_PER_DAY
|
||||||
|
) {
|
||||||
|
return { ok: false, error: 'Enter a valid start and end time.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal clock times would be a zero- or full-length block; both are
|
||||||
|
// ambiguous, so reject them. Overnight ranges (end before start) are valid.
|
||||||
|
if (startMinutes === endMinutes) {
|
||||||
|
return { ok: false, error: "Start and end time can't be the same." };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(proclamationIds) || proclamationIds.length === 0) {
|
||||||
|
return { ok: false, error: 'Select at least one proclamation.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.isCreate && siblings.length >= MAX_BLOCKS) {
|
||||||
|
return { ok: false, error: `You can have at most ${MAX_BLOCKS} blocks.` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlap = findOverlap(input, siblings);
|
||||||
|
if (overlap) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: `This overlaps with your ${formatTime(overlap.startMinutes)}–${formatTime(
|
||||||
|
overlap.endMinutes
|
||||||
|
)} block.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actor for the current request, used by the Prisma audit extension to
|
||||||
|
* attribute database mutations. Populated in `hooks.server.ts`.
|
||||||
|
*/
|
||||||
|
export type AuditContext = {
|
||||||
|
userId: string | null;
|
||||||
|
email: string | null;
|
||||||
|
roleId: number | null;
|
||||||
|
page: string;
|
||||||
|
ip: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage = new AsyncLocalStorage<AuditContext>();
|
||||||
|
|
||||||
|
export function runWithAuditContext<T>(context: AuditContext, fn: () => T): T {
|
||||||
|
return storage.run(context, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuditContext(): AuditContext | undefined {
|
||||||
|
return storage.getStore();
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { describeAction, entityIdFor, sanitizeAuditValue, shouldAudit } from './audit';
|
||||||
|
|
||||||
|
describe('shouldAudit', () => {
|
||||||
|
it('audits mutations', () => {
|
||||||
|
expect(shouldAudit('User', 'create')).toBe(true);
|
||||||
|
expect(shouldAudit('Profile', 'update')).toBe(true);
|
||||||
|
expect(shouldAudit('Proclamation', 'delete')).toBe(true);
|
||||||
|
expect(shouldAudit('LicenseKey', 'updateMany')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores reads', () => {
|
||||||
|
expect(shouldAudit('User', 'findMany')).toBe(false);
|
||||||
|
expect(shouldAudit('User', 'findUnique')).toBe(false);
|
||||||
|
expect(shouldAudit('User', 'count')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores the audit log and auth internals', () => {
|
||||||
|
for (const model of ['AuditLog', 'Session', 'Account', 'Verification']) {
|
||||||
|
expect(shouldAudit(model, 'create')).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sanitizeAuditValue', () => {
|
||||||
|
it('redacts sensitive keys', () => {
|
||||||
|
const out = sanitizeAuditValue({
|
||||||
|
password: 'hunter2',
|
||||||
|
token: 'abc',
|
||||||
|
keyHash: 'deadbeef',
|
||||||
|
email: 'jane@example.com'
|
||||||
|
}) as Record<string, unknown>;
|
||||||
|
|
||||||
|
expect(out.password).toBe('[redacted]');
|
||||||
|
expect(out.token).toBe('[redacted]');
|
||||||
|
expect(out.keyHash).toBe('[redacted]');
|
||||||
|
expect(out.email).toBe('jane@example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts dates to ISO strings', () => {
|
||||||
|
const date = new Date('2026-01-01T00:00:00.000Z');
|
||||||
|
expect(sanitizeAuditValue({ at: date })).toEqual({ at: '2026-01-01T00:00:00.000Z' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles arrays and nullish values', () => {
|
||||||
|
expect(sanitizeAuditValue([1, null, 'a'])).toEqual([1, null, 'a']);
|
||||||
|
expect(sanitizeAuditValue(null)).toBeNull();
|
||||||
|
expect(sanitizeAuditValue(undefined)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('truncates very long strings', () => {
|
||||||
|
const long = 'a'.repeat(21000);
|
||||||
|
const out = sanitizeAuditValue(long) as string;
|
||||||
|
expect(out.length).toBe(20001);
|
||||||
|
expect(out.endsWith('…')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('entityIdFor', () => {
|
||||||
|
it('prefers the result id', () => {
|
||||||
|
expect(entityIdFor({ where: { id: 'w' } }, { id: 'r' }, { id: 'b' })).toBe('r');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to where, data, then before', () => {
|
||||||
|
expect(entityIdFor({ where: { id: 5 } }, null, null)).toBe('5');
|
||||||
|
expect(entityIdFor({ data: { id: 'd' } }, null, null)).toBe('d');
|
||||||
|
expect(entityIdFor({}, null, { id: 'b' })).toBe('b');
|
||||||
|
expect(entityIdFor({}, null, null)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('describeAction', () => {
|
||||||
|
it('labels bulk operations', () => {
|
||||||
|
expect(describeAction('updateMany')).toBe('bulk update');
|
||||||
|
expect(describeAction('deleteMany')).toBe('bulk delete');
|
||||||
|
expect(describeAction('create')).toBe('create');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Pure helpers for the audit extension. Kept free of database imports so the
|
||||||
|
* extension in `$lib/db.ts` can use them without a cycle.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Prisma operations that change data. Reads are never audited. */
|
||||||
|
const MUTATION_OPERATIONS = new Set([
|
||||||
|
'create',
|
||||||
|
'createMany',
|
||||||
|
'createManyAndReturn',
|
||||||
|
'update',
|
||||||
|
'updateMany',
|
||||||
|
'updateManyAndReturn',
|
||||||
|
'upsert',
|
||||||
|
'delete',
|
||||||
|
'deleteMany'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models we never audit:
|
||||||
|
* - `AuditLog` itself (would recurse and is not user data)
|
||||||
|
* - `Session`/`Account`/`Verification` are high-churn auth internals that hold
|
||||||
|
* tokens, password hashes and other secrets. Business mutations are audited.
|
||||||
|
*/
|
||||||
|
const EXCLUDED_MODELS = new Set(['AuditLog', 'Session', 'Account', 'Verification']);
|
||||||
|
|
||||||
|
export function shouldAudit(model: string, operation: string): boolean {
|
||||||
|
return !EXCLUDED_MODELS.has(model) && MUTATION_OPERATIONS.has(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SENSITIVE_KEY = /pass(word)?|secret|token|hash/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a value safe to persist as JSON: dates become ISO strings, sensitive
|
||||||
|
* keys are redacted, very long strings are truncated, and cycles/depth are
|
||||||
|
* bounded.
|
||||||
|
*/
|
||||||
|
export function sanitizeAuditValue(value: unknown, depth = 0): unknown {
|
||||||
|
if (value === undefined || value === null) return null;
|
||||||
|
if (depth > 8) return '[truncated]';
|
||||||
|
if (value instanceof Date) return value.toISOString();
|
||||||
|
if (Array.isArray(value)) return value.map((item) => sanitizeAuditValue(item, depth + 1));
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const out: Record<string, unknown> = {};
|
||||||
|
for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {
|
||||||
|
out[key] = SENSITIVE_KEY.test(key) ? '[redacted]' : sanitizeAuditValue(nested, depth + 1);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string' && value.length > 20000) return `${value.slice(0, 20000)}…`;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Best-effort row id for the changed record. */
|
||||||
|
export function entityIdFor(args: unknown, result: unknown, before: unknown): string | null {
|
||||||
|
const where = (args as { where?: { id?: unknown } } | null)?.where;
|
||||||
|
const data = (args as { data?: { id?: unknown } } | null)?.data;
|
||||||
|
const resultId = (result as { id?: unknown } | null)?.id;
|
||||||
|
const first = Array.isArray(before) ? before[0] : before;
|
||||||
|
const beforeId = (first as { id?: unknown } | undefined)?.id;
|
||||||
|
const id = resultId ?? where?.id ?? data?.id ?? beforeId;
|
||||||
|
return id === undefined || id === null ? null : String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human label for an operation, e.g. `updateMany` → `bulk update`. */
|
||||||
|
export function describeAction(operation: string): string {
|
||||||
|
switch (operation) {
|
||||||
|
case 'create':
|
||||||
|
return 'create';
|
||||||
|
case 'update':
|
||||||
|
return 'update';
|
||||||
|
case 'delete':
|
||||||
|
return 'delete';
|
||||||
|
case 'upsert':
|
||||||
|
return 'upsert';
|
||||||
|
case 'createMany':
|
||||||
|
case 'createManyAndReturn':
|
||||||
|
return 'bulk create';
|
||||||
|
case 'updateMany':
|
||||||
|
case 'updateManyAndReturn':
|
||||||
|
return 'bulk update';
|
||||||
|
case 'deleteMany':
|
||||||
|
return 'bulk delete';
|
||||||
|
default:
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { PrismaClient } from '$lib/generated/prisma/client';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import {
|
||||||
|
getScheduleForLicense,
|
||||||
|
setScheduleType,
|
||||||
|
createBlock,
|
||||||
|
updateBlock,
|
||||||
|
copySchedule,
|
||||||
|
listBlocksForLicense,
|
||||||
|
scheduleBlocksToInput,
|
||||||
|
type OwnedLicense
|
||||||
|
} from '$lib/server/schedule';
|
||||||
|
import { buildSchedulePayload } from '$lib/server/v2-serializers';
|
||||||
|
|
||||||
|
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const adapter = new PrismaPg(pool);
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
|
const K_A1 = 'ALCE-AAAA-AAAA-0001';
|
||||||
|
const K_A2 = 'ALCE-AAAA-AAAA-0002';
|
||||||
|
const K_B1 = 'BOBB-BBBB-BBBB-0001';
|
||||||
|
|
||||||
|
const results: { id: string; pass: boolean; note: string }[] = [];
|
||||||
|
|
||||||
|
let PROCS: string[] = [];
|
||||||
|
|
||||||
|
async function licenseByKey(key: string) {
|
||||||
|
return prisma.licenseKey.findUnique({ where: { key } });
|
||||||
|
}
|
||||||
|
async function ownedFor(email: string): Promise<OwnedLicense[]> {
|
||||||
|
const user = await prisma.user.findUnique({ where: { email } });
|
||||||
|
if (!user) throw new Error('no user');
|
||||||
|
return prisma.licenseKey.findMany({
|
||||||
|
where: { userId: user.id },
|
||||||
|
select: { id: true, systemName: true, key: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function payloadFor(schedule: Awaited<ReturnType<typeof getScheduleForLicense>>) {
|
||||||
|
const type = schedule?.type === 'daily' || schedule?.type === 'weekly' ? schedule.type : null;
|
||||||
|
return buildSchedulePayload(type, scheduleBlocksToInput(schedule?.blocks ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const [ka1, ka2, kb1] = await Promise.all([
|
||||||
|
licenseByKey(K_A1),
|
||||||
|
licenseByKey(K_A2),
|
||||||
|
licenseByKey(K_B1)
|
||||||
|
]);
|
||||||
|
await prisma.schedule.deleteMany({
|
||||||
|
where: { licenseKeyId: { in: [ka1!.id, ka2!.id, kb1!.id] } }
|
||||||
|
});
|
||||||
|
const procs = await prisma.proclamation.findMany({
|
||||||
|
where: { name: { startsWith: 'P5 ' } },
|
||||||
|
select: { id: true },
|
||||||
|
orderBy: { name: 'asc' }
|
||||||
|
});
|
||||||
|
PROCS = procs.map((p) => p.id);
|
||||||
|
expect(PROCS.length).toBeGreaterThanOrEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
console.log('\nP5_RESULTS ' + JSON.stringify(results));
|
||||||
|
await prisma.$disconnect();
|
||||||
|
await pool.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const enabled = process.env.P5_INTEGRATION === '1';
|
||||||
|
|
||||||
|
describe.skipIf(!enabled)('Phase 5 end-to-end service scenarios', () => {
|
||||||
|
it('e: create a daily schedule via the Portal path', async () => {
|
||||||
|
const ka1 = await licenseByKey(K_A1);
|
||||||
|
await setScheduleType(ka1!.id, 'daily', { confirmed: true });
|
||||||
|
await createBlock(ka1!.id, {
|
||||||
|
startMinutes: 7 * 60,
|
||||||
|
endMinutes: 9 * 60,
|
||||||
|
proclamationIds: [PROCS[0]],
|
||||||
|
dynamicAudio: true
|
||||||
|
});
|
||||||
|
await createBlock(ka1!.id, {
|
||||||
|
startMinutes: 18 * 60,
|
||||||
|
endMinutes: 20 * 60 + 30,
|
||||||
|
proclamationIds: [PROCS[1]],
|
||||||
|
dynamicAudio: false
|
||||||
|
});
|
||||||
|
const daily = await getScheduleForLicense(ka1!.id);
|
||||||
|
const p = payloadFor(daily);
|
||||||
|
results.push({
|
||||||
|
id: 'e',
|
||||||
|
pass: p.type === 'daily' && p.daily.length === 2,
|
||||||
|
note: `type=${p.type} blocks=${p.daily.length}`
|
||||||
|
});
|
||||||
|
expect(p.type).toBe('daily');
|
||||||
|
expect(p.daily).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('f: weekly adjacent + midnight-crossing + Sunday spillover rejection', async () => {
|
||||||
|
const kb1 = await licenseByKey(K_B1);
|
||||||
|
await setScheduleType(kb1!.id, 'weekly', { confirmed: true });
|
||||||
|
await createBlock(kb1!.id, {
|
||||||
|
day: 'sun',
|
||||||
|
startMinutes: 23 * 60,
|
||||||
|
endMinutes: 60,
|
||||||
|
proclamationIds: [PROCS[2]]
|
||||||
|
});
|
||||||
|
await createBlock(kb1!.id, {
|
||||||
|
day: 'mon',
|
||||||
|
startMinutes: 6 * 60,
|
||||||
|
endMinutes: 7 * 60,
|
||||||
|
proclamationIds: [PROCS[0]]
|
||||||
|
});
|
||||||
|
await createBlock(kb1!.id, {
|
||||||
|
day: 'mon',
|
||||||
|
startMinutes: 7 * 60,
|
||||||
|
endMinutes: 8 * 60 + 30,
|
||||||
|
proclamationIds: [PROCS[1]]
|
||||||
|
});
|
||||||
|
|
||||||
|
const p = payloadFor(await getScheduleForLicense(kb1!.id));
|
||||||
|
expect(p.type).toBe('weekly');
|
||||||
|
expect(p.weekly.sun).toHaveLength(1);
|
||||||
|
expect(p.weekly.sun[0].overnight).toBe(true);
|
||||||
|
expect(p.weekly.mon).toHaveLength(2);
|
||||||
|
expect(p.weekly.mon[0].endMinutes).toBe(p.weekly.mon[1].startMinutes);
|
||||||
|
|
||||||
|
// Sunday's 23:00->01:00 spills into Monday [00:00,01:00); a Monday 00:00-00:30 block must be rejected.
|
||||||
|
const rejected = await createBlock(kb1!.id, {
|
||||||
|
day: 'mon',
|
||||||
|
startMinutes: 0,
|
||||||
|
endMinutes: 30,
|
||||||
|
proclamationIds: [PROCS[3]]
|
||||||
|
});
|
||||||
|
const monCount = (await getScheduleForLicense(kb1!.id))!.blocks.filter(
|
||||||
|
(b) => b.day === 'mon'
|
||||||
|
).length;
|
||||||
|
results.push({
|
||||||
|
id: 'f',
|
||||||
|
pass: !rejected.ok && monCount === 2,
|
||||||
|
note: `sun overnight=${p.weekly.sun[0].overnight} mon adjacent=${p.weekly.mon[0].endMinutes === p.weekly.mon[1].startMinutes} spilloverRejected=${!rejected.ok}`
|
||||||
|
});
|
||||||
|
expect(rejected.ok).toBe(false);
|
||||||
|
expect(monCount).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('i: schedules are per-License-Key (A does not affect B)', async () => {
|
||||||
|
const [ka1, kb1] = await Promise.all([licenseByKey(K_A1), licenseByKey(K_B1)]);
|
||||||
|
const a = payloadFor(await getScheduleForLicense(ka1!.id));
|
||||||
|
const b = payloadFor(await getScheduleForLicense(kb1!.id));
|
||||||
|
results.push({
|
||||||
|
id: 'i',
|
||||||
|
pass: a.type === 'daily' && b.type === 'weekly',
|
||||||
|
note: `A=${a.type} B=${b.type}`
|
||||||
|
});
|
||||||
|
expect(a.type).toBe('daily');
|
||||||
|
expect(b.type).toBe('weekly');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('j: copy A -> second system and verify independence', async () => {
|
||||||
|
const [ka1, ka2] = await Promise.all([licenseByKey(K_A1), licenseByKey(K_A2)]);
|
||||||
|
const owned = await ownedFor('p5-alice@example.com');
|
||||||
|
const copy = await copySchedule(owned, ka1!.id, [ka2!.id]);
|
||||||
|
expect(copy.ok).toBe(true);
|
||||||
|
|
||||||
|
const ka2Before = payloadFor(await getScheduleForLicense(ka2!.id));
|
||||||
|
expect(ka2Before.type).toBe('daily');
|
||||||
|
expect(ka2Before.daily).toHaveLength(2);
|
||||||
|
|
||||||
|
const src = payloadFor(await getScheduleForLicense(ka1!.id));
|
||||||
|
expect(src.version).not.toBe(ka2Before.version);
|
||||||
|
|
||||||
|
// Edit source; copy must not change.
|
||||||
|
const blocks = await listBlocksForLicense(ka1!.id);
|
||||||
|
await updateBlock(ka1!.id, blocks[0].id, {
|
||||||
|
startMinutes: 6 * 60,
|
||||||
|
endMinutes: 7 * 60,
|
||||||
|
proclamationIds: blocks[0].proclamations.map((p) => p.proclamation.id),
|
||||||
|
dynamicAudio: true
|
||||||
|
});
|
||||||
|
const ka2After = payloadFor(await getScheduleForLicense(ka2!.id));
|
||||||
|
results.push({
|
||||||
|
id: 'j',
|
||||||
|
pass: ka2After.version === ka2Before.version && src.version !== ka2Before.version,
|
||||||
|
note: `copied daily blocks=${ka2Before.daily.length} independent=${ka2After.version === ka2Before.version}`
|
||||||
|
});
|
||||||
|
expect(ka2After.version).toBe(ka2Before.version);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
import { sendMail } from './resend';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the "finish your registration" email to a user created by an admin.
|
||||||
|
*
|
||||||
|
* The link points at Better Auth's reset-password callback, which redirects to
|
||||||
|
* `/reset-password?invite=1&token=...`. There the user sets a password, then
|
||||||
|
* logs in and is sent to `/profile/setup` to complete their profile.
|
||||||
|
*
|
||||||
|
* Uses a Resend-hosted template (`registration-email` by default, or
|
||||||
|
* `RESEND_REGISTRATION_TEMPLATE_ID`) with variables:
|
||||||
|
* - `target_email_address`
|
||||||
|
* - `registration_link`
|
||||||
|
*/
|
||||||
|
export async function sendRegistrationEmail(options: { email: string; url: string }) {
|
||||||
|
await sendMail({
|
||||||
|
to: options.email,
|
||||||
|
subject: 'Welcome to Radiant Wave — set your password',
|
||||||
|
templateId: env.RESEND_REGISTRATION_TEMPLATE_ID || 'registration-email',
|
||||||
|
data: {
|
||||||
|
target_email_address: options.email,
|
||||||
|
registration_link: options.url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { auth } from '$lib/auth';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email an invited user a link to set their password and finish their profile.
|
||||||
|
*
|
||||||
|
* Reuses Better Auth's password-reset token flow; the `invite=1` marker makes
|
||||||
|
* the reset-password hook send the `registration-email` template instead of the
|
||||||
|
* usual "you requested a reset" email. The link lands on /reset-password, and
|
||||||
|
* after setting a password the user logs in and is sent to /profile/setup.
|
||||||
|
*/
|
||||||
|
export async function sendRegistrationInvite(email: string): Promise<void> {
|
||||||
|
await auth.api.requestPasswordReset({
|
||||||
|
body: {
|
||||||
|
email,
|
||||||
|
redirectTo: '/reset-password?invite=1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ function getResend(): Resend {
|
|||||||
* @example
|
* @example
|
||||||
* await sendMail({
|
* await sendMail({
|
||||||
* to: 'jane@example.com',
|
* to: 'jane@example.com',
|
||||||
* templateId: 'tmpl_abc123', // optional — defaults to RESEND_TEMPLATE_ID
|
* templateId: 'tmpl_abc123', // required — Resend template id or alias
|
||||||
* subject: 'Your license key is ready', // optional — defaults to 'Template Email'
|
* subject: 'Your license key is ready', // optional — defaults to 'Template Email'
|
||||||
* data: { // optional — template variables
|
* data: { // optional — template variables
|
||||||
* firstName: 'Jane',
|
* firstName: 'Jane',
|
||||||
@@ -40,7 +40,7 @@ function getResend(): Resend {
|
|||||||
export async function sendMail(options: {
|
export async function sendMail(options: {
|
||||||
to: string;
|
to: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
templateId?: string;
|
templateId: string;
|
||||||
data?: Record<string, string | number>;
|
data?: Record<string, string | number>;
|
||||||
}) {
|
}) {
|
||||||
const { to, subject, templateId, data } = options;
|
const { to, subject, templateId, data } = options;
|
||||||
@@ -50,7 +50,7 @@ export async function sendMail(options: {
|
|||||||
to: [to],
|
to: [to],
|
||||||
subject: subject ?? 'Template Email',
|
subject: subject ?? 'Template Email',
|
||||||
template: {
|
template: {
|
||||||
id: templateId ?? env.RESEND_TEMPLATE_ID,
|
id: templateId,
|
||||||
variables: data ?? {}
|
variables: data ?? {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Role hierarchy. Lower ids are more privileged:
|
||||||
|
* SUPER = 1, ADMIN = 2, STAFF = 3, USER = 4
|
||||||
|
*
|
||||||
|
* `roleId` is exposed on the Better Auth session user (see `auth.ts`
|
||||||
|
* `user.additionalFields`), so these helpers never need a database call.
|
||||||
|
*/
|
||||||
|
export const SUPER = 1;
|
||||||
|
export const ADMIN = 2;
|
||||||
|
export const STAFF = 3;
|
||||||
|
export const USER = 4;
|
||||||
|
|
||||||
|
/** The current user's role id, defaulting to the least privileged role. */
|
||||||
|
export function roleId(locals: App.Locals): number {
|
||||||
|
return locals.user?.roleId ?? USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSuper(locals: App.Locals): boolean {
|
||||||
|
return roleId(locals) <= SUPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAdmin(locals: App.Locals): boolean {
|
||||||
|
return roleId(locals) <= ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStaff(locals: App.Locals): boolean {
|
||||||
|
return roleId(locals) <= STAFF;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user