includio-cms 0.36.9 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +178 -3
- package/CHANGELOG.md +54 -0
- package/DOCS.md +28 -3
- package/ROADMAP.md +18 -0
- package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
- package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
- package/dist/admin/client/booking/customer-panel.svelte +102 -0
- package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
- package/dist/admin/client/booking/customer-view.d.ts +52 -0
- package/dist/admin/client/booking/customer-view.js +134 -0
- package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
- package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
- package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
- package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
- package/dist/admin/client/booking/reservations-page.svelte +221 -0
- package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
- package/dist/admin/client/collection/collection-entries.svelte +1 -1
- package/dist/admin/client/collection/entry-link.svelte +6 -4
- package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
- package/dist/admin/client/collection/status-badge.svelte +39 -3
- package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
- package/dist/admin/client/entry/entry.svelte +26 -9
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
- package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
- package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
- package/dist/admin/client/entry/preview-views.d.ts +27 -0
- package/dist/admin/client/entry/preview-views.js +33 -0
- package/dist/admin/client/index.d.ts +3 -0
- package/dist/admin/client/index.js +3 -0
- package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
- package/dist/admin/client/modules/dashboard-registry.js +11 -0
- package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
- package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
- package/dist/admin/client/modules/stat-card.svelte +22 -0
- package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
- package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
- package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
- package/dist/admin/components/fields/relation-field.svelte +11 -2
- package/dist/admin/components/icons/icon-map.js +8 -0
- package/dist/admin/components/layout/app-sidebar.svelte +45 -14
- package/dist/admin/components/layout/context-switcher.svelte +90 -0
- package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
- package/dist/admin/components/layout/lang.d.ts +4 -0
- package/dist/admin/components/layout/lang.js +8 -0
- package/dist/admin/components/layout/layout-renderer.svelte +2 -1
- package/dist/admin/components/layout/layout-tabs.svelte +14 -0
- package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
- package/dist/admin/components/layout/nav-collections.svelte +16 -9
- package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
- package/dist/admin/components/layout/nav-global.svelte +45 -0
- package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
- package/dist/admin/components/layout/nav-main.svelte +8 -32
- package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
- package/dist/admin/components/layout/nav-module.svelte +57 -0
- package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
- package/dist/admin/components/layout/nav-section.svelte +1 -1
- package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
- package/dist/admin/components/layout/nav-singletons.svelte +14 -7
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
- package/dist/admin/remote/booking.remote.d.ts +244 -0
- package/dist/admin/remote/booking.remote.js +283 -0
- package/dist/admin/remote/index.d.ts +2 -0
- package/dist/admin/remote/index.js +2 -0
- package/dist/admin/remote/modules.remote.d.ts +10 -0
- package/dist/admin/remote/modules.remote.js +14 -0
- package/dist/admin/state/active-context-resolve.d.ts +37 -0
- package/dist/admin/state/active-context-resolve.js +66 -0
- package/dist/admin/state/active-context.svelte.d.ts +8 -0
- package/dist/admin/state/active-context.svelte.js +27 -0
- package/dist/booking/adjustments.d.ts +33 -0
- package/dist/booking/adjustments.js +26 -0
- package/dist/booking/capacity.d.ts +12 -0
- package/dist/booking/capacity.js +10 -0
- package/dist/booking/client/index.d.ts +39 -0
- package/dist/booking/client/index.js +44 -0
- package/dist/booking/client/use-booking.svelte.d.ts +25 -0
- package/dist/booking/client/use-booking.svelte.js +56 -0
- package/dist/booking/config.d.ts +156 -0
- package/dist/booking/config.js +36 -0
- package/dist/booking/draft.d.ts +77 -0
- package/dist/booking/draft.js +67 -0
- package/dist/booking/expiry-logic.d.ts +30 -0
- package/dist/booking/expiry-logic.js +33 -0
- package/dist/booking/index.d.ts +18 -0
- package/dist/booking/index.js +52 -0
- package/dist/booking/ledger.d.ts +46 -0
- package/dist/booking/ledger.js +60 -0
- package/dist/booking/number.d.ts +2 -0
- package/dist/booking/number.js +11 -0
- package/dist/booking/participant-fields/access.d.ts +10 -0
- package/dist/booking/participant-fields/access.js +14 -0
- package/dist/booking/participant-fields/age.d.ts +12 -0
- package/dist/booking/participant-fields/age.js +28 -0
- package/dist/booking/participant-fields/completeness.d.ts +8 -0
- package/dist/booking/participant-fields/completeness.js +13 -0
- package/dist/booking/participant-fields/group.d.ts +6 -0
- package/dist/booking/participant-fields/group.js +15 -0
- package/dist/booking/participant-fields/index.d.ts +6 -0
- package/dist/booking/participant-fields/index.js +6 -0
- package/dist/booking/participant-fields/types.d.ts +50 -0
- package/dist/booking/participant-fields/types.js +5 -0
- package/dist/booking/participant-fields/validate.d.ts +7 -0
- package/dist/booking/participant-fields/validate.js +44 -0
- package/dist/booking/participant-pricing.d.ts +93 -0
- package/dist/booking/participant-pricing.js +91 -0
- package/dist/booking/participants-view.d.ts +32 -0
- package/dist/booking/participants-view.js +22 -0
- package/dist/booking/payment-context.d.ts +19 -0
- package/dist/booking/payment-context.js +29 -0
- package/dist/booking/pricing-inputs.d.ts +28 -0
- package/dist/booking/pricing-inputs.js +35 -0
- package/dist/booking/pricing.d.ts +86 -0
- package/dist/booking/pricing.js +125 -0
- package/dist/booking/recompute.d.ts +22 -0
- package/dist/booking/recompute.js +31 -0
- package/dist/booking/reminders-schedule.d.ts +11 -0
- package/dist/booking/reminders-schedule.js +28 -0
- package/dist/booking/server/adjustments.d.ts +27 -0
- package/dist/booking/server/adjustments.js +136 -0
- package/dist/booking/server/bookings.d.ts +45 -0
- package/dist/booking/server/bookings.js +288 -0
- package/dist/booking/server/catalog.d.ts +9 -0
- package/dist/booking/server/catalog.js +20 -0
- package/dist/booking/server/db.d.ts +7 -0
- package/dist/booking/server/db.js +18 -0
- package/dist/booking/server/email.d.ts +23 -0
- package/dist/booking/server/email.js +59 -0
- package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
- package/dist/booking/server/emailTemplateRegistry.js +125 -0
- package/dist/booking/server/http/create-handler.d.ts +5 -0
- package/dist/booking/server/http/create-handler.js +68 -0
- package/dist/booking/server/http/index.d.ts +4 -0
- package/dist/booking/server/http/index.js +4 -0
- package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
- package/dist/booking/server/http/maintenance-handler.js +36 -0
- package/dist/booking/server/http/portal-handler.d.ts +13 -0
- package/dist/booking/server/http/portal-handler.js +99 -0
- package/dist/booking/server/http/webhook-handler.d.ts +9 -0
- package/dist/booking/server/http/webhook-handler.js +39 -0
- package/dist/booking/server/index.d.ts +12 -0
- package/dist/booking/server/index.js +14 -0
- package/dist/booking/server/maintenance.d.ts +28 -0
- package/dist/booking/server/maintenance.js +134 -0
- package/dist/booking/server/participants.d.ts +26 -0
- package/dist/booking/server/participants.js +92 -0
- package/dist/booking/server/payments.d.ts +34 -0
- package/dist/booking/server/payments.js +207 -0
- package/dist/booking/server/portal-session.d.ts +16 -0
- package/dist/booking/server/portal-session.js +42 -0
- package/dist/booking/server/portal.d.ts +207 -0
- package/dist/booking/server/portal.js +234 -0
- package/dist/booking/server/refunds.d.ts +7 -0
- package/dist/booking/server/refunds.js +58 -0
- package/dist/booking/server/reminders.d.ts +9 -0
- package/dist/booking/server/reminders.js +24 -0
- package/dist/booking/server/validation.d.ts +17 -0
- package/dist/booking/server/validation.js +55 -0
- package/dist/booking/state-machine.d.ts +41 -0
- package/dist/booking/state-machine.js +73 -0
- package/dist/booking/templates/_partials/footer.en.html +4 -0
- package/dist/booking/templates/_partials/footer.pl.html +4 -0
- package/dist/booking/templates/_partials/header.en.html +4 -0
- package/dist/booking/templates/_partials/header.pl.html +4 -0
- package/dist/booking/templates/balance-due.en.html +10 -0
- package/dist/booking/templates/balance-due.pl.html +10 -0
- package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
- package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
- package/dist/booking/templates/booking-received.en.html +9 -0
- package/dist/booking/templates/booking-received.pl.html +9 -0
- package/dist/booking/templates/deposit-paid.en.html +6 -0
- package/dist/booking/templates/deposit-paid.pl.html +6 -0
- package/dist/booking/templates/payment-link.en.html +5 -0
- package/dist/booking/templates/payment-link.pl.html +5 -0
- package/dist/booking/templates/payment-received.en.html +10 -0
- package/dist/booking/templates/payment-received.pl.html +10 -0
- package/dist/booking/totals.d.ts +38 -0
- package/dist/booking/totals.js +61 -0
- package/dist/booking/webhook-logic.d.ts +28 -0
- package/dist/booking/webhook-logic.js +50 -0
- package/dist/cli/create-user.d.ts +7 -1
- package/dist/cli/create-user.js +23 -15
- package/dist/cli/index.js +55 -7
- package/dist/cli/scaffold/admin.d.ts +6 -0
- package/dist/cli/scaffold/admin.js +5 -3
- package/dist/cli/scaffold/booking.d.ts +12 -0
- package/dist/cli/scaffold/booking.js +103 -0
- package/dist/core/cms.d.ts +24 -9
- package/dist/core/cms.js +72 -34
- package/dist/core/config/validatePreviews.d.ts +12 -0
- package/dist/core/config/validatePreviews.js +90 -0
- package/dist/core/fields/slugPath.d.ts +9 -1
- package/dist/core/fields/slugPath.js +16 -2
- package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
- package/dist/core/server/fields/resolveRelationFields.js +12 -3
- package/dist/core/server/generator/generator.js +7 -2
- package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
- package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
- package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
- package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
- package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
- package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
- package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
- package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
- package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
- package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
- package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
- package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
- package/dist/db-postgres/schema/booking/bookings.d.ts +307 -0
- package/dist/db-postgres/schema/booking/bookings.js +40 -0
- package/dist/db-postgres/schema/booking/index.d.ts +7 -0
- package/dist/db-postgres/schema/booking/index.js +7 -0
- package/dist/db-postgres/schema/index.d.ts +1 -0
- package/dist/db-postgres/schema/index.js +1 -0
- package/dist/db-postgres/schema-booking.d.ts +1 -0
- package/dist/db-postgres/schema-booking.js +1 -0
- package/dist/server/security/rate-limit-handler.d.ts +19 -0
- package/dist/server/security/rate-limit-handler.js +34 -0
- package/dist/shop/adapters/payu/client.js +3 -1
- package/dist/shop/adapters/payu/index.js +37 -17
- package/dist/shop/adapters/payu/payload.d.ts +6 -0
- package/dist/shop/adapters/payu/payload.js +1 -1
- package/dist/shop/adapters/stripe/index.js +36 -21
- package/dist/shop/index.d.ts +11 -2
- package/dist/shop/index.js +43 -1
- package/dist/shop/rate-limit.d.ts +1 -9
- package/dist/shop/rate-limit.js +3 -28
- package/dist/shop/types.d.ts +19 -0
- package/dist/sveltekit/config.d.ts +4 -0
- package/dist/sveltekit/config.js +6 -0
- package/dist/types/cms.d.ts +15 -1
- package/dist/types/cms.schema.d.ts +96 -2
- package/dist/types/cms.schema.js +84 -1
- package/dist/types/config.d.ts +33 -0
- package/dist/types/layout.d.ts +6 -0
- package/dist/types/modules.d.ts +133 -0
- package/dist/types/modules.js +27 -0
- package/dist/updates/0.37.0/index.d.ts +2 -0
- package/dist/updates/0.37.0/index.js +52 -0
- package/dist/updates/index.js +3 -1
- package/package.json +19 -1
- package/dist/admin/components/layout/nav-shop.svelte +0 -33
- package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
package/dist/core/cms.js
CHANGED
|
@@ -5,7 +5,6 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
|
|
5
5
|
import { admin } from 'better-auth/plugins';
|
|
6
6
|
import { resetPasswordEmailTemplate } from '../admin/email/reset-password-template.js';
|
|
7
7
|
import * as authSchema from '../server/db/schema/auth-schema.js';
|
|
8
|
-
import { applyVariantAttributeIndexes } from '../shop/server/init.js';
|
|
9
8
|
export class CMS {
|
|
10
9
|
config;
|
|
11
10
|
databaseAdapter;
|
|
@@ -21,21 +20,15 @@ export class CMS {
|
|
|
21
20
|
mediaConfig;
|
|
22
21
|
typographyConfig;
|
|
23
22
|
sidebarHelp;
|
|
24
|
-
shopConfig;
|
|
25
23
|
cmpConfig;
|
|
26
24
|
adminConfig;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
* by `initCMS()`. `null` when the CMS is configured without a shop. Tests
|
|
30
|
-
* can `await` this to gate on init completion; production callers don't
|
|
31
|
-
* need to — index application is idempotent and non-blocking.
|
|
32
|
-
* @internal
|
|
33
|
-
*/
|
|
34
|
-
shopInitPromise = null;
|
|
25
|
+
/** Per-module init promises (`serverHooks.init`), keyed by module id. @internal */
|
|
26
|
+
_moduleInitPromises = new Map();
|
|
35
27
|
plugins = [];
|
|
36
28
|
customFields = new Map();
|
|
37
29
|
iconSets = new Map();
|
|
38
30
|
apiKeys = [];
|
|
31
|
+
modules = new Map();
|
|
39
32
|
constructor(config) {
|
|
40
33
|
this.config = config;
|
|
41
34
|
this.databaseAdapter = config.db;
|
|
@@ -46,9 +39,14 @@ export class CMS {
|
|
|
46
39
|
this.mediaConfig = config.media || {};
|
|
47
40
|
this.typographyConfig = config.typography || {};
|
|
48
41
|
this.sidebarHelp = config.sidebarHelp ?? true;
|
|
49
|
-
this.shopConfig = config.shop ?? null;
|
|
50
42
|
this.cmpConfig = config.cmp ?? null;
|
|
51
43
|
this.adminConfig = config.admin ?? null;
|
|
44
|
+
for (const module of config.modules ?? []) {
|
|
45
|
+
if (this.modules.has(module.id)) {
|
|
46
|
+
throw new Error(`Duplicate module id: "${module.id}"`);
|
|
47
|
+
}
|
|
48
|
+
this.modules.set(module.id, module);
|
|
49
|
+
}
|
|
52
50
|
this.collections = {};
|
|
53
51
|
this.singles = {};
|
|
54
52
|
this.forms = {};
|
|
@@ -170,6 +168,66 @@ export class CMS {
|
|
|
170
168
|
}
|
|
171
169
|
return form;
|
|
172
170
|
}
|
|
171
|
+
getModule(id) {
|
|
172
|
+
return this.modules.get(id);
|
|
173
|
+
}
|
|
174
|
+
getModuleConfig(id) {
|
|
175
|
+
return this.modules.get(id)?.config;
|
|
176
|
+
}
|
|
177
|
+
hasModule(id) {
|
|
178
|
+
return this.modules.has(id);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Back-compat shim — the shop config now lives on the `shop` module
|
|
182
|
+
* (`defineConfig({ modules: [defineShop(...)] })`). Reads through the module
|
|
183
|
+
* registry so the entire shop server/http/remote layer keeps working unchanged.
|
|
184
|
+
*/
|
|
185
|
+
get shopConfig() {
|
|
186
|
+
return this.getModuleConfig('shop') ?? null;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Resolves once the shop module's init (variant-attribute GIN indexes) has run.
|
|
190
|
+
* `null` when no shop module is configured. Idempotent; tests can `await` it.
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
get shopInitPromise() {
|
|
194
|
+
return this._moduleInitPromises.get('shop') ?? null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Runs each module's `serverHooks.init` (idempotent, non-blocking). Called by
|
|
198
|
+
* `initCMS`. Errors are logged, never thrown. @internal
|
|
199
|
+
*/
|
|
200
|
+
runModuleInit() {
|
|
201
|
+
const drizzle = this.databaseAdapter._drizzle;
|
|
202
|
+
const first = this.languages[0];
|
|
203
|
+
const defaultLanguage = typeof first === 'string'
|
|
204
|
+
? first
|
|
205
|
+
: first && typeof first === 'object' && 'code' in first
|
|
206
|
+
? String(first.code)
|
|
207
|
+
: 'pl';
|
|
208
|
+
for (const module of this.modules.values()) {
|
|
209
|
+
const hooks = module.serverHooks;
|
|
210
|
+
if (!hooks)
|
|
211
|
+
continue;
|
|
212
|
+
const ctx = { drizzle, config: module.config, defaultLanguage };
|
|
213
|
+
const onError = (e) => console.warn(`[module:${module.id}] init failed:`, e);
|
|
214
|
+
let p = null;
|
|
215
|
+
if (hooks.init) {
|
|
216
|
+
p = Promise.resolve(hooks.init(ctx)).catch(onError);
|
|
217
|
+
}
|
|
218
|
+
else if (hooks.initModule) {
|
|
219
|
+
// Server-only: resolve the module's server entry from node_modules at
|
|
220
|
+
// runtime (a plain string in the module's client-safe barrel — no import
|
|
221
|
+
// there — so browser builds stay clean) and run its `serverInit`.
|
|
222
|
+
const specifier = hooks.initModule;
|
|
223
|
+
p = import(/* @vite-ignore */ specifier)
|
|
224
|
+
.then((m) => m.serverInit?.(ctx))
|
|
225
|
+
.catch(onError);
|
|
226
|
+
}
|
|
227
|
+
if (p)
|
|
228
|
+
this._moduleInitPromises.set(module.id, p);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
173
231
|
}
|
|
174
232
|
let cms;
|
|
175
233
|
export function initCMS(config) {
|
|
@@ -178,6 +236,9 @@ export function initCMS(config) {
|
|
|
178
236
|
import('./server/media/operations/backgroundMaintenance.js')
|
|
179
237
|
.then((m) => m.startBackgroundMaintenance())
|
|
180
238
|
.catch((e) => console.warn('[cms] Failed to start background maintenance:', e));
|
|
239
|
+
// Run each registered module's server-side init (idempotent, non-blocking).
|
|
240
|
+
// Shop init applies variant-attribute GIN indexes + smoke-tests email templates.
|
|
241
|
+
cms.runModuleInit();
|
|
181
242
|
// Start in-process media optimization worker (generuje warianty poza ścieżką renderu).
|
|
182
243
|
// Wyłączalny przez media.optimization.worker = false.
|
|
183
244
|
if (cms.mediaConfig.optimization?.worker !== false) {
|
|
@@ -185,29 +246,6 @@ export function initCMS(config) {
|
|
|
185
246
|
.then((m) => m.startMediaOptimizationWorker())
|
|
186
247
|
.catch((e) => console.warn('[cms] Failed to start media worker:', e));
|
|
187
248
|
}
|
|
188
|
-
// Apply shop variantAttribute GIN indexes (idempotent CREATE INDEX IF NOT EXISTS).
|
|
189
|
-
// Pass shop + drizzle explicitly so the dynamic import doesn't depend on a
|
|
190
|
-
// shared CMS singleton (vitest can give the dynamic module a fresh instance).
|
|
191
|
-
if (cms.shopConfig) {
|
|
192
|
-
const drizzle = cms.databaseAdapter._drizzle;
|
|
193
|
-
if (drizzle) {
|
|
194
|
-
cms.shopInitPromise = applyVariantAttributeIndexes(cms.shopConfig, drizzle).catch((e) => {
|
|
195
|
-
console.warn('[shop] Failed to apply variant attribute indexes:', e);
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
// Smoke-test built-in shop email templates — fast lookup-only sweep.
|
|
199
|
-
// Catches corrupt installs (missing files in dist) at boot instead of
|
|
200
|
-
// at first email send. Dynamic import avoids loading shop server code
|
|
201
|
-
// when there is no shop configured.
|
|
202
|
-
import('../shop/server/email.js')
|
|
203
|
-
.then(({ REQUIRED_TEMPLATE_NAMES }) => import('../shop/server/emailTemplateRegistry.js').then(({ validateBuiltinTemplates }) => {
|
|
204
|
-
const defaultLang = cms.languages[0] ?? 'pl';
|
|
205
|
-
validateBuiltinTemplates([...REQUIRED_TEMPLATE_NAMES], defaultLang);
|
|
206
|
-
}))
|
|
207
|
-
.catch((e) => {
|
|
208
|
-
console.error('[shop] Built-in email template validation failed:', e);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
249
|
return cms;
|
|
212
250
|
}
|
|
213
251
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CMSConfig } from '../../types/cms.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-field validation of preview views: Zod checks the shape, this checks the
|
|
4
|
+
* relationships it cannot see (`layout` is typed as `unknown` there).
|
|
5
|
+
*
|
|
6
|
+
* Runs from `defineConfig`, i.e. at dev-server start and at build time, so a
|
|
7
|
+
* typo in `tab.preview` fails the build instead of reaching production.
|
|
8
|
+
*
|
|
9
|
+
* @throws {ConfigValidationError} when any collection/single is inconsistent.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare function validatePreviews(config: CMSConfig): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ConfigValidationError } from '../errors.js';
|
|
2
|
+
/** Collect every `tab` node in a layout tree — tabs may sit inside sections/cards/columns/stacks. */
|
|
3
|
+
function collectTabs(nodes) {
|
|
4
|
+
const out = [];
|
|
5
|
+
for (const node of nodes) {
|
|
6
|
+
if (node.type === 'tab')
|
|
7
|
+
out.push(node);
|
|
8
|
+
const children = node.children;
|
|
9
|
+
if (Array.isArray(children))
|
|
10
|
+
out.push(...collectTabs(children));
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
function tabLabel(tab) {
|
|
15
|
+
if (typeof tab.label === 'string')
|
|
16
|
+
return tab.label;
|
|
17
|
+
return Object.values(tab.label ?? {})[0] ?? '(unnamed tab)';
|
|
18
|
+
}
|
|
19
|
+
function checkOne(config, path, issues) {
|
|
20
|
+
const previews = config.previews;
|
|
21
|
+
if (previews && config.previewUrl) {
|
|
22
|
+
issues.push({
|
|
23
|
+
path: `${path}.previews`,
|
|
24
|
+
message: 'cannot declare both `previewUrl` and `previews`',
|
|
25
|
+
hint: 'keep `previewUrl` for a single preview, or move it into `previews[]` as one view'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (previews && previews.length === 0) {
|
|
29
|
+
issues.push({
|
|
30
|
+
path: `${path}.previews`,
|
|
31
|
+
message: '`previews` must contain at least one view',
|
|
32
|
+
hint: 'remove the key, or declare a view: { name, url }'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const names = new Set();
|
|
36
|
+
let defaults = 0;
|
|
37
|
+
previews?.forEach((view, i) => {
|
|
38
|
+
if (names.has(view.name)) {
|
|
39
|
+
issues.push({
|
|
40
|
+
path: `${path}.previews[${i}].name`,
|
|
41
|
+
message: `duplicate preview view name '${view.name}'`,
|
|
42
|
+
hint: 'view names must be unique within a collection'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
names.add(view.name);
|
|
46
|
+
if (view.default)
|
|
47
|
+
defaults++;
|
|
48
|
+
});
|
|
49
|
+
if (defaults > 1) {
|
|
50
|
+
issues.push({
|
|
51
|
+
path: `${path}.previews`,
|
|
52
|
+
message: `${defaults} views are marked as \`default\``,
|
|
53
|
+
hint: 'mark at most one view as default (otherwise the first one wins)'
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const layout = config.layout;
|
|
57
|
+
if (!Array.isArray(layout))
|
|
58
|
+
return;
|
|
59
|
+
for (const tab of collectTabs(layout)) {
|
|
60
|
+
if (!tab.preview)
|
|
61
|
+
continue;
|
|
62
|
+
if (names.has(tab.preview))
|
|
63
|
+
continue;
|
|
64
|
+
const available = [...names];
|
|
65
|
+
issues.push({
|
|
66
|
+
path: `${path}.layout`,
|
|
67
|
+
message: `tab '${tabLabel(tab)}' references unknown preview view '${tab.preview}'`,
|
|
68
|
+
hint: available.length
|
|
69
|
+
? `declared views: ${available.join(', ')}`
|
|
70
|
+
: 'this collection declares no `previews` — add them, or drop `preview` from the tab'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Cross-field validation of preview views: Zod checks the shape, this checks the
|
|
76
|
+
* relationships it cannot see (`layout` is typed as `unknown` there).
|
|
77
|
+
*
|
|
78
|
+
* Runs from `defineConfig`, i.e. at dev-server start and at build time, so a
|
|
79
|
+
* typo in `tab.preview` fails the build instead of reaching production.
|
|
80
|
+
*
|
|
81
|
+
* @throws {ConfigValidationError} when any collection/single is inconsistent.
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export function validatePreviews(config) {
|
|
85
|
+
const issues = [];
|
|
86
|
+
config.collections?.forEach((c, i) => checkOne(c, `collections[${i}]`, issues));
|
|
87
|
+
config.singles?.forEach((s, i) => checkOne(s, `singles[${i}]`, issues));
|
|
88
|
+
if (issues.length)
|
|
89
|
+
throw new ConfigValidationError(issues);
|
|
90
|
+
}
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
export declare function resolveSlugPath(slugField: string | undefined): string;
|
|
3
3
|
/** @internal Traverse dot-path; handles string & localized { lang: string }. */
|
|
4
4
|
export declare function getSlugFromEntryData(data: Record<string, unknown>, slugPath: string, language?: string): string | undefined;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* @internal Pure: apply pathTemplate (no getCMS).
|
|
7
|
+
*
|
|
8
|
+
* Always yields a root-relative path with exactly one leading slash — the
|
|
9
|
+
* contract the docs describe (`'blog/{slug}'` → `/blog/my-post`). Both the
|
|
10
|
+
* template and the slug may carry their own leading slash, so they are joined
|
|
11
|
+
* without doubling: neither the admin list (which used to print `//blog/x`) nor
|
|
12
|
+
* a consumer `href` (which used to get a *relative* `blog/x`) has to patch it.
|
|
13
|
+
*/
|
|
6
14
|
export declare function applyPathTemplate(pathTemplate: string | undefined, slug: string): string;
|
|
7
15
|
/** @internal Compose full entry URL; undefined when no resolvable slug. */
|
|
8
16
|
export declare function resolveEntryUrl(opts: {
|
|
@@ -19,9 +19,23 @@ export function getSlugFromEntryData(data, slugPath, language) {
|
|
|
19
19
|
}
|
|
20
20
|
return undefined;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/** Absolute URL with a scheme (`https://…`) — not a site path, leave it alone. */
|
|
23
|
+
const ABSOLUTE_URL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
24
|
+
/**
|
|
25
|
+
* @internal Pure: apply pathTemplate (no getCMS).
|
|
26
|
+
*
|
|
27
|
+
* Always yields a root-relative path with exactly one leading slash — the
|
|
28
|
+
* contract the docs describe (`'blog/{slug}'` → `/blog/my-post`). Both the
|
|
29
|
+
* template and the slug may carry their own leading slash, so they are joined
|
|
30
|
+
* without doubling: neither the admin list (which used to print `//blog/x`) nor
|
|
31
|
+
* a consumer `href` (which used to get a *relative* `blog/x`) has to patch it.
|
|
32
|
+
*/
|
|
23
33
|
export function applyPathTemplate(pathTemplate, slug) {
|
|
24
|
-
|
|
34
|
+
const raw = pathTemplate ? pathTemplate.replace('{slug}', slug) : slug;
|
|
35
|
+
if (ABSOLUTE_URL_RE.test(raw))
|
|
36
|
+
return raw;
|
|
37
|
+
const collapsed = raw.replace(/\/{2,}/g, '/');
|
|
38
|
+
return collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
|
|
25
39
|
}
|
|
26
40
|
/** @internal Compose full entry URL; undefined when no resolvable slug. */
|
|
27
41
|
export function resolveEntryUrl(opts) {
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type { EntryData, PopulatedEntryData } from '../../../types/entries.js';
|
|
1
|
+
import type { EntryData, PopulatedEntryData, DbEntryVersion } from '../../../types/entries.js';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
|
-
import type { PopulateCtx } from '../entries/operations/resolveEntry.js';
|
|
3
|
+
import type { PopulateCtx, ResolveStatus } from '../entries/operations/resolveEntry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Version of a *related* entry to populate. Exported for tests.
|
|
6
|
+
*
|
|
7
|
+
* `draft` falls back to the published version: a related entry that has never been
|
|
8
|
+
* drafted (only published versions) must still resolve, otherwise draft previews
|
|
9
|
+
* silently drop every relation to `null` — see preview.remote.ts, which populates
|
|
10
|
+
* form data with `status: 'draft'`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pickVersion(versions: DbEntryVersion[], status: ResolveStatus): DbEntryVersion | null;
|
|
4
13
|
export declare function resolveRelationFields(data: EntryData, fields: Field[], ctx: PopulateCtx): Promise<PopulatedEntryData>;
|
|
@@ -2,14 +2,23 @@ import { walkInlineBlockNodes, cloneDoc } from '../../../admin/components/tiptap
|
|
|
2
2
|
import { getCMS } from '../../cms.js';
|
|
3
3
|
import { getFieldsFromConfig } from '../../fields/layoutUtils.js';
|
|
4
4
|
import { getEntrySlugPath, getSlugFromEntryData, getEntryPath } from './slugResolver.js';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Version of a *related* entry to populate. Exported for tests.
|
|
7
|
+
*
|
|
8
|
+
* `draft` falls back to the published version: a related entry that has never been
|
|
9
|
+
* drafted (only published versions) must still resolve, otherwise draft previews
|
|
10
|
+
* silently drop every relation to `null` — see preview.remote.ts, which populates
|
|
11
|
+
* form data with `status: 'draft'`.
|
|
12
|
+
*/
|
|
13
|
+
export function pickVersion(versions, status) {
|
|
6
14
|
const sorted = versions.slice().sort((a, b) => b.versionNumber - a.versionNumber);
|
|
7
15
|
const now = new Date();
|
|
16
|
+
const published = () => sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) ?? null;
|
|
8
17
|
switch (status) {
|
|
9
18
|
case 'published':
|
|
10
|
-
return
|
|
19
|
+
return published();
|
|
11
20
|
case 'draft':
|
|
12
|
-
return sorted.find((v) => v.publishedAt == null) ??
|
|
21
|
+
return sorted.find((v) => v.publishedAt == null) ?? published();
|
|
13
22
|
case 'scheduled':
|
|
14
23
|
return sorted.find((v) => v.publishedAt != null && v.publishedAt > now) ?? null;
|
|
15
24
|
}
|
|
@@ -276,9 +276,12 @@ function generateDrizzleSchema(config) {
|
|
|
276
276
|
code += `// Point your drizzle.config.ts schema field at this file.\n\n`;
|
|
277
277
|
code += `export * from 'includio-cms/db-postgres/schema-core';\n`;
|
|
278
278
|
code += `export * from 'includio-cms/auth-schema';\n`;
|
|
279
|
-
if (config.shop) {
|
|
279
|
+
if (config.modules?.some((m) => m.id === 'shop')) {
|
|
280
280
|
code += `export * from 'includio-cms/db-postgres/schema-shop';\n`;
|
|
281
281
|
}
|
|
282
|
+
if (config.modules?.some((m) => m.id === 'booking')) {
|
|
283
|
+
code += `export * from 'includio-cms/db-postgres/schema-booking';\n`;
|
|
284
|
+
}
|
|
282
285
|
writeIfChanged(filePath, code);
|
|
283
286
|
}
|
|
284
287
|
function generateRemote(config) {
|
|
@@ -313,7 +316,9 @@ export function generateRuntime(config) {
|
|
|
313
316
|
}
|
|
314
317
|
}
|
|
315
318
|
setGeneratorCustomFields(customFields);
|
|
316
|
-
|
|
319
|
+
const shopModule = config.modules?.find((m) => m.id === 'shop');
|
|
320
|
+
const shopConfig = shopModule?.config;
|
|
321
|
+
setGeneratorShopVariantAttributes(shopConfig?.variantAttributes ?? {});
|
|
317
322
|
createCmsRuntimeDir();
|
|
318
323
|
generateTypes(config);
|
|
319
324
|
generateAPI(config);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { AdjustmentKind } from '../../../booking/adjustments.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manual staff adjustments to a booking's price (surcharge / negotiated
|
|
4
|
+
* discount). Append-only with soft-void (`voidedAt`) for audit. `amount` is
|
|
5
|
+
* signed minor units (surcharge > 0, discount < 0). Kept separate from
|
|
6
|
+
* rule-engine discounts so a recompute never clears them.
|
|
7
|
+
*/
|
|
8
|
+
export declare const bookingAdjustmentsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
9
|
+
name: "booking_adjustments";
|
|
10
|
+
schema: undefined;
|
|
11
|
+
columns: {
|
|
12
|
+
id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
13
|
+
name: "id";
|
|
14
|
+
tableName: "booking_adjustments";
|
|
15
|
+
dataType: "string";
|
|
16
|
+
columnType: "PgUUID";
|
|
17
|
+
data: string;
|
|
18
|
+
driverParam: string;
|
|
19
|
+
notNull: true;
|
|
20
|
+
hasDefault: true;
|
|
21
|
+
isPrimaryKey: true;
|
|
22
|
+
isAutoincrement: false;
|
|
23
|
+
hasRuntimeDefault: false;
|
|
24
|
+
enumValues: undefined;
|
|
25
|
+
baseColumn: never;
|
|
26
|
+
identity: undefined;
|
|
27
|
+
generated: undefined;
|
|
28
|
+
}, {}, {}>;
|
|
29
|
+
bookingId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
30
|
+
name: "booking_id";
|
|
31
|
+
tableName: "booking_adjustments";
|
|
32
|
+
dataType: "string";
|
|
33
|
+
columnType: "PgUUID";
|
|
34
|
+
data: string;
|
|
35
|
+
driverParam: string;
|
|
36
|
+
notNull: true;
|
|
37
|
+
hasDefault: false;
|
|
38
|
+
isPrimaryKey: false;
|
|
39
|
+
isAutoincrement: false;
|
|
40
|
+
hasRuntimeDefault: false;
|
|
41
|
+
enumValues: undefined;
|
|
42
|
+
baseColumn: never;
|
|
43
|
+
identity: undefined;
|
|
44
|
+
generated: undefined;
|
|
45
|
+
}, {}, {}>;
|
|
46
|
+
kind: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
47
|
+
name: "kind";
|
|
48
|
+
tableName: "booking_adjustments";
|
|
49
|
+
dataType: "string";
|
|
50
|
+
columnType: "PgText";
|
|
51
|
+
data: AdjustmentKind;
|
|
52
|
+
driverParam: string;
|
|
53
|
+
notNull: true;
|
|
54
|
+
hasDefault: false;
|
|
55
|
+
isPrimaryKey: false;
|
|
56
|
+
isAutoincrement: false;
|
|
57
|
+
hasRuntimeDefault: false;
|
|
58
|
+
enumValues: [string, ...string[]];
|
|
59
|
+
baseColumn: never;
|
|
60
|
+
identity: undefined;
|
|
61
|
+
generated: undefined;
|
|
62
|
+
}, {}, {
|
|
63
|
+
$type: AdjustmentKind;
|
|
64
|
+
}>;
|
|
65
|
+
amount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
66
|
+
name: "amount";
|
|
67
|
+
tableName: "booking_adjustments";
|
|
68
|
+
dataType: "number";
|
|
69
|
+
columnType: "PgInteger";
|
|
70
|
+
data: number;
|
|
71
|
+
driverParam: string | number;
|
|
72
|
+
notNull: true;
|
|
73
|
+
hasDefault: false;
|
|
74
|
+
isPrimaryKey: false;
|
|
75
|
+
isAutoincrement: false;
|
|
76
|
+
hasRuntimeDefault: false;
|
|
77
|
+
enumValues: undefined;
|
|
78
|
+
baseColumn: never;
|
|
79
|
+
identity: undefined;
|
|
80
|
+
generated: undefined;
|
|
81
|
+
}, {}, {}>;
|
|
82
|
+
label: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
83
|
+
name: "label";
|
|
84
|
+
tableName: "booking_adjustments";
|
|
85
|
+
dataType: "string";
|
|
86
|
+
columnType: "PgText";
|
|
87
|
+
data: string;
|
|
88
|
+
driverParam: string;
|
|
89
|
+
notNull: true;
|
|
90
|
+
hasDefault: false;
|
|
91
|
+
isPrimaryKey: false;
|
|
92
|
+
isAutoincrement: false;
|
|
93
|
+
hasRuntimeDefault: false;
|
|
94
|
+
enumValues: [string, ...string[]];
|
|
95
|
+
baseColumn: never;
|
|
96
|
+
identity: undefined;
|
|
97
|
+
generated: undefined;
|
|
98
|
+
}, {}, {}>;
|
|
99
|
+
reason: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
100
|
+
name: "reason";
|
|
101
|
+
tableName: "booking_adjustments";
|
|
102
|
+
dataType: "string";
|
|
103
|
+
columnType: "PgText";
|
|
104
|
+
data: string;
|
|
105
|
+
driverParam: string;
|
|
106
|
+
notNull: false;
|
|
107
|
+
hasDefault: false;
|
|
108
|
+
isPrimaryKey: false;
|
|
109
|
+
isAutoincrement: false;
|
|
110
|
+
hasRuntimeDefault: false;
|
|
111
|
+
enumValues: [string, ...string[]];
|
|
112
|
+
baseColumn: never;
|
|
113
|
+
identity: undefined;
|
|
114
|
+
generated: undefined;
|
|
115
|
+
}, {}, {}>;
|
|
116
|
+
createdBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
117
|
+
name: "created_by";
|
|
118
|
+
tableName: "booking_adjustments";
|
|
119
|
+
dataType: "string";
|
|
120
|
+
columnType: "PgText";
|
|
121
|
+
data: string;
|
|
122
|
+
driverParam: string;
|
|
123
|
+
notNull: true;
|
|
124
|
+
hasDefault: true;
|
|
125
|
+
isPrimaryKey: false;
|
|
126
|
+
isAutoincrement: false;
|
|
127
|
+
hasRuntimeDefault: false;
|
|
128
|
+
enumValues: [string, ...string[]];
|
|
129
|
+
baseColumn: never;
|
|
130
|
+
identity: undefined;
|
|
131
|
+
generated: undefined;
|
|
132
|
+
}, {}, {}>;
|
|
133
|
+
createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
134
|
+
name: "created_at";
|
|
135
|
+
tableName: "booking_adjustments";
|
|
136
|
+
dataType: "date";
|
|
137
|
+
columnType: "PgTimestamp";
|
|
138
|
+
data: Date;
|
|
139
|
+
driverParam: string;
|
|
140
|
+
notNull: true;
|
|
141
|
+
hasDefault: true;
|
|
142
|
+
isPrimaryKey: false;
|
|
143
|
+
isAutoincrement: false;
|
|
144
|
+
hasRuntimeDefault: false;
|
|
145
|
+
enumValues: undefined;
|
|
146
|
+
baseColumn: never;
|
|
147
|
+
identity: undefined;
|
|
148
|
+
generated: undefined;
|
|
149
|
+
}, {}, {}>;
|
|
150
|
+
voidedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
151
|
+
name: "voided_at";
|
|
152
|
+
tableName: "booking_adjustments";
|
|
153
|
+
dataType: "date";
|
|
154
|
+
columnType: "PgTimestamp";
|
|
155
|
+
data: Date;
|
|
156
|
+
driverParam: string;
|
|
157
|
+
notNull: false;
|
|
158
|
+
hasDefault: false;
|
|
159
|
+
isPrimaryKey: false;
|
|
160
|
+
isAutoincrement: false;
|
|
161
|
+
hasRuntimeDefault: false;
|
|
162
|
+
enumValues: undefined;
|
|
163
|
+
baseColumn: never;
|
|
164
|
+
identity: undefined;
|
|
165
|
+
generated: undefined;
|
|
166
|
+
}, {}, {}>;
|
|
167
|
+
voidedBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
168
|
+
name: "voided_by";
|
|
169
|
+
tableName: "booking_adjustments";
|
|
170
|
+
dataType: "string";
|
|
171
|
+
columnType: "PgText";
|
|
172
|
+
data: string;
|
|
173
|
+
driverParam: string;
|
|
174
|
+
notNull: false;
|
|
175
|
+
hasDefault: false;
|
|
176
|
+
isPrimaryKey: false;
|
|
177
|
+
isAutoincrement: false;
|
|
178
|
+
hasRuntimeDefault: false;
|
|
179
|
+
enumValues: [string, ...string[]];
|
|
180
|
+
baseColumn: never;
|
|
181
|
+
identity: undefined;
|
|
182
|
+
generated: undefined;
|
|
183
|
+
}, {}, {}>;
|
|
184
|
+
};
|
|
185
|
+
dialect: "pg";
|
|
186
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { bookingsTable } from './bookings.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manual staff adjustments to a booking's price (surcharge / negotiated
|
|
5
|
+
* discount). Append-only with soft-void (`voidedAt`) for audit. `amount` is
|
|
6
|
+
* signed minor units (surcharge > 0, discount < 0). Kept separate from
|
|
7
|
+
* rule-engine discounts so a recompute never clears them.
|
|
8
|
+
*/
|
|
9
|
+
export const bookingAdjustmentsTable = pgTable('booking_adjustments', {
|
|
10
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
11
|
+
bookingId: uuid('booking_id')
|
|
12
|
+
.notNull()
|
|
13
|
+
.references(() => bookingsTable.id, { onDelete: 'cascade' }),
|
|
14
|
+
kind: text('kind').$type().notNull(),
|
|
15
|
+
amount: integer('amount').notNull(),
|
|
16
|
+
label: text('label').notNull(),
|
|
17
|
+
reason: text('reason'),
|
|
18
|
+
createdBy: text('created_by').notNull().default('staff'),
|
|
19
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
20
|
+
voidedAt: timestamp('voided_at', { withTimezone: true }),
|
|
21
|
+
voidedBy: text('voided_by')
|
|
22
|
+
});
|