admin: Superforms + Zod validation, three-address-defaults schema, profiles toast fix
- Add Superforms + Zod 4 for all admin form validation - Replace address isDefault with three separate boolean defaults (Mailing, Billing, Shipping) - Update profiles page with three-checkbox address form and toast improvements - Convert all admin page server actions to pass request directly to crud - Export superFormErrorsToFieldErrors helper for profiles server actions - Regenerate Prisma client for new schema fields
This commit is contained in:
@@ -23,15 +23,18 @@ description: Configure Better Auth server and client, set up database adapters,
|
|||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
|
- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
|
||||||
- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
|
- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
|
||||||
|
|
||||||
Only define `baseURL`/`secret` in config if env vars are NOT set.
|
Only define `baseURL`/`secret` in config if env vars are NOT set.
|
||||||
|
|
||||||
### File Location
|
### File Location
|
||||||
|
|
||||||
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
|
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
|
||||||
|
|
||||||
### CLI Commands
|
### CLI Commands
|
||||||
|
|
||||||
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
|
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
|
||||||
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
|
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
|
||||||
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
|
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
|
||||||
@@ -42,18 +45,18 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
|
|||||||
|
|
||||||
## Core Config Options
|
## Core Config Options
|
||||||
|
|
||||||
| Option | Notes |
|
| Option | Notes |
|
||||||
|--------|-------|
|
| ------------------ | ---------------------------------------------- |
|
||||||
| `appName` | Optional display name |
|
| `appName` | Optional display name |
|
||||||
| `baseURL` | Only if `BETTER_AUTH_URL` not set |
|
| `baseURL` | Only if `BETTER_AUTH_URL` not set |
|
||||||
| `basePath` | Default `/api/auth`. Set `/` for root. |
|
| `basePath` | Default `/api/auth`. Set `/` for root. |
|
||||||
| `secret` | Only if `BETTER_AUTH_SECRET` not set |
|
| `secret` | Only if `BETTER_AUTH_SECRET` not set |
|
||||||
| `database` | Required for most features. See adapters docs. |
|
| `database` | Required for most features. See adapters docs. |
|
||||||
| `secondaryStorage` | Redis/KV for sessions & rate limits |
|
| `secondaryStorage` | Redis/KV for sessions & rate limits |
|
||||||
| `emailAndPassword` | `{ enabled: true }` to activate |
|
| `emailAndPassword` | `{ enabled: true }` to activate |
|
||||||
| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
|
| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
|
||||||
| `plugins` | Array of plugins |
|
| `plugins` | Array of plugins |
|
||||||
| `trustedOrigins` | CSRF whitelist |
|
| `trustedOrigins` | CSRF whitelist |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -70,11 +73,13 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
|
|||||||
## Session Management
|
## Session Management
|
||||||
|
|
||||||
**Storage priority:**
|
**Storage priority:**
|
||||||
|
|
||||||
1. If `secondaryStorage` defined → sessions go there (not DB)
|
1. If `secondaryStorage` defined → sessions go there (not DB)
|
||||||
2. Set `session.storeSessionInDatabase: true` to also persist to DB
|
2. Set `session.storeSessionInDatabase: true` to also persist to DB
|
||||||
3. No database + `cookieCache` → fully stateless mode
|
3. No database + `cookieCache` → fully stateless mode
|
||||||
|
|
||||||
**Cookie cache strategies:**
|
**Cookie cache strategies:**
|
||||||
|
|
||||||
- `compact` (default) - Base64url + HMAC. Smallest.
|
- `compact` (default) - Base64url + HMAC. Smallest.
|
||||||
- `jwt` - Standard JWT. Readable but signed.
|
- `jwt` - Standard JWT. Readable but signed.
|
||||||
- `jwe` - Encrypted. Maximum security.
|
- `jwe` - Encrypted. Maximum security.
|
||||||
@@ -104,6 +109,7 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
|
|||||||
## Security
|
## Security
|
||||||
|
|
||||||
**In `advanced`:**
|
**In `advanced`:**
|
||||||
|
|
||||||
- `useSecureCookies` - Force HTTPS cookies
|
- `useSecureCookies` - Force HTTPS cookies
|
||||||
- `disableCSRFCheck` - ⚠️ Security risk
|
- `disableCSRFCheck` - ⚠️ Security risk
|
||||||
- `disableOriginCheck` - ⚠️ Security risk
|
- `disableOriginCheck` - ⚠️ Security risk
|
||||||
@@ -128,9 +134,11 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
|
|||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
**Import from dedicated paths for tree-shaking:**
|
**Import from dedicated paths for tree-shaking:**
|
||||||
|
|
||||||
```
|
```
|
||||||
import { twoFactor } from "better-auth/plugins/two-factor"
|
import { twoFactor } from "better-auth/plugins/two-factor"
|
||||||
```
|
```
|
||||||
|
|
||||||
NOT `from "better-auth/plugins"`.
|
NOT `from "better-auth/plugins"`.
|
||||||
|
|
||||||
**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
|
**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ description: Configure rate limiting, manage auth secrets, set up CSRF protectio
|
|||||||
### Configuring the Secret
|
### Configuring the Secret
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
secret: process.env.BETTER_AUTH_SECRET, // or via `BETTER_AUTH_SECRET` env
|
secret: process.env.BETTER_AUTH_SECRET // or via `BETTER_AUTH_SECRET` env
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Better Auth looks for secrets in this order:
|
Better Auth looks for secrets in this order:
|
||||||
|
|
||||||
1. `options.secret` in your config
|
1. `options.secret` in your config
|
||||||
2. `BETTER_AUTH_SECRET` environment variable
|
2. `BETTER_AUTH_SECRET` environment variable
|
||||||
3. `AUTH_SECRET` environment variable
|
3. `AUTH_SECRET` environment variable
|
||||||
@@ -34,14 +35,14 @@ Enabled in production by default. Applies to all endpoints. Plugins can override
|
|||||||
### Default Configuration
|
### Default Configuration
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
enabled: true, // Default: true in production
|
enabled: true, // Default: true in production
|
||||||
window: 10, // Time window in seconds (default: 10)
|
window: 10, // Time window in seconds (default: 10)
|
||||||
max: 100, // Max requests per window (default: 100)
|
max: 100 // Max requests per window (default: 100)
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -95,12 +96,12 @@ Multi-layer protection: origin header validation, Fetch Metadata checks, and fir
|
|||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
advanced: {
|
advanced: {
|
||||||
disableCSRFCheck: false, // Default: false (keep enabled)
|
disableCSRFCheck: false // Default: false (keep enabled)
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -111,14 +112,11 @@ Only disable for testing or with an alternative CSRF mechanism.
|
|||||||
### Configuring Trusted Origins
|
### Configuring Trusted Origins
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
baseURL: "https://api.example.com",
|
baseURL: 'https://api.example.com',
|
||||||
trustedOrigins: [
|
trustedOrigins: ['https://app.example.com', 'https://admin.example.com']
|
||||||
"https://app.example.com",
|
|
||||||
"https://admin.example.com",
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -128,10 +126,10 @@ The `baseURL` origin is automatically trusted. Also configurable via env: `BETTE
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
trustedOrigins: [
|
trustedOrigins: [
|
||||||
"*.example.com", // Matches any subdomain
|
'*.example.com', // Matches any subdomain
|
||||||
"https://*.example.com", // Protocol-specific wildcard
|
'https://*.example.com', // Protocol-specific wildcard
|
||||||
"exp://192.168.*.*:*/*", // Custom schemes (e.g., Expo)
|
'exp://192.168.*.*:*/*' // Custom schemes (e.g., Expo)
|
||||||
]
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dynamic Trusted Origins
|
### Dynamic Trusted Origins
|
||||||
@@ -140,10 +138,10 @@ Compute trusted origins based on the request:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
trustedOrigins: async (request) => {
|
trustedOrigins: async (request) => {
|
||||||
// Validate against database, header, etc.
|
// Validate against database, header, etc.
|
||||||
const tenant = getTenantFromRequest(request);
|
const tenant = getTenantFromRequest(request);
|
||||||
return [`https://${tenant}.myapp.com`];
|
return [`https://${tenant}.myapp.com`];
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Validates `callbackURL`, `redirectTo`, `errorCallbackURL`, `newUserCallbackURL`, and `origin` against trusted origins. Invalid URLs receive 403.
|
Validates `callbackURL`, `redirectTo`, `errorCallbackURL`, `newUserCallbackURL`, and `origin` against trusted origins. Invalid URLs receive 403.
|
||||||
@@ -153,13 +151,13 @@ Validates `callbackURL`, `redirectTo`, `errorCallbackURL`, `newUserCallbackURL`,
|
|||||||
### Session Expiration
|
### Session Expiration
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
session: {
|
session: {
|
||||||
expiresIn: 60 * 60 * 24 * 7, // 7 days (default)
|
expiresIn: 60 * 60 * 24 * 7, // 7 days (default)
|
||||||
updateAge: 60 * 60 * 24, // Refresh session every 24 hours (default)
|
updateAge: 60 * 60 * 24 // Refresh session every 24 hours (default)
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -186,17 +184,17 @@ Defaults: `secure: true` (HTTPS/production), `sameSite: "lax"`, `httpOnly: true`
|
|||||||
### Custom Cookie Configuration
|
### Custom Cookie Configuration
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
advanced: {
|
advanced: {
|
||||||
useSecureCookies: true, // Force secure cookies
|
useSecureCookies: true, // Force secure cookies
|
||||||
cookiePrefix: "myapp", // Custom prefix (default: "better-auth")
|
cookiePrefix: 'myapp', // Custom prefix (default: "better-auth")
|
||||||
defaultCookieAttributes: {
|
defaultCookieAttributes: {
|
||||||
sameSite: "strict", // Stricter CSRF protection
|
sameSite: 'strict', // Stricter CSRF protection
|
||||||
path: "/auth", // Limit cookie scope
|
path: '/auth' // Limit cookie scope
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -221,12 +219,12 @@ PKCE is automatic for all OAuth flows. State tokens are 32-char random strings e
|
|||||||
### State Parameter Storage
|
### State Parameter Storage
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
account: {
|
account: {
|
||||||
storeStateStrategy: "cookie", // Options: "cookie" (default), "database"
|
storeStateStrategy: 'cookie' // Options: "cookie" (default), "database"
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -245,15 +243,15 @@ Enable if storing OAuth tokens for API access on behalf of users. Use `skipState
|
|||||||
### IP Address Configuration
|
### IP Address Configuration
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
advanced: {
|
advanced: {
|
||||||
ipAddress: {
|
ipAddress: {
|
||||||
ipAddressHeaders: ["x-forwarded-for", "x-real-ip"], // Headers to check
|
ipAddressHeaders: ['x-forwarded-for', 'x-real-ip'], // Headers to check
|
||||||
disableIpTracking: false, // Keep enabled for rate limiting
|
disableIpTracking: false // Keep enabled for rate limiting
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -262,50 +260,50 @@ Set `ipv6Subnet` (128, 64, 48, 32; default 64) to group IPv6 addresses. Enable `
|
|||||||
## Database Hooks for Security Auditing
|
## Database Hooks for Security Auditing
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
databaseHooks: {
|
databaseHooks: {
|
||||||
session: {
|
session: {
|
||||||
create: {
|
create: {
|
||||||
after: async ({ data, ctx }) => {
|
after: async ({ data, ctx }) => {
|
||||||
await auditLog("session.created", {
|
await auditLog('session.created', {
|
||||||
userId: data.userId,
|
userId: data.userId,
|
||||||
ip: ctx?.request?.headers.get("x-forwarded-for"),
|
ip: ctx?.request?.headers.get('x-forwarded-for'),
|
||||||
userAgent: ctx?.request?.headers.get("user-agent"),
|
userAgent: ctx?.request?.headers.get('user-agent')
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
before: async ({ data }) => {
|
before: async ({ data }) => {
|
||||||
await auditLog("session.revoked", { sessionId: data.id });
|
await auditLog('session.revoked', { sessionId: data.id });
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
update: {
|
update: {
|
||||||
after: async ({ data, oldData }) => {
|
after: async ({ data, oldData }) => {
|
||||||
if (oldData?.email !== data.email) {
|
if (oldData?.email !== data.email) {
|
||||||
await auditLog("user.email_changed", {
|
await auditLog('user.email_changed', {
|
||||||
userId: data.id,
|
userId: data.id,
|
||||||
oldEmail: oldData?.email,
|
oldEmail: oldData?.email,
|
||||||
newEmail: data.email,
|
newEmail: data.email
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
create: {
|
create: {
|
||||||
after: async ({ data }) => {
|
after: async ({ data }) => {
|
||||||
await auditLog("account.linked", {
|
await auditLog('account.linked', {
|
||||||
userId: data.userId,
|
userId: data.userId,
|
||||||
provider: data.providerId,
|
provider: data.providerId
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -314,19 +312,19 @@ Return `false` from a `before` hook to prevent an operation.
|
|||||||
## Background Tasks
|
## Background Tasks
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
advanced: {
|
advanced: {
|
||||||
backgroundTasks: {
|
backgroundTasks: {
|
||||||
handler: (promise) => {
|
handler: (promise) => {
|
||||||
// Platform-specific handler
|
// Platform-specific handler
|
||||||
// Vercel: waitUntil(promise)
|
// Vercel: waitUntil(promise)
|
||||||
// Cloudflare: ctx.waitUntil(promise)
|
// Cloudflare: ctx.waitUntil(promise)
|
||||||
waitUntil(promise);
|
waitUntil(promise);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -339,80 +337,76 @@ Built-in: consistent response messages, dummy operations on invalid requests, ba
|
|||||||
## Complete Security Configuration Example
|
## Complete Security Configuration Example
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
secret: process.env.BETTER_AUTH_SECRET,
|
secret: process.env.BETTER_AUTH_SECRET,
|
||||||
baseURL: "https://api.example.com",
|
baseURL: 'https://api.example.com',
|
||||||
trustedOrigins: [
|
trustedOrigins: ['https://app.example.com', 'https://*.preview.example.com'],
|
||||||
"https://app.example.com",
|
|
||||||
"https://*.preview.example.com",
|
|
||||||
],
|
|
||||||
|
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
storage: "secondary-storage",
|
storage: 'secondary-storage',
|
||||||
customRules: {
|
customRules: {
|
||||||
"/api/auth/sign-in/email": { window: 60, max: 5 },
|
'/api/auth/sign-in/email': { window: 60, max: 5 },
|
||||||
"/api/auth/sign-up/email": { window: 60, max: 3 },
|
'/api/auth/sign-up/email': { window: 60, max: 3 }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Session security
|
// Session security
|
||||||
session: {
|
session: {
|
||||||
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
||||||
updateAge: 60 * 60 * 24, // 24 hours
|
updateAge: 60 * 60 * 24, // 24 hours
|
||||||
freshAge: 60 * 60, // 1 hour for sensitive actions
|
freshAge: 60 * 60, // 1 hour for sensitive actions
|
||||||
cookieCache: {
|
cookieCache: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maxAge: 300,
|
maxAge: 300,
|
||||||
strategy: "jwe", // Encrypted session data
|
strategy: 'jwe' // Encrypted session data
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// OAuth security
|
// OAuth security
|
||||||
account: {
|
account: {
|
||||||
encryptOAuthTokens: true,
|
encryptOAuthTokens: true,
|
||||||
storeStateStrategy: "cookie",
|
storeStateStrategy: 'cookie'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Advanced settings
|
||||||
|
advanced: {
|
||||||
|
useSecureCookies: true,
|
||||||
|
cookiePrefix: 'myapp',
|
||||||
|
defaultCookieAttributes: {
|
||||||
|
sameSite: 'lax'
|
||||||
|
},
|
||||||
|
ipAddress: {
|
||||||
|
ipAddressHeaders: ['x-forwarded-for'],
|
||||||
|
ipv6Subnet: 64
|
||||||
|
},
|
||||||
|
backgroundTasks: {
|
||||||
|
handler: (promise) => waitUntil(promise)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Advanced settings
|
// Security auditing
|
||||||
advanced: {
|
databaseHooks: {
|
||||||
useSecureCookies: true,
|
session: {
|
||||||
cookiePrefix: "myapp",
|
create: {
|
||||||
defaultCookieAttributes: {
|
after: async ({ data, ctx }) => {
|
||||||
sameSite: "lax",
|
console.log(`New session for user ${data.userId}`);
|
||||||
},
|
}
|
||||||
ipAddress: {
|
}
|
||||||
ipAddressHeaders: ["x-forwarded-for"],
|
},
|
||||||
ipv6Subnet: 64,
|
user: {
|
||||||
},
|
update: {
|
||||||
backgroundTasks: {
|
after: async ({ data, oldData }) => {
|
||||||
handler: (promise) => waitUntil(promise),
|
if (oldData?.email !== data.email) {
|
||||||
},
|
console.log(`Email changed for user ${data.id}`);
|
||||||
},
|
}
|
||||||
|
}
|
||||||
// Security auditing
|
}
|
||||||
databaseHooks: {
|
}
|
||||||
session: {
|
}
|
||||||
create: {
|
|
||||||
after: async ({ data, ctx }) => {
|
|
||||||
console.log(`New session for user ${data.userId}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
update: {
|
|
||||||
after: async ({ data, oldData }) => {
|
|
||||||
if (oldData?.email !== data.email) {
|
|
||||||
console.log(`Email changed for user ${data.id}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Before writing any code, gather requirements by scanning the project and asking
|
|||||||
### Step 1: Scan the project
|
### Step 1: Scan the project
|
||||||
|
|
||||||
Analyze the codebase to auto-detect:
|
Analyze the codebase to auto-detect:
|
||||||
|
|
||||||
- **Framework** — Look for `next.config`, `svelte.config`, `nuxt.config`, `astro.config`, `vite.config`, or Express/Hono entry files.
|
- **Framework** — Look for `next.config`, `svelte.config`, `nuxt.config`, `astro.config`, `vite.config`, or Express/Hono entry files.
|
||||||
- **Database/ORM** — Look for `prisma/schema.prisma`, `drizzle.config`, `package.json` deps (`pg`, `mysql2`, `better-sqlite3`, `mongoose`, `mongodb`).
|
- **Database/ORM** — Look for `prisma/schema.prisma`, `drizzle.config`, `package.json` deps (`pg`, `mysql2`, `better-sqlite3`, `mongoose`, `mongodb`).
|
||||||
- **Existing auth** — Look for existing auth libraries (`next-auth`, `lucia`, `clerk`, `supabase/auth`, `firebase/auth`) in `package.json` or imports.
|
- **Existing auth** — Look for existing auth libraries (`next-auth`, `lucia`, `clerk`, `supabase/auth`, `firebase/auth`) in `package.json` or imports.
|
||||||
@@ -75,8 +76,9 @@ Use the `AskQuestion` tool to ask the user **all applicable questions in a singl
|
|||||||
- `allow_multiple: true`
|
- `allow_multiple: true`
|
||||||
|
|
||||||
10. **Auth UI style** (always ask)
|
10. **Auth UI style** (always ask)
|
||||||
- Prompt: "What style do you want for the auth pages? Pick one or describe your own."
|
|
||||||
- Options: Minimal & clean | Centered card with background | Split layout (form + hero image) | Floating / glassmorphism | Other (I'll describe)
|
- Prompt: "What style do you want for the auth pages? Pick one or describe your own."
|
||||||
|
- Options: Minimal & clean | Centered card with background | Split layout (form + hero image) | Floating / glassmorphism | Other (I'll describe)
|
||||||
|
|
||||||
### Step 3: Summarize the plan
|
### Step 3: Summarize the plan
|
||||||
|
|
||||||
@@ -180,15 +182,18 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
**Location:** `lib/auth.ts` or `src/lib/auth.ts`
|
**Location:** `lib/auth.ts` or `src/lib/auth.ts`
|
||||||
|
|
||||||
**Minimal config needs:**
|
**Minimal config needs:**
|
||||||
|
|
||||||
- `database` - Connection or adapter
|
- `database` - Connection or adapter
|
||||||
- `emailAndPassword: { enabled: true }` - For email/password auth
|
- `emailAndPassword: { enabled: true }` - For email/password auth
|
||||||
|
|
||||||
**Standard config adds:**
|
**Standard config adds:**
|
||||||
|
|
||||||
- `socialProviders` - OAuth providers (google, github, etc.)
|
- `socialProviders` - OAuth providers (google, github, etc.)
|
||||||
- `emailVerification.sendVerificationEmail` - Email verification handler
|
- `emailVerification.sendVerificationEmail` - Email verification handler
|
||||||
- `emailAndPassword.sendResetPassword` - Password reset handler
|
- `emailAndPassword.sendResetPassword` - Password reset handler
|
||||||
|
|
||||||
**Full config adds:**
|
**Full config adds:**
|
||||||
|
|
||||||
- `plugins` - Array of feature plugins
|
- `plugins` - Array of feature plugins
|
||||||
- `session` - Expiry, cookie cache settings
|
- `session` - Expiry, cookie cache settings
|
||||||
- `account.accountLinking` - Multi-provider linking
|
- `account.accountLinking` - Multi-provider linking
|
||||||
@@ -217,14 +222,14 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
|
|
||||||
## Route Handler Setup
|
## Route Handler Setup
|
||||||
|
|
||||||
| Framework | File | Handler |
|
| Framework | File | Handler |
|
||||||
|-----------|------|---------|
|
| ------------------ | -------------------------------- | ------------------------------------------------ |
|
||||||
| Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` |
|
| Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` |
|
||||||
| Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export |
|
| Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export |
|
||||||
| Express | Any file | `app.all("/api/auth/*", toNodeHandler(auth))` |
|
| Express | Any file | `app.all("/api/auth/*", toNodeHandler(auth))` |
|
||||||
| SvelteKit | `src/hooks.server.ts` | `svelteKitHandler(auth)` |
|
| SvelteKit | `src/hooks.server.ts` | `svelteKitHandler(auth)` |
|
||||||
| SolidStart | Route file | `solidStartHandler(auth)` |
|
| SolidStart | Route file | `solidStartHandler(auth)` |
|
||||||
| Hono | Route file | `auth.handler(c.req.raw)` |
|
| Hono | Route file | `auth.handler(c.req.raw)` |
|
||||||
|
|
||||||
**Next.js Server Components:** Add `nextCookies()` plugin to auth config.
|
**Next.js Server Components:** Add `nextCookies()` plugin to auth config.
|
||||||
|
|
||||||
@@ -232,11 +237,11 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
|
|
||||||
## Database Migrations
|
## Database Migrations
|
||||||
|
|
||||||
| Adapter | Command |
|
| Adapter | Command |
|
||||||
|---------|---------|
|
| --------------- | -------------------------------------------------------------------------------------------------- |
|
||||||
| Built-in Kysely | `npx @better-auth/cli@latest migrate` (applies directly) |
|
| Built-in Kysely | `npx @better-auth/cli@latest migrate` (applies directly) |
|
||||||
| Prisma | `npx @better-auth/cli@latest generate --output prisma/schema.prisma` then `npx prisma migrate dev` |
|
| Prisma | `npx @better-auth/cli@latest generate --output prisma/schema.prisma` then `npx prisma migrate dev` |
|
||||||
| Drizzle | `npx @better-auth/cli@latest generate --output src/db/auth-schema.ts` then `npx drizzle-kit push` |
|
| Drizzle | `npx @better-auth/cli@latest generate --output src/db/auth-schema.ts` then `npx drizzle-kit push` |
|
||||||
|
|
||||||
**Re-run after adding plugins.**
|
**Re-run after adding plugins.**
|
||||||
|
|
||||||
@@ -244,28 +249,28 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
|
|
||||||
## Database Adapters
|
## Database Adapters
|
||||||
|
|
||||||
| Database | Setup |
|
| Database | Setup |
|
||||||
|----------|-------|
|
| ---------- | -------------------------------------------------------------------------------------- |
|
||||||
| SQLite | Pass `better-sqlite3` or `bun:sqlite` instance directly |
|
| SQLite | Pass `better-sqlite3` or `bun:sqlite` instance directly |
|
||||||
| PostgreSQL | Pass `pg.Pool` instance directly |
|
| PostgreSQL | Pass `pg.Pool` instance directly |
|
||||||
| MySQL | Pass `mysql2` pool directly |
|
| MySQL | Pass `mysql2` pool directly |
|
||||||
| Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` |
|
| Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` |
|
||||||
| Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` |
|
| Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` |
|
||||||
| MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` |
|
| MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Common Plugins
|
## Common Plugins
|
||||||
|
|
||||||
| Plugin | Server Import | Client Import | Purpose |
|
| Plugin | Server Import | Client Import | Purpose |
|
||||||
|--------|---------------|---------------|---------|
|
| -------------- | ---------------------- | -------------------- | ----------------- |
|
||||||
| `twoFactor` | `better-auth/plugins` | `twoFactorClient` | 2FA with TOTP/OTP |
|
| `twoFactor` | `better-auth/plugins` | `twoFactorClient` | 2FA with TOTP/OTP |
|
||||||
| `organization` | `better-auth/plugins` | `organizationClient` | Teams/orgs |
|
| `organization` | `better-auth/plugins` | `organizationClient` | Teams/orgs |
|
||||||
| `admin` | `better-auth/plugins` | `adminClient` | User management |
|
| `admin` | `better-auth/plugins` | `adminClient` | User management |
|
||||||
| `bearer` | `better-auth/plugins` | - | API token auth |
|
| `bearer` | `better-auth/plugins` | - | API token auth |
|
||||||
| `openAPI` | `better-auth/plugins` | - | API docs |
|
| `openAPI` | `better-auth/plugins` | - | API docs |
|
||||||
| `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn |
|
| `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn |
|
||||||
| `sso` | `@better-auth/sso` | - | Enterprise SSO |
|
| `sso` | `@better-auth/sso` | - | Enterprise SSO |
|
||||||
|
|
||||||
**Plugin pattern:** Server plugin + client plugin + run migrations.
|
**Plugin pattern:** Server plugin + client plugin + run migrations.
|
||||||
|
|
||||||
@@ -274,6 +279,7 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
## Auth UI Implementation
|
## Auth UI Implementation
|
||||||
|
|
||||||
**Sign in flow:**
|
**Sign in flow:**
|
||||||
|
|
||||||
1. `signIn.email({ email, password })` or `signIn.social({ provider, callbackURL })`
|
1. `signIn.email({ email, password })` or `signIn.social({ provider, callbackURL })`
|
||||||
2. Handle `error` in response
|
2. Handle `error` in response
|
||||||
3. Redirect on success
|
3. Redirect on success
|
||||||
@@ -302,13 +308,13 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
| Issue | Fix |
|
| Issue | Fix |
|
||||||
|-------|-----|
|
| ------------------------------- | ------------------------------------------------------------- |
|
||||||
| "Secret not set" | Add `BETTER_AUTH_SECRET` env var |
|
| "Secret not set" | Add `BETTER_AUTH_SECRET` env var |
|
||||||
| "Invalid Origin" | Add domain to `trustedOrigins` |
|
| "Invalid Origin" | Add domain to `trustedOrigins` |
|
||||||
| Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod |
|
| Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod |
|
||||||
| OAuth callback errors | Verify redirect URIs in provider dashboard |
|
| OAuth callback errors | Verify redirect URIs in provider dashboard |
|
||||||
| Type errors after adding plugin | Re-run CLI generate/migrate |
|
| Type errors after adding plugin | Re-run CLI generate/migrate |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ description: Configure email verification, implement password reset flows, set p
|
|||||||
Configure `emailVerification.sendVerificationEmail` to verify user email addresses.
|
Configure `emailVerification.sendVerificationEmail` to verify user email addresses.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { sendEmail } from "./email"; // your email sending function
|
import { sendEmail } from './email'; // your email sending function
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailVerification: {
|
emailVerification: {
|
||||||
sendVerificationEmail: async ({ user, url, token }, request) => {
|
sendVerificationEmail: async ({ user, url, token }, request) => {
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: "Verify your email address",
|
subject: 'Verify your email address',
|
||||||
text: `Click the link to verify your email: ${url}`,
|
text: `Click the link to verify your email: ${url}`
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,9 +42,9 @@ For stricter security, enable `emailAndPassword.requireEmailVerification` to blo
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
requireEmailVerification: true,
|
requireEmailVerification: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ Always use absolute URLs (including the origin) for callback URLs in sign-up and
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { data, error } = await authClient.signUp.email({
|
const { data, error } = await authClient.signUp.email({
|
||||||
callbackURL: "https://example.com/callback", // absolute URL with origin
|
callbackURL: 'https://example.com/callback' // absolute URL with origin
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,26 +69,26 @@ const { data, error } = await authClient.signUp.email({
|
|||||||
Provide `sendResetPassword` in the email and password config to enable password resets.
|
Provide `sendResetPassword` in the email and password config to enable password resets.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { sendEmail } from "./email"; // your email sending function
|
import { sendEmail } from './email'; // your email sending function
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
// Custom email sending function to send reset-password email
|
// Custom email sending function to send reset-password email
|
||||||
sendResetPassword: async ({ user, url, token }, request) => {
|
sendResetPassword: async ({ user, url, token }, request) => {
|
||||||
void sendEmail({
|
void sendEmail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: "Reset your password",
|
subject: 'Reset your password',
|
||||||
text: `Click the link to reset your password: ${url}`,
|
text: `Click the link to reset your password: ${url}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Optional event hook
|
// Optional event hook
|
||||||
onPasswordReset: async ({ user }, request) => {
|
onPasswordReset: async ({ user }, request) => {
|
||||||
// your logic here
|
// your logic here
|
||||||
console.log(`Password for user ${user.email} has been reset.`);
|
console.log(`Password for user ${user.email} has been reset.`);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -100,14 +100,14 @@ On serverless platforms, configure a background task handler:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
advanced: {
|
advanced: {
|
||||||
backgroundTasks: {
|
backgroundTasks: {
|
||||||
handler: (promise) => {
|
handler: (promise) => {
|
||||||
// Use platform-specific methods like waitUntil
|
// Use platform-specific methods like waitUntil
|
||||||
waitUntil(promise);
|
waitUntil(promise);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -117,10 +117,10 @@ Tokens expire after 1 hour by default. Configure with `resetPasswordTokenExpires
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
resetPasswordTokenExpiresIn: 60 * 30, // 30 minutes
|
resetPasswordTokenExpiresIn: 60 * 30 // 30 minutes
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -132,10 +132,10 @@ Enable `revokeSessionsOnPasswordReset` to invalidate all existing sessions on pa
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
revokeSessionsOnPasswordReset: true,
|
revokeSessionsOnPasswordReset: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,11 +145,11 @@ Password length limits (configurable):
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
minPasswordLength: 12,
|
minPasswordLength: 12,
|
||||||
maxPasswordLength: 256,
|
maxPasswordLength: 256
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -159,10 +159,10 @@ Call `requestPasswordReset` to send the reset link. Triggers the `sendResetPassw
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const data = await auth.api.requestPasswordReset({
|
const data = await auth.api.requestPasswordReset({
|
||||||
body: {
|
body: {
|
||||||
email: "john.doe@example.com", // required
|
email: 'john.doe@example.com', // required
|
||||||
redirectTo: "https://example.com/reset-password",
|
redirectTo: 'https://example.com/reset-password'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -170,8 +170,8 @@ Or authClient:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { data, error } = await authClient.requestPasswordReset({
|
const { data, error } = await authClient.requestPasswordReset({
|
||||||
email: "john.doe@example.com", // required
|
email: 'john.doe@example.com', // required
|
||||||
redirectTo: "https://example.com/reset-password",
|
redirectTo: 'https://example.com/reset-password'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -186,26 +186,25 @@ Default: `scrypt` (Node.js native, no external dependencies).
|
|||||||
To use Argon2id or another algorithm, provide custom `hash` and `verify` functions:
|
To use Argon2id or another algorithm, provide custom `hash` and `verify` functions:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { hash, verify, type Options } from "@node-rs/argon2";
|
import { hash, verify, type Options } from '@node-rs/argon2';
|
||||||
|
|
||||||
const argon2Options: Options = {
|
const argon2Options: Options = {
|
||||||
memoryCost: 65536, // 64 MiB
|
memoryCost: 65536, // 64 MiB
|
||||||
timeCost: 3, // 3 iterations
|
timeCost: 3, // 3 iterations
|
||||||
parallelism: 4, // 4 parallel lanes
|
parallelism: 4, // 4 parallel lanes
|
||||||
outputLen: 32, // 32 byte output
|
outputLen: 32, // 32 byte output
|
||||||
algorithm: 2, // Argon2id variant
|
algorithm: 2 // Argon2id variant
|
||||||
};
|
};
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
password: {
|
password: {
|
||||||
hash: (password) => hash(password, argon2Options),
|
hash: (password) => hash(password, argon2Options),
|
||||||
verify: ({ password, hash: storedHash }) =>
|
verify: ({ password, hash: storedHash }) => verify(storedHash, password, argon2Options)
|
||||||
verify(storedHash, password, argon2Options),
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,28 +11,28 @@ description: Configure multi-tenant organizations, manage members and invitation
|
|||||||
4. Verify: check that organization, member, invitation tables exist in your database
|
4. Verify: check that organization, member, invitation tables exist in your database
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { organization } from "better-auth/plugins";
|
import { organization } from 'better-auth/plugins';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
organization({
|
organization({
|
||||||
allowUserToCreateOrganization: true,
|
allowUserToCreateOrganization: true,
|
||||||
organizationLimit: 5, // Max orgs per user
|
organizationLimit: 5, // Max orgs per user
|
||||||
membershipLimit: 100, // Max members per org
|
membershipLimit: 100 // Max members per org
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Client-Side Setup
|
### Client-Side Setup
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createAuthClient } from "better-auth/client";
|
import { createAuthClient } from 'better-auth/client';
|
||||||
import { organizationClient } from "better-auth/client/plugins";
|
import { organizationClient } from 'better-auth/client/plugins';
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
plugins: [organizationClient()],
|
plugins: [organizationClient()]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,12 +42,12 @@ The creator is automatically assigned the `owner` role.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const createOrg = async () => {
|
const createOrg = async () => {
|
||||||
const { data, error } = await authClient.organization.create({
|
const { data, error } = await authClient.organization.create({
|
||||||
name: "My Company",
|
name: 'My Company',
|
||||||
slug: "my-company",
|
slug: 'my-company',
|
||||||
logo: "https://example.com/logo.png",
|
logo: 'https://example.com/logo.png',
|
||||||
metadata: { plan: "pro" },
|
metadata: { plan: 'pro' }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ Restrict who can create organizations based on user attributes:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
allowUserToCreateOrganization: async (user) => {
|
allowUserToCreateOrganization: async (user) => {
|
||||||
return user.emailVerified === true;
|
return user.emailVerified === true;
|
||||||
},
|
},
|
||||||
organizationLimit: async (user) => {
|
organizationLimit: async (user) => {
|
||||||
// Premium users get more organizations
|
// Premium users get more organizations
|
||||||
return user.plan === "premium" ? 20 : 3;
|
return user.plan === 'premium' ? 20 : 3;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,26 +73,25 @@ Administrators can create organizations for other users (server-side only):
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
await auth.api.createOrganization({
|
await auth.api.createOrganization({
|
||||||
body: {
|
body: {
|
||||||
name: "Client Organization",
|
name: 'Client Organization',
|
||||||
slug: "client-org",
|
slug: 'client-org',
|
||||||
userId: "user-id-who-will-be-owner", // `userId` is required
|
userId: 'user-id-who-will-be-owner' // `userId` is required
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: The `userId` parameter cannot be used alongside session headers.
|
**Note**: The `userId` parameter cannot be used alongside session headers.
|
||||||
|
|
||||||
|
|
||||||
## Active Organizations
|
## Active Organizations
|
||||||
|
|
||||||
Stored in the session and scopes subsequent API calls. Set after user selects one.
|
Stored in the session and scopes subsequent API calls. Set after user selects one.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const setActive = async (organizationId: string) => {
|
const setActive = async (organizationId: string) => {
|
||||||
const { data, error } = await authClient.organization.setActive({
|
const { data, error } = await authClient.organization.setActive({
|
||||||
organizationId,
|
organizationId
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,11 +105,11 @@ Use `getFullOrganization()` to retrieve the active org with all members, invitat
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
await auth.api.addMember({
|
await auth.api.addMember({
|
||||||
body: {
|
body: {
|
||||||
userId: "user-id",
|
userId: 'user-id',
|
||||||
role: "member",
|
role: 'member',
|
||||||
organizationId: "org-id",
|
organizationId: 'org-id'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -120,11 +119,11 @@ For client-side member additions, use the invitation system instead.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
await auth.api.addMember({
|
await auth.api.addMember({
|
||||||
body: {
|
body: {
|
||||||
userId: "user-id",
|
userId: 'user-id',
|
||||||
role: ["admin", "moderator"],
|
role: ['admin', 'moderator'],
|
||||||
organizationId: "org-id",
|
organizationId: 'org-id'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,12 +139,12 @@ Use `updateMemberRole({ memberId, role })`.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
membershipLimit: async (user, organization) => {
|
membershipLimit: async (user, organization) => {
|
||||||
if (organization.metadata?.plan === "enterprise") {
|
if (organization.metadata?.plan === 'enterprise') {
|
||||||
return 1000;
|
return 1000;
|
||||||
}
|
}
|
||||||
return 50;
|
return 50;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -154,29 +153,29 @@ organization({
|
|||||||
### Setting Up Invitation Emails
|
### Setting Up Invitation Emails
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { organization } from "better-auth/plugins";
|
import { organization } from 'better-auth/plugins';
|
||||||
import { sendEmail } from "./email";
|
import { sendEmail } from './email';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
organization({
|
organization({
|
||||||
sendInvitationEmail: async (data) => {
|
sendInvitationEmail: async (data) => {
|
||||||
const { email, organization, inviter, invitation } = data;
|
const { email, organization, inviter, invitation } = data;
|
||||||
|
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
to: email,
|
to: email,
|
||||||
subject: `Join ${organization.name}`,
|
subject: `Join ${organization.name}`,
|
||||||
html: `
|
html: `
|
||||||
<p>${inviter.user.name} invited you to join ${organization.name}</p>
|
<p>${inviter.user.name} invited you to join ${organization.name}</p>
|
||||||
<a href="https://yourapp.com/accept-invite?id=${invitation.id}">
|
<a href="https://yourapp.com/accept-invite?id=${invitation.id}">
|
||||||
Accept Invitation
|
Accept Invitation
|
||||||
</a>
|
</a>
|
||||||
`,
|
`
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -184,8 +183,8 @@ export const auth = betterAuth({
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
await authClient.organization.inviteMember({
|
await authClient.organization.inviteMember({
|
||||||
email: "newuser@example.com",
|
email: 'newuser@example.com',
|
||||||
role: "member",
|
role: 'member'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -193,9 +192,9 @@ await authClient.organization.inviteMember({
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { data } = await authClient.organization.getInvitationURL({
|
const { data } = await authClient.organization.getInvitationURL({
|
||||||
email: "newuser@example.com",
|
email: 'newuser@example.com',
|
||||||
role: "member",
|
role: 'member',
|
||||||
callbackURL: "https://yourapp.com/dashboard",
|
callbackURL: 'https://yourapp.com/dashboard'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Share data.url via any channel
|
// Share data.url via any channel
|
||||||
@@ -207,9 +206,9 @@ This endpoint does not call `sendInvitationEmail` — handle delivery yourself.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days (default: 48 hours)
|
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days (default: 48 hours)
|
||||||
invitationLimit: 100, // Max pending invitations per org
|
invitationLimit: 100, // Max pending invitations per org
|
||||||
cancelPendingInvitationsOnReInvite: true, // Cancel old invites when re-inviting
|
cancelPendingInvitationsOnReInvite: true // Cancel old invites when re-inviting
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -221,11 +220,11 @@ Default roles: `owner` (full access), `admin` (manage members/invitations/settin
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { data } = await authClient.organization.hasPermission({
|
const { data } = await authClient.organization.hasPermission({
|
||||||
permission: "member:write",
|
permission: 'member:write'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data?.hasPermission) {
|
if (data?.hasPermission) {
|
||||||
// User can manage members
|
// User can manage members
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -236,16 +235,16 @@ Use `checkRolePermission({ role, permissions })` for client-side UI rendering (s
|
|||||||
### Enabling Teams
|
### Enabling Teams
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { organization } from "better-auth/plugins";
|
import { organization } from 'better-auth/plugins';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
organization({
|
organization({
|
||||||
teams: {
|
teams: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -253,7 +252,7 @@ export const auth = betterAuth({
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { data } = await authClient.organization.createTeam({
|
const { data } = await authClient.organization.createTeam({
|
||||||
name: "Engineering",
|
name: 'Engineering'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -267,11 +266,11 @@ Set active team with `setActiveTeam({ teamId })`.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
teams: {
|
teams: {
|
||||||
maximumTeams: 20, // Max teams per org
|
maximumTeams: 20, // Max teams per org
|
||||||
maximumMembersPerTeam: 50, // Max members per team
|
maximumMembersPerTeam: 50, // Max members per team
|
||||||
allowRemovingAllTeams: false, // Prevent removing last team
|
allowRemovingAllTeams: false // Prevent removing last team
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -280,17 +279,17 @@ organization({
|
|||||||
### Enabling Dynamic Access Control
|
### Enabling Dynamic Access Control
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { organization } from "better-auth/plugins";
|
import { organization } from 'better-auth/plugins';
|
||||||
import { dynamicAccessControl } from "@better-auth/organization/addons";
|
import { dynamicAccessControl } from '@better-auth/organization/addons';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
organization({
|
organization({
|
||||||
dynamicAccessControl: {
|
dynamicAccessControl: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -298,11 +297,11 @@ export const auth = betterAuth({
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
await authClient.organization.createRole({
|
await authClient.organization.createRole({
|
||||||
role: "moderator",
|
role: 'moderator',
|
||||||
permission: {
|
permission: {
|
||||||
member: ["read"],
|
member: ['read'],
|
||||||
invitation: ["read"],
|
invitation: ['read']
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -314,37 +313,37 @@ Execute custom logic at various points in the organization lifecycle:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
hooks: {
|
hooks: {
|
||||||
organization: {
|
organization: {
|
||||||
beforeCreate: async ({ data, user }) => {
|
beforeCreate: async ({ data, user }) => {
|
||||||
// Validate or modify data before creation
|
// Validate or modify data before creation
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
metadata: { ...data.metadata, createdBy: user.id },
|
metadata: { ...data.metadata, createdBy: user.id }
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
afterCreate: async ({ organization, member }) => {
|
afterCreate: async ({ organization, member }) => {
|
||||||
// Post-creation logic (e.g., send welcome email, create default resources)
|
// Post-creation logic (e.g., send welcome email, create default resources)
|
||||||
await createDefaultResources(organization.id);
|
await createDefaultResources(organization.id);
|
||||||
},
|
},
|
||||||
beforeDelete: async ({ organization }) => {
|
beforeDelete: async ({ organization }) => {
|
||||||
// Cleanup before deletion
|
// Cleanup before deletion
|
||||||
await archiveOrganizationData(organization.id);
|
await archiveOrganizationData(organization.id);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
member: {
|
member: {
|
||||||
afterCreate: async ({ member, organization }) => {
|
afterCreate: async ({ member, organization }) => {
|
||||||
await notifyAdmins(organization.id, `New member joined`);
|
await notifyAdmins(organization.id, `New member joined`);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
invitation: {
|
invitation: {
|
||||||
afterCreate: async ({ invitation, organization, inviter }) => {
|
afterCreate: async ({ invitation, organization, inviter }) => {
|
||||||
await logInvitation(invitation);
|
await logInvitation(invitation);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -354,32 +353,32 @@ Customize table names, field names, and add additional fields:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
schema: {
|
schema: {
|
||||||
organization: {
|
organization: {
|
||||||
modelName: "workspace", // Rename table
|
modelName: 'workspace', // Rename table
|
||||||
fields: {
|
fields: {
|
||||||
name: "workspaceName", // Rename fields
|
name: 'workspaceName' // Rename fields
|
||||||
},
|
},
|
||||||
additionalFields: {
|
additionalFields: {
|
||||||
billingId: {
|
billingId: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
required: false,
|
required: false
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
member: {
|
member: {
|
||||||
additionalFields: {
|
additionalFields: {
|
||||||
department: {
|
department: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
required: false,
|
required: false
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
required: false,
|
required: false
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -396,8 +395,8 @@ Always ensure ownership transfer before removing the current owner:
|
|||||||
```ts
|
```ts
|
||||||
// Transfer ownership first
|
// Transfer ownership first
|
||||||
await authClient.organization.updateMemberRole({
|
await authClient.organization.updateMemberRole({
|
||||||
memberId: "new-owner-member-id",
|
memberId: 'new-owner-member-id',
|
||||||
role: "owner",
|
role: 'owner'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then the previous owner can be demoted or removed
|
// Then the previous owner can be demoted or removed
|
||||||
@@ -409,7 +408,7 @@ Deleting an organization removes all associated data (members, invitations, team
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
disableOrganizationDeletion: true, // Disable via config
|
disableOrganizationDeletion: true // Disable via config
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -417,15 +416,15 @@ Or implement soft delete via hooks:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
organization({
|
organization({
|
||||||
hooks: {
|
hooks: {
|
||||||
organization: {
|
organization: {
|
||||||
beforeDelete: async ({ organization }) => {
|
beforeDelete: async ({ organization }) => {
|
||||||
// Archive instead of delete
|
// Archive instead of delete
|
||||||
await archiveOrganization(organization.id);
|
await archiveOrganization(organization.id);
|
||||||
throw new Error("Organization archived, not deleted");
|
throw new Error('Organization archived, not deleted');
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -438,42 +437,42 @@ organization({
|
|||||||
## Complete Configuration Example
|
## Complete Configuration Example
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { organization } from "better-auth/plugins";
|
import { organization } from 'better-auth/plugins';
|
||||||
import { sendEmail } from "./email";
|
import { sendEmail } from './email';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
organization({
|
organization({
|
||||||
// Organization limits
|
// Organization limits
|
||||||
allowUserToCreateOrganization: true,
|
allowUserToCreateOrganization: true,
|
||||||
organizationLimit: 10,
|
organizationLimit: 10,
|
||||||
membershipLimit: 100,
|
membershipLimit: 100,
|
||||||
creatorRole: "owner",
|
creatorRole: 'owner',
|
||||||
|
|
||||||
// Slugs
|
// Slugs
|
||||||
defaultOrganizationIdField: "slug",
|
defaultOrganizationIdField: 'slug',
|
||||||
|
|
||||||
// Invitations
|
// Invitations
|
||||||
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
|
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
|
||||||
invitationLimit: 50,
|
invitationLimit: 50,
|
||||||
sendInvitationEmail: async (data) => {
|
sendInvitationEmail: async (data) => {
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
to: data.email,
|
to: data.email,
|
||||||
subject: `Join ${data.organization.name}`,
|
subject: `Join ${data.organization.name}`,
|
||||||
html: `<a href="https://app.com/invite/${data.invitation.id}">Accept</a>`,
|
html: `<a href="https://app.com/invite/${data.invitation.id}">Accept</a>`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
hooks: {
|
hooks: {
|
||||||
organization: {
|
organization: {
|
||||||
afterCreate: async ({ organization }) => {
|
afterCreate: async ({ organization }) => {
|
||||||
console.log(`Organization ${organization.name} created`);
|
console.log(`Organization ${organization.name} created`);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,33 +11,33 @@ description: Configure TOTP authenticator apps, send OTP codes via email/SMS, ma
|
|||||||
4. Verify: check that `twoFactorSecret` column exists on user table
|
4. Verify: check that `twoFactorSecret` column exists on user table
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { twoFactor } from "better-auth/plugins";
|
import { twoFactor } from 'better-auth/plugins';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
appName: "My App",
|
appName: 'My App',
|
||||||
plugins: [
|
plugins: [
|
||||||
twoFactor({
|
twoFactor({
|
||||||
issuer: "My App",
|
issuer: 'My App'
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Client-Side Setup
|
### Client-Side Setup
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createAuthClient } from "better-auth/client";
|
import { createAuthClient } from 'better-auth/client';
|
||||||
import { twoFactorClient } from "better-auth/client/plugins";
|
import { twoFactorClient } from 'better-auth/client/plugins';
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
plugins: [
|
plugins: [
|
||||||
twoFactorClient({
|
twoFactorClient({
|
||||||
onTwoFactorRedirect() {
|
onTwoFactorRedirect() {
|
||||||
window.location.href = "/2fa";
|
window.location.href = '/2fa';
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,14 +47,14 @@ Requires password verification. Returns TOTP URI (for QR code) and backup codes.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const enable2FA = async (password: string) => {
|
const enable2FA = async (password: string) => {
|
||||||
const { data, error } = await authClient.twoFactor.enable({
|
const { data, error } = await authClient.twoFactor.enable({
|
||||||
password,
|
password
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// data.totpURI — generate a QR code from this
|
// data.totpURI — generate a QR code from this
|
||||||
// data.backupCodes — display to user
|
// data.backupCodes — display to user
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -65,10 +65,10 @@ const enable2FA = async (password: string) => {
|
|||||||
### Displaying the QR Code
|
### Displaying the QR Code
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import QRCode from "react-qr-code";
|
import QRCode from 'react-qr-code';
|
||||||
|
|
||||||
const TotpSetup = ({ totpURI }: { totpURI: string }) => {
|
const TotpSetup = ({ totpURI }: { totpURI: string }) => {
|
||||||
return <QRCode value={totpURI} />;
|
return <QRCode value={totpURI} />;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,10 +78,10 @@ Accepts codes from one period before/after current time:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const verifyTotp = async (code: string) => {
|
const verifyTotp = async (code: string) => {
|
||||||
const { data, error } = await authClient.twoFactor.verifyTotp({
|
const { data, error } = await authClient.twoFactor.verifyTotp({
|
||||||
code,
|
code,
|
||||||
trustDevice: true,
|
trustDevice: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -89,10 +89,10 @@ const verifyTotp = async (code: string) => {
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
totpOptions: {
|
totpOptions: {
|
||||||
digits: 6, // 6 or 8 digits (default: 6)
|
digits: 6, // 6 or 8 digits (default: 6)
|
||||||
period: 30, // Code validity period in seconds (default: 30)
|
period: 30 // Code validity period in seconds (default: 30)
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -101,27 +101,27 @@ twoFactor({
|
|||||||
### Configuring OTP Delivery
|
### Configuring OTP Delivery
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { twoFactor } from "better-auth/plugins";
|
import { twoFactor } from 'better-auth/plugins';
|
||||||
import { sendEmail } from "./email";
|
import { sendEmail } from './email';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
plugins: [
|
plugins: [
|
||||||
twoFactor({
|
twoFactor({
|
||||||
otpOptions: {
|
otpOptions: {
|
||||||
sendOTP: async ({ user, otp }, ctx) => {
|
sendOTP: async ({ user, otp }, ctx) => {
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: "Your verification code",
|
subject: 'Your verification code',
|
||||||
text: `Your code is: ${otp}`,
|
text: `Your code is: ${otp}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
period: 5, // Code validity in minutes (default: 3)
|
period: 5, // Code validity in minutes (default: 3)
|
||||||
digits: 6, // Number of digits (default: 6)
|
digits: 6, // Number of digits (default: 6)
|
||||||
allowedAttempts: 5, // Max verification attempts (default: 5)
|
allowedAttempts: 5 // Max verification attempts (default: 5)
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -135,9 +135,9 @@ Configure how OTP codes are stored in the database:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
otpOptions: {
|
otpOptions: {
|
||||||
storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
|
storeOTP: 'encrypted' // Options: "plain", "encrypted", "hashed"
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,12 +145,12 @@ For custom encryption:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
otpOptions: {
|
otpOptions: {
|
||||||
storeOTP: {
|
storeOTP: {
|
||||||
encrypt: async (token) => myEncrypt(token),
|
encrypt: async (token) => myEncrypt(token),
|
||||||
decrypt: async (token) => myDecrypt(token),
|
decrypt: async (token) => myDecrypt(token)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -162,16 +162,16 @@ Generated automatically when 2FA is enabled. Each code is single-use.
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const BackupCodes = ({ codes }: { codes: string[] }) => {
|
const BackupCodes = ({ codes }: { codes: string[] }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Save these codes in a secure location:</p>
|
<p>Save these codes in a secure location:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{codes.map((code, i) => (
|
{codes.map((code, i) => (
|
||||||
<li key={i}>{code}</li>
|
<li key={i}>{code}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -181,10 +181,10 @@ Invalidates all previous codes:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const regenerateBackupCodes = async (password: string) => {
|
const regenerateBackupCodes = async (password: string) => {
|
||||||
const { data, error } = await authClient.twoFactor.generateBackupCodes({
|
const { data, error } = await authClient.twoFactor.generateBackupCodes({
|
||||||
password,
|
password
|
||||||
});
|
});
|
||||||
// data.backupCodes contains the new codes
|
// data.backupCodes contains the new codes
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -192,10 +192,10 @@ const regenerateBackupCodes = async (password: string) => {
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const verifyBackupCode = async (code: string) => {
|
const verifyBackupCode = async (code: string) => {
|
||||||
const { data, error } = await authClient.twoFactor.verifyBackupCode({
|
const { data, error } = await authClient.twoFactor.verifyBackupCode({
|
||||||
code,
|
code,
|
||||||
trustDevice: true,
|
trustDevice: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -203,11 +203,11 @@ const verifyBackupCode = async (code: string) => {
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
backupCodeOptions: {
|
backupCodeOptions: {
|
||||||
amount: 10, // Number of codes to generate (default: 10)
|
amount: 10, // Number of codes to generate (default: 10)
|
||||||
length: 10, // Length of each code (default: 10)
|
length: 10, // Length of each code (default: 10)
|
||||||
storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
|
storeBackupCodes: 'encrypted' // Options: "plain", "encrypted"
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -225,16 +225,16 @@ Response includes `twoFactorRedirect: true` when 2FA is required:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const signIn = async (email: string, password: string) => {
|
const signIn = async (email: string, password: string) => {
|
||||||
const { data, error } = await authClient.signIn.email(
|
const { data, error } = await authClient.signIn.email(
|
||||||
{ email, password },
|
{ email, password },
|
||||||
{
|
{
|
||||||
onSuccess(context) {
|
onSuccess(context) {
|
||||||
if (context.data.twoFactorRedirect) {
|
if (context.data.twoFactorRedirect) {
|
||||||
window.location.href = "/2fa";
|
window.location.href = '/2fa';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ Flow: credentials → session removed → temporary 2FA cookie (10 min default)
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
|
twoFactorCookieMaxAge: 600 // 10 minutes in seconds (default)
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -262,9 +262,9 @@ Built-in: 3 requests per 10 seconds for all 2FA endpoints. OTP has additional at
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
twoFactor({
|
twoFactor({
|
||||||
otpOptions: {
|
otpOptions: {
|
||||||
allowedAttempts: 5, // Max attempts per OTP code (default: 5)
|
allowedAttempts: 5 // Max attempts per OTP code (default: 5)
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -280,52 +280,52 @@ Requires password confirmation. Revokes trusted device records:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const disable2FA = async (password: string) => {
|
const disable2FA = async (password: string) => {
|
||||||
const { data, error } = await authClient.twoFactor.disable({
|
const { data, error } = await authClient.twoFactor.disable({
|
||||||
password,
|
password
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Complete Configuration Example
|
## Complete Configuration Example
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from 'better-auth';
|
||||||
import { twoFactor } from "better-auth/plugins";
|
import { twoFactor } from 'better-auth/plugins';
|
||||||
import { sendEmail } from "./email";
|
import { sendEmail } from './email';
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
appName: "My App",
|
appName: 'My App',
|
||||||
plugins: [
|
plugins: [
|
||||||
twoFactor({
|
twoFactor({
|
||||||
// TOTP settings
|
// TOTP settings
|
||||||
issuer: "My App",
|
issuer: 'My App',
|
||||||
totpOptions: {
|
totpOptions: {
|
||||||
digits: 6,
|
digits: 6,
|
||||||
period: 30,
|
period: 30
|
||||||
},
|
},
|
||||||
// OTP settings
|
// OTP settings
|
||||||
otpOptions: {
|
otpOptions: {
|
||||||
sendOTP: async ({ user, otp }) => {
|
sendOTP: async ({ user, otp }) => {
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: "Your verification code",
|
subject: 'Your verification code',
|
||||||
text: `Your code is: ${otp}`,
|
text: `Your code is: ${otp}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
period: 5,
|
period: 5,
|
||||||
allowedAttempts: 5,
|
allowedAttempts: 5,
|
||||||
storeOTP: "encrypted",
|
storeOTP: 'encrypted'
|
||||||
},
|
},
|
||||||
// Backup code settings
|
// Backup code settings
|
||||||
backupCodeOptions: {
|
backupCodeOptions: {
|
||||||
amount: 10,
|
amount: 10,
|
||||||
length: 10,
|
length: 10,
|
||||||
storeBackupCodes: "encrypted",
|
storeBackupCodes: 'encrypted'
|
||||||
},
|
},
|
||||||
// Session settings
|
// Session settings
|
||||||
twoFactorCookieMaxAge: 600, // 10 minutes
|
twoFactorCookieMaxAge: 600, // 10 minutes
|
||||||
trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days
|
trustDeviceMaxAge: 30 * 24 * 60 * 60 // 30 days
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-4
@@ -4,10 +4,7 @@
|
|||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": [
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
"prettier-plugin-svelte",
|
|
||||||
"prettier-plugin-tailwindcss"
|
|
||||||
],
|
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "*.svelte",
|
"files": "*.svelte",
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ Requires `.env` with at least `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH
|
|||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
| npm run ... | What it does |
|
| npm run ... | What it does |
|
||||||
|----------------|-------------|
|
| -------------------------- | ------------------------------------------------ |
|
||||||
| `check` | `svelte-kit sync && svelte-check` (typecheck) |
|
| `check` | `svelte-kit sync && svelte-check` (typecheck) |
|
||||||
| `lint` | `prettier --check . && eslint .` (order matters) |
|
| `lint` | `prettier --check . && eslint .` (order matters) |
|
||||||
| `format` | `prettier --write .` |
|
| `format` | `prettier --write .` |
|
||||||
| `test` | `vitest --run` (single run) |
|
| `test` | `vitest --run` (single run) |
|
||||||
| `test:unit` | `vitest` (watch mode) |
|
| `test:unit` | `vitest` (watch mode) |
|
||||||
| `migrate -- --name <name>` | Create a Prisma migration |
|
| `migrate -- --name <name>` | Create a Prisma migration |
|
||||||
| `deploy` | Apply pending migrations |
|
| `deploy` | Apply pending migrations |
|
||||||
| `build` | Production build (adapter-node) |
|
| `build` | Production build (adapter-node) |
|
||||||
|
|
||||||
## Testing quirks
|
## Testing quirks
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,21 @@
|
|||||||
# Rhema Wave Technologies - User Portal
|
# Rhema Wave Technologies - User Portal
|
||||||
|
|
||||||
|
SvelteKit 2 + Svelte 5 + Tailwind CSS v4 + Prisma 7 + PostgreSQL + Better-Auth.
|
||||||
|
|
||||||
|
## Admin
|
||||||
|
|
||||||
|
Admin CRUD pages at `/admin`. 9 models have per-page routes with modals for create/edit:
|
||||||
|
|
||||||
|
| Included | Excluded |
|
||||||
|
| -------- | --------------------- |
|
||||||
|
| users | Session (Better Auth) |
|
||||||
|
| profiles | Account (Better Auth) |
|
||||||
|
| addresses| Verification (Better Auth) |
|
||||||
|
| user-roles | UserProclamations (composite key) |
|
||||||
|
| proclamations | |
|
||||||
|
| solfeggio-frequencies | |
|
||||||
|
| proclamation-categories | |
|
||||||
|
| license-keys (list-only) | |
|
||||||
|
| subscription-levels | |
|
||||||
|
|
||||||
|
See `src/lib/svelte-admin/README.md` for architecture and configuration docs.
|
||||||
|
|||||||
Generated
+724
-361
File diff suppressed because it is too large
Load Diff
+7
-1
@@ -42,6 +42,7 @@
|
|||||||
"svelte": "^5.45.6",
|
"svelte": "^5.45.6",
|
||||||
"svelte-check": "^4.3.4",
|
"svelte-check": "^4.3.4",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
|
"tsx": "^4.23.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.48.1",
|
"typescript-eslint": "^8.48.1",
|
||||||
"vite": "^7.2.6",
|
"vite": "^7.2.6",
|
||||||
@@ -52,10 +53,15 @@
|
|||||||
"@prisma/adapter-pg": "^7.2.0",
|
"@prisma/adapter-pg": "^7.2.0",
|
||||||
"@prisma/client": "^7.2.0",
|
"@prisma/client": "^7.2.0",
|
||||||
"@tiptap/core": "^3.15.3",
|
"@tiptap/core": "^3.15.3",
|
||||||
|
"@tiptap/extension-color": "^3.27.3",
|
||||||
|
"@tiptap/extension-highlight": "^3.27.3",
|
||||||
|
"@tiptap/extension-text-style": "^3.27.3",
|
||||||
"@tiptap/starter-kit": "^3.15.3",
|
"@tiptap/starter-kit": "^3.15.3",
|
||||||
"better-auth": "^1.4.10",
|
"better-auth": "^1.4.10",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"resend": "^6.17.1"
|
"resend": "^6.17.1",
|
||||||
|
"sveltekit-superforms": "^2.30.2",
|
||||||
|
"zod": "^4.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@ import { defineConfig } from 'prisma/config';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: 'prisma/schema.prisma',
|
schema: 'prisma/schema.prisma',
|
||||||
migrations: {
|
migrations: {
|
||||||
path: 'prisma/migrations'
|
path: 'prisma/migrations',
|
||||||
|
seed: 'tsx prisma/seed.ts'
|
||||||
},
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
url: process.env['DATABASE_URL']
|
url: process.env['DATABASE_URL']
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable: Remove price from Proclamation
|
||||||
|
ALTER TABLE "Proclamation" DROP COLUMN "price";
|
||||||
|
|
||||||
|
-- AlterTable: Change SolfeggioFrequency value from Float to Int
|
||||||
|
ALTER TABLE "SolfeggioFrequency" ALTER COLUMN "value" SET DATA TYPE INTEGER;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `isDefault` on the `Address` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Address" DROP COLUMN "isDefault",
|
||||||
|
ADD COLUMN "isDefaultBilling" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "isDefaultMailing" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "isDefaultShipping" BOOLEAN NOT NULL DEFAULT false;
|
||||||
+12
-10
@@ -47,16 +47,18 @@ model Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Address {
|
model Address {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
profileId String
|
profileId String
|
||||||
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
||||||
street String
|
street String
|
||||||
city String
|
city String
|
||||||
state String
|
state String
|
||||||
postalCode String
|
postalCode String
|
||||||
country String
|
country String
|
||||||
type String @default("Mailing")
|
type String @default("Mailing")
|
||||||
isDefault Boolean @default(false)
|
isDefaultMailing Boolean @default(false)
|
||||||
|
isDefaultBilling Boolean @default(false)
|
||||||
|
isDefaultShipping Boolean @default(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
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 });
|
||||||
|
|
||||||
|
const userRoles = [
|
||||||
|
{ id: 1, name: 'Super' },
|
||||||
|
{ id: 2, name: 'Admin' },
|
||||||
|
{ id: 3, name: 'Staff' },
|
||||||
|
{ id: 4, name: 'User' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const solfeggioFrequencies = [
|
||||||
|
{ id: 1, name: 'Crown', value: 936 },
|
||||||
|
{ id: 2, name: '3rd Eye', value: 852 },
|
||||||
|
{ id: 3, name: 'Throat', value: 741 },
|
||||||
|
{ id: 4, name: 'Heart', value: 639 },
|
||||||
|
{ id: 5, name: 'Solar Plexus', value: 528 },
|
||||||
|
{ id: 6, name: 'Sacral', value: 417 },
|
||||||
|
{ id: 7, name: 'Root', value: 396 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const subscriptionLevels = [
|
||||||
|
{ id: 1, name: 'Basic', description: 'General only' },
|
||||||
|
{ id: 2, name: 'Advanced', description: 'General + Specific' },
|
||||||
|
{ id: 3, name: 'Elite', description: 'General + Specific + Precise' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const proclamationCategories = [
|
||||||
|
{ id: 4, name: 'Spiritual', parentId: null },
|
||||||
|
{ id: 5, name: 'General', parentId: 4 },
|
||||||
|
{ id: 9, name: 'Specific', parentId: 4 },
|
||||||
|
{ id: 40, name: 'Precise', parentId: 4 },
|
||||||
|
{ id: 2, name: 'Physical', parentId: null },
|
||||||
|
{ id: 3, name: 'General', parentId: 2 },
|
||||||
|
{ id: 10, name: 'Specific', parentId: 2 },
|
||||||
|
{ id: 32, name: 'Precise', parentId: 2 },
|
||||||
|
{ id: 16, name: 'Mental', parentId: null },
|
||||||
|
{ id: 30, name: 'General', parentId: 16 },
|
||||||
|
{ 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() {
|
||||||
|
console.log('Seeding UserRole...');
|
||||||
|
for (const role of userRoles) {
|
||||||
|
await prisma.userRole.upsert({
|
||||||
|
where: { id: role.id },
|
||||||
|
create: role,
|
||||||
|
update: role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seeding SolfeggioFrequency...');
|
||||||
|
for (const freq of solfeggioFrequencies) {
|
||||||
|
await prisma.solfeggioFrequency.upsert({
|
||||||
|
where: { id: freq.id },
|
||||||
|
create: freq,
|
||||||
|
update: freq
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seeding ProclamationCategory...');
|
||||||
|
for (const cat of proclamationCategories) {
|
||||||
|
await prisma.proclamationCategory.upsert({
|
||||||
|
where: { id: cat.id },
|
||||||
|
create: cat,
|
||||||
|
update: cat
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seeding SubscriptionLevel...');
|
||||||
|
for (const level of subscriptionLevels) {
|
||||||
|
await prisma.subscriptionLevel.upsert({
|
||||||
|
where: { id: level.id },
|
||||||
|
create: level,
|
||||||
|
update: level
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seed complete.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('Seed failed:', e);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(() => prisma.$disconnect());
|
||||||
+39
-39
@@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"skills": {
|
"skills": {
|
||||||
"better-auth-best-practices": {
|
"better-auth-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "better-auth/best-practices/SKILL.md",
|
"skillPath": "better-auth/best-practices/SKILL.md",
|
||||||
"computedHash": "a4c830509e85557b59339d8d93a4e243e9e59c686e7678854d39230e12c2a6dc"
|
"computedHash": "a4c830509e85557b59339d8d93a4e243e9e59c686e7678854d39230e12c2a6dc"
|
||||||
},
|
},
|
||||||
"better-auth-security-best-practices": {
|
"better-auth-security-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "security/SKILL.md",
|
"skillPath": "security/SKILL.md",
|
||||||
"computedHash": "5717187bba9c22030c7174c74012f01c359f21e2ce87e0eb80e24244d8b8c915"
|
"computedHash": "5717187bba9c22030c7174c74012f01c359f21e2ce87e0eb80e24244d8b8c915"
|
||||||
},
|
},
|
||||||
"create-auth-skill": {
|
"create-auth-skill": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "better-auth/create-auth/SKILL.md",
|
"skillPath": "better-auth/create-auth/SKILL.md",
|
||||||
"computedHash": "31c027d02dbf1faa37f58191f574b9443143042fb2b684fe32df53ab0270487c"
|
"computedHash": "31c027d02dbf1faa37f58191f574b9443143042fb2b684fe32df53ab0270487c"
|
||||||
},
|
},
|
||||||
"email-and-password-best-practices": {
|
"email-and-password-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "better-auth/emailAndPassword/SKILL.md",
|
"skillPath": "better-auth/emailAndPassword/SKILL.md",
|
||||||
"computedHash": "747d7f73bd9e2ec5317b8ef88dc70f97df716d00e3d02de4051feebaa48b062d"
|
"computedHash": "747d7f73bd9e2ec5317b8ef88dc70f97df716d00e3d02de4051feebaa48b062d"
|
||||||
},
|
},
|
||||||
"organization-best-practices": {
|
"organization-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "better-auth/organization/SKILL.md",
|
"skillPath": "better-auth/organization/SKILL.md",
|
||||||
"computedHash": "79a5a85b43d10e9fe37582b3506b051a33a991817b938f349099baa5ddba21aa"
|
"computedHash": "79a5a85b43d10e9fe37582b3506b051a33a991817b938f349099baa5ddba21aa"
|
||||||
},
|
},
|
||||||
"two-factor-authentication-best-practices": {
|
"two-factor-authentication-best-practices": {
|
||||||
"source": "better-auth/skills",
|
"source": "better-auth/skills",
|
||||||
"sourceType": "github",
|
"sourceType": "github",
|
||||||
"skillPath": "better-auth/twoFactor/SKILL.md",
|
"skillPath": "better-auth/twoFactor/SKILL.md",
|
||||||
"computedHash": "7e297aaf887e11fdc03e52bdbf44974e161e8f7c393170ced7dc66cace4f6d46"
|
"computedHash": "7e297aaf887e11fdc03e52bdbf44974e161e8f7c393170ced7dc66cace4f6d46"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { Session, User } from "better-auth";
|
import type { Session, User } from 'better-auth';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
@@ -9,4 +9,4 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {};
|
||||||
|
|||||||
+21
-20
@@ -1,24 +1,25 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const theme = localStorage.getItem('theme');
|
||||||
|
if (
|
||||||
|
theme === 'dark' ||
|
||||||
|
(!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
|
||||||
<head>
|
<body class="m-0" data-sveltekit-preload-data="hover">
|
||||||
<meta charset="utf-8" />
|
<div class="flex h-dvh w-dvw flex-col">%sveltekit.body%</div>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
</body>
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
const theme = localStorage.getItem('theme');
|
|
||||||
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="m-0" data-sveltekit-preload-data="hover">
|
|
||||||
<div class="h-dvh w-dvw flex flex-col">%sveltekit.body%</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -87,9 +87,7 @@ describe('hooks.server handle', () => {
|
|||||||
const event = createMockEvent();
|
const event = createMockEvent();
|
||||||
await handle({ event, resolve: event.resolve });
|
await handle({ event, resolve: event.resolve });
|
||||||
|
|
||||||
expect(svelteKitHandler).toHaveBeenCalledWith(
|
expect(svelteKitHandler).toHaveBeenCalledWith(expect.objectContaining({ building: false }));
|
||||||
expect.objectContaining({ building: false })
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls svelteKitHandler with auth instance', async () => {
|
it('calls svelteKitHandler with auth instance', async () => {
|
||||||
|
|||||||
+13
-13
@@ -1,18 +1,18 @@
|
|||||||
import { auth } from "$lib/auth";
|
import { auth } from '$lib/auth';
|
||||||
import { svelteKitHandler } from "better-auth/svelte-kit";
|
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
||||||
import { building } from "$app/environment";
|
import { building } from '$app/environment';
|
||||||
|
|
||||||
export async function handle({ event, resolve }) {
|
export async function handle({ event, resolve }) {
|
||||||
// Fetch current session from Better Auth
|
// Fetch current session from Better Auth
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: event.request.headers,
|
headers: event.request.headers
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make session and user available on server
|
// Make session and user available on server
|
||||||
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 svelteKitHandler({ event, resolve, auth, building });
|
return svelteKitHandler({ event, resolve, auth, building });
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { createAuthClient } from "better-auth/svelte"
|
import { createAuthClient } from 'better-auth/svelte';
|
||||||
|
|
||||||
export const authClient = createAuthClient({});
|
export const authClient = createAuthClient({});
|
||||||
+1
-4
@@ -46,9 +46,6 @@ export const auth = betterAuth({
|
|||||||
window: 60,
|
window: 60,
|
||||||
max: 100
|
max: 100
|
||||||
},
|
},
|
||||||
trustedOrigins: [
|
trustedOrigins: ['http://localhost:5173', 'https://portal.rhemawavetech.com'],
|
||||||
'http://localhost:5173',
|
|
||||||
'https://portal.rhemawavetech.com'
|
|
||||||
],
|
|
||||||
plugins: [sveltekitCookies(getRequestEvent)]
|
plugins: [sveltekitCookies(getRequestEvent)]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<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} />
|
||||||
@@ -28,7 +28,9 @@ export * from "./enums.ts"
|
|||||||
* Type-safe database client for TypeScript
|
* Type-safe database client for TypeScript
|
||||||
* @example
|
* @example
|
||||||
* ```
|
* ```
|
||||||
* const prisma = new PrismaClient()
|
* const prisma = new PrismaClient({
|
||||||
|
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
||||||
|
* })
|
||||||
* // Fetch zero or more Users
|
* // Fetch zero or more Users
|
||||||
* const users = await prisma.user.findMany()
|
* const users = await prisma.user.findMany()
|
||||||
* ```
|
* ```
|
||||||
|
|||||||
@@ -202,33 +202,6 @@ export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
|||||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FloatFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FloatWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
@@ -430,20 +403,4 @@ export type NestedFloatFilter<$PrismaModel = never> = {
|
|||||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
|
||||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -80,12 +80,12 @@ export type PrismaVersion = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 7.2.0
|
* Prisma Client JS version: 7.8.0
|
||||||
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
* Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
|
||||||
*/
|
*/
|
||||||
export const prismaVersion: PrismaVersion = {
|
export const prismaVersion: PrismaVersion = {
|
||||||
client: "7.2.0",
|
client: "7.8.0",
|
||||||
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1436,7 +1436,8 @@ export const ProfileScalarFieldEnum = {
|
|||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
firstName: 'firstName',
|
firstName: 'firstName',
|
||||||
lastName: 'lastName',
|
lastName: 'lastName',
|
||||||
phoneNumber: 'phoneNumber'
|
phoneNumber: 'phoneNumber',
|
||||||
|
ghlContactId: 'ghlContactId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ProfileScalarFieldEnum = (typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum]
|
export type ProfileScalarFieldEnum = (typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum]
|
||||||
@@ -1449,7 +1450,11 @@ export const AddressScalarFieldEnum = {
|
|||||||
city: 'city',
|
city: 'city',
|
||||||
state: 'state',
|
state: 'state',
|
||||||
postalCode: 'postalCode',
|
postalCode: 'postalCode',
|
||||||
country: 'country'
|
country: 'country',
|
||||||
|
type: 'type',
|
||||||
|
isDefaultMailing: 'isDefaultMailing',
|
||||||
|
isDefaultBilling: 'isDefaultBilling',
|
||||||
|
isDefaultShipping: 'isDefaultShipping'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type AddressScalarFieldEnum = (typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum]
|
export type AddressScalarFieldEnum = (typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum]
|
||||||
@@ -1514,7 +1519,6 @@ export const ProclamationScalarFieldEnum = {
|
|||||||
content: 'content',
|
content: 'content',
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
categoryId: 'categoryId',
|
categoryId: 'categoryId',
|
||||||
price: 'price',
|
|
||||||
subscriptionLevelId: 'subscriptionLevelId'
|
subscriptionLevelId: 'subscriptionLevelId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
@@ -1754,6 +1758,21 @@ export type PrismaClientOptions = ({
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
comments?: runtime.SqlCommenterPlugin[]
|
comments?: runtime.SqlCommenterPlugin[]
|
||||||
|
/**
|
||||||
|
* Optional maximum size for the query plan cache. If not provided, a default size will be used.
|
||||||
|
* A value of `0` can be used to disable the cache entirely. A higher cache size can improve
|
||||||
|
* performance for applications that execute a large number of unique queries, while a smaller
|
||||||
|
* cache size can reduce memory usage.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient({
|
||||||
|
* adapter,
|
||||||
|
* queryPlanCacheMaxSize: 100,
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
queryPlanCacheMaxSize?: number
|
||||||
}
|
}
|
||||||
export type GlobalOmitConfig = {
|
export type GlobalOmitConfig = {
|
||||||
user?: Prisma.UserOmit
|
user?: Prisma.UserOmit
|
||||||
|
|||||||
@@ -72,12 +72,12 @@ export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
|||||||
* Enums
|
* Enums
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const TransactionIsolationLevel = {
|
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||||
ReadUncommitted: 'ReadUncommitted',
|
ReadUncommitted: 'ReadUncommitted',
|
||||||
ReadCommitted: 'ReadCommitted',
|
ReadCommitted: 'ReadCommitted',
|
||||||
RepeatableRead: 'RepeatableRead',
|
RepeatableRead: 'RepeatableRead',
|
||||||
Serializable: 'Serializable'
|
Serializable: 'Serializable'
|
||||||
} as const
|
} as const)
|
||||||
|
|
||||||
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||||
|
|
||||||
@@ -101,7 +101,8 @@ export const ProfileScalarFieldEnum = {
|
|||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
firstName: 'firstName',
|
firstName: 'firstName',
|
||||||
lastName: 'lastName',
|
lastName: 'lastName',
|
||||||
phoneNumber: 'phoneNumber'
|
phoneNumber: 'phoneNumber',
|
||||||
|
ghlContactId: 'ghlContactId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ProfileScalarFieldEnum = (typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum]
|
export type ProfileScalarFieldEnum = (typeof ProfileScalarFieldEnum)[keyof typeof ProfileScalarFieldEnum]
|
||||||
@@ -114,7 +115,11 @@ export const AddressScalarFieldEnum = {
|
|||||||
city: 'city',
|
city: 'city',
|
||||||
state: 'state',
|
state: 'state',
|
||||||
postalCode: 'postalCode',
|
postalCode: 'postalCode',
|
||||||
country: 'country'
|
country: 'country',
|
||||||
|
type: 'type',
|
||||||
|
isDefaultMailing: 'isDefaultMailing',
|
||||||
|
isDefaultBilling: 'isDefaultBilling',
|
||||||
|
isDefaultShipping: 'isDefaultShipping'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type AddressScalarFieldEnum = (typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum]
|
export type AddressScalarFieldEnum = (typeof AddressScalarFieldEnum)[keyof typeof AddressScalarFieldEnum]
|
||||||
@@ -179,7 +184,6 @@ export const ProclamationScalarFieldEnum = {
|
|||||||
content: 'content',
|
content: 'content',
|
||||||
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
solfeggioFrequencyId: 'solfeggioFrequencyId',
|
||||||
categoryId: 'categoryId',
|
categoryId: 'categoryId',
|
||||||
price: 'price',
|
|
||||||
subscriptionLevelId: 'subscriptionLevelId'
|
subscriptionLevelId: 'subscriptionLevelId'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export type AccountGroupByOutputType = {
|
|||||||
_max: AccountMaxAggregateOutputType | null
|
_max: AccountMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAccountGroupByPayload<T extends AccountGroupByArgs> = Prisma.PrismaPromise<
|
export type GetAccountGroupByPayload<T extends AccountGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<AccountGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<AccountGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1406,6 +1406,11 @@ export type AccountFindManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
|
|||||||
* Skip the first `n` Accounts.
|
* Skip the first `n` Accounts.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Accounts.
|
||||||
|
*/
|
||||||
distinct?: Prisma.AccountScalarFieldEnum | Prisma.AccountScalarFieldEnum[]
|
distinct?: Prisma.AccountScalarFieldEnum | Prisma.AccountScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export type AddressMinAggregateOutputType = {
|
|||||||
state: string | null
|
state: string | null
|
||||||
postalCode: string | null
|
postalCode: string | null
|
||||||
country: string | null
|
country: string | null
|
||||||
|
type: string | null
|
||||||
|
isDefaultMailing: boolean | null
|
||||||
|
isDefaultBilling: boolean | null
|
||||||
|
isDefaultShipping: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressMaxAggregateOutputType = {
|
export type AddressMaxAggregateOutputType = {
|
||||||
@@ -42,6 +46,10 @@ export type AddressMaxAggregateOutputType = {
|
|||||||
state: string | null
|
state: string | null
|
||||||
postalCode: string | null
|
postalCode: string | null
|
||||||
country: string | null
|
country: string | null
|
||||||
|
type: string | null
|
||||||
|
isDefaultMailing: boolean | null
|
||||||
|
isDefaultBilling: boolean | null
|
||||||
|
isDefaultShipping: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCountAggregateOutputType = {
|
export type AddressCountAggregateOutputType = {
|
||||||
@@ -52,6 +60,10 @@ export type AddressCountAggregateOutputType = {
|
|||||||
state: number
|
state: number
|
||||||
postalCode: number
|
postalCode: number
|
||||||
country: number
|
country: number
|
||||||
|
type: number
|
||||||
|
isDefaultMailing: number
|
||||||
|
isDefaultBilling: number
|
||||||
|
isDefaultShipping: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +76,10 @@ export type AddressMinAggregateInputType = {
|
|||||||
state?: true
|
state?: true
|
||||||
postalCode?: true
|
postalCode?: true
|
||||||
country?: true
|
country?: true
|
||||||
|
type?: true
|
||||||
|
isDefaultMailing?: true
|
||||||
|
isDefaultBilling?: true
|
||||||
|
isDefaultShipping?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressMaxAggregateInputType = {
|
export type AddressMaxAggregateInputType = {
|
||||||
@@ -74,6 +90,10 @@ export type AddressMaxAggregateInputType = {
|
|||||||
state?: true
|
state?: true
|
||||||
postalCode?: true
|
postalCode?: true
|
||||||
country?: true
|
country?: true
|
||||||
|
type?: true
|
||||||
|
isDefaultMailing?: true
|
||||||
|
isDefaultBilling?: true
|
||||||
|
isDefaultShipping?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCountAggregateInputType = {
|
export type AddressCountAggregateInputType = {
|
||||||
@@ -84,6 +104,10 @@ export type AddressCountAggregateInputType = {
|
|||||||
state?: true
|
state?: true
|
||||||
postalCode?: true
|
postalCode?: true
|
||||||
country?: true
|
country?: true
|
||||||
|
type?: true
|
||||||
|
isDefaultMailing?: true
|
||||||
|
isDefaultBilling?: true
|
||||||
|
isDefaultShipping?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,12 +191,16 @@ export type AddressGroupByOutputType = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type: string
|
||||||
|
isDefaultMailing: boolean
|
||||||
|
isDefaultBilling: boolean
|
||||||
|
isDefaultShipping: boolean
|
||||||
_count: AddressCountAggregateOutputType | null
|
_count: AddressCountAggregateOutputType | null
|
||||||
_min: AddressMinAggregateOutputType | null
|
_min: AddressMinAggregateOutputType | null
|
||||||
_max: AddressMaxAggregateOutputType | null
|
_max: AddressMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAddressGroupByPayload<T extends AddressGroupByArgs> = Prisma.PrismaPromise<
|
export type GetAddressGroupByPayload<T extends AddressGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<AddressGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<AddressGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -198,6 +226,10 @@ export type AddressWhereInput = {
|
|||||||
state?: Prisma.StringFilter<"Address"> | string
|
state?: Prisma.StringFilter<"Address"> | string
|
||||||
postalCode?: Prisma.StringFilter<"Address"> | string
|
postalCode?: Prisma.StringFilter<"Address"> | string
|
||||||
country?: Prisma.StringFilter<"Address"> | string
|
country?: Prisma.StringFilter<"Address"> | string
|
||||||
|
type?: Prisma.StringFilter<"Address"> | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
profile?: Prisma.XOR<Prisma.ProfileScalarRelationFilter, Prisma.ProfileWhereInput>
|
profile?: Prisma.XOR<Prisma.ProfileScalarRelationFilter, Prisma.ProfileWhereInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +241,10 @@ export type AddressOrderByWithRelationInput = {
|
|||||||
state?: Prisma.SortOrder
|
state?: Prisma.SortOrder
|
||||||
postalCode?: Prisma.SortOrder
|
postalCode?: Prisma.SortOrder
|
||||||
country?: Prisma.SortOrder
|
country?: Prisma.SortOrder
|
||||||
|
type?: Prisma.SortOrder
|
||||||
|
isDefaultMailing?: Prisma.SortOrder
|
||||||
|
isDefaultBilling?: Prisma.SortOrder
|
||||||
|
isDefaultShipping?: Prisma.SortOrder
|
||||||
profile?: Prisma.ProfileOrderByWithRelationInput
|
profile?: Prisma.ProfileOrderByWithRelationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +259,10 @@ export type AddressWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
state?: Prisma.StringFilter<"Address"> | string
|
state?: Prisma.StringFilter<"Address"> | string
|
||||||
postalCode?: Prisma.StringFilter<"Address"> | string
|
postalCode?: Prisma.StringFilter<"Address"> | string
|
||||||
country?: Prisma.StringFilter<"Address"> | string
|
country?: Prisma.StringFilter<"Address"> | string
|
||||||
|
type?: Prisma.StringFilter<"Address"> | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
profile?: Prisma.XOR<Prisma.ProfileScalarRelationFilter, Prisma.ProfileWhereInput>
|
profile?: Prisma.XOR<Prisma.ProfileScalarRelationFilter, Prisma.ProfileWhereInput>
|
||||||
}, "id">
|
}, "id">
|
||||||
|
|
||||||
@@ -234,6 +274,10 @@ export type AddressOrderByWithAggregationInput = {
|
|||||||
state?: Prisma.SortOrder
|
state?: Prisma.SortOrder
|
||||||
postalCode?: Prisma.SortOrder
|
postalCode?: Prisma.SortOrder
|
||||||
country?: Prisma.SortOrder
|
country?: Prisma.SortOrder
|
||||||
|
type?: Prisma.SortOrder
|
||||||
|
isDefaultMailing?: Prisma.SortOrder
|
||||||
|
isDefaultBilling?: Prisma.SortOrder
|
||||||
|
isDefaultShipping?: Prisma.SortOrder
|
||||||
_count?: Prisma.AddressCountOrderByAggregateInput
|
_count?: Prisma.AddressCountOrderByAggregateInput
|
||||||
_max?: Prisma.AddressMaxOrderByAggregateInput
|
_max?: Prisma.AddressMaxOrderByAggregateInput
|
||||||
_min?: Prisma.AddressMinOrderByAggregateInput
|
_min?: Prisma.AddressMinOrderByAggregateInput
|
||||||
@@ -250,6 +294,10 @@ export type AddressScalarWhereWithAggregatesInput = {
|
|||||||
state?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
state?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
||||||
postalCode?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
postalCode?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
||||||
country?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
country?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
||||||
|
type?: Prisma.StringWithAggregatesFilter<"Address"> | string
|
||||||
|
isDefaultMailing?: Prisma.BoolWithAggregatesFilter<"Address"> | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolWithAggregatesFilter<"Address"> | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolWithAggregatesFilter<"Address"> | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCreateInput = {
|
export type AddressCreateInput = {
|
||||||
@@ -259,6 +307,10 @@ export type AddressCreateInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
profile: Prisma.ProfileCreateNestedOneWithoutAddressesInput
|
profile: Prisma.ProfileCreateNestedOneWithoutAddressesInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +322,10 @@ export type AddressUncheckedCreateInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUpdateInput = {
|
export type AddressUpdateInput = {
|
||||||
@@ -279,6 +335,10 @@ export type AddressUpdateInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
profile?: Prisma.ProfileUpdateOneRequiredWithoutAddressesNestedInput
|
profile?: Prisma.ProfileUpdateOneRequiredWithoutAddressesNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +350,10 @@ export type AddressUncheckedUpdateInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCreateManyInput = {
|
export type AddressCreateManyInput = {
|
||||||
@@ -300,6 +364,10 @@ export type AddressCreateManyInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUpdateManyMutationInput = {
|
export type AddressUpdateManyMutationInput = {
|
||||||
@@ -309,6 +377,10 @@ export type AddressUpdateManyMutationInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUncheckedUpdateManyInput = {
|
export type AddressUncheckedUpdateManyInput = {
|
||||||
@@ -319,6 +391,10 @@ export type AddressUncheckedUpdateManyInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressListRelationFilter = {
|
export type AddressListRelationFilter = {
|
||||||
@@ -339,6 +415,10 @@ export type AddressCountOrderByAggregateInput = {
|
|||||||
state?: Prisma.SortOrder
|
state?: Prisma.SortOrder
|
||||||
postalCode?: Prisma.SortOrder
|
postalCode?: Prisma.SortOrder
|
||||||
country?: Prisma.SortOrder
|
country?: Prisma.SortOrder
|
||||||
|
type?: Prisma.SortOrder
|
||||||
|
isDefaultMailing?: Prisma.SortOrder
|
||||||
|
isDefaultBilling?: Prisma.SortOrder
|
||||||
|
isDefaultShipping?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressMaxOrderByAggregateInput = {
|
export type AddressMaxOrderByAggregateInput = {
|
||||||
@@ -349,6 +429,10 @@ export type AddressMaxOrderByAggregateInput = {
|
|||||||
state?: Prisma.SortOrder
|
state?: Prisma.SortOrder
|
||||||
postalCode?: Prisma.SortOrder
|
postalCode?: Prisma.SortOrder
|
||||||
country?: Prisma.SortOrder
|
country?: Prisma.SortOrder
|
||||||
|
type?: Prisma.SortOrder
|
||||||
|
isDefaultMailing?: Prisma.SortOrder
|
||||||
|
isDefaultBilling?: Prisma.SortOrder
|
||||||
|
isDefaultShipping?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressMinOrderByAggregateInput = {
|
export type AddressMinOrderByAggregateInput = {
|
||||||
@@ -359,6 +443,10 @@ export type AddressMinOrderByAggregateInput = {
|
|||||||
state?: Prisma.SortOrder
|
state?: Prisma.SortOrder
|
||||||
postalCode?: Prisma.SortOrder
|
postalCode?: Prisma.SortOrder
|
||||||
country?: Prisma.SortOrder
|
country?: Prisma.SortOrder
|
||||||
|
type?: Prisma.SortOrder
|
||||||
|
isDefaultMailing?: Prisma.SortOrder
|
||||||
|
isDefaultBilling?: Prisma.SortOrder
|
||||||
|
isDefaultShipping?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCreateNestedManyWithoutProfileInput = {
|
export type AddressCreateNestedManyWithoutProfileInput = {
|
||||||
@@ -410,6 +498,10 @@ export type AddressCreateWithoutProfileInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUncheckedCreateWithoutProfileInput = {
|
export type AddressUncheckedCreateWithoutProfileInput = {
|
||||||
@@ -419,6 +511,10 @@ export type AddressUncheckedCreateWithoutProfileInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCreateOrConnectWithoutProfileInput = {
|
export type AddressCreateOrConnectWithoutProfileInput = {
|
||||||
@@ -458,6 +554,10 @@ export type AddressScalarWhereInput = {
|
|||||||
state?: Prisma.StringFilter<"Address"> | string
|
state?: Prisma.StringFilter<"Address"> | string
|
||||||
postalCode?: Prisma.StringFilter<"Address"> | string
|
postalCode?: Prisma.StringFilter<"Address"> | string
|
||||||
country?: Prisma.StringFilter<"Address"> | string
|
country?: Prisma.StringFilter<"Address"> | string
|
||||||
|
type?: Prisma.StringFilter<"Address"> | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFilter<"Address"> | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressCreateManyProfileInput = {
|
export type AddressCreateManyProfileInput = {
|
||||||
@@ -467,6 +567,10 @@ export type AddressCreateManyProfileInput = {
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type?: string
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUpdateWithoutProfileInput = {
|
export type AddressUpdateWithoutProfileInput = {
|
||||||
@@ -476,6 +580,10 @@ export type AddressUpdateWithoutProfileInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUncheckedUpdateWithoutProfileInput = {
|
export type AddressUncheckedUpdateWithoutProfileInput = {
|
||||||
@@ -485,6 +593,10 @@ export type AddressUncheckedUpdateWithoutProfileInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressUncheckedUpdateManyWithoutProfileInput = {
|
export type AddressUncheckedUpdateManyWithoutProfileInput = {
|
||||||
@@ -494,6 +606,10 @@ export type AddressUncheckedUpdateManyWithoutProfileInput = {
|
|||||||
state?: Prisma.StringFieldUpdateOperationsInput | string
|
state?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
postalCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
country?: Prisma.StringFieldUpdateOperationsInput | string
|
country?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
type?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
isDefaultMailing?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultBilling?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
isDefaultShipping?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -506,6 +622,10 @@ export type AddressSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||||||
state?: boolean
|
state?: boolean
|
||||||
postalCode?: boolean
|
postalCode?: boolean
|
||||||
country?: boolean
|
country?: boolean
|
||||||
|
type?: boolean
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["address"]>
|
}, ExtArgs["result"]["address"]>
|
||||||
|
|
||||||
@@ -517,6 +637,10 @@ export type AddressSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||||||
state?: boolean
|
state?: boolean
|
||||||
postalCode?: boolean
|
postalCode?: boolean
|
||||||
country?: boolean
|
country?: boolean
|
||||||
|
type?: boolean
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["address"]>
|
}, ExtArgs["result"]["address"]>
|
||||||
|
|
||||||
@@ -528,6 +652,10 @@ export type AddressSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||||||
state?: boolean
|
state?: boolean
|
||||||
postalCode?: boolean
|
postalCode?: boolean
|
||||||
country?: boolean
|
country?: boolean
|
||||||
|
type?: boolean
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["address"]>
|
}, ExtArgs["result"]["address"]>
|
||||||
|
|
||||||
@@ -539,9 +667,13 @@ export type AddressSelectScalar = {
|
|||||||
state?: boolean
|
state?: boolean
|
||||||
postalCode?: boolean
|
postalCode?: boolean
|
||||||
country?: boolean
|
country?: boolean
|
||||||
|
type?: boolean
|
||||||
|
isDefaultMailing?: boolean
|
||||||
|
isDefaultBilling?: boolean
|
||||||
|
isDefaultShipping?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "profileId" | "street" | "city" | "state" | "postalCode" | "country", ExtArgs["result"]["address"]>
|
export type AddressOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "profileId" | "street" | "city" | "state" | "postalCode" | "country" | "type" | "isDefaultMailing" | "isDefaultBilling" | "isDefaultShipping", ExtArgs["result"]["address"]>
|
||||||
export type AddressInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type AddressInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
profile?: boolean | Prisma.ProfileDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
@@ -565,6 +697,10 @@ export type $AddressPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
|||||||
state: string
|
state: string
|
||||||
postalCode: string
|
postalCode: string
|
||||||
country: string
|
country: string
|
||||||
|
type: string
|
||||||
|
isDefaultMailing: boolean
|
||||||
|
isDefaultBilling: boolean
|
||||||
|
isDefaultShipping: boolean
|
||||||
}, ExtArgs["result"]["address"]>
|
}, ExtArgs["result"]["address"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
}
|
}
|
||||||
@@ -996,6 +1132,10 @@ export interface AddressFieldRefs {
|
|||||||
readonly state: Prisma.FieldRef<"Address", 'String'>
|
readonly state: Prisma.FieldRef<"Address", 'String'>
|
||||||
readonly postalCode: Prisma.FieldRef<"Address", 'String'>
|
readonly postalCode: Prisma.FieldRef<"Address", 'String'>
|
||||||
readonly country: Prisma.FieldRef<"Address", 'String'>
|
readonly country: Prisma.FieldRef<"Address", 'String'>
|
||||||
|
readonly type: Prisma.FieldRef<"Address", 'String'>
|
||||||
|
readonly isDefaultMailing: Prisma.FieldRef<"Address", 'Boolean'>
|
||||||
|
readonly isDefaultBilling: Prisma.FieldRef<"Address", 'Boolean'>
|
||||||
|
readonly isDefaultShipping: Prisma.FieldRef<"Address", 'Boolean'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1192,6 +1332,11 @@ export type AddressFindManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
|
|||||||
* Skip the first `n` Addresses.
|
* Skip the first `n` Addresses.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Addresses.
|
||||||
|
*/
|
||||||
distinct?: Prisma.AddressScalarFieldEnum | Prisma.AddressScalarFieldEnum[]
|
distinct?: Prisma.AddressScalarFieldEnum | Prisma.AddressScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export type LicenseKeyGroupByOutputType = {
|
|||||||
_max: LicenseKeyMaxAggregateOutputType | null
|
_max: LicenseKeyMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetLicenseKeyGroupByPayload<T extends LicenseKeyGroupByArgs> = Prisma.PrismaPromise<
|
export type GetLicenseKeyGroupByPayload<T extends LicenseKeyGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<LicenseKeyGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<LicenseKeyGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1122,6 +1122,11 @@ export type LicenseKeyFindManyArgs<ExtArgs extends runtime.Types.Extensions.Inte
|
|||||||
* Skip the first `n` LicenseKeys.
|
* Skip the first `n` LicenseKeys.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of LicenseKeys.
|
||||||
|
*/
|
||||||
distinct?: Prisma.LicenseKeyScalarFieldEnum | Prisma.LicenseKeyScalarFieldEnum[]
|
distinct?: Prisma.LicenseKeyScalarFieldEnum | Prisma.LicenseKeyScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,14 +29,12 @@ export type AggregateProclamation = {
|
|||||||
export type ProclamationAvgAggregateOutputType = {
|
export type ProclamationAvgAggregateOutputType = {
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
price: number | null
|
|
||||||
subscriptionLevelId: number | null
|
subscriptionLevelId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumAggregateOutputType = {
|
export type ProclamationSumAggregateOutputType = {
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
price: number | null
|
|
||||||
subscriptionLevelId: number | null
|
subscriptionLevelId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +44,6 @@ export type ProclamationMinAggregateOutputType = {
|
|||||||
content: string | null
|
content: string | null
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
price: number | null
|
|
||||||
subscriptionLevelId: number | null
|
subscriptionLevelId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +53,6 @@ export type ProclamationMaxAggregateOutputType = {
|
|||||||
content: string | null
|
content: string | null
|
||||||
solfeggioFrequencyId: number | null
|
solfeggioFrequencyId: number | null
|
||||||
categoryId: number | null
|
categoryId: number | null
|
||||||
price: number | null
|
|
||||||
subscriptionLevelId: number | null
|
subscriptionLevelId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +62,6 @@ export type ProclamationCountAggregateOutputType = {
|
|||||||
content: number
|
content: number
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price: number
|
|
||||||
subscriptionLevelId: number
|
subscriptionLevelId: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
@@ -75,14 +70,12 @@ export type ProclamationCountAggregateOutputType = {
|
|||||||
export type ProclamationAvgAggregateInputType = {
|
export type ProclamationAvgAggregateInputType = {
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
price?: true
|
|
||||||
subscriptionLevelId?: true
|
subscriptionLevelId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumAggregateInputType = {
|
export type ProclamationSumAggregateInputType = {
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
price?: true
|
|
||||||
subscriptionLevelId?: true
|
subscriptionLevelId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +85,6 @@ export type ProclamationMinAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
price?: true
|
|
||||||
subscriptionLevelId?: true
|
subscriptionLevelId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +94,6 @@ export type ProclamationMaxAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
price?: true
|
|
||||||
subscriptionLevelId?: true
|
subscriptionLevelId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +103,6 @@ export type ProclamationCountAggregateInputType = {
|
|||||||
content?: true
|
content?: true
|
||||||
solfeggioFrequencyId?: true
|
solfeggioFrequencyId?: true
|
||||||
categoryId?: true
|
categoryId?: true
|
||||||
price?: true
|
|
||||||
subscriptionLevelId?: true
|
subscriptionLevelId?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
@@ -209,7 +199,6 @@ export type ProclamationGroupByOutputType = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price: number
|
|
||||||
subscriptionLevelId: number
|
subscriptionLevelId: number
|
||||||
_count: ProclamationCountAggregateOutputType | null
|
_count: ProclamationCountAggregateOutputType | null
|
||||||
_avg: ProclamationAvgAggregateOutputType | null
|
_avg: ProclamationAvgAggregateOutputType | null
|
||||||
@@ -218,7 +207,7 @@ export type ProclamationGroupByOutputType = {
|
|||||||
_max: ProclamationMaxAggregateOutputType | null
|
_max: ProclamationMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProclamationGroupByPayload<T extends ProclamationGroupByArgs> = Prisma.PrismaPromise<
|
export type GetProclamationGroupByPayload<T extends ProclamationGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<ProclamationGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<ProclamationGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -242,7 +231,6 @@ 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
|
||||||
price?: Prisma.FloatFilter<"Proclamation"> | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
subscriptionLevelId?: 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>
|
||||||
@@ -256,7 +244,6 @@ export type ProclamationOrderByWithRelationInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyOrderByWithRelationInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyOrderByWithRelationInput
|
||||||
category?: Prisma.ProclamationCategoryOrderByWithRelationInput
|
category?: Prisma.ProclamationCategoryOrderByWithRelationInput
|
||||||
@@ -273,7 +260,6 @@ 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
|
||||||
price?: Prisma.FloatFilter<"Proclamation"> | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
subscriptionLevelId?: 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>
|
||||||
@@ -287,7 +273,6 @@ export type ProclamationOrderByWithAggregationInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
_count?: Prisma.ProclamationCountOrderByAggregateInput
|
_count?: Prisma.ProclamationCountOrderByAggregateInput
|
||||||
_avg?: Prisma.ProclamationAvgOrderByAggregateInput
|
_avg?: Prisma.ProclamationAvgOrderByAggregateInput
|
||||||
@@ -305,7 +290,6 @@ 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
|
||||||
price?: Prisma.FloatWithAggregatesFilter<"Proclamation"> | number
|
|
||||||
subscriptionLevelId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
subscriptionLevelId?: Prisma.IntWithAggregatesFilter<"Proclamation"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +297,6 @@ export type ProclamationCreateInput = {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
price?: number
|
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
@@ -326,7 +309,6 @@ export type ProclamationUncheckedCreateInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
@@ -335,7 +317,6 @@ export type ProclamationUpdateInput = {
|
|||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
@@ -348,7 +329,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
@@ -359,7 +339,6 @@ export type ProclamationCreateManyInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +346,6 @@ export type ProclamationUpdateManyMutationInput = {
|
|||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUncheckedUpdateManyInput = {
|
export type ProclamationUncheckedUpdateManyInput = {
|
||||||
@@ -376,7 +354,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,14 +363,12 @@ export type ProclamationCountOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationAvgOrderByAggregateInput = {
|
export type ProclamationAvgOrderByAggregateInput = {
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +378,6 @@ export type ProclamationMaxOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,14 +387,12 @@ export type ProclamationMinOrderByAggregateInput = {
|
|||||||
content?: Prisma.SortOrder
|
content?: Prisma.SortOrder
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationSumOrderByAggregateInput = {
|
export type ProclamationSumOrderByAggregateInput = {
|
||||||
solfeggioFrequencyId?: Prisma.SortOrder
|
solfeggioFrequencyId?: Prisma.SortOrder
|
||||||
categoryId?: Prisma.SortOrder
|
categoryId?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
|
||||||
subscriptionLevelId?: Prisma.SortOrder
|
subscriptionLevelId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,14 +411,6 @@ export type ProclamationOrderByRelationAggregateInput = {
|
|||||||
_count?: Prisma.SortOrder
|
_count?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FloatFieldUpdateOperationsInput = {
|
|
||||||
set?: number
|
|
||||||
increment?: number
|
|
||||||
decrement?: number
|
|
||||||
multiply?: number
|
|
||||||
divide?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
@@ -591,7 +555,6 @@ export type ProclamationCreateWithoutUserProclamationsInput = {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
price?: number
|
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
||||||
@@ -603,7 +566,6 @@ export type ProclamationUncheckedCreateWithoutUserProclamationsInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +589,6 @@ export type ProclamationUpdateWithoutUserProclamationsInput = {
|
|||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
@@ -639,7 +600,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +607,6 @@ export type ProclamationCreateWithoutSolfeggioFrequencyInput = {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
price?: number
|
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
||||||
@@ -658,7 +617,6 @@ export type ProclamationUncheckedCreateWithoutSolfeggioFrequencyInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
@@ -698,7 +656,6 @@ export type ProclamationScalarWhereInput = {
|
|||||||
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
|
||||||
price?: Prisma.FloatFilter<"Proclamation"> | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
subscriptionLevelId?: Prisma.IntFilter<"Proclamation"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,7 +663,6 @@ export type ProclamationCreateWithoutCategoryInput = {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
price?: number
|
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
subscriptionLevel?: Prisma.SubscriptionLevelCreateNestedOneWithoutProclamationsInput
|
||||||
@@ -717,7 +673,6 @@ export type ProclamationUncheckedCreateWithoutCategoryInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
@@ -752,7 +707,6 @@ export type ProclamationCreateWithoutSubscriptionLevelInput = {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
price?: number
|
|
||||||
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
solfeggioFrequency: Prisma.SolfeggioFrequencyCreateNestedOneWithoutProclamationsInput
|
||||||
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
category: Prisma.ProclamationCategoryCreateNestedOneWithoutProclamationsInput
|
||||||
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsCreateNestedManyWithoutProclamationInput
|
||||||
@@ -764,7 +718,6 @@ export type ProclamationUncheckedCreateWithoutSubscriptionLevelInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
userProclamations?: Prisma.UserProclamationsUncheckedCreateNestedManyWithoutProclamationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,7 +752,6 @@ export type ProclamationCreateManySolfeggioFrequencyInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,7 +759,6 @@ export type ProclamationUpdateWithoutSolfeggioFrequencyInput = {
|
|||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
@@ -818,7 +769,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
@@ -828,7 +778,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,7 +786,6 @@ export type ProclamationCreateManyCategoryInput = {
|
|||||||
name: string
|
name: string
|
||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
price?: number
|
|
||||||
subscriptionLevelId?: number
|
subscriptionLevelId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,7 +793,6 @@ export type ProclamationUpdateWithoutCategoryInput = {
|
|||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
subscriptionLevel?: Prisma.SubscriptionLevelUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
@@ -856,7 +803,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
@@ -866,7 +812,6 @@ 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
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
subscriptionLevelId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,14 +821,12 @@ export type ProclamationCreateManySubscriptionLevelInput = {
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationUpdateWithoutSubscriptionLevelInput = {
|
export type ProclamationUpdateWithoutSubscriptionLevelInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
solfeggioFrequency?: Prisma.SolfeggioFrequencyUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
category?: Prisma.ProclamationCategoryUpdateOneRequiredWithoutProclamationsNestedInput
|
||||||
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUpdateManyWithoutProclamationNestedInput
|
||||||
@@ -895,7 +838,6 @@ export type ProclamationUncheckedUpdateWithoutSubscriptionLevelInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
userProclamations?: Prisma.UserProclamationsUncheckedUpdateManyWithoutProclamationNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,7 +847,6 @@ export type ProclamationUncheckedUpdateManyWithoutSubscriptionLevelInput = {
|
|||||||
content?: Prisma.StringFieldUpdateOperationsInput | string
|
content?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
solfeggioFrequencyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
categoryId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.FloatFieldUpdateOperationsInput | number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -945,7 +886,6 @@ export type ProclamationSelect<ExtArgs extends runtime.Types.Extensions.Internal
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
price?: boolean
|
|
||||||
subscriptionLevelId?: boolean
|
subscriptionLevelId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
@@ -960,7 +900,6 @@ export type ProclamationSelectCreateManyAndReturn<ExtArgs extends runtime.Types.
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
price?: boolean
|
|
||||||
subscriptionLevelId?: boolean
|
subscriptionLevelId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
@@ -973,7 +912,6 @@ export type ProclamationSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
price?: boolean
|
|
||||||
subscriptionLevelId?: boolean
|
subscriptionLevelId?: boolean
|
||||||
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
solfeggioFrequency?: boolean | Prisma.SolfeggioFrequencyDefaultArgs<ExtArgs>
|
||||||
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
category?: boolean | Prisma.ProclamationCategoryDefaultArgs<ExtArgs>
|
||||||
@@ -986,11 +924,10 @@ export type ProclamationSelectScalar = {
|
|||||||
content?: boolean
|
content?: boolean
|
||||||
solfeggioFrequencyId?: boolean
|
solfeggioFrequencyId?: boolean
|
||||||
categoryId?: boolean
|
categoryId?: boolean
|
||||||
price?: boolean
|
|
||||||
subscriptionLevelId?: boolean
|
subscriptionLevelId?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProclamationOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "content" | "solfeggioFrequencyId" | "categoryId" | "price" | "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" | "subscriptionLevelId", 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>
|
||||||
@@ -1023,7 +960,6 @@ export type $ProclamationPayload<ExtArgs extends runtime.Types.Extensions.Intern
|
|||||||
content: string
|
content: string
|
||||||
solfeggioFrequencyId: number
|
solfeggioFrequencyId: number
|
||||||
categoryId: number
|
categoryId: number
|
||||||
price: number
|
|
||||||
subscriptionLevelId: number
|
subscriptionLevelId: number
|
||||||
}, ExtArgs["result"]["proclamation"]>
|
}, ExtArgs["result"]["proclamation"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
@@ -1457,7 +1393,6 @@ 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 price: Prisma.FieldRef<"Proclamation", 'Float'>
|
|
||||||
readonly subscriptionLevelId: Prisma.FieldRef<"Proclamation", 'Int'>
|
readonly subscriptionLevelId: Prisma.FieldRef<"Proclamation", 'Int'>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1655,6 +1590,11 @@ export type ProclamationFindManyArgs<ExtArgs extends runtime.Types.Extensions.In
|
|||||||
* Skip the first `n` Proclamations.
|
* Skip the first `n` Proclamations.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Proclamations.
|
||||||
|
*/
|
||||||
distinct?: Prisma.ProclamationScalarFieldEnum | Prisma.ProclamationScalarFieldEnum[]
|
distinct?: Prisma.ProclamationScalarFieldEnum | Prisma.ProclamationScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export type ProclamationCategoryGroupByOutputType = {
|
|||||||
_max: ProclamationCategoryMaxAggregateOutputType | null
|
_max: ProclamationCategoryMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProclamationCategoryGroupByPayload<T extends ProclamationCategoryGroupByArgs> = Prisma.PrismaPromise<
|
export type GetProclamationCategoryGroupByPayload<T extends ProclamationCategoryGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<ProclamationCategoryGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<ProclamationCategoryGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1297,6 +1297,11 @@ export type ProclamationCategoryFindManyArgs<ExtArgs extends runtime.Types.Exten
|
|||||||
* Skip the first `n` ProclamationCategories.
|
* Skip the first `n` ProclamationCategories.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of ProclamationCategories.
|
||||||
|
*/
|
||||||
distinct?: Prisma.ProclamationCategoryScalarFieldEnum | Prisma.ProclamationCategoryScalarFieldEnum[]
|
distinct?: Prisma.ProclamationCategoryScalarFieldEnum | Prisma.ProclamationCategoryScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type ProfileMinAggregateOutputType = {
|
|||||||
firstName: string | null
|
firstName: string | null
|
||||||
lastName: string | null
|
lastName: string | null
|
||||||
phoneNumber: string | null
|
phoneNumber: string | null
|
||||||
|
ghlContactId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileMaxAggregateOutputType = {
|
export type ProfileMaxAggregateOutputType = {
|
||||||
@@ -38,6 +39,7 @@ export type ProfileMaxAggregateOutputType = {
|
|||||||
firstName: string | null
|
firstName: string | null
|
||||||
lastName: string | null
|
lastName: string | null
|
||||||
phoneNumber: string | null
|
phoneNumber: string | null
|
||||||
|
ghlContactId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCountAggregateOutputType = {
|
export type ProfileCountAggregateOutputType = {
|
||||||
@@ -46,6 +48,7 @@ export type ProfileCountAggregateOutputType = {
|
|||||||
firstName: number
|
firstName: number
|
||||||
lastName: number
|
lastName: number
|
||||||
phoneNumber: number
|
phoneNumber: number
|
||||||
|
ghlContactId: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ export type ProfileMinAggregateInputType = {
|
|||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
|
ghlContactId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileMaxAggregateInputType = {
|
export type ProfileMaxAggregateInputType = {
|
||||||
@@ -64,6 +68,7 @@ export type ProfileMaxAggregateInputType = {
|
|||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
|
ghlContactId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCountAggregateInputType = {
|
export type ProfileCountAggregateInputType = {
|
||||||
@@ -72,6 +77,7 @@ export type ProfileCountAggregateInputType = {
|
|||||||
firstName?: true
|
firstName?: true
|
||||||
lastName?: true
|
lastName?: true
|
||||||
phoneNumber?: true
|
phoneNumber?: true
|
||||||
|
ghlContactId?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,12 +159,13 @@ export type ProfileGroupByOutputType = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId: string | null
|
||||||
_count: ProfileCountAggregateOutputType | null
|
_count: ProfileCountAggregateOutputType | null
|
||||||
_min: ProfileMinAggregateOutputType | null
|
_min: ProfileMinAggregateOutputType | null
|
||||||
_max: ProfileMaxAggregateOutputType | null
|
_max: ProfileMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProfileGroupByPayload<T extends ProfileGroupByArgs> = Prisma.PrismaPromise<
|
export type GetProfileGroupByPayload<T extends ProfileGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<ProfileGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<ProfileGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -182,6 +189,7 @@ export type ProfileWhereInput = {
|
|||||||
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
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
||||||
addresses?: Prisma.AddressListRelationFilter
|
addresses?: Prisma.AddressListRelationFilter
|
||||||
}
|
}
|
||||||
@@ -192,6 +200,7 @@ export type ProfileOrderByWithRelationInput = {
|
|||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
|
ghlContactId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
user?: Prisma.UserOrderByWithRelationInput
|
user?: Prisma.UserOrderByWithRelationInput
|
||||||
addresses?: Prisma.AddressOrderByRelationAggregateInput
|
addresses?: Prisma.AddressOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
@@ -205,6 +214,7 @@ export type ProfileWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
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
|
||||||
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
|
||||||
addresses?: Prisma.AddressListRelationFilter
|
addresses?: Prisma.AddressListRelationFilter
|
||||||
}, "id" | "userId">
|
}, "id" | "userId">
|
||||||
@@ -215,6 +225,7 @@ export type ProfileOrderByWithAggregationInput = {
|
|||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
|
ghlContactId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
_count?: Prisma.ProfileCountOrderByAggregateInput
|
_count?: Prisma.ProfileCountOrderByAggregateInput
|
||||||
_max?: Prisma.ProfileMaxOrderByAggregateInput
|
_max?: Prisma.ProfileMaxOrderByAggregateInput
|
||||||
_min?: Prisma.ProfileMinOrderByAggregateInput
|
_min?: Prisma.ProfileMinOrderByAggregateInput
|
||||||
@@ -229,6 +240,7 @@ export type ProfileScalarWhereWithAggregatesInput = {
|
|||||||
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
|
||||||
|
ghlContactId?: Prisma.StringNullableWithAggregatesFilter<"Profile"> | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCreateInput = {
|
export type ProfileCreateInput = {
|
||||||
@@ -236,6 +248,7 @@ export type ProfileCreateInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
||||||
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
||||||
}
|
}
|
||||||
@@ -246,6 +259,7 @@ export type ProfileUncheckedCreateInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
addresses?: Prisma.AddressUncheckedCreateNestedManyWithoutProfileInput
|
addresses?: Prisma.AddressUncheckedCreateNestedManyWithoutProfileInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +268,7 @@ export type ProfileUpdateInput = {
|
|||||||
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
|
||||||
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
||||||
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
@@ -264,6 +279,7 @@ export type ProfileUncheckedUpdateInput = {
|
|||||||
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
|
||||||
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +289,7 @@ export type ProfileCreateManyInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUpdateManyMutationInput = {
|
export type ProfileUpdateManyMutationInput = {
|
||||||
@@ -280,6 +297,7 @@ export type ProfileUpdateManyMutationInput = {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileUncheckedUpdateManyInput = {
|
export type ProfileUncheckedUpdateManyInput = {
|
||||||
@@ -288,6 +306,7 @@ export type ProfileUncheckedUpdateManyInput = {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileNullableScalarRelationFilter = {
|
export type ProfileNullableScalarRelationFilter = {
|
||||||
@@ -301,6 +320,7 @@ export type ProfileCountOrderByAggregateInput = {
|
|||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
|
ghlContactId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileMaxOrderByAggregateInput = {
|
export type ProfileMaxOrderByAggregateInput = {
|
||||||
@@ -309,6 +329,7 @@ export type ProfileMaxOrderByAggregateInput = {
|
|||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
|
ghlContactId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileMinOrderByAggregateInput = {
|
export type ProfileMinOrderByAggregateInput = {
|
||||||
@@ -317,6 +338,7 @@ export type ProfileMinOrderByAggregateInput = {
|
|||||||
firstName?: Prisma.SortOrder
|
firstName?: Prisma.SortOrder
|
||||||
lastName?: Prisma.SortOrder
|
lastName?: Prisma.SortOrder
|
||||||
phoneNumber?: Prisma.SortOrder
|
phoneNumber?: Prisma.SortOrder
|
||||||
|
ghlContactId?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileScalarRelationFilter = {
|
export type ProfileScalarRelationFilter = {
|
||||||
@@ -375,6 +397,7 @@ export type ProfileCreateWithoutUserInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
addresses?: Prisma.AddressCreateNestedManyWithoutProfileInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +406,7 @@ export type ProfileUncheckedCreateWithoutUserInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
addresses?: Prisma.AddressUncheckedCreateNestedManyWithoutProfileInput
|
addresses?: Prisma.AddressUncheckedCreateNestedManyWithoutProfileInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +431,7 @@ export type ProfileUpdateWithoutUserInput = {
|
|||||||
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
|
||||||
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
addresses?: Prisma.AddressUpdateManyWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,6 +440,7 @@ export type ProfileUncheckedUpdateWithoutUserInput = {
|
|||||||
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
|
||||||
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
addresses?: Prisma.AddressUncheckedUpdateManyWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,6 +449,7 @@ export type ProfileCreateWithoutAddressesInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
user: Prisma.UserCreateNestedOneWithoutProfileInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,6 +459,7 @@ export type ProfileUncheckedCreateWithoutAddressesInput = {
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProfileCreateOrConnectWithoutAddressesInput = {
|
export type ProfileCreateOrConnectWithoutAddressesInput = {
|
||||||
@@ -455,6 +483,7 @@ export type ProfileUpdateWithoutAddressesInput = {
|
|||||||
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
|
||||||
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
user?: Prisma.UserUpdateOneRequiredWithoutProfileNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +493,7 @@ export type ProfileUncheckedUpdateWithoutAddressesInput = {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -503,6 +533,7 @@ export type ProfileSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
||||||
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ProfileCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
@@ -514,6 +545,7 @@ export type ProfileSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
|
|
||||||
@@ -523,6 +555,7 @@ export type ProfileSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: boolean
|
||||||
|
ghlContactId?: boolean
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
|
|
||||||
@@ -532,9 +565,10 @@ export type ProfileSelectScalar = {
|
|||||||
firstName?: boolean
|
firstName?: boolean
|
||||||
lastName?: boolean
|
lastName?: boolean
|
||||||
phoneNumber?: boolean
|
phoneNumber?: 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", ExtArgs["result"]["profile"]>
|
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 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.UserDefaultArgs<ExtArgs>
|
||||||
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
addresses?: boolean | Prisma.Profile$addressesArgs<ExtArgs>
|
||||||
@@ -559,6 +593,7 @@ export type $ProfilePayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
|||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
|
ghlContactId: string | null
|
||||||
}, ExtArgs["result"]["profile"]>
|
}, ExtArgs["result"]["profile"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
}
|
}
|
||||||
@@ -989,6 +1024,7 @@ export interface ProfileFieldRefs {
|
|||||||
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'>
|
||||||
|
readonly ghlContactId: Prisma.FieldRef<"Profile", 'String'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1185,6 +1221,11 @@ export type ProfileFindManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
|
|||||||
* Skip the first `n` Profiles.
|
* Skip the first `n` Profiles.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Profiles.
|
||||||
|
*/
|
||||||
distinct?: Prisma.ProfileScalarFieldEnum | Prisma.ProfileScalarFieldEnum[]
|
distinct?: Prisma.ProfileScalarFieldEnum | Prisma.ProfileScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export type SessionGroupByOutputType = {
|
|||||||
_max: SessionMaxAggregateOutputType | null
|
_max: SessionMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetSessionGroupByPayload<T extends SessionGroupByArgs> = Prisma.PrismaPromise<
|
export type GetSessionGroupByPayload<T extends SessionGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<SessionGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<SessionGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1227,6 +1227,11 @@ export type SessionFindManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
|
|||||||
* Skip the first `n` Sessions.
|
* Skip the first `n` Sessions.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Sessions.
|
||||||
|
*/
|
||||||
distinct?: Prisma.SessionScalarFieldEnum | Prisma.SessionScalarFieldEnum[]
|
distinct?: Prisma.SessionScalarFieldEnum | Prisma.SessionScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export type SolfeggioFrequencyGroupByOutputType = {
|
|||||||
_max: SolfeggioFrequencyMaxAggregateOutputType | null
|
_max: SolfeggioFrequencyMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetSolfeggioFrequencyGroupByPayload<T extends SolfeggioFrequencyGroupByArgs> = Prisma.PrismaPromise<
|
export type GetSolfeggioFrequencyGroupByPayload<T extends SolfeggioFrequencyGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<SolfeggioFrequencyGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<SolfeggioFrequencyGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -203,7 +203,7 @@ export type SolfeggioFrequencyWhereInput = {
|
|||||||
NOT?: Prisma.SolfeggioFrequencyWhereInput | Prisma.SolfeggioFrequencyWhereInput[]
|
NOT?: Prisma.SolfeggioFrequencyWhereInput | Prisma.SolfeggioFrequencyWhereInput[]
|
||||||
id?: Prisma.IntFilter<"SolfeggioFrequency"> | number
|
id?: Prisma.IntFilter<"SolfeggioFrequency"> | number
|
||||||
name?: Prisma.StringFilter<"SolfeggioFrequency"> | string
|
name?: Prisma.StringFilter<"SolfeggioFrequency"> | string
|
||||||
value?: Prisma.FloatFilter<"SolfeggioFrequency"> | number
|
value?: Prisma.IntFilter<"SolfeggioFrequency"> | number
|
||||||
proclamations?: Prisma.ProclamationListRelationFilter
|
proclamations?: Prisma.ProclamationListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ export type SolfeggioFrequencyScalarWhereWithAggregatesInput = {
|
|||||||
NOT?: Prisma.SolfeggioFrequencyScalarWhereWithAggregatesInput | Prisma.SolfeggioFrequencyScalarWhereWithAggregatesInput[]
|
NOT?: Prisma.SolfeggioFrequencyScalarWhereWithAggregatesInput | Prisma.SolfeggioFrequencyScalarWhereWithAggregatesInput[]
|
||||||
id?: Prisma.IntWithAggregatesFilter<"SolfeggioFrequency"> | number
|
id?: Prisma.IntWithAggregatesFilter<"SolfeggioFrequency"> | number
|
||||||
name?: Prisma.StringWithAggregatesFilter<"SolfeggioFrequency"> | string
|
name?: Prisma.StringWithAggregatesFilter<"SolfeggioFrequency"> | string
|
||||||
value?: Prisma.FloatWithAggregatesFilter<"SolfeggioFrequency"> | number
|
value?: Prisma.IntWithAggregatesFilter<"SolfeggioFrequency"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SolfeggioFrequencyCreateInput = {
|
export type SolfeggioFrequencyCreateInput = {
|
||||||
@@ -259,14 +259,14 @@ export type SolfeggioFrequencyUncheckedCreateInput = {
|
|||||||
|
|
||||||
export type SolfeggioFrequencyUpdateInput = {
|
export type SolfeggioFrequencyUpdateInput = {
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
proclamations?: Prisma.ProclamationUpdateManyWithoutSolfeggioFrequencyNestedInput
|
proclamations?: Prisma.ProclamationUpdateManyWithoutSolfeggioFrequencyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SolfeggioFrequencyUncheckedUpdateInput = {
|
export type SolfeggioFrequencyUncheckedUpdateInput = {
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyNestedInput
|
proclamations?: Prisma.ProclamationUncheckedUpdateManyWithoutSolfeggioFrequencyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,13 +278,13 @@ export type SolfeggioFrequencyCreateManyInput = {
|
|||||||
|
|
||||||
export type SolfeggioFrequencyUpdateManyMutationInput = {
|
export type SolfeggioFrequencyUpdateManyMutationInput = {
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SolfeggioFrequencyUncheckedUpdateManyInput = {
|
export type SolfeggioFrequencyUncheckedUpdateManyInput = {
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SolfeggioFrequencyScalarRelationFilter = {
|
export type SolfeggioFrequencyScalarRelationFilter = {
|
||||||
@@ -363,13 +363,13 @@ export type SolfeggioFrequencyUpdateToOneWithWhereWithoutProclamationsInput = {
|
|||||||
|
|
||||||
export type SolfeggioFrequencyUpdateWithoutProclamationsInput = {
|
export type SolfeggioFrequencyUpdateWithoutProclamationsInput = {
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SolfeggioFrequencyUncheckedUpdateWithoutProclamationsInput = {
|
export type SolfeggioFrequencyUncheckedUpdateWithoutProclamationsInput = {
|
||||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
value?: Prisma.FloatFieldUpdateOperationsInput | number
|
value?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -872,7 +872,7 @@ export interface Prisma__SolfeggioFrequencyClient<T, Null = never, ExtArgs exten
|
|||||||
export interface SolfeggioFrequencyFieldRefs {
|
export interface SolfeggioFrequencyFieldRefs {
|
||||||
readonly id: Prisma.FieldRef<"SolfeggioFrequency", 'Int'>
|
readonly id: Prisma.FieldRef<"SolfeggioFrequency", 'Int'>
|
||||||
readonly name: Prisma.FieldRef<"SolfeggioFrequency", 'String'>
|
readonly name: Prisma.FieldRef<"SolfeggioFrequency", 'String'>
|
||||||
readonly value: Prisma.FieldRef<"SolfeggioFrequency", 'Float'>
|
readonly value: Prisma.FieldRef<"SolfeggioFrequency", 'Int'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1069,6 +1069,11 @@ export type SolfeggioFrequencyFindManyArgs<ExtArgs extends runtime.Types.Extensi
|
|||||||
* Skip the first `n` SolfeggioFrequencies.
|
* Skip the first `n` SolfeggioFrequencies.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of SolfeggioFrequencies.
|
||||||
|
*/
|
||||||
distinct?: Prisma.SolfeggioFrequencyScalarFieldEnum | Prisma.SolfeggioFrequencyScalarFieldEnum[]
|
distinct?: Prisma.SolfeggioFrequencyScalarFieldEnum | Prisma.SolfeggioFrequencyScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export type SubscriptionLevelGroupByOutputType = {
|
|||||||
_max: SubscriptionLevelMaxAggregateOutputType | null
|
_max: SubscriptionLevelMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetSubscriptionLevelGroupByPayload<T extends SubscriptionLevelGroupByArgs> = Prisma.PrismaPromise<
|
export type GetSubscriptionLevelGroupByPayload<T extends SubscriptionLevelGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<SubscriptionLevelGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<SubscriptionLevelGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1063,6 +1063,11 @@ export type SubscriptionLevelFindManyArgs<ExtArgs extends runtime.Types.Extensio
|
|||||||
* Skip the first `n` SubscriptionLevels.
|
* Skip the first `n` SubscriptionLevels.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of SubscriptionLevels.
|
||||||
|
*/
|
||||||
distinct?: Prisma.SubscriptionLevelScalarFieldEnum | Prisma.SubscriptionLevelScalarFieldEnum[]
|
distinct?: Prisma.SubscriptionLevelScalarFieldEnum | Prisma.SubscriptionLevelScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ export type UserGroupByOutputType = {
|
|||||||
_max: UserMaxAggregateOutputType | null
|
_max: UserMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserGroupByPayload<T extends UserGroupByArgs> = Prisma.PrismaPromise<
|
export type GetUserGroupByPayload<T extends UserGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<UserGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<UserGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1883,6 +1883,11 @@ export type UserFindManyArgs<ExtArgs extends runtime.Types.Extensions.InternalAr
|
|||||||
* Skip the first `n` Users.
|
* Skip the first `n` Users.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Users.
|
||||||
|
*/
|
||||||
distinct?: Prisma.UserScalarFieldEnum | Prisma.UserScalarFieldEnum[]
|
distinct?: Prisma.UserScalarFieldEnum | Prisma.UserScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export type UserProclamationsGroupByOutputType = {
|
|||||||
_max: UserProclamationsMaxAggregateOutputType | null
|
_max: UserProclamationsMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserProclamationsGroupByPayload<T extends UserProclamationsGroupByArgs> = Prisma.PrismaPromise<
|
export type GetUserProclamationsGroupByPayload<T extends UserProclamationsGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<UserProclamationsGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<UserProclamationsGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1207,6 +1207,11 @@ export type UserProclamationsFindManyArgs<ExtArgs extends runtime.Types.Extensio
|
|||||||
* Skip the first `n` UserProclamations.
|
* Skip the first `n` UserProclamations.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of UserProclamations.
|
||||||
|
*/
|
||||||
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
distinct?: Prisma.UserProclamationsScalarFieldEnum | Prisma.UserProclamationsScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export type UserRoleGroupByOutputType = {
|
|||||||
_max: UserRoleMaxAggregateOutputType | null
|
_max: UserRoleMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserRoleGroupByPayload<T extends UserRoleGroupByArgs> = Prisma.PrismaPromise<
|
export type GetUserRoleGroupByPayload<T extends UserRoleGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<UserRoleGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<UserRoleGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -1041,6 +1041,11 @@ export type UserRoleFindManyArgs<ExtArgs extends runtime.Types.Extensions.Intern
|
|||||||
* Skip the first `n` UserRoles.
|
* Skip the first `n` UserRoles.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of UserRoles.
|
||||||
|
*/
|
||||||
distinct?: Prisma.UserRoleScalarFieldEnum | Prisma.UserRoleScalarFieldEnum[]
|
distinct?: Prisma.UserRoleScalarFieldEnum | Prisma.UserRoleScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export type VerificationGroupByOutputType = {
|
|||||||
_max: VerificationMaxAggregateOutputType | null
|
_max: VerificationMaxAggregateOutputType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetVerificationGroupByPayload<T extends VerificationGroupByArgs> = Prisma.PrismaPromise<
|
export type GetVerificationGroupByPayload<T extends VerificationGroupByArgs> = Prisma.PrismaPromise<
|
||||||
Array<
|
Array<
|
||||||
Prisma.PickEnumerable<VerificationGroupByOutputType, T['by']> &
|
Prisma.PickEnumerable<VerificationGroupByOutputType, T['by']> &
|
||||||
{
|
{
|
||||||
@@ -982,6 +982,11 @@ export type VerificationFindManyArgs<ExtArgs extends runtime.Types.Extensions.In
|
|||||||
* Skip the first `n` Verifications.
|
* Skip the first `n` Verifications.
|
||||||
*/
|
*/
|
||||||
skip?: number
|
skip?: number
|
||||||
|
/**
|
||||||
|
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
|
||||||
|
*
|
||||||
|
* Filter by unique combinations of Verifications.
|
||||||
|
*/
|
||||||
distinct?: Prisma.VerificationScalarFieldEnum | Prisma.VerificationScalarFieldEnum[]
|
distinct?: Prisma.VerificationScalarFieldEnum | Prisma.VerificationScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-43
@@ -10,62 +10,66 @@
|
|||||||
// ✔ Delete a card
|
// ✔ Delete a card
|
||||||
// ✔ Issue a refund
|
// ✔ Issue a refund
|
||||||
|
|
||||||
import type { Decimal } from "@prisma/client/runtime/client";
|
import type { Decimal } from '@prisma/client/runtime/client';
|
||||||
|
|
||||||
export interface CreditDebitCard {
|
export interface CreditDebitCard {
|
||||||
first_name: string,
|
first_name: string;
|
||||||
last_name: string,
|
last_name: string;
|
||||||
card_number: number,
|
card_number: number;
|
||||||
expiration_month: number,
|
expiration_month: number;
|
||||||
expiration_year: number,
|
expiration_year: number;
|
||||||
security_code: number,
|
security_code: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function chargeCreditDebitCard (card: CreditDebitCard, amount: Decimal): Promise<null> {
|
export async function chargeCreditDebitCard(card: CreditDebitCard, amount: Decimal): Promise<null> {
|
||||||
/**
|
/**
|
||||||
* chargeCreditDebitCard attempts to request payment from a valid card,
|
* chargeCreditDebitCard attempts to request payment from a valid card,
|
||||||
* for the amount specified.
|
* for the amount specified.
|
||||||
*/
|
*/
|
||||||
console.log(`charged card ${card} for $${amount}`);
|
console.log(`charged card ${card} for $${amount}`);
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function chargedStoredCard(cardNumber: number, profileID: string, amount: Decimal): Promise<null> {
|
export async function chargedStoredCard(
|
||||||
/**
|
cardNumber: number,
|
||||||
* chargedStoredCard attempts to request payment from a valid card,
|
profileID: string,
|
||||||
* for the amount specified.
|
amount: Decimal
|
||||||
* The card is identified by the last four digits of the card
|
): Promise<null> {
|
||||||
* number, and the profile ID.
|
/**
|
||||||
* NOTE: This should be enough to identify a saved card, but the
|
* chargedStoredCard attempts to request payment from a valid card,
|
||||||
* function signature may change in the future.
|
* for the amount specified.
|
||||||
*/
|
* The card is identified by the last four digits of the card
|
||||||
console.log(`charged ${profileID}'s card ${cardNumber} for ${cardNumber} $${amount}`);
|
* number, and the profile ID.
|
||||||
return null;
|
* NOTE: This should be enough to identify a saved card, but the
|
||||||
|
* function signature may change in the future.
|
||||||
|
*/
|
||||||
|
console.log(`charged ${profileID}'s card ${cardNumber} for ${cardNumber} $${amount}`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCardFromProfile(cardNumber: number, profileID: string): Promise<null> {
|
export async function deleteCardFromProfile(cardNumber: number, profileID: string): Promise<null> {
|
||||||
/**
|
/**
|
||||||
* deleteCardFromProfile deletes a stored card on a user's profile.
|
* deleteCardFromProfile deletes a stored card on a user's profile.
|
||||||
*/
|
*/
|
||||||
console.log(`Deleted card ${cardNumber} from ${profileID}'s profile.`);
|
console.log(`Deleted card ${cardNumber} from ${profileID}'s profile.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function storeCardToProfile(card: CreditDebitCard, profileID: string): Promise<null> {
|
export async function storeCardToProfile(card: CreditDebitCard, profileID: string): Promise<null> {
|
||||||
/**
|
/**
|
||||||
* storeCardToProfile stores a new card to a user's profile
|
* storeCardToProfile stores a new card to a user's profile
|
||||||
* for easy re-use later.
|
* for easy re-use later.
|
||||||
*/
|
*/
|
||||||
console.log(`Deleted card ${card.card_number} from ${profileID}'s profile.`);
|
console.log(`Deleted card ${card.card_number} from ${profileID}'s profile.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestRefund(card: CreditDebitCard, amount: Decimal): Promise<null> {
|
export async function requestRefund(card: CreditDebitCard, amount: Decimal): Promise<null> {
|
||||||
/**
|
/**
|
||||||
* requestRefund attempts to process a refund for the amount to the
|
* requestRefund attempts to process a refund for the amount to the
|
||||||
* provided card.
|
* provided card.
|
||||||
*/
|
*/
|
||||||
console.log(`Refunded ${amount} to ${card.card_number}`);
|
console.log(`Refunded ${amount} to ${card.card_number}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { BETTER_AUTH_URL } from '$env/static/private';
|
import { BETTER_AUTH_URL } from '$env/static/private';
|
||||||
import { sendMail } from './resend';
|
import { sendMail } from './resend';
|
||||||
|
|
||||||
export async function sendVerificationEmail(options: {
|
export async function sendVerificationEmail(options: { email: string; token: string }) {
|
||||||
email: string;
|
|
||||||
token: string;
|
|
||||||
}) {
|
|
||||||
const verificationUrl = `${BETTER_AUTH_URL}/api/auth/verify-email?token=${options.token}&callbackURL=/login?verified=true`;
|
const verificationUrl = `${BETTER_AUTH_URL}/api/auth/verify-email?token=${options.token}&callbackURL=/login?verified=true`;
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
|
|||||||
+18
-18
@@ -31,26 +31,26 @@ export const resend = new Resend(RESEND_API_KEY);
|
|||||||
* @throws {Error} If the Resend API call fails.
|
* @throws {Error} If the Resend API call fails.
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
|
||||||
const result = await resend.emails.send({
|
const result = await resend.emails.send({
|
||||||
from: EMAIL_FROM,
|
from: EMAIL_FROM,
|
||||||
to: [to],
|
to: [to],
|
||||||
subject: subject ?? 'Template Email',
|
subject: subject ?? 'Template Email',
|
||||||
template: {
|
template: {
|
||||||
id: templateId ?? RESEND_TEMPLATE_ID,
|
id: templateId ?? RESEND_TEMPLATE_ID,
|
||||||
variables: data ?? {}
|
variables: data ?? {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error(result.error.message);
|
throw new Error(result.error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
+102
-81
@@ -1,8 +1,8 @@
|
|||||||
# svelte-admin
|
# svelte-admin
|
||||||
|
|
||||||
A config-driven admin panel framework for SvelteKit + Prisma. Define your models once in a
|
A config-driven admin panel framework for SvelteKit + Prisma. Define your models once in a
|
||||||
registry and get full CRUD UI with dynamic routing, role-based access control, searchable
|
registry and get full CRUD UI with per-model static pages, role-based access control, searchable
|
||||||
dropdowns, and related-record panels — no per-model route files needed.
|
tables, FK dropdowns, TipTap editor for rich text, and related-record panels.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -13,27 +13,30 @@ src/lib/svelte-admin/ # Framework core
|
|||||||
├── roles.ts # Role hierarchy and access helpers
|
├── roles.ts # Role hierarchy and access helpers
|
||||||
├── prisma-delegate.ts # Dynamic Prisma delegate lookup
|
├── prisma-delegate.ts # Dynamic Prisma delegate lookup
|
||||||
├── introspect.ts # Field label generation and dot-notation resolution
|
├── introspect.ts # Field label generation and dot-notation resolution
|
||||||
├── form-data.ts # FormData → typed object parser
|
├── form-data.ts # FormData -> typed object parser
|
||||||
|
├── crud.ts # CRUD action factory (load, upsert, delete)
|
||||||
└── index.ts # Barrel exports
|
└── index.ts # Barrel exports
|
||||||
|
|
||||||
src/routes/(protected)/admin/ # Dynamic admin routes
|
src/routes/(protected)/admin/ # Per-model admin pages (no dynamic [model] routing)
|
||||||
├── +layout.server.ts # Role-based access guard
|
├── +layout.server.ts # Role-based access guard
|
||||||
├── +layout.svelte # Layout shell
|
├── +layout.svelte # Layout shell
|
||||||
├── +page.server.ts # Admin home (model grid)
|
├── +page.server.ts # Admin home (model grid)
|
||||||
├── +page.svelte # Admin home UI
|
├── +page.svelte # Admin home UI
|
||||||
├── [model]/
|
├── users/ # Each model gets its own page directory
|
||||||
│ ├── +page.server.ts # List view + create action
|
├── profiles/
|
||||||
│ ├── +page.svelte # List view UI
|
├── addresses/
|
||||||
│ └── [id]/
|
├── user-roles/
|
||||||
│ ├── +page.server.ts # Edit view + update/delete actions
|
├── proclamations/
|
||||||
│ └── +page.svelte # Edit view UI
|
├── solfeggio-frequencies/
|
||||||
|
├── proclamation-categories/
|
||||||
|
├── subscription-levels/
|
||||||
|
└── license-keys/ # listOnly — no create/edit modal
|
||||||
|
|
||||||
src/lib/components/admin/ # Reusable admin UI components
|
src/lib/components/admin/ # Reusable admin UI components
|
||||||
├── AdminTable.svelte # Sortable, searchable data table
|
├── AdminTable.svelte # Sortable, searchable data table
|
||||||
├── AdminForm.svelte # Dynamic form with type-aware inputs
|
├── AdminForm.svelte # Dynamic form with type-aware inputs
|
||||||
├── ComboBox.svelte # Searchable dropdown for FK fields
|
├── AdminModal.svelte # Modal overlay for CRUD forms
|
||||||
├── RelatedHasOne.svelte # Display panel for 1:1 related records
|
├── TipTap.svelte # Rich-text editor (in components/tiptap/)
|
||||||
└── RelatedHasMany.svelte # Display table for 1:N related records
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding a New Model
|
## Adding a New Model
|
||||||
@@ -74,10 +77,11 @@ src/lib/components/admin/ # Reusable admin UI components
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Done.** Visit `/admin/widgets`. The dynamic `[model]` routes handle list, create, edit,
|
3. **Create the page directory** `src/routes/(protected)/admin/widgets/` with:
|
||||||
and delete automatically.
|
- `+page.server.ts` — load records + form actions using `crud.ts` helpers
|
||||||
|
- `+page.svelte` — render AdminTable + AdminModal with AdminForm
|
||||||
|
|
||||||
No new route files, no new components. The registry is the only thing you touch.
|
4. **Done.** Visit `/admin/widgets`. The page handles list, create, edit via modal.
|
||||||
|
|
||||||
## Configuration Reference
|
## Configuration Reference
|
||||||
|
|
||||||
@@ -95,25 +99,26 @@ No new route files, no new components. The registry is the only thing you touch.
|
|||||||
| `searchFields` | `string[]` | Yes | Fields searched by the client-side search filter. |
|
| `searchFields` | `string[]` | Yes | Fields searched by the client-side search filter. |
|
||||||
| `formFields` | `string[]` | Yes | Fields rendered in create/edit forms, in display order. |
|
| `formFields` | `string[]` | Yes | Fields rendered in create/edit forms, in display order. |
|
||||||
| `readonlyFields` | `string[]` | No | Fields shown as read-only on the edit page. |
|
| `readonlyFields` | `string[]` | No | Fields shown as read-only on the edit page. |
|
||||||
| `listOnly` | `boolean` | No | If `true`, suppresses edit links in the table. |
|
| `listOnly` | `boolean` | No | If `true`, suppresses edit links in the table and no modal. |
|
||||||
| `fieldTypes` | `Record<string, PrismaScalarType>` | Yes | Maps field names to scalar types. Controls input rendering and FormData parsing. |
|
| `fieldTypes` | `Record<string, PrismaScalarType>` | Yes | Maps field names to scalar types. Controls input rendering and FormData parsing. |
|
||||||
| `relationOptions` | `Record<string, RelationOptionConfig>` | No | FK fields that render as ComboBox dropdowns. |
|
| `relationOptions` | `Record<string, RelationOptionConfig>` | No | FK fields that render as `<select>` dropdowns. |
|
||||||
| `relatedRecords` | `RelatedRecordConfig[]` | No | Has-one/has-many panels shown on edit pages. |
|
| `relatedRecords` | `RelatedRecordConfig[]` | No | Has-one/has-many panels shown on edit pages (display only via related records table). |
|
||||||
| `accessFilter` | `(viewerRoleId: number) => where \| undefined` | No | Returns a Prisma `where` clause to filter records by viewer role. `undefined` = no filter. |
|
| `accessFilter` | `(viewerRoleId: number) => where \| undefined` | No | Returns a Prisma `where` clause to filter records by viewer role. `undefined` = no filter. |
|
||||||
|
|
||||||
### PrismaScalarType
|
### PrismaScalarType
|
||||||
|
|
||||||
`'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime'`
|
`'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Tiptap'`
|
||||||
|
|
||||||
Controls both form input rendering and FormData parsing:
|
Controls both form input rendering and FormData parsing:
|
||||||
|
|
||||||
| Type | HTML Input | Parse Logic |
|
| Type | HTML Input | Parse Logic |
|
||||||
| ---------- | ----------------------------------- | ------------------------------ |
|
| ---------- | ------------------------------------- | ------------------------------ |
|
||||||
| `String` | `<input type="text">` | `raw.toString()` |
|
| `String` | `<input type="text">` | `raw.toString()` |
|
||||||
| `Int` | `<input type="number" step="1">` | `parseInt()`, empty → `null` |
|
| `Int` | `<input type="number" step="1">` | `parseInt()`, empty -> `null` |
|
||||||
| `Float` | `<input type="number" step="0.01">` | `parseFloat()`, empty → `null` |
|
| `Float` | `<input type="number" step="0.01">` | `parseFloat()`, empty -> `null` |
|
||||||
| `Boolean` | `<input type="checkbox">` | `'on'` or `'true'` → `true` |
|
| `Boolean` | `<input type="checkbox">` | `'on'` or `'true'` -> `true` |
|
||||||
| `DateTime` | `<input type="datetime-local">` | `new Date()`, empty → `null` |
|
| `DateTime` | `<input type="datetime-local">` | `new Date()`, empty -> `null` |
|
||||||
|
| `Tiptap` | TipTap rich-text editor (hidden input)| `raw.toString()` |
|
||||||
|
|
||||||
### RelationOptionConfig
|
### RelationOptionConfig
|
||||||
|
|
||||||
@@ -142,14 +147,18 @@ Controls both form input rendering and FormData parsing:
|
|||||||
The `fieldTypes` mapping is the central mechanism connecting Prisma scalar types to HTML inputs
|
The `fieldTypes` mapping is the central mechanism connecting Prisma scalar types to HTML inputs
|
||||||
and form data parsing. Every field in `formFields` must have a corresponding entry in `fieldTypes`.
|
and form data parsing. Every field in `formFields` must have a corresponding entry in `fieldTypes`.
|
||||||
|
|
||||||
When a field also has a `relationOptions` entry, the framework renders a `ComboBox` (searchable
|
When a field also has a `relationOptions` entry, the framework renders a `<select>` dropdown
|
||||||
dropdown) instead of the default input for that type.
|
instead of the default input for that type. The options are fetched server-side by `crud.ts`
|
||||||
|
and passed to the client as `relationOptionsData`.
|
||||||
|
|
||||||
|
For `Tiptap` fields, the TipTap rich-text editor is rendered. The editor content is stored in a
|
||||||
|
hidden input and submitted with the form.
|
||||||
|
|
||||||
## Relations
|
## Relations
|
||||||
|
|
||||||
### Foreign Key Dropdowns (relationOptions)
|
### Foreign Key Dropdowns (relationOptions)
|
||||||
|
|
||||||
When a form field is a foreign key, add a `relationOptions` entry to render a searchable ComboBox:
|
When a form field is a foreign key, add a `relationOptions` entry to render a `<select>` dropdown:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
@@ -165,7 +174,8 @@ When a form field is a foreign key, add a `relationOptions` entry to render a se
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The route loader automatically queries the related model and passes options to the ComboBox.
|
The `crud.ts` `loadRecords` function automatically queries the related model and passes options
|
||||||
|
to the page as `relationOptionsData`.
|
||||||
|
|
||||||
### Eager Loading (include)
|
### Eager Loading (include)
|
||||||
|
|
||||||
@@ -200,9 +210,7 @@ relatedRecords: [
|
|||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
The `relationKey` must match a key in `include` so the data is actually loaded. `has-one` renders
|
The `relationKey` must match a key in `include` so the data is actually loaded.
|
||||||
a key-value panel via `RelatedHasOne`; `has-many` renders a table via `RelatedHasMany`. Both
|
|
||||||
support an optional `editSlug` to link to the related record's edit page.
|
|
||||||
|
|
||||||
## Access Control
|
## Access Control
|
||||||
|
|
||||||
@@ -228,23 +236,23 @@ Use `accessFilter` to restrict which records a viewer can see:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
accessFilter: (viewerRoleId: number) => {
|
accessFilter: (viewerRoleId: number) => {
|
||||||
if (viewerRoleId <= ROLES.SUPER) return undefined; // no filter
|
if (viewerRoleId <= ROLES.SUPER) return undefined;
|
||||||
if (viewerRoleId <= ROLES.ADMIN) {
|
if (viewerRoleId <= ROLES.ADMIN) {
|
||||||
return { roleId: { gte: ROLES.STAFF } }; // can't see other admins
|
return { roleId: { gte: ROLES.STAFF } };
|
||||||
}
|
}
|
||||||
return { roleId: { gt: ROLES.STAFF } }; // staff sees only regular users
|
return { roleId: { gt: ROLES.STAFF } };
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## How Dynamic Routing Works
|
## How Static Pages Work
|
||||||
|
|
||||||
1. User visits `/admin/proclamations` (or `/admin/proclamations/abc123`)
|
1. User visits `/admin/proclamations`
|
||||||
2. SvelteKit matches the `[model]` (and `[id]`) route params
|
2. SvelteKit matches the static route `/admin/proclamations/+page.server.ts`
|
||||||
3. `+page.server.ts` calls `getModelConfig(params.model)` to look up the config
|
3. `+page.server.ts` calls `getModelConfig('proclamations')` to look up the config
|
||||||
4. `getDelegate(config)` returns `prisma[lcfirst(config.modelName)]` (e.g., `prisma.proclamation`)
|
4. `loadRecords(config)` calls `prisma.proclamation.findMany()` with the config's `include` and `accessFilter`
|
||||||
5. The delegate runs `findMany` (list), `findUnique` (edit), `create`, `update`, or `delete`
|
5. For each FK field with a `relationOptions`, it also queries the related model for dropdown options
|
||||||
6. Data + config are passed to the Svelte page component
|
6. Data + config are passed to `+page.svelte`
|
||||||
7. Components (`AdminTable`, `AdminForm`, etc.) render based on config
|
7. The page renders `AdminTable` with records and `AdminModal` + `AdminForm` for create/edit
|
||||||
|
|
||||||
## Component API
|
## Component API
|
||||||
|
|
||||||
@@ -255,11 +263,13 @@ accessFilter: (viewerRoleId: number) => {
|
|||||||
| `records` | `Record<string, unknown>[]` | Data rows |
|
| `records` | `Record<string, unknown>[]` | Data rows |
|
||||||
| `listFields` | `string[]` | Column keys (dot-notation supported) |
|
| `listFields` | `string[]` | Column keys (dot-notation supported) |
|
||||||
| `searchFields` | `string[]` | Fields for client-side search |
|
| `searchFields` | `string[]` | Fields for client-side search |
|
||||||
| `modelSlug` | `string` | Used for building edit links |
|
| `fieldTypes` | `Record<string, PrismaScalarType>` | Type mapping for cell formatting |
|
||||||
| `listOnly` | `boolean?` | Suppress edit links |
|
| `singular` | `string` | Singular label for UI text |
|
||||||
|
| `onRowClick` | `(record) => void?` | Click handler for edit modal |
|
||||||
|
| `onNew` | `() => void?` | Click handler for create modal |
|
||||||
|
|
||||||
Features: client-side search, click-to-sort columns, auto-formatted cells (dates, booleans,
|
Features: client-side search, click-to-sort columns, auto-formatted cells (dates, booleans,
|
||||||
decimals), filtered count display.
|
decimals), empty state.
|
||||||
|
|
||||||
### AdminForm
|
### AdminForm
|
||||||
|
|
||||||
@@ -270,46 +280,31 @@ decimals), filtered count display.
|
|||||||
| `relationOptions` | `Record<string, RelationOptionConfig>?` | FK dropdown configs |
|
| `relationOptions` | `Record<string, RelationOptionConfig>?` | FK dropdown configs |
|
||||||
| `relationOptionsData` | `Record<string, { label; value }[]>?` | Loaded dropdown options |
|
| `relationOptionsData` | `Record<string, { label; value }[]>?` | Loaded dropdown options |
|
||||||
| `record` | `Record<string, unknown>?` | Pre-fill values (edit mode) |
|
| `record` | `Record<string, unknown>?` | Pre-fill values (edit mode) |
|
||||||
| `form` | `ActionData?` | Carries validation errors |
|
| `readonlyFields` | `string[]?` | Read-only field names |
|
||||||
| `action` | `string` | Form action (e.g., `'?/create'`) |
|
| `action` | `string` | Form action (e.g., `'?/upsert'`) |
|
||||||
| `submitLabel` | `string?` | Button text |
|
| `submitLabel` | `string?` | Button text |
|
||||||
|
|
||||||
### ComboBox
|
### AdminModal
|
||||||
|
|
||||||
| Prop | Type | Description |
|
| Prop | Type | Description |
|
||||||
| ------------- | -------------------- | ------------------ |
|
| --------- | ----------------- | ------------------------ |
|
||||||
| `id` | `string` | Element ID |
|
| `open` | `boolean` | Show/hide modal |
|
||||||
| `name` | `string` | Form field name |
|
| `title` | `string` | Modal heading |
|
||||||
| `options` | `{ label; value }[]` | Dropdown items |
|
| `onClose` | `() => void` | Close handler |
|
||||||
| `placeholder` | `string?` | Placeholder text |
|
| `children`| `Snippet` | Modal body content |
|
||||||
| `value` | `string \| number?` | Pre-selected value |
|
|
||||||
|
|
||||||
Features: type-to-search filtering, keyboard navigation (ArrowUp/Down, Enter, Escape),
|
### TipTap
|
||||||
clear button, scroll-to-highlight.
|
|
||||||
|
|
||||||
### RelatedHasOne
|
| Prop | Type | Description |
|
||||||
|
| ---------------- | -------- | --------------------------------- |
|
||||||
| Prop | Type | Description |
|
| `initialContent` | `string` | Initial HTML content |
|
||||||
| --------------- | --------------------------------- | ------------------------------------ |
|
| `name` | `string` | Hidden input name for form submit |
|
||||||
| `label` | `string` | Section heading |
|
|
||||||
| `record` | `Record<string, unknown> \| null` | Related record |
|
|
||||||
| `displayFields` | `string[]` | Fields to display as key-value pairs |
|
|
||||||
| `editSlug` | `string?` | Links to edit the related record |
|
|
||||||
|
|
||||||
### RelatedHasMany
|
|
||||||
|
|
||||||
| Prop | Type | Description |
|
|
||||||
| --------------- | --------------------------- | ----------------------------- |
|
|
||||||
| `label` | `string` | Section heading (shows count) |
|
|
||||||
| `records` | `Record<string, unknown>[]` | Related records |
|
|
||||||
| `displayFields` | `string[]` | Table columns |
|
|
||||||
| `editSlug` | `string?` | Per-row edit links |
|
|
||||||
|
|
||||||
## Utility Functions
|
## Utility Functions
|
||||||
|
|
||||||
| Function | Module | Description |
|
| Function | Module | Description |
|
||||||
| ------------------------------------------ | -------------------- | ----------------------------------------------------------------------------- |
|
| ------------------------------------------ | -------------------- | ----------------------------------------------------------------------------- |
|
||||||
| `fieldNameToLabel(name)` | `introspect.ts` | `'solfeggioFrequencyId'` → `'Solfeggio Frequency'` (strips `Id`, adds spaces) |
|
| `fieldNameToLabel(name)` | `introspect.ts` | `'solfeggioFrequencyId'` -> `'Solfeggio Frequency'` (strips `Id`, adds spaces) |
|
||||||
| `resolveFieldValue(record, path)` | `introspect.ts` | Resolves dot-notation paths like `'category.name'` on nested objects |
|
| `resolveFieldValue(record, path)` | `introspect.ts` | Resolves dot-notation paths like `'category.name'` on nested objects |
|
||||||
| `extractFormData(formData, fields, types)` | `form-data.ts` | Parses `FormData` with type-aware conversion per `PrismaScalarType` |
|
| `extractFormData(formData, fields, types)` | `form-data.ts` | Parses `FormData` with type-aware conversion per `PrismaScalarType` |
|
||||||
| `getDelegate(config)` | `prisma-delegate.ts` | Returns `prisma[lcfirst(modelName)]` delegate |
|
| `getDelegate(config)` | `prisma-delegate.ts` | Returns `prisma[lcfirst(modelName)]` delegate |
|
||||||
@@ -318,6 +313,32 @@ clear button, scroll-to-highlight.
|
|||||||
| `getAllModelConfigs()` | `registry.ts` | Returns all registered model configs |
|
| `getAllModelConfigs()` | `registry.ts` | Returns all registered model configs |
|
||||||
| `hasRole(roleId, required)` | `roles.ts` | `true` if `roleId <= required` |
|
| `hasRole(roleId, required)` | `roles.ts` | `true` if `roleId <= required` |
|
||||||
| `canAccessAdmin(roleId)` | `roles.ts` | `true` if `roleId <= ROLES.STAFF` |
|
| `canAccessAdmin(roleId)` | `roles.ts` | `true` if `roleId <= ROLES.STAFF` |
|
||||||
|
| `loadRecords(locals, config)` | `crud.ts` | Fetches records + FK dropdown options for a model |
|
||||||
|
| `upsertRecord(formData, config, locals)` | `crud.ts` | Creates or updates a record based on presence of `id` in form data |
|
||||||
|
| `deleteRecord(formData, config, locals)` | `crud.ts` | Deletes a record by ID |
|
||||||
|
|
||||||
|
## Models in Admin
|
||||||
|
|
||||||
|
| Registry Slug | Prisma Model | Notes |
|
||||||
|
| -------------------------- | ---------------------- | ---------------------------------- |
|
||||||
|
| `users` | User | Role assignment, view-only fields |
|
||||||
|
| `profiles` | Profile | Linked to user FK |
|
||||||
|
| `addresses` | Address | Linked to profile FK |
|
||||||
|
| `user-roles` | UserRole | |
|
||||||
|
| `proclamations` | Proclamation | TipTap rich-text editor for content|
|
||||||
|
| `solfeggio-frequencies` | SolfeggioFrequency | |
|
||||||
|
| `proclamation-categories` | ProclamationCategory | Parent category hierarchy |
|
||||||
|
| `license-keys` | LicenseKey | listOnly — read-only table, no CRUD|
|
||||||
|
| `subscription-levels` | SubscriptionLevel | |
|
||||||
|
|
||||||
|
### Excluded from Admin
|
||||||
|
|
||||||
|
| Model | Reason |
|
||||||
|
| ------------------ | -------------------------------------------- |
|
||||||
|
| Session | Better Auth internal — not for manual editing|
|
||||||
|
| Account | Better Auth internal |
|
||||||
|
| Verification | Better Auth internal |
|
||||||
|
| UserProclamations | Composite key — managed via other models |
|
||||||
|
|
||||||
## Complete Example
|
## Complete Example
|
||||||
|
|
||||||
@@ -364,7 +385,7 @@ A hypothetical `Product` model showing all configuration features:
|
|||||||
],
|
],
|
||||||
accessFilter: (viewerRoleId: number) => {
|
accessFilter: (viewerRoleId: number) => {
|
||||||
if (viewerRoleId <= 2) return undefined;
|
if (viewerRoleId <= 2) return undefined;
|
||||||
return { active: true }; // staff only sees active products
|
return { active: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { createLocals, mockUser } from '$lib/test-utils';
|
||||||
|
import { getModelConfig } from './registry';
|
||||||
|
|
||||||
|
vi.mock('$lib/db', () => ({
|
||||||
|
prisma: {
|
||||||
|
userRole: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
findUnique: vi.fn(),
|
||||||
|
findFirst: vi.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { prisma } from '$lib/db';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from './crud';
|
||||||
|
|
||||||
|
const findMany = prisma.userRole.findMany as ReturnType<typeof vi.fn>;
|
||||||
|
const create = prisma.userRole.create as ReturnType<typeof vi.fn>;
|
||||||
|
const update = prisma.userRole.update as ReturnType<typeof vi.fn>;
|
||||||
|
const deletes = prisma.userRole.delete as ReturnType<typeof vi.fn>;
|
||||||
|
const findUnique = prisma.userRole.findUnique as ReturnType<typeof vi.fn>;
|
||||||
|
const findFirst = prisma.userRole.findFirst as ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
const config = getModelConfig('user-roles')!;
|
||||||
|
|
||||||
|
describe('crud loadRecords', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
findMany.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns records', async () => {
|
||||||
|
findMany.mockResolvedValue([{ id: 1, name: 'Admin' }]);
|
||||||
|
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await loadRecords(locals, config);
|
||||||
|
|
||||||
|
expect(result.records).toEqual([{ id: 1, name: 'Admin' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty records array', async () => {
|
||||||
|
findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await loadRecords(locals, config);
|
||||||
|
|
||||||
|
expect(result.records).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('crud upsertRecord', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
create.mockReset();
|
||||||
|
update.mockReset();
|
||||||
|
findUnique.mockReset();
|
||||||
|
findFirst.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new record when no id', async () => {
|
||||||
|
create.mockResolvedValue({ id: 2, name: 'Editor' });
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('name', 'Editor');
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await upsertRecord(fd, config, locals);
|
||||||
|
|
||||||
|
expect(create).toHaveBeenCalledWith({ data: { name: 'Editor' } });
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates an existing record when id is present', async () => {
|
||||||
|
findUnique.mockResolvedValue({ id: 1, name: 'Old Name' });
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('id', '1');
|
||||||
|
fd.set('name', 'New Name');
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await upsertRecord(fd, config, locals);
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledWith({ where: { id: 1 }, data: { name: 'New Name' } });
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when updating non-existent record', async () => {
|
||||||
|
findUnique.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('id', '999');
|
||||||
|
fd.set('name', 'Ghost');
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await upsertRecord(fd, config, locals);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: false, error: 'Record not found' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('crud deleteRecord', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
deletes.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deletes a record by id', async () => {
|
||||||
|
deletes.mockResolvedValue({ id: 1, name: 'Admin' });
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('id', '1');
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await deleteRecord(fd, config, locals);
|
||||||
|
|
||||||
|
expect(deletes).toHaveBeenCalledWith({ where: { id: 1 } });
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when id is missing', async () => {
|
||||||
|
const fd = new FormData();
|
||||||
|
const locals = createLocals(mockUser() as any);
|
||||||
|
const result = await deleteRecord(fd, config, locals);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: false, error: 'Missing id' });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { getDelegate, parseId } from './prisma-delegate';
|
||||||
|
import { extractFormData } from './form-data';
|
||||||
|
import type { AdminModelConfig } from './types';
|
||||||
|
import { superValidate } from 'sveltekit-superforms';
|
||||||
|
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||||
|
import * as Schemas from './schemas';
|
||||||
|
import type { z } from 'zod';
|
||||||
|
|
||||||
|
const modelSchemas: Record<string, z.ZodObject<any>> = {
|
||||||
|
User: Schemas.User,
|
||||||
|
Profile: Schemas.Profile,
|
||||||
|
UserRole: Schemas.UserRole,
|
||||||
|
Proclamation: Schemas.Proclamation,
|
||||||
|
ProclamationCategory: Schemas.ProclamationCategory,
|
||||||
|
SolfeggioFrequency: Schemas.SolfeggioFrequency,
|
||||||
|
SubscriptionLevel: Schemas.SubscriptionLevel,
|
||||||
|
LicenseKey: Schemas.LicenseKey
|
||||||
|
};
|
||||||
|
|
||||||
|
export function superFormErrorsToFieldErrors(superErrors: Record<string, unknown>): {
|
||||||
|
error: string;
|
||||||
|
fieldErrors: Record<string, string>;
|
||||||
|
} {
|
||||||
|
const fieldErrors: Record<string, string> = {};
|
||||||
|
let formError = '';
|
||||||
|
for (const [key, value] of Object.entries(superErrors)) {
|
||||||
|
if (key === '_errors' && Array.isArray(value)) {
|
||||||
|
formError = (value[0] as string) ?? '';
|
||||||
|
} else if (
|
||||||
|
value &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
'_errors' in value &&
|
||||||
|
Array.isArray((value as Record<string, unknown>)._errors)
|
||||||
|
) {
|
||||||
|
const errMsg = ((value as Record<string, unknown>)._errors as string[])[0];
|
||||||
|
fieldErrors[key] = errMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { error: formError, fieldErrors };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadRecords(locals: App.Locals, config: AdminModelConfig) {
|
||||||
|
const delegate = getDelegate(config);
|
||||||
|
|
||||||
|
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||||
|
const where = config.accessFilter?.(viewerRoleId);
|
||||||
|
|
||||||
|
const records = await delegate.findMany({
|
||||||
|
where,
|
||||||
|
include: config.include,
|
||||||
|
orderBy: { id: 'asc' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const relationOptionsData: Record<string, { label: string; value: string | number }[]> = {};
|
||||||
|
// For cascade child fields: maps field -> { childValue -> parentValue }
|
||||||
|
const cascadeOptionParentMap: Record<string, Record<string | number, string | number>> = {};
|
||||||
|
|
||||||
|
for (const [field, relConfig] of Object.entries(config.relationOptions ?? {})) {
|
||||||
|
const relDelegate = getDelegate({ ...config, modelName: relConfig.model });
|
||||||
|
const selectFields: Record<string, boolean> = {
|
||||||
|
[relConfig.valueField]: true,
|
||||||
|
[relConfig.labelField]: true
|
||||||
|
};
|
||||||
|
// Check if this field is a cascade child (needs parentId)
|
||||||
|
const cascadeEntry = Object.entries(config.cascadeFields ?? {}).find(
|
||||||
|
([, c]) => c.parentField === field
|
||||||
|
);
|
||||||
|
if (cascadeEntry) {
|
||||||
|
selectFields['parentId'] = true;
|
||||||
|
}
|
||||||
|
const options = await relDelegate.findMany({
|
||||||
|
select: selectFields,
|
||||||
|
orderBy: { [relConfig.labelField]: 'asc' }
|
||||||
|
});
|
||||||
|
const mapped = (options as Record<string, unknown>[]).map((opt: Record<string, unknown>) => ({
|
||||||
|
label: String(opt[relConfig.labelField]),
|
||||||
|
value: opt[relConfig.valueField] as string | number
|
||||||
|
}));
|
||||||
|
relationOptionsData[field] = mapped;
|
||||||
|
|
||||||
|
// Build parent map if cascade child
|
||||||
|
if (cascadeEntry) {
|
||||||
|
const childField = cascadeEntry[0];
|
||||||
|
const parentMap: Record<string | number, string | number> = {};
|
||||||
|
const rows = options as Record<string, unknown>[];
|
||||||
|
for (const row of rows) {
|
||||||
|
const childVal = row[relConfig.valueField] as string | number;
|
||||||
|
const parentVal = row['parentId'] as number | null;
|
||||||
|
if (parentVal != null) {
|
||||||
|
parentMap[childVal] = parentVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cascadeOptionParentMap[childField] = parentMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records as Record<string, unknown>[],
|
||||||
|
relationOptionsData,
|
||||||
|
cascadeOptionParentMap
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertRecord(
|
||||||
|
requestOrFormData: Request | FormData,
|
||||||
|
config: AdminModelConfig,
|
||||||
|
locals: App.Locals
|
||||||
|
) {
|
||||||
|
const schema = modelSchemas[config.modelName];
|
||||||
|
const form = schema ? await superValidate(requestOrFormData, zod4(schema)) : null;
|
||||||
|
|
||||||
|
if (form && !form.valid) {
|
||||||
|
const { error, fieldErrors } = superFormErrorsToFieldErrors(
|
||||||
|
form.errors as Record<string, unknown>
|
||||||
|
);
|
||||||
|
return { ...form, success: false, error, fieldErrors };
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData =
|
||||||
|
requestOrFormData instanceof FormData ? requestOrFormData : await requestOrFormData.formData();
|
||||||
|
const rawId = formData.get('id')?.toString();
|
||||||
|
const data =
|
||||||
|
form?.data ??
|
||||||
|
(extractFormData(formData, config.formFields, config.fieldTypes) as Record<string, unknown>);
|
||||||
|
|
||||||
|
// Strip virtual/UI-only fields from Prisma payload
|
||||||
|
for (const f of config.dataOnlyFields ?? []) {
|
||||||
|
delete data[f];
|
||||||
|
}
|
||||||
|
|
||||||
|
const delegate = getDelegate(config);
|
||||||
|
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||||
|
const where = config.accessFilter?.(viewerRoleId);
|
||||||
|
|
||||||
|
if (rawId) {
|
||||||
|
const id = parseId(config, rawId);
|
||||||
|
|
||||||
|
const existing = await delegate.findUnique({
|
||||||
|
where: { id },
|
||||||
|
...((config.include ? { include: config.include } : {}) as object)
|
||||||
|
});
|
||||||
|
if (!existing) {
|
||||||
|
return { success: false, error: 'Record not found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
const match = await delegate.findFirst({
|
||||||
|
where: { id, ...where }
|
||||||
|
});
|
||||||
|
if (!match) {
|
||||||
|
return { success: false, error: 'Access denied' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await delegate.update({
|
||||||
|
where: { id },
|
||||||
|
data
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await delegate.create({ data });
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (e && typeof e === 'object' && 'code' in e && (e as { code: string }).code === 'P2002') {
|
||||||
|
const meta = (e as { meta?: { target?: string[] } }).meta;
|
||||||
|
const fields = meta?.target ?? [];
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `A record with this ${fields.join(', ')} already exists.`,
|
||||||
|
fieldErrors: Object.fromEntries(fields.map((f) => [f, 'Already taken']))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRecord(
|
||||||
|
requestOrFormData: Request | FormData,
|
||||||
|
config: AdminModelConfig,
|
||||||
|
locals: App.Locals
|
||||||
|
) {
|
||||||
|
const delegate = getDelegate(config);
|
||||||
|
const formData =
|
||||||
|
requestOrFormData instanceof FormData ? requestOrFormData : await requestOrFormData.formData();
|
||||||
|
const rawId = formData.get('id')?.toString();
|
||||||
|
if (!rawId) return { success: false, error: 'Missing id' };
|
||||||
|
|
||||||
|
const id = parseId(config, rawId);
|
||||||
|
|
||||||
|
const viewerRoleId = (locals.user as { roleId?: number } | null)?.roleId ?? 4;
|
||||||
|
const where = config.accessFilter?.(viewerRoleId);
|
||||||
|
if (where) {
|
||||||
|
const match = await delegate.findFirst({ where: { id, ...where } });
|
||||||
|
if (!match) return { success: false, error: 'Access denied' };
|
||||||
|
}
|
||||||
|
|
||||||
|
await delegate.delete({ where: { id } });
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
@@ -31,6 +31,10 @@ export function extractFormData(
|
|||||||
data[field] = val === '' ? null : new Date(val);
|
data[field] = val === '' ? null : new Date(val);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'Tiptap': {
|
||||||
|
data[field] = raw?.toString() ?? '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
data[field] = raw?.toString() ?? '';
|
data[field] = raw?.toString() ?? '';
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const email = z.string().email('Invalid email address');
|
||||||
|
const phone = z
|
||||||
|
.string()
|
||||||
|
.min(10, 'Phone number must be at least 10 digits')
|
||||||
|
.regex(/^[\d\-\+\(\) ]+$/, 'Invalid phone number format');
|
||||||
|
const name = z.string().min(1, 'Required').max(100, 'Too long');
|
||||||
|
|
||||||
|
export const UserRole = z.object({
|
||||||
|
name: z.string().min(1, 'Required')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const User = z.object({
|
||||||
|
name: z.string().min(1, 'Required'),
|
||||||
|
email,
|
||||||
|
roleId: z.coerce.number().int().min(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Profile = z.object({
|
||||||
|
userId: z.string().min(1, 'Required'),
|
||||||
|
firstName: name,
|
||||||
|
lastName: name,
|
||||||
|
phoneNumber: phone
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Address = z.object({
|
||||||
|
profileId: z.string().min(1),
|
||||||
|
street: z.string().min(1, 'Street is required'),
|
||||||
|
city: z.string().min(1, 'City is required'),
|
||||||
|
state: z.string().min(1, 'State is required'),
|
||||||
|
postalCode: z.string().min(1, 'Postal code is required'),
|
||||||
|
country: z.string().min(1, 'Country is required'),
|
||||||
|
type: z.enum(['Mailing', 'Billing', 'Shipping']).default('Mailing'),
|
||||||
|
isDefaultMailing: z.coerce.boolean().default(false),
|
||||||
|
isDefaultBilling: z.coerce.boolean().default(false),
|
||||||
|
isDefaultShipping: z.coerce.boolean().default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SolfeggioFrequency = z.object({
|
||||||
|
name: z.string().min(1, 'Required'),
|
||||||
|
value: z.coerce.number().int('Must be a whole number')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SubscriptionLevel = z.object({
|
||||||
|
name: z.string().min(1, 'Required'),
|
||||||
|
description: z.string().optional().default('')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ProclamationCategory = z.object({
|
||||||
|
name: z.string().min(1, 'Required'),
|
||||||
|
parentId: z.coerce.number().int().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Proclamation = z.object({
|
||||||
|
name: z.string().min(1, 'Required'),
|
||||||
|
solfeggioFrequencyId: z.coerce.number().int(),
|
||||||
|
parentCategoryId: z.coerce.number().int(),
|
||||||
|
categoryId: z.coerce.number().int(),
|
||||||
|
subscriptionLevelId: z.coerce.number().int(),
|
||||||
|
content: z.string().optional().default('')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LicenseKey = z.object({
|
||||||
|
active: z.coerce.boolean(),
|
||||||
|
level: z.string().min(1, 'Required'),
|
||||||
|
userId: z.string().min(1, 'Required')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UserUpdate = z.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
name: z.string().min(1, 'Required').optional(),
|
||||||
|
email: z.string().email().optional()
|
||||||
|
});
|
||||||
@@ -19,6 +19,12 @@ export type RelatedRecordConfig = {
|
|||||||
editSlug?: string;
|
editSlug?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ActionResult = {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
fieldErrors?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
export type AdminModelConfig = {
|
export type AdminModelConfig = {
|
||||||
slug: string;
|
slug: string;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
@@ -35,4 +41,7 @@ export type AdminModelConfig = {
|
|||||||
relationOptions?: Record<string, RelationOptionConfig>;
|
relationOptions?: Record<string, RelationOptionConfig>;
|
||||||
relatedRecords?: RelatedRecordConfig[];
|
relatedRecords?: RelatedRecordConfig[];
|
||||||
accessFilter?: (viewerRoleId: number) => Record<string, unknown> | undefined;
|
accessFilter?: (viewerRoleId: number) => Record<string, unknown> | undefined;
|
||||||
|
/** Fields that should be excluded from Prisma create/update (virtual/UI-only) */
|
||||||
|
dataOnlyFields?: string[];
|
||||||
|
cascadeFields?: Record<string, { parentField: string }>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,12 +37,16 @@
|
|||||||
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">Profile</h2>
|
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">Profile</h2>
|
||||||
|
|
||||||
{#if form?.action === 'updateProfile' && form?.success}
|
{#if form?.action === 'updateProfile' && form?.success}
|
||||||
<div class="mb-4 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
Profile updated successfully.
|
Profile updated successfully.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if form?.action === 'updateProfile' && form?.error}
|
{#if form?.action === 'updateProfile' && form?.error}
|
||||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
{form.error}
|
{form.error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -115,12 +119,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if form?.action === 'addAddress' && form?.success}
|
{#if form?.action === 'addAddress' && form?.success}
|
||||||
<div class="mb-4 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
Address added successfully.
|
Address added successfully.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if form?.action === 'addAddress' && form?.error}
|
{#if form?.action === 'addAddress' && form?.error}
|
||||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
{form.error}
|
{form.error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -133,7 +141,9 @@
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label for="street" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Street</label>
|
<label for="street" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>Street</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="street"
|
id="street"
|
||||||
@@ -144,7 +154,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label for="city" class="block text-sm font-medium text-gray-700 dark:text-gray-300">City</label>
|
<label for="city" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>City</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="city"
|
id="city"
|
||||||
@@ -154,7 +166,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block text-sm font-medium text-gray-700 dark:text-gray-300">State</label>
|
<label for="state" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>State</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="state"
|
id="state"
|
||||||
@@ -166,7 +180,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label for="postalCode" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="postalCode"
|
||||||
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
Postal Code
|
Postal Code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -178,7 +195,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="country" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Country</label>
|
<label
|
||||||
|
for="country"
|
||||||
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300">Country</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="country"
|
id="country"
|
||||||
@@ -204,10 +224,13 @@
|
|||||||
{#if data.user.profile?.addresses && data.user.profile.addresses.length > 0}
|
{#if data.user.profile?.addresses && data.user.profile.addresses.length > 0}
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
{#each data.user.profile.addresses as address}
|
{#each data.user.profile.addresses as address}
|
||||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
<div
|
||||||
|
class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900"
|
||||||
|
>
|
||||||
<p class="text-sm text-gray-900 dark:text-gray-100">{address.street}</p>
|
<p class="text-sm text-gray-900 dark:text-gray-100">{address.street}</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{address.city}, {address.state} {address.postalCode}
|
{address.city}, {address.state}
|
||||||
|
{address.postalCode}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">{address.country}</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">{address.country}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ describe('(protected)/account page server', () => {
|
|||||||
country: 'US'
|
country: 'US'
|
||||||
});
|
});
|
||||||
|
|
||||||
const err = await Promise.resolve(
|
const err = await Promise.resolve(invokeAction('addAddress', request, createLocals())).catch(
|
||||||
invokeAction('addAddress', request, createLocals())
|
(e: unknown) => e
|
||||||
).catch((e: unknown) => e);
|
);
|
||||||
expect(isHttpError(err, 403)).toBe(true);
|
expect(isHttpError(err, 403)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,9 +201,9 @@ describe('(protected)/account page server', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const locals = createLocals(mockUser() as any);
|
const locals = createLocals(mockUser() as any);
|
||||||
const err = await Promise.resolve(
|
const err = await Promise.resolve(invokeAction('addAddress', request, locals)).catch(
|
||||||
invokeAction('addAddress', request, locals)
|
(e: unknown) => e
|
||||||
).catch((e: unknown) => e);
|
);
|
||||||
expect(isHttpError(err, 400)).toBe(true);
|
expect(isHttpError(err, 400)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
There should be a form in the middle
|
There should be a form in the middle
|
||||||
rounded corners
|
rounded corners
|
||||||
@@ -44,17 +43,29 @@
|
|||||||
|
|
||||||
<Window>
|
<Window>
|
||||||
<form method="POST" class="border p-6 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100">
|
<form method="POST" class="border p-6 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100">
|
||||||
<p class="mx-auto text-center my-4 italic">Please finish setting up your profile</p>
|
<p class="mx-auto my-4 text-center italic">Please finish setting up your profile</p>
|
||||||
{#if data.user?.image}
|
{#if data.user?.image}
|
||||||
<img src={data.user?.image} class="rounded-full w-[8rem] mx-auto" alt="Profile" />
|
<img src={data.user?.image} class="mx-auto w-[8rem] rounded-full" alt="Profile" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="mx-auto aspect-square w-[8rem] rounded-full bg-slate-900"></div>
|
<div class="mx-auto aspect-square w-[8rem] rounded-full bg-slate-900"></div>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="mx-auto text-center my-4">{data.user?.email}</p>
|
<p class="mx-auto my-4 text-center">{data.user?.email}</p>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<input type="text" name="userId" value={data.user?.id} class="hidden"/>
|
<input type="text" name="userId" value={data.user?.id} class="hidden" />
|
||||||
<input type="text" name="firstName" placeholder="First Name" class="my-2" bind:value={firstName} />
|
<input
|
||||||
<input type="text" name="lastName" placeholder="Last Name" class="my-2" bind:value={lastName} />
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
placeholder="First Name"
|
||||||
|
class="my-2"
|
||||||
|
bind:value={firstName}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="lastName"
|
||||||
|
placeholder="Last Name"
|
||||||
|
class="my-2"
|
||||||
|
bind:value={lastName}
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
@@ -64,6 +75,8 @@
|
|||||||
oninput={handlePhoneInput}
|
oninput={handlePhoneInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="p-2 px-4 bg-blue-300 dark:bg-gray-700 dark:text-gray-100">Submit</button>
|
<button type="submit" class="bg-blue-300 p-2 px-4 dark:bg-gray-700 dark:text-gray-100"
|
||||||
|
>Submit</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ describe('account setup page server load', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws 403 when user is not logged in', async () => {
|
it('throws 403 when user is not logged in', async () => {
|
||||||
const err = await Promise.resolve(
|
const err = await Promise.resolve(load({ locals: createLocals() } as any)).catch(
|
||||||
load({ locals: createLocals() } as any)
|
(e: unknown) => e
|
||||||
).catch((e: unknown) => e);
|
);
|
||||||
expect(isHttpError(err, 403)).toBe(true);
|
expect(isHttpError(err, 403)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full min-h-0 flex-col gap-6 p-6 dark:text-gray-100">
|
<div class="flex h-full min-h-0 flex-col gap-6 p-6 dark:text-gray-100">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Welcome, {data.firstName}!</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
Welcome, {data.firstName}!
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<a
|
<a
|
||||||
@@ -22,7 +24,9 @@
|
|||||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-900"
|
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-900"
|
||||||
>
|
>
|
||||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Proclamations</p>
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Proclamations</p>
|
||||||
<p class="mt-2 text-3xl font-bold text-gray-900 dark:text-gray-100">{data.proclamationCount}</p>
|
<p class="mt-2 text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
|
{data.proclamationCount}
|
||||||
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-400 dark:text-gray-500">Browse all</p>
|
<p class="mt-1 text-sm text-gray-400 dark:text-gray-500">Browse all</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,19 @@
|
|||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th
|
||||||
|
class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||||
|
>
|
||||||
Key
|
Key
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th
|
||||||
|
class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||||
|
>
|
||||||
Level
|
Level
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th
|
||||||
|
class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||||
|
>
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -26,15 +32,17 @@
|
|||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-900">
|
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-900">
|
||||||
{#each data.licenseKeys as key}
|
{#each data.licenseKeys as key}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="whitespace-nowrap px-6 py-4 font-mono text-sm text-gray-900 dark:text-gray-100">
|
<td
|
||||||
|
class="px-6 py-4 font-mono text-sm whitespace-nowrap text-gray-900 dark:text-gray-100"
|
||||||
|
>
|
||||||
{key.key}
|
{key.key}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
<td class="px-6 py-4 text-sm whitespace-nowrap text-gray-700 dark:text-gray-300">
|
||||||
{key.level}
|
{key.level}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
<td class="px-6 py-4 text-sm whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
class="inline-flex rounded-full px-2 text-xs font-semibold leading-5 {key.active
|
class="inline-flex rounded-full px-2 text-xs leading-5 font-semibold {key.active
|
||||||
? 'bg-green-100 text-green-800 dark:bg-gray-700 dark:text-gray-200'
|
? 'bg-green-100 text-green-800 dark:bg-gray-700 dark:text-gray-200'
|
||||||
: 'bg-red-100 text-red-800 dark:bg-gray-700 dark:text-gray-200'}"
|
: 'bg-red-100 text-red-800 dark:bg-gray-700 dark:text-gray-200'}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const load: PageServerLoad = async ({ url }) => {
|
|||||||
const [proclamations, categories] = await Promise.all([
|
const [proclamations, categories] = await Promise.all([
|
||||||
prisma.proclamation.findMany({
|
prisma.proclamation.findMany({
|
||||||
where,
|
where,
|
||||||
include: { solfeggioFrequency: true, category: { include: { parent: true } } },
|
include: { solfeggioFrequency: true, category: { include: { parent: true } }, subscriptionLevel: true },
|
||||||
orderBy: { name: 'asc' }
|
orderBy: { name: 'asc' }
|
||||||
}),
|
}),
|
||||||
prisma.proclamationCategory.findMany({ include: { parent: true }, orderBy: { name: 'asc' } })
|
prisma.proclamationCategory.findMany({ include: { parent: true }, orderBy: { name: 'asc' } })
|
||||||
|
|||||||
@@ -40,7 +40,9 @@
|
|||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="flex flex-wrap items-end gap-4">
|
<div class="flex flex-wrap items-end gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Search</label>
|
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>Search</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="search"
|
id="search"
|
||||||
@@ -51,7 +53,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="category" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Category</label>
|
<label for="category" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>Category</label
|
||||||
|
>
|
||||||
<select
|
<select
|
||||||
id="category"
|
id="category"
|
||||||
bind:value={categoryId}
|
bind:value={categoryId}
|
||||||
@@ -60,7 +64,7 @@
|
|||||||
>
|
>
|
||||||
<option value={null}>All Categories</option>
|
<option value={null}>All Categories</option>
|
||||||
{#each data.categories as cat}
|
{#each data.categories as cat}
|
||||||
<option value={cat.id}>{displayCategoryName(cat)}</option>
|
<option value={cat.id}>{displayCategoryName(cat)}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,14 +95,16 @@
|
|||||||
class="flex flex-col rounded-lg border border-gray-200 bg-white p-5 shadow-sm transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-900"
|
class="flex flex-col rounded-lg border border-gray-200 bg-white p-5 shadow-sm transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-900"
|
||||||
>
|
>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">{proclamation.name}</h2>
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">{proclamation.name}</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{displayCategoryName(proclamation.category)}</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{displayCategoryName(proclamation.category)}
|
||||||
|
</p>
|
||||||
<div class="mt-auto flex items-center justify-between pt-4">
|
<div class="mt-auto flex items-center justify-between pt-4">
|
||||||
<span class="text-sm text-purple-600 dark:text-gray-400">
|
<span class="text-sm text-purple-600 dark:text-gray-400">
|
||||||
{proclamation.solfeggioFrequency.value} Hz —
|
{proclamation.solfeggioFrequency.value} Hz —
|
||||||
{proclamation.solfeggioFrequency.name}
|
{proclamation.solfeggioFrequency.name}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
${proclamation.price.toFixed(2)}
|
{proclamation.subscriptionLevel?.name ?? 'Standard'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export const load: PageServerLoad = async ({ params, parent }) => {
|
|||||||
where: { id: params.id },
|
where: { id: params.id },
|
||||||
include: {
|
include: {
|
||||||
solfeggioFrequency: true,
|
solfeggioFrequency: true,
|
||||||
category: { include: { parent: true } }
|
category: { include: { parent: true } },
|
||||||
|
subscriptionLevel: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,27 +19,35 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{data.proclamation.name}</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{data.proclamation.name}
|
||||||
|
</h1>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{categoryBreadcrumb}</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{categoryBreadcrumb}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<div class="rounded-lg border border-purple-200 bg-purple-50 px-4 py-2 dark:border-gray-700 dark:bg-gray-800">
|
<div
|
||||||
|
class="rounded-lg border border-purple-200 bg-purple-50 px-4 py-2 dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
>
|
||||||
<p class="text-xs font-medium text-purple-600 dark:text-gray-400">Frequency</p>
|
<p class="text-xs font-medium text-purple-600 dark:text-gray-400">Frequency</p>
|
||||||
<p class="text-sm font-semibold text-purple-900 dark:text-gray-100">
|
<p class="text-sm font-semibold text-purple-900 dark:text-gray-100">
|
||||||
{data.proclamation.solfeggioFrequency.value} Hz —
|
{data.proclamation.solfeggioFrequency.value} Hz —
|
||||||
{data.proclamation.solfeggioFrequency.name}
|
{data.proclamation.solfeggioFrequency.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-2 dark:border-gray-700 dark:bg-gray-800">
|
<div
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">Price</p>
|
class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-2 dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
>
|
||||||
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">Level</p>
|
||||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||||
${data.proclamation.price.toFixed(2)}
|
{data.proclamation.subscriptionLevel?.name ?? 'Standard'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prose max-w-none rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900 dark:prose-invert">
|
<div
|
||||||
|
class="prose max-w-none rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900 dark:prose-invert"
|
||||||
|
>
|
||||||
{@html data.proclamation.content}
|
{@html data.proclamation.content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ describe('(protected)/admin/proclamations/[id] page server load', () => {
|
|||||||
it('throws 404 when proclamation is not found', async () => {
|
it('throws 404 when proclamation is not found', async () => {
|
||||||
findUnique.mockResolvedValue(null);
|
findUnique.mockResolvedValue(null);
|
||||||
|
|
||||||
const err = await Promise.resolve(
|
const err = await Promise.resolve(load({ params: { id: 'nonexistent' } } as any)).catch(
|
||||||
load({ params: { id: 'nonexistent' } } as any)
|
(e: unknown) => e
|
||||||
).catch((e: unknown) => e);
|
);
|
||||||
expect(isHttpError(err, 404)).toBe(true);
|
expect(isHttpError(err, 404)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,7 +44,8 @@ describe('(protected)/admin/proclamations/[id] page server load', () => {
|
|||||||
where: { id: 'proc-1' },
|
where: { id: 'proc-1' },
|
||||||
include: {
|
include: {
|
||||||
solfeggioFrequency: true,
|
solfeggioFrequency: true,
|
||||||
category: { include: { parent: true } }
|
category: { include: { parent: true } },
|
||||||
|
subscriptionLevel: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { load } from './+page.server';
|
|||||||
const proclamationFindMany = prisma.proclamation.findMany as ReturnType<typeof vi.fn>;
|
const proclamationFindMany = prisma.proclamation.findMany as ReturnType<typeof vi.fn>;
|
||||||
const categoryFindMany = prisma.proclamationCategory.findMany as ReturnType<typeof vi.fn>;
|
const categoryFindMany = prisma.proclamationCategory.findMany as ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
describe('(protected)/admin/proclamations page server load', () => {
|
describe('(protected)/admin/proclamations page server load', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
proclamationFindMany.mockReset();
|
proclamationFindMany.mockReset();
|
||||||
categoryFindMany.mockReset();
|
categoryFindMany.mockReset();
|
||||||
@@ -29,10 +29,12 @@ const categoryFindMany = prisma.proclamationCategory.findMany as ReturnType<type
|
|||||||
const url = createUrl('/admin/proclamations');
|
const url = createUrl('/admin/proclamations');
|
||||||
const result = (await load({ url } as any)) as any;
|
const result = (await load({ url } as any)) as any;
|
||||||
expect(result.proclamations).toEqual(proclamations);
|
expect(result.proclamations).toEqual(proclamations);
|
||||||
expect(result.categories[0]).toEqual(expect.objectContaining({
|
expect(result.categories[0]).toEqual(
|
||||||
...categories[0],
|
expect.objectContaining({
|
||||||
displayName: categories[0].name
|
...categories[0],
|
||||||
}));
|
displayName: categories[0].name
|
||||||
|
})
|
||||||
|
);
|
||||||
expect(result.search).toBe('');
|
expect(result.search).toBe('');
|
||||||
expect(result.categoryId).toBeNull();
|
expect(result.categoryId).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -109,7 +111,7 @@ const categoryFindMany = prisma.proclamationCategory.findMany as ReturnType<type
|
|||||||
|
|
||||||
expect(proclamationFindMany).toHaveBeenCalledWith(
|
expect(proclamationFindMany).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
include: { solfeggioFrequency: true, category: { include: { parent: true } } }
|
include: { solfeggioFrequency: true, category: { include: { parent: true } }, subscriptionLevel: true }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,9 +48,7 @@
|
|||||||
{#each adminModels as model}
|
{#each adminModels as model}
|
||||||
<a
|
<a
|
||||||
href="/admin/{model.slug}"
|
href="/admin/{model.slug}"
|
||||||
class="rounded px-3 py-1 text-sm transition-colors {isActive(
|
class="rounded px-3 py-1 text-sm transition-colors {isActive('/admin/' + model.slug)
|
||||||
'/admin/' + model.slug
|
|
||||||
)
|
|
||||||
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
||||||
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export const load: LayoutServerLoad = async ({ locals }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user || !canAccessAdmin(user.roleId)) {
|
if (!user || !canAccessAdmin(user.roleId)) {
|
||||||
console.log(`Attempted to access /admin via ${user?.id} : ${user?.name} with a role of ${user?.role}`);
|
console.log(
|
||||||
|
`Attempted to access /admin via ${user?.id} : ${user?.name} with a role of ${user?.role}`
|
||||||
|
);
|
||||||
throw error(403, 'not authorized');
|
throw error(403, 'not authorized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getAllModelConfigs } from '$lib/svelte-admin/registry';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async () => {
|
||||||
|
const models = getAllModelConfigs().filter((m) => m.slug !== 'license-keys');
|
||||||
|
return { models };
|
||||||
|
};
|
||||||
@@ -1 +1,21 @@
|
|||||||
<h1 class="p-6 dark:text-gray-100">Admin Dashboard</h1>
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
|
||||||
|
let { data }: PageProps = $props();
|
||||||
|
let models = $derived(data.models);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1 class="p-6 text-xl font-bold text-gray-900 dark:text-gray-100">Admin Dashboard</h1>
|
||||||
|
<div class="grid grid-cols-1 gap-4 px-6 pb-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{#each models as model (model.slug)}
|
||||||
|
<a
|
||||||
|
href={'/admin/' + model.slug}
|
||||||
|
class="block border border-gray-200 p-4 hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">{model.displayName}</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{model.listFields.length} fields
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
import { prisma } from "$lib/db";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals }) => {
|
|
||||||
console.log(locals.user?.name);
|
|
||||||
// Load proclamation categories
|
|
||||||
const categories = await prisma.proclamationCategory.findMany();
|
|
||||||
console.log(categories);
|
|
||||||
return { categories };
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { PageProps } from './$types';
|
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
|
||||||
|
|
||||||
function getCategoryNameFromId(id: number): string {
|
|
||||||
if (data.categories) {
|
|
||||||
const target = data.categories.find((category) => category.id === id);
|
|
||||||
return target ? target.name : '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Display a list of categories -->
|
|
||||||
<div class="p-6 dark:text-gray-100">
|
|
||||||
<table class="w-full">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<th class="hidden">ID</th>
|
|
||||||
<th class="px-4 py-2 text-left text-sm font-medium text-gray-500 dark:text-gray-400">Name</th>
|
|
||||||
<th class="px-4 py-2 text-left text-sm font-medium text-gray-500 dark:text-gray-400">Parent Category</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#if data.categories}
|
|
||||||
{#each data.categories as category, i (category.id)}
|
|
||||||
<tr class="border-b border-gray-200 hover:bg-gray-300 dark:border-gray-700 dark:hover:bg-gray-800">
|
|
||||||
<td class="hidden">{i}</td>
|
|
||||||
<td class="px-4 py-2 text-gray-900 dark:text-gray-100">{category.name}</td>
|
|
||||||
{#if category.parentId}
|
|
||||||
<td class="px-4 py-2 text-gray-700 dark:text-gray-300">{getCategoryNameFromId(category.parentId)}</td>
|
|
||||||
{:else}
|
|
||||||
<td class="px-4 py-2"></td>
|
|
||||||
{/if}
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords } from '$lib/svelte-admin/crud';
|
||||||
|
import { prisma } from '$lib/db';
|
||||||
|
import { sendMail } from '$lib/server/resend';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
const config = getModelConfig('license-keys')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
createKey: async ({ request, locals }) => {
|
||||||
|
if (!locals.user) throw error(401, 'Unauthorized');
|
||||||
|
|
||||||
|
const formData = await request.formData();
|
||||||
|
const email = formData.get('email')?.toString();
|
||||||
|
const deactivateOld = formData.get('deactivateOld') === 'on';
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return { success: false, error: 'Email is required' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
return { success: false, error: 'No user found with that email' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate old keys if requested
|
||||||
|
if (deactivateOld) {
|
||||||
|
await prisma.licenseKey.updateMany({
|
||||||
|
where: { userId: user.id, active: true },
|
||||||
|
data: { active: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate 16-char hex UUID grouped as 4-4-4-4
|
||||||
|
const hex = crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase();
|
||||||
|
const key = `${hex.slice(0, 4)}-${hex.slice(4, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}`;
|
||||||
|
|
||||||
|
const licenseKey = await prisma.licenseKey.create({
|
||||||
|
data: {
|
||||||
|
key,
|
||||||
|
active: true,
|
||||||
|
level: 'standard',
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send email with license key
|
||||||
|
try {
|
||||||
|
await sendMail({
|
||||||
|
to: email,
|
||||||
|
subject: 'Your License Key',
|
||||||
|
data: {
|
||||||
|
firstName: user.name ?? 'Valued Customer',
|
||||||
|
licenseKey: key
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to send license key email:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, error: undefined };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import type { ActionResult } from '$lib/svelte-admin/types';
|
||||||
|
import AdminTable from '$lib/components/admin/AdminTable.svelte';
|
||||||
|
import Toast from '$lib/components/admin/Toast.svelte';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
|
||||||
|
const config = getModelConfig('license-keys')!;
|
||||||
|
|
||||||
|
let showCreateForm = $state(false);
|
||||||
|
let toastMessage = $state('');
|
||||||
|
let toastType = $state<'success' | 'error'>('success');
|
||||||
|
let createError = $state('');
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (form && 'success' in (form as ActionResult)) {
|
||||||
|
const result = form as ActionResult;
|
||||||
|
if (result.success) {
|
||||||
|
toastMessage = 'License key created and emailed';
|
||||||
|
toastType = 'success';
|
||||||
|
showCreateForm = false;
|
||||||
|
} else {
|
||||||
|
createError = result.error ?? 'Failed to create license key';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleForm() {
|
||||||
|
showCreateForm = !showCreateForm;
|
||||||
|
createError = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">{config.displayName}</h1>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={toggleForm}
|
||||||
|
class="border border-gray-300 px-3 py-1.5 text-sm font-medium dark:border-gray-600 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
{showCreateForm ? 'Cancel' : 'Create Key +'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showCreateForm}
|
||||||
|
<form method="POST" action="?/createKey" use:enhance class="mb-6 border border-gray-200 p-4 dark:border-gray-700">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||||
|
Create New License Key
|
||||||
|
</h2>
|
||||||
|
{#if createError}
|
||||||
|
<div class="mb-3 border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||||
|
{createError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<label for="email" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
User Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
class="w-full border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
||||||
|
placeholder="user@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<input type="checkbox" name="deactivateOld" class="h-4 w-4" />
|
||||||
|
Deactivate existing active keys for this user
|
||||||
|
</label>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="border border-gray-300 bg-gray-100 px-4 py-1.5 text-sm font-medium hover:bg-gray-200 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Create & Email Key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<AdminTable
|
||||||
|
records={data.records}
|
||||||
|
listFields={config.listFields}
|
||||||
|
searchFields={config.searchFields}
|
||||||
|
fieldTypes={config.fieldTypes}
|
||||||
|
singular={config.displayNameSingular}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if toastMessage}
|
||||||
|
<Toast message={toastMessage} type={toastType} />
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('proclamation-categories')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="proclamation-categories" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('proclamations')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="proclamations" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { getModelConfig } from '$lib/svelte-admin/registry';
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
import {
|
||||||
|
loadRecords,
|
||||||
|
upsertRecord,
|
||||||
|
deleteRecord,
|
||||||
|
superFormErrorsToFieldErrors
|
||||||
|
} from '$lib/svelte-admin/crud';
|
||||||
import { prisma } from '$lib/db';
|
import { prisma } from '$lib/db';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { superValidate } from 'sveltekit-superforms';
|
||||||
|
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||||
|
import * as Schemas from '$lib/svelte-admin/schemas';
|
||||||
|
|
||||||
const config = getModelConfig('profiles')!;
|
const config = getModelConfig('profiles')!;
|
||||||
|
|
||||||
@@ -12,67 +20,73 @@ export const load: PageServerLoad = async ({ locals }) => {
|
|||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
upsert: async ({ request, locals }) => {
|
upsert: async ({ request, locals }) => {
|
||||||
const formData = await request.formData();
|
return upsertRecord(request, config, locals);
|
||||||
return upsertRecord(formData, config, locals);
|
|
||||||
},
|
},
|
||||||
delete: async ({ request, locals }) => {
|
delete: async ({ request, locals }) => {
|
||||||
const formData = await request.formData();
|
return deleteRecord(request, config, locals);
|
||||||
return deleteRecord(formData, config, locals);
|
|
||||||
},
|
},
|
||||||
upsertAddress: async ({ request, locals }) => {
|
upsertAddress: async ({ request, locals }) => {
|
||||||
if (!locals.user) throw error(401, 'Unauthorized');
|
if (!locals.user) throw error(401, 'Unauthorized');
|
||||||
|
|
||||||
const formData = await request.formData();
|
const form = await superValidate(request, zod4(Schemas.Address));
|
||||||
const id = formData.get('id')?.toString();
|
if (!form.valid) {
|
||||||
const profileId = formData.get('profileId')?.toString();
|
const { error: addrError, fieldErrors } = superFormErrorsToFieldErrors(
|
||||||
|
form.errors as Record<string, unknown>
|
||||||
|
);
|
||||||
|
return { success: false, addressError: addrError, addressFieldErrors: fieldErrors };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { profileId, isDefaultMailing, isDefaultBilling, isDefaultShipping, ...addressData } =
|
||||||
|
form.data;
|
||||||
|
|
||||||
if (!profileId) {
|
if (!profileId) {
|
||||||
return { success: false, addressError: 'Missing profile ID' };
|
return { success: false, addressError: 'Missing profile ID' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = formData.get('type')?.toString() || 'Mailing';
|
// Unique-default enforcement: clear defaults of same type before setting
|
||||||
const street = formData.get('street')?.toString() || '';
|
if (isDefaultMailing) {
|
||||||
const city = formData.get('city')?.toString() || '';
|
|
||||||
const state = formData.get('state')?.toString() || '';
|
|
||||||
const postalCode = formData.get('postalCode')?.toString() || '';
|
|
||||||
const country = formData.get('country')?.toString() || '';
|
|
||||||
const isDefault = formData.get('isDefault') === 'on';
|
|
||||||
|
|
||||||
if (!street || !city || !state || !postalCode || !country) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
addressError: 'All address fields are required'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefault) {
|
|
||||||
await prisma.address.updateMany({
|
await prisma.address.updateMany({
|
||||||
where: { profileId, type, isDefault: true },
|
where: { profileId, isDefaultMailing: true },
|
||||||
data: { isDefault: false }
|
data: { isDefaultMailing: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isDefaultBilling) {
|
||||||
|
await prisma.address.updateMany({
|
||||||
|
where: { profileId, isDefaultBilling: true },
|
||||||
|
data: { isDefaultBilling: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isDefaultShipping) {
|
||||||
|
await prisma.address.updateMany({
|
||||||
|
where: { profileId, isDefaultShipping: true },
|
||||||
|
data: { isDefaultShipping: false }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
const payload = {
|
||||||
const updated = await prisma.address.update({
|
...addressData,
|
||||||
where: { id },
|
profileId,
|
||||||
data: { type, street, city, state, postalCode, country, isDefault }
|
isDefaultMailing,
|
||||||
});
|
isDefaultBilling,
|
||||||
return { success: true, address: updated };
|
isDefaultShipping
|
||||||
}
|
};
|
||||||
|
|
||||||
const created = await prisma.address.create({
|
const created = await prisma.address.create({ data: payload });
|
||||||
data: { profileId, type, street, city, state, postalCode, country, isDefault }
|
|
||||||
});
|
|
||||||
return { success: true, address: created };
|
return { success: true, address: created };
|
||||||
},
|
},
|
||||||
updateUser: async ({ request, locals }) => {
|
updateUser: async ({ request, locals }) => {
|
||||||
if (!locals.user) throw error(401, 'Unauthorized');
|
if (!locals.user) throw error(401, 'Unauthorized');
|
||||||
|
|
||||||
const formData = await request.formData();
|
const form = await superValidate(request, zod4(Schemas.UserUpdate));
|
||||||
const userId = formData.get('id')?.toString();
|
if (!form.valid) {
|
||||||
const email = formData.get('email')?.toString();
|
const { error: userErr, fieldErrors } = superFormErrorsToFieldErrors(
|
||||||
const name = formData.get('name')?.toString();
|
form.errors as Record<string, unknown>
|
||||||
|
);
|
||||||
|
return { success: false, userError: userErr, userFieldErrors: fieldErrors };
|
||||||
|
}
|
||||||
|
|
||||||
if (!userId) {
|
const { id, email, name } = form.data;
|
||||||
|
if (!id) {
|
||||||
return { success: false, userError: 'Missing user ID' };
|
return { success: false, userError: 'Missing user ID' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +97,7 @@ export const actions = {
|
|||||||
return { success: false, userError: 'No fields to update' };
|
return { success: false, userError: 'No fields to update' };
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.user.update({ where: { id: userId }, data });
|
await prisma.user.update({ where: { id }, data });
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,8 +30,9 @@
|
|||||||
state: '',
|
state: '',
|
||||||
postalCode: '',
|
postalCode: '',
|
||||||
country: '',
|
country: '',
|
||||||
type: 'Mailing',
|
isDefaultMailing: 'false',
|
||||||
isDefault: 'false'
|
isDefaultBilling: 'false',
|
||||||
|
isDefaultShipping: 'false'
|
||||||
});
|
});
|
||||||
|
|
||||||
let userEmail = $state('');
|
let userEmail = $state('');
|
||||||
@@ -44,11 +45,9 @@
|
|||||||
} else if ((form as any).userError) {
|
} else if ((form as any).userError) {
|
||||||
userError = (form as any).userError;
|
userError = (form as any).userError;
|
||||||
} else if (form.success) {
|
} else if (form.success) {
|
||||||
toastMessage = (form as any).addressError
|
|
||||||
? 'Address saved'
|
|
||||||
: 'Saved successfully';
|
|
||||||
toastType = 'success';
|
toastType = 'success';
|
||||||
if ((form as any).address) {
|
if ((form as any).address) {
|
||||||
|
toastMessage = 'Address saved';
|
||||||
const addr = (form as any).address as Record<string, unknown>;
|
const addr = (form as any).address as Record<string, unknown>;
|
||||||
if (editingRecord) {
|
if (editingRecord) {
|
||||||
const addrs = (editingRecord.addresses as Record<string, unknown>[]) ?? [];
|
const addrs = (editingRecord.addresses as Record<string, unknown>[]) ?? [];
|
||||||
@@ -59,7 +58,8 @@
|
|||||||
}
|
}
|
||||||
addressFormOpen = false;
|
addressFormOpen = false;
|
||||||
addressError = '';
|
addressError = '';
|
||||||
} else if (!(form as any).addressError && !(form as any).userError) {
|
} else {
|
||||||
|
toastMessage = 'Saved successfully';
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
} else if (!(form as any).addressError && !(form as any).userError) {
|
} else if (!(form as any).addressError && !(form as any).userError) {
|
||||||
@@ -86,8 +86,8 @@
|
|||||||
userFormOpen = false;
|
userFormOpen = false;
|
||||||
addressError = '';
|
addressError = '';
|
||||||
userError = '';
|
userError = '';
|
||||||
userEmail = (record.user as Record<string, unknown>)?.email as string ?? '';
|
userEmail = ((record.user as Record<string, unknown>)?.email as string) ?? '';
|
||||||
userName = (record.user as Record<string, unknown>)?.name as string ?? '';
|
userName = ((record.user as Record<string, unknown>)?.name as string) ?? '';
|
||||||
modalOpen = true;
|
modalOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +162,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mb-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{user?.name as string ?? ''} ({user?.email as string ?? ''})
|
{(user?.name as string) ?? ''} ({(user?.email as string) ?? ''})
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{#if userFormOpen}
|
{#if userFormOpen}
|
||||||
<form method="POST" action="?/updateUser" use:enhance class="mt-3 rounded border border-gray-200 p-3 dark:border-gray-700">
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/updateUser"
|
||||||
|
use:enhance
|
||||||
|
class="mt-3 rounded border border-gray-200 p-3 dark:border-gray-700"
|
||||||
|
>
|
||||||
<input type="hidden" name="id" value={String(user?.id ?? '')} />
|
<input type="hidden" name="id" value={String(user?.id ?? '')} />
|
||||||
{#if userError}
|
{#if userError}
|
||||||
<div class="mb-2 border border-red-300 bg-red-50 px-3 py-1.5 text-sm text-red-700">
|
<div class="mb-2 border border-red-300 bg-red-50 px-3 py-1.5 text-sm text-red-700">
|
||||||
@@ -175,7 +180,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label for="user-name" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="user-name"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -187,7 +195,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="user-email" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="user-email"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -227,7 +238,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if addressFormOpen}
|
{#if addressFormOpen}
|
||||||
<form method="POST" action="?/upsertAddress" use:enhance class="mb-3 rounded border border-gray-200 p-3 dark:border-gray-700">
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/upsertAddress"
|
||||||
|
use:enhance
|
||||||
|
class="mb-3 rounded border border-gray-200 p-3 dark:border-gray-700"
|
||||||
|
>
|
||||||
<input type="hidden" name="profileId" value={String(editingRecord.id)} />
|
<input type="hidden" name="profileId" value={String(editingRecord.id)} />
|
||||||
{#if addressError}
|
{#if addressError}
|
||||||
<div class="mb-2 border border-red-300 bg-red-50 px-3 py-1.5 text-sm text-red-700">
|
<div class="mb-2 border border-red-300 bg-red-50 px-3 py-1.5 text-sm text-red-700">
|
||||||
@@ -236,21 +252,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label for="addr-type" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
Type
|
for="addr-street"
|
||||||
</label>
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
<select
|
|
||||||
id="addr-type"
|
|
||||||
name="type"
|
|
||||||
class="w-full border border-gray-300 px-2 py-1 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
|
||||||
>
|
>
|
||||||
<option value="Mailing">Mailing</option>
|
|
||||||
<option value="Billing">Billing</option>
|
|
||||||
<option value="Shipping">Shipping</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addr-street" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
|
||||||
Street
|
Street
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -262,7 +267,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="addr-city" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="addr-city"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
City
|
City
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -274,7 +282,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="addr-state" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="addr-state"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
State
|
State
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -286,7 +297,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="addr-postal" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="addr-postal"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
Postal Code
|
Postal Code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -298,7 +312,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="addr-country" class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
<label
|
||||||
|
for="addr-country"
|
||||||
|
class="mb-0.5 block text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
|
>
|
||||||
Country
|
Country
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -310,10 +327,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="mt-2 flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
<div class="mt-2 flex flex-wrap gap-4">
|
||||||
<input type="checkbox" name="isDefault" class="h-3.5 w-3.5" />
|
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||||
Set as default for this type
|
<input type="checkbox" name="isDefaultMailing" class="h-3.5 w-3.5" />
|
||||||
</label>
|
Default Mailing
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
<input type="checkbox" name="isDefaultBilling" class="h-3.5 w-3.5" />
|
||||||
|
Default Billing
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
<input type="checkbox" name="isDefaultShipping" class="h-3.5 w-3.5" />
|
||||||
|
Default Shipping
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="mt-2 flex justify-end">
|
<div class="mt-2 flex justify-end">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -329,25 +356,30 @@
|
|||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Street</th
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Street</th>
|
>
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">City</th>
|
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">City</th>
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">State</th>
|
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">State</th>
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Zip</th>
|
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Zip</th>
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Country</th>
|
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase"
|
||||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 uppercase">Default</th>
|
>Country</th
|
||||||
|
>
|
||||||
|
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 uppercase">M</th>
|
||||||
|
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 uppercase">B</th>
|
||||||
|
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 uppercase">S</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each addresses as addr}
|
{#each addresses as addr}
|
||||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||||
<td class="px-2 py-1 text-xs">{addr.type as string}</td>
|
|
||||||
<td class="px-2 py-1">{addr.street as string}</td>
|
<td class="px-2 py-1">{addr.street as string}</td>
|
||||||
<td class="px-2 py-1">{addr.city as string}</td>
|
<td class="px-2 py-1">{addr.city as string}</td>
|
||||||
<td class="px-2 py-1">{addr.state as string}</td>
|
<td class="px-2 py-1">{addr.state as string}</td>
|
||||||
<td class="px-2 py-1">{addr.postalCode as string}</td>
|
<td class="px-2 py-1">{addr.postalCode as string}</td>
|
||||||
<td class="px-2 py-1">{addr.country as string}</td>
|
<td class="px-2 py-1">{addr.country as string}</td>
|
||||||
<td class="px-2 py-1">{addr.isDefault ? 'Yes' : ''}</td>
|
<td class="px-2 py-1 text-center text-xs">{addr.isDefaultMailing ? '✓' : ''}</td>
|
||||||
|
<td class="px-2 py-1 text-center text-xs">{addr.isDefaultBilling ? '✓' : ''}</td>
|
||||||
|
<td class="px-2 py-1 text-center text-xs">{addr.isDefaultShipping ? '✓' : ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('solfeggio-frequencies')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="solfeggio-frequencies" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('subscription-levels')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="subscription-levels" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('user-roles')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="user-roles" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
|
import { getModelConfig } from '$lib/svelte-admin/registry';
|
||||||
|
import { loadRecords, upsertRecord, deleteRecord } from '$lib/svelte-admin/crud';
|
||||||
|
|
||||||
|
const config = getModelConfig('users')!;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return loadRecords(locals, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
upsert: async ({ request, locals }) => {
|
||||||
|
return upsertRecord(request, config, locals);
|
||||||
|
},
|
||||||
|
delete: async ({ request, locals }) => {
|
||||||
|
return deleteRecord(request, config, locals);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import AdminPage from '$lib/components/admin/AdminPage.svelte';
|
||||||
|
|
||||||
|
let { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AdminPage modelSlug="users" {data} {form} adminRoleId={data.adminUser?.roleId ?? 4} />
|
||||||
@@ -25,7 +25,9 @@ describe('(protected) layout server load', () => {
|
|||||||
it('throws 403 when locals.user is undefined', async () => {
|
it('throws 403 when locals.user is undefined', async () => {
|
||||||
findUnique.mockResolvedValue(null);
|
findUnique.mockResolvedValue(null);
|
||||||
|
|
||||||
const err = await Promise.resolve(load({ locals: createLocals() } as any)).catch((e: unknown) => e);
|
const err = await Promise.resolve(load({ locals: createLocals() } as any)).catch(
|
||||||
|
(e: unknown) => e
|
||||||
|
);
|
||||||
expect(isHttpError(err, 403)).toBe(true);
|
expect(isHttpError(err, 403)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,11 @@
|
|||||||
<Window>
|
<Window>
|
||||||
{#if $page.status === 403}
|
{#if $page.status === 403}
|
||||||
<h1 class="dark:text-gray-100">Not Authorized</h1>
|
<h1 class="dark:text-gray-100">Not Authorized</h1>
|
||||||
<p class="dark:text-gray-300">
|
<p class="dark:text-gray-300">
|
||||||
You don't have permission to view this page. <a href="/" class="dark:text-gray-400 dark:hover:text-gray-200">Go home</a>.
|
You don't have permission to view this page. <a
|
||||||
</p>
|
href="/"
|
||||||
|
class="dark:text-gray-400 dark:hover:text-gray-200">Go home</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import type { RequestHandler } from "./$types";
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export const POST: RequestHandler = async () => {
|
export const POST: RequestHandler = async () => {
|
||||||
// Request validation (400)
|
// Request validation (400)
|
||||||
// 1. Validate and extract information
|
// 1. Validate and extract information
|
||||||
|
|
||||||
// Business rule validation (409)
|
// Business rule validation (409)
|
||||||
// 2. Check for existing entries
|
// 2. Check for existing entries
|
||||||
|
|
||||||
// Resource creation (500 on unexpected failures)
|
// Resource creation (500 on unexpected failures)
|
||||||
// 3. Create new entries
|
// 3. Create new entries
|
||||||
// 5. Create license key
|
// 5. Create license key
|
||||||
|
|
||||||
// External operations (502/503/504 or 500, depending on your API)
|
// External operations (502/503/504 or 500, depending on your API)
|
||||||
// 4. Send profile setup email
|
// 4. Send profile setup email
|
||||||
// 6. Send license key email
|
// 6. Send license key email
|
||||||
|
|
||||||
// Success (200/201)
|
// Success (200/201)
|
||||||
// 7. Send response
|
// 7. Send response
|
||||||
return new Response(JSON.stringify({ "msg": "success" }))
|
return new Response(JSON.stringify({ msg: 'success' }));
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import type { RequestHandler } from './$types';
|
|||||||
import { prisma } from '$lib/db';
|
import { prisma } from '$lib/db';
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => {
|
export const GET: RequestHandler = async () => {
|
||||||
const categories = await prisma.proclamationCategory.findMany()
|
const categories = await prisma.proclamationCategory.findMany();
|
||||||
return new Response(JSON.stringify(categories))
|
return new Response(JSON.stringify(categories));
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import type { RequestHandler } from './$types';
|
|||||||
import { prisma } from '$lib/db';
|
import { prisma } from '$lib/db';
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => {
|
export const GET: RequestHandler = async () => {
|
||||||
const frequencies = await prisma.solfeggioFrequency.findMany()
|
const frequencies = await prisma.solfeggioFrequency.findMany();
|
||||||
return new Response(JSON.stringify(frequencies))
|
return new Response(JSON.stringify(frequencies));
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -9,13 +9,17 @@
|
|||||||
<h1 class="mb-6 text-2xl font-semibold text-gray-900 dark:text-gray-100">Forgot Password</h1>
|
<h1 class="mb-6 text-2xl font-semibold text-gray-900 dark:text-gray-100">Forgot Password</h1>
|
||||||
|
|
||||||
{#if form?.success}
|
{#if form?.success}
|
||||||
<div class="mb-4 rounded-lg border border-green-200 bg-green-50 p-4 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-green-200 bg-green-50 p-4 text-green-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
If an account with that email exists, we've sent a password reset link.
|
If an account with that email exists, we've sent a password reset link.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if form?.error && !form?.success}
|
{#if form?.error && !form?.success}
|
||||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
{form.error}
|
{form.error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -36,7 +40,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href={resolve('/login')} class="mt-4 block text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200">
|
<a
|
||||||
|
href={resolve('/login')}
|
||||||
|
class="mt-4 block text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
Back to Login
|
Back to Login
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+65
-65
@@ -10,68 +10,68 @@
|
|||||||
/* Use var(--color-gray-N) so dark-mode inversion flows through */
|
/* Use var(--color-gray-N) so dark-mode inversion flows through */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@theme {
|
@theme {
|
||||||
--color-blue-50: var(--color-gray-100);
|
--color-blue-50: var(--color-gray-100);
|
||||||
--color-blue-100: var(--color-gray-200);
|
--color-blue-100: var(--color-gray-200);
|
||||||
--color-blue-200: var(--color-gray-300);
|
--color-blue-200: var(--color-gray-300);
|
||||||
--color-blue-300: var(--color-gray-400);
|
--color-blue-300: var(--color-gray-400);
|
||||||
--color-blue-400: var(--color-gray-500);
|
--color-blue-400: var(--color-gray-500);
|
||||||
--color-blue-500: var(--color-gray-500);
|
--color-blue-500: var(--color-gray-500);
|
||||||
--color-blue-600: var(--color-gray-600);
|
--color-blue-600: var(--color-gray-600);
|
||||||
--color-blue-700: var(--color-gray-700);
|
--color-blue-700: var(--color-gray-700);
|
||||||
--color-blue-800: var(--color-gray-800);
|
--color-blue-800: var(--color-gray-800);
|
||||||
--color-green-50: var(--color-gray-100);
|
--color-green-50: var(--color-gray-100);
|
||||||
--color-green-100: var(--color-gray-200);
|
--color-green-100: var(--color-gray-200);
|
||||||
--color-green-200: var(--color-gray-300);
|
--color-green-200: var(--color-gray-300);
|
||||||
--color-green-300: var(--color-gray-400);
|
--color-green-300: var(--color-gray-400);
|
||||||
--color-green-400: var(--color-gray-500);
|
--color-green-400: var(--color-gray-500);
|
||||||
--color-green-500: var(--color-gray-500);
|
--color-green-500: var(--color-gray-500);
|
||||||
--color-green-600: var(--color-gray-600);
|
--color-green-600: var(--color-gray-600);
|
||||||
--color-green-700: var(--color-gray-700);
|
--color-green-700: var(--color-gray-700);
|
||||||
--color-green-800: var(--color-gray-800);
|
--color-green-800: var(--color-gray-800);
|
||||||
--color-red-50: var(--color-gray-100);
|
--color-red-50: var(--color-gray-100);
|
||||||
--color-red-100: var(--color-gray-200);
|
--color-red-100: var(--color-gray-200);
|
||||||
--color-red-200: var(--color-gray-300);
|
--color-red-200: var(--color-gray-300);
|
||||||
--color-red-300: var(--color-gray-400);
|
--color-red-300: var(--color-gray-400);
|
||||||
--color-red-400: var(--color-gray-500);
|
--color-red-400: var(--color-gray-500);
|
||||||
--color-red-500: var(--color-gray-500);
|
--color-red-500: var(--color-gray-500);
|
||||||
--color-red-600: var(--color-gray-600);
|
--color-red-600: var(--color-gray-600);
|
||||||
--color-red-700: var(--color-gray-700);
|
--color-red-700: var(--color-gray-700);
|
||||||
--color-red-800: var(--color-gray-800);
|
--color-red-800: var(--color-gray-800);
|
||||||
--color-purple-50: var(--color-gray-100);
|
--color-purple-50: var(--color-gray-100);
|
||||||
--color-purple-100: var(--color-gray-200);
|
--color-purple-100: var(--color-gray-200);
|
||||||
--color-purple-200: var(--color-gray-300);
|
--color-purple-200: var(--color-gray-300);
|
||||||
--color-purple-300: var(--color-gray-400);
|
--color-purple-300: var(--color-gray-400);
|
||||||
--color-purple-400: var(--color-gray-500);
|
--color-purple-400: var(--color-gray-500);
|
||||||
--color-purple-500: var(--color-gray-500);
|
--color-purple-500: var(--color-gray-500);
|
||||||
--color-purple-600: var(--color-gray-600);
|
--color-purple-600: var(--color-gray-600);
|
||||||
--color-purple-700: var(--color-gray-700);
|
--color-purple-700: var(--color-gray-700);
|
||||||
--color-purple-800: var(--color-gray-800);
|
--color-purple-800: var(--color-gray-800);
|
||||||
--color-purple-900: var(--color-gray-900);
|
--color-purple-900: var(--color-gray-900);
|
||||||
--color-slate-50: var(--color-gray-50);
|
--color-slate-50: var(--color-gray-50);
|
||||||
--color-slate-100: var(--color-gray-100);
|
--color-slate-100: var(--color-gray-100);
|
||||||
--color-slate-200: var(--color-gray-200);
|
--color-slate-200: var(--color-gray-200);
|
||||||
--color-slate-300: var(--color-gray-300);
|
--color-slate-300: var(--color-gray-300);
|
||||||
--color-slate-400: var(--color-gray-400);
|
--color-slate-400: var(--color-gray-400);
|
||||||
--color-slate-500: var(--color-gray-500);
|
--color-slate-500: var(--color-gray-500);
|
||||||
--color-slate-600: var(--color-gray-600);
|
--color-slate-600: var(--color-gray-600);
|
||||||
--color-slate-700: var(--color-gray-700);
|
--color-slate-700: var(--color-gray-700);
|
||||||
--color-slate-800: var(--color-gray-800);
|
--color-slate-800: var(--color-gray-800);
|
||||||
--color-slate-900: var(--color-gray-900);
|
--color-slate-900: var(--color-gray-900);
|
||||||
|
|
||||||
--radius: 0px;
|
--radius: 0px;
|
||||||
--radius-sm: 0px;
|
--radius-sm: 0px;
|
||||||
--radius-md: 0px;
|
--radius-md: 0px;
|
||||||
--radius-lg: 0px;
|
--radius-lg: 0px;
|
||||||
--radius-xl: 0px;
|
--radius-xl: 0px;
|
||||||
--radius-2xl: 0px;
|
--radius-2xl: 0px;
|
||||||
--radius-3xl: 0px;
|
--radius-3xl: 0px;
|
||||||
|
|
||||||
--shadow-sm: 0 0 #0000;
|
--shadow-sm: 0 0 #0000;
|
||||||
--shadow: 0 0 #0000;
|
--shadow: 0 0 #0000;
|
||||||
--shadow-md: 0 0 #0000;
|
--shadow-md: 0 0 #0000;
|
||||||
--shadow-lg: 0 0 #0000;
|
--shadow-lg: 0 0 #0000;
|
||||||
--shadow-xl: 0 0 #0000;
|
--shadow-xl: 0 0 #0000;
|
||||||
--shadow-2xl: 0 0 #0000;
|
--shadow-2xl: 0 0 #0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -80,9 +80,9 @@
|
|||||||
.dark input,
|
.dark input,
|
||||||
.dark select,
|
.dark select,
|
||||||
.dark textarea {
|
.dark textarea {
|
||||||
background-color: #1f2937;
|
background-color: #1f2937;
|
||||||
color: #f9fafb;
|
color: #f9fafb;
|
||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -91,6 +91,6 @@
|
|||||||
*,
|
*,
|
||||||
::before,
|
::before,
|
||||||
::after {
|
::after {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,20 +37,24 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<span class="text-red-600 dark:text-gray-300">{error}</span>
|
<span class="text-red-600 dark:text-gray-300">{error}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<form
|
<form onsubmit={handleSubmit} class="my-6 flex flex-col items-center justify-center">
|
||||||
onsubmit={handleSubmit}
|
|
||||||
class="my-6 flex flex-col items-center justify-center"
|
|
||||||
>
|
|
||||||
<p>Login</p>
|
<p>Login</p>
|
||||||
<input type="email" placeholder="Email Address" bind:value={email} required class="my-2" />
|
<input type="email" placeholder="Email Address" bind:value={email} required class="my-2" />
|
||||||
<input type="password" placeholder="Password" bind:value={password} required class="my-2" />
|
<input type="password" placeholder="Password" bind:value={password} required class="my-2" />
|
||||||
<button class="mt-4 rounded bg-blue-200 px-4 py-2 dark:bg-gray-700 dark:text-gray-100" type="submit">Sign In</button>
|
<button
|
||||||
|
class="mt-4 rounded bg-blue-200 px-4 py-2 dark:bg-gray-700 dark:text-gray-100"
|
||||||
|
type="submit">Sign In</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
<div class="mt-2 flex flex-col items-center gap-2">
|
<div class="mt-2 flex flex-col items-center gap-2">
|
||||||
<a href="/forgot-password" class="text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200"
|
<a
|
||||||
|
href="/forgot-password"
|
||||||
|
class="text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
>Forgot Password?</a
|
>Forgot Password?</a
|
||||||
>
|
>
|
||||||
<a href="/register" class="text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200"
|
<a
|
||||||
|
href="/register"
|
||||||
|
class="text-sm text-blue-600 hover:text-blue-800 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
>Don't have an account? Register</a
|
>Don't have an account? Register</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col items-start justify-center">
|
<div class="flex flex-col items-start justify-center">
|
||||||
{#if form?.error}
|
{#if form?.error}
|
||||||
<div class="mb-4 w-full rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 w-full rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
{form.error}
|
{form.error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -32,8 +34,16 @@
|
|||||||
<input type="text" name="lastName" placeholder="Last Name" required class="mb-2" />
|
<input type="text" name="lastName" placeholder="Last Name" required class="mb-2" />
|
||||||
<input type="email" name="email" placeholder="Email Address" required class="mb-2" />
|
<input type="email" name="email" placeholder="Email Address" required class="mb-2" />
|
||||||
<input type="password" name="password" placeholder="Password" required class="mb-2" />
|
<input type="password" name="password" placeholder="Password" required class="mb-2" />
|
||||||
<input type="password" name="password2" placeholder="Verify Password" required class="mb-2" />
|
<input
|
||||||
<button type="submit" class="mx-auto rounded-full bg-blue-600 px-6 py-2 text-white dark:bg-gray-700 dark:text-gray-100"
|
type="password"
|
||||||
|
name="password2"
|
||||||
|
placeholder="Verify Password"
|
||||||
|
required
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="mx-auto rounded-full bg-blue-600 px-6 py-2 text-white dark:bg-gray-700 dark:text-gray-100"
|
||||||
>Submit</button
|
>Submit</button
|
||||||
>
|
>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
<h1 class="mb-6 text-2xl font-semibold text-gray-900 dark:text-gray-100">Reset Password</h1>
|
<h1 class="mb-6 text-2xl font-semibold text-gray-900 dark:text-gray-100">Reset Password</h1>
|
||||||
|
|
||||||
{#if form?.error}
|
{#if form?.error}
|
||||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
<div
|
||||||
|
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||||
|
>
|
||||||
{form.error}
|
{form.error}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ describe('reset-password page server action', () => {
|
|||||||
resetPassword.mockReset();
|
resetPassword.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
const invokeDefault = (url: URL, request: Request) =>
|
const invokeDefault = (url: URL, request: Request) => actions.default({ request, url } as any);
|
||||||
actions.default({ request, url } as any);
|
|
||||||
|
|
||||||
it('returns fail(400) when token is missing', async () => {
|
it('returns fail(400) when token is missing', async () => {
|
||||||
const url = createUrl('/reset-password');
|
const url = createUrl('/reset-password');
|
||||||
@@ -120,10 +119,7 @@ describe('reset-password page server action', () => {
|
|||||||
it('returns fail(400) when password is empty', async () => {
|
it('returns fail(400) when password is empty', async () => {
|
||||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||||
|
|
||||||
const result = await invokeDefault(
|
const result = await invokeDefault(url, createFormRequest({ password: '', password2: '' }));
|
||||||
url,
|
|
||||||
createFormRequest({ password: '', password2: '' })
|
|
||||||
);
|
|
||||||
expect(isActionFailure(result)).toBe(true);
|
expect(isActionFailure(result)).toBe(true);
|
||||||
if (isActionFailure(result)) {
|
if (isActionFailure(result)) {
|
||||||
expect(result.data).toEqual({ error: 'Password must be at least 8 characters.' });
|
expect(result.data).toEqual({ error: 'Password must be at least 8 characters.' });
|
||||||
@@ -181,10 +177,7 @@ describe('reset-password page server action', () => {
|
|||||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||||
|
|
||||||
const err = await Promise.resolve(
|
const err = await Promise.resolve(
|
||||||
invokeDefault(
|
invokeDefault(url, createFormRequest({ password: 'longenough1', password2: 'longenough1' }))
|
||||||
url,
|
|
||||||
createFormRequest({ password: 'longenough1', password2: 'longenough1' })
|
|
||||||
)
|
|
||||||
).catch((e: unknown) => e);
|
).catch((e: unknown) => e);
|
||||||
expect(isRedirect(err)).toBe(true);
|
expect(isRedirect(err)).toBe(true);
|
||||||
if (isRedirect(err)) {
|
if (isRedirect(err)) {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import { sendMail } from '$lib/server/resend';
|
|||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request }) => {
|
export const POST: RequestHandler = async ({ request }) => {
|
||||||
const { to, subject, templateId, data } = await request.json();
|
const { to, subject, templateId, data } = await request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await sendMail({ to, subject, templateId, data });
|
const result = await sendMail({ to, subject, templateId, data });
|
||||||
return json(result);
|
return json(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return json({ error: (e as Error).message }, { status: 400 });
|
return json({ error: (e as Error).message }, { status: 400 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user