@beechcms/api 0.6.6 → 0.7.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/assets/dashboard/assets/{chart-DA5IFPWq.js → chart-B1qG9c0-.js} +1 -1
- package/assets/dashboard/assets/{index-CM0tZF7n.js → index-0fKOe8Wq.js} +35 -35
- package/assets/dashboard/assets/index-Bj2upDZk.css +1 -0
- package/assets/dashboard/assets/{pie-chart-recharts-BiqT5XVc.js → pie-chart-recharts-DSf6kc6u.js} +1 -1
- package/assets/dashboard/assets/{timeseries-chart-recharts-B6lrNF3r.js → timeseries-chart-recharts-CqM5c6MV.js} +1 -1
- package/assets/dashboard/index.html +2 -2
- package/dist/auth/constants.d.ts +10 -0
- package/dist/auth/utils/refresh-token.d.ts +1 -0
- package/dist/features/automations/api/automations.schema.d.ts +16 -16
- package/dist/features/automations/engine/cron-runner.utils.d.ts +1 -0
- package/dist/features/content/constants.d.ts +12 -0
- package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +150 -125
- package/dist/public/access-policy.d.ts +1 -1
- package/dist/public/api-key-middleware.d.ts +6 -0
- package/dist/public/cache-utils.d.ts +10 -0
- package/dist/public/problem-details.d.ts +32 -0
- package/dist/public/public-errors.d.ts +13 -0
- package/dist/public/response-builder.d.ts +22 -0
- package/dist/shared/db/fixtures/demo-data.fixtures.d.ts +12 -0
- package/dist/shared/db/repositories/content.repository.d1.d.ts +4 -3
- package/dist/shared/db/repositories/d1-setup-checklist.repository.d.ts +12 -0
- package/dist/shared/db/repositories/demo-data.repository.d1.d.ts +1 -1
- package/dist/shared/demo-data-sql.d.ts +1 -0
- package/dist/shared/email/email.types.d.ts +79 -0
- package/dist/shared/email/templates/automation-mail.d.ts +7 -0
- package/dist/shared/email/templates/password-changed.d.ts +15 -0
- package/dist/shared/email/templates/password-reset.d.ts +18 -0
- package/dist/shared/email/templates/shell.d.ts +58 -0
- package/dist/shared/jobs/fts-sync.d.ts +1 -0
- package/dist/shared/storage/upload.d.ts +7 -0
- package/dist/shared/utils/request-utils.d.ts +13 -0
- package/dist/shared/utils/storage-utils.d.ts +7 -0
- package/migrations/0000_v040_base.sql +14 -1
- package/package.json +2 -2
- package/assets/dashboard/assets/index-B58kBLrh.css +0 -1
package/dist/index.js
CHANGED
|
@@ -2249,25 +2249,25 @@ setupApp.post("/auth/setup", async (context) => {
|
|
|
2249
2249
|
id: "sec-0",
|
|
2250
2250
|
hideLabel: true,
|
|
2251
2251
|
columns: [
|
|
2252
|
-
{ id: "c0", widgets: [{ id: "w0", type: "core/text", config: { content: "# Acme SaaS Analytics\
|
|
2252
|
+
{ id: "c0", widgets: [{ id: "w0", type: "core/text", config: { content: "# Acme SaaS Analytics\nWelcome to your production dashboard. Monitor real-time metrics and KPIs here." } }] }
|
|
2253
2253
|
]
|
|
2254
2254
|
},
|
|
2255
2255
|
{
|
|
2256
2256
|
id: "sec-1",
|
|
2257
2257
|
hideLabel: true,
|
|
2258
2258
|
columns: [
|
|
2259
|
-
{ id: "c1", widgets: [{ id: "w1", type: "core/stat", config: { seedSlug: "clienti", formula: { op: "sum", column: "mrr" }, window: "all" }, title: "
|
|
2260
|
-
{ id: "c2", widgets: [{ id: "w2", type: "core/stat", config: { seedSlug: "clienti", formula: { op: "count" }, window: "all" }, title: "
|
|
2261
|
-
{ id: "c3", widgets: [{ id: "w3", type: "core/stat", config: { seedSlug: "abbonamenti", formula: { op: "count" }, window: "all" }, title: "
|
|
2262
|
-
{ id: "c4", widgets: [{ id: "w4", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "ticket_status", value: "open" }, window: "all" }, title: "
|
|
2259
|
+
{ id: "c1", widgets: [{ id: "w1", type: "core/stat", config: { seedSlug: "clienti", formula: { op: "sum", column: "mrr" }, window: "all" }, title: "Total MRR" }] },
|
|
2260
|
+
{ id: "c2", widgets: [{ id: "w2", type: "core/stat", config: { seedSlug: "clienti", formula: { op: "count" }, window: "all" }, title: "Active Customers" }] },
|
|
2261
|
+
{ id: "c3", widgets: [{ id: "w3", type: "core/stat", config: { seedSlug: "abbonamenti", formula: { op: "count" }, window: "all" }, title: "Subscriptions" }] },
|
|
2262
|
+
{ id: "c4", widgets: [{ id: "w4", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "ticket_status", value: "open" }, window: "all" }, title: "Open Tickets" }] }
|
|
2263
2263
|
]
|
|
2264
2264
|
},
|
|
2265
2265
|
{
|
|
2266
2266
|
id: "sec-2",
|
|
2267
2267
|
hideLabel: true,
|
|
2268
2268
|
columns: [
|
|
2269
|
-
{ id: "c5", widgets: [{ id: "w5", type: "core/area-chart", config: { seedSlug: "clienti", groupColumn: "created_at", window: "all" }, title: "
|
|
2270
|
-
{ id: "c6", widgets: [{ id: "w6", type: "core/pie-chart", config: { seedSlug: "clienti", column: "tier", window: "all" }, title: "
|
|
2269
|
+
{ id: "c5", widgets: [{ id: "w5", type: "core/area-chart", config: { seedSlug: "clienti", groupColumn: "created_at", window: "all" }, title: "Signups Trend" }] },
|
|
2270
|
+
{ id: "c6", widgets: [{ id: "w6", type: "core/pie-chart", config: { seedSlug: "clienti", column: "tier", window: "all" }, title: "Plan Distribution" }] }
|
|
2271
2271
|
],
|
|
2272
2272
|
columnSpans: [8, 4]
|
|
2273
2273
|
},
|
|
@@ -2275,8 +2275,8 @@ setupApp.post("/auth/setup", async (context) => {
|
|
|
2275
2275
|
id: "sec-3",
|
|
2276
2276
|
hideLabel: true,
|
|
2277
2277
|
columns: [
|
|
2278
|
-
{ id: "c7", widgets: [{ id: "w7", type: "core/data-table", config: { seedSlug: "clienti", pageSize: 5 }, title: "
|
|
2279
|
-
{ id: "c8", widgets: [{ id: "w8", type: "core/data-table", config: { seedSlug: "changelog", pageSize: 5 }, title: "
|
|
2278
|
+
{ id: "c7", widgets: [{ id: "w7", type: "core/data-table", config: { seedSlug: "clienti", pageSize: 5 }, title: "Recent Signups" }] },
|
|
2279
|
+
{ id: "c8", widgets: [{ id: "w8", type: "core/data-table", config: { seedSlug: "changelog", pageSize: 5 }, title: "Latest Releases" }] }
|
|
2280
2280
|
],
|
|
2281
2281
|
columnSpans: [6, 6]
|
|
2282
2282
|
}
|
|
@@ -2292,17 +2292,17 @@ setupApp.post("/auth/setup", async (context) => {
|
|
|
2292
2292
|
id: "sec-4",
|
|
2293
2293
|
hideLabel: true,
|
|
2294
2294
|
columns: [
|
|
2295
|
-
{ id: "c9", widgets: [{ id: "w9", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "priority", value: "high" }, window: "all" }, title: "
|
|
2296
|
-
{ id: "c10", widgets: [{ id: "w10", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "ticket_status", value: "closed" }, window: "all" }, title: "
|
|
2297
|
-
{ id: "c11", widgets: [{ id: "w11", type: "core/stat", config: { seedSlug: "changelog", formula: { op: "count" }, window: "all" }, title: "
|
|
2295
|
+
{ id: "c9", widgets: [{ id: "w9", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "priority", value: "high" }, window: "all" }, title: "High Priority Tickets" }] },
|
|
2296
|
+
{ id: "c10", widgets: [{ id: "w10", type: "core/stat", config: { seedSlug: "ticket", formula: { op: "countWhere", column: "ticket_status", value: "closed" }, window: "all" }, title: "Closed Tickets" }] },
|
|
2297
|
+
{ id: "c11", widgets: [{ id: "w11", type: "core/stat", config: { seedSlug: "changelog", formula: { op: "count" }, window: "all" }, title: "Released Updates" }] }
|
|
2298
2298
|
]
|
|
2299
2299
|
},
|
|
2300
2300
|
{
|
|
2301
2301
|
id: "sec-5",
|
|
2302
2302
|
hideLabel: true,
|
|
2303
2303
|
columns: [
|
|
2304
|
-
{ id: "c12", widgets: [{ id: "w12", type: "core/bar-chart", config: { seedSlug: "ticket", groupColumn: "created_at", window: "all" }, title: "
|
|
2305
|
-
{ id: "c13", widgets: [{ id: "w13", type: "core/line-chart", config: { seedSlug: "abbonamenti", groupColumn: "created_at", window: "all" }, title: "
|
|
2304
|
+
{ id: "c12", widgets: [{ id: "w12", type: "core/bar-chart", config: { seedSlug: "ticket", groupColumn: "created_at", window: "all" }, title: "Tickets Over Time" }] },
|
|
2305
|
+
{ id: "c13", widgets: [{ id: "w13", type: "core/line-chart", config: { seedSlug: "abbonamenti", groupColumn: "created_at", window: "all" }, title: "New Subscriptions" }] }
|
|
2306
2306
|
],
|
|
2307
2307
|
columnSpans: [6, 6]
|
|
2308
2308
|
},
|
|
@@ -2310,7 +2310,7 @@ setupApp.post("/auth/setup", async (context) => {
|
|
|
2310
2310
|
id: "sec-6",
|
|
2311
2311
|
hideLabel: true,
|
|
2312
2312
|
columns: [
|
|
2313
|
-
{ id: "c14", widgets: [{ id: "w14", type: "core/data-table", config: { seedSlug: "ticket", pageSize: 10 }, title: "
|
|
2313
|
+
{ id: "c14", widgets: [{ id: "w14", type: "core/data-table", config: { seedSlug: "ticket", pageSize: 10 }, title: "Active Tickets" }] }
|
|
2314
2314
|
]
|
|
2315
2315
|
}
|
|
2316
2316
|
]
|
|
@@ -2342,6 +2342,7 @@ import { Hono as Hono6 } from "hono";
|
|
|
2342
2342
|
import {
|
|
2343
2343
|
validateAndSanitizeSeedPayload as validateAndSanitizeSeedPayload5,
|
|
2344
2344
|
resolvePolicies as resolvePolicies5,
|
|
2345
|
+
EntryNotFoundError as EntryNotFoundError6,
|
|
2345
2346
|
RelationTargetNotFoundError
|
|
2346
2347
|
} from "@beechcms/core";
|
|
2347
2348
|
|
|
@@ -2503,18 +2504,17 @@ draftApp.post("/:slug/:id/draft/publish", draftGuard, async (context) => {
|
|
|
2503
2504
|
const id = context.req.param("id");
|
|
2504
2505
|
const seed = context.get("getSeed")(slug);
|
|
2505
2506
|
const repository = context.get("repository");
|
|
2506
|
-
const draft = await repository.getDraft(seed, id);
|
|
2507
|
-
if (!draft) {
|
|
2508
|
-
return publicProblem(context, {
|
|
2509
|
-
type: "draft-not-found",
|
|
2510
|
-
title: "Not Found",
|
|
2511
|
-
status: 404,
|
|
2512
|
-
detail: "No pending draft to publish"
|
|
2513
|
-
});
|
|
2514
|
-
}
|
|
2515
2507
|
try {
|
|
2516
2508
|
await repository.publishDraft(seed, id);
|
|
2517
2509
|
} catch (err) {
|
|
2510
|
+
if (err instanceof EntryNotFoundError6) {
|
|
2511
|
+
return publicProblem(context, {
|
|
2512
|
+
type: "draft-not-found",
|
|
2513
|
+
title: "Not Found",
|
|
2514
|
+
status: 404,
|
|
2515
|
+
detail: "No pending draft to publish"
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
2518
|
if (err instanceof RelationTargetNotFoundError) {
|
|
2519
2519
|
return publicProblem(context, {
|
|
2520
2520
|
type: "relation-target-not-found",
|
|
@@ -2525,9 +2525,7 @@ draftApp.post("/:slug/:id/draft/publish", draftGuard, async (context) => {
|
|
|
2525
2525
|
}
|
|
2526
2526
|
throw err;
|
|
2527
2527
|
}
|
|
2528
|
-
|
|
2529
|
-
const displayStr = typeof displayValue === "string" ? displayValue : id;
|
|
2530
|
-
logDraftActivity(context, id, slug, displayStr, "draft published");
|
|
2528
|
+
logDraftActivity(context, id, slug, id, "draft published");
|
|
2531
2529
|
return context.json({ success: true });
|
|
2532
2530
|
});
|
|
2533
2531
|
draftApp.delete("/:slug/:id/draft", draftGuard, async (context) => {
|
|
@@ -4736,7 +4734,7 @@ function withCachedResponse(edgeCache, cacheKey, response) {
|
|
|
4736
4734
|
}
|
|
4737
4735
|
|
|
4738
4736
|
// src/public/read-single.ts
|
|
4739
|
-
import { EntryNotFoundError as
|
|
4737
|
+
import { EntryNotFoundError as EntryNotFoundError7 } from "@beechcms/core";
|
|
4740
4738
|
|
|
4741
4739
|
// src/public/entry-projection.ts
|
|
4742
4740
|
import { filterEntryForActor as filterEntryForActor2 } from "@beechcms/core";
|
|
@@ -4787,7 +4785,7 @@ async function readSingleEntry(input) {
|
|
|
4787
4785
|
meta: buildPublicSingleMeta(seedSlug)
|
|
4788
4786
|
};
|
|
4789
4787
|
} catch (error) {
|
|
4790
|
-
if (error instanceof
|
|
4788
|
+
if (error instanceof EntryNotFoundError7) {
|
|
4791
4789
|
return { ok: false, detail: `Entry '${label}' not found for content type '${seedSlug}'.` };
|
|
4792
4790
|
}
|
|
4793
4791
|
throw error;
|
|
@@ -5171,7 +5169,7 @@ async function publicAddHandler(context) {
|
|
|
5171
5169
|
}
|
|
5172
5170
|
|
|
5173
5171
|
// src/public/public-edit.ts
|
|
5174
|
-
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as resolvePolicies9, EntryNotFoundError as
|
|
5172
|
+
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as resolvePolicies9, EntryNotFoundError as EntryNotFoundError8 } from "@beechcms/core";
|
|
5175
5173
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
5176
5174
|
function errorMessage(context, error) {
|
|
5177
5175
|
if (context.env.ENV !== "production" && error instanceof Error) return error.message;
|
|
@@ -5298,7 +5296,7 @@ async function publicEditHandler(context) {
|
|
|
5298
5296
|
);
|
|
5299
5297
|
return context.json({ success: true, id, slug: slugResult.value.nextSlug }, 200);
|
|
5300
5298
|
} catch (error) {
|
|
5301
|
-
if (error instanceof
|
|
5299
|
+
if (error instanceof EntryNotFoundError8) {
|
|
5302
5300
|
return publicProblem(context, { type: "entry-not-found", title: "Not Found", status: 404, detail: `Entry '${id}' not found for content type '${seedSlug}'.` });
|
|
5303
5301
|
}
|
|
5304
5302
|
console.error("Public edit error:", error);
|
|
@@ -5604,7 +5602,7 @@ import { createMiddleware as createMiddleware2 } from "hono/factory";
|
|
|
5604
5602
|
|
|
5605
5603
|
// src/shared/db/repositories/content.repository.d1.ts
|
|
5606
5604
|
import {
|
|
5607
|
-
EntryNotFoundError as
|
|
5605
|
+
EntryNotFoundError as EntryNotFoundError9,
|
|
5608
5606
|
RepositoryError as RepositoryError2,
|
|
5609
5607
|
SlugConflictError as SlugConflictError4,
|
|
5610
5608
|
RelationTargetNotFoundError as RelationTargetNotFoundError2,
|
|
@@ -5862,13 +5860,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
5862
5860
|
const tableName = this.getTableName(seed.slug);
|
|
5863
5861
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ? LIMIT 1`).bind(id).first();
|
|
5864
5862
|
if (!entryRow) {
|
|
5865
|
-
throw new
|
|
5863
|
+
throw new EntryNotFoundError9(`Entry ${id} not found in ${seed.slug}`);
|
|
5866
5864
|
}
|
|
5867
5865
|
const data = await this.rowToData(seed, entryRow);
|
|
5868
5866
|
await this.attachMultiRelations(seed, id, data);
|
|
5869
5867
|
return data;
|
|
5870
5868
|
} catch (error) {
|
|
5871
|
-
if (error instanceof
|
|
5869
|
+
if (error instanceof EntryNotFoundError9) throw error;
|
|
5872
5870
|
throw this.mapError(error, `findById(${seed.slug}, ${id})`);
|
|
5873
5871
|
}
|
|
5874
5872
|
}
|
|
@@ -5882,13 +5880,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
5882
5880
|
const tableName = this.getTableName(seed.slug);
|
|
5883
5881
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE slug = ? LIMIT 1`).bind(slug).first();
|
|
5884
5882
|
if (!entryRow) {
|
|
5885
|
-
throw new
|
|
5883
|
+
throw new EntryNotFoundError9(`Entry with slug "${slug}" not found in ${seed.slug}`);
|
|
5886
5884
|
}
|
|
5887
5885
|
const data = await this.rowToData(seed, entryRow);
|
|
5888
5886
|
await this.attachMultiRelations(seed, entryRow.id, data);
|
|
5889
5887
|
return data;
|
|
5890
5888
|
} catch (error) {
|
|
5891
|
-
if (error instanceof
|
|
5889
|
+
if (error instanceof EntryNotFoundError9) throw error;
|
|
5892
5890
|
throw this.mapError(error, `findBySlug(${seed.slug}, ${slug})`);
|
|
5893
5891
|
}
|
|
5894
5892
|
}
|
|
@@ -6265,16 +6263,16 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6265
6263
|
if (batchStmts.length === 1) {
|
|
6266
6264
|
const updateResult = await batchStmts[0].run();
|
|
6267
6265
|
if (updateResult.meta.changes === 0) {
|
|
6268
|
-
throw new
|
|
6266
|
+
throw new EntryNotFoundError9(`Entry ${id} not found in ${seed.slug}`);
|
|
6269
6267
|
}
|
|
6270
6268
|
} else if (batchStmts.length > 1) {
|
|
6271
6269
|
const results = await this.database.batch(batchStmts);
|
|
6272
6270
|
if (stmt && (results[0].meta?.changes ?? 0) === 0) {
|
|
6273
|
-
throw new
|
|
6271
|
+
throw new EntryNotFoundError9(`Entry ${id} not found in ${seed.slug}`);
|
|
6274
6272
|
}
|
|
6275
6273
|
}
|
|
6276
6274
|
} catch (error) {
|
|
6277
|
-
if (error instanceof
|
|
6275
|
+
if (error instanceof EntryNotFoundError9) throw error;
|
|
6278
6276
|
throw this.mapError(error, `update(${seed.slug}, ${id})`);
|
|
6279
6277
|
}
|
|
6280
6278
|
if (this.hooks?.afterUpdate) {
|
|
@@ -6299,12 +6297,12 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6299
6297
|
const tableName = this.getTableName(seed.slug);
|
|
6300
6298
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).bind(id).first();
|
|
6301
6299
|
if (!entryRow) {
|
|
6302
|
-
throw new
|
|
6300
|
+
throw new EntryNotFoundError9(`Entry ${id} not found in ${seed.slug}`);
|
|
6303
6301
|
}
|
|
6304
6302
|
await this.database.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(id).run();
|
|
6305
6303
|
row = await this.rowToData(seed, entryRow);
|
|
6306
6304
|
} catch (error) {
|
|
6307
|
-
if (error instanceof
|
|
6305
|
+
if (error instanceof EntryNotFoundError9) throw error;
|
|
6308
6306
|
throw this.mapError(error, `delete(${seed.slug}, ${id})`);
|
|
6309
6307
|
}
|
|
6310
6308
|
if (this.hooks?.afterDelete) {
|
|
@@ -6560,7 +6558,8 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6560
6558
|
try {
|
|
6561
6559
|
if (!seed.allowDrafts) return false;
|
|
6562
6560
|
const draftTableName = this.getTableName(seed.slug, true);
|
|
6563
|
-
const
|
|
6561
|
+
const liveTableName = this.getTableName(seed.slug);
|
|
6562
|
+
const draftExistsResult = await this.database.prepare(`SELECT 1 FROM ${draftTableName} WHERE entry_id = ? UNION SELECT 1 FROM ${liveTableName} WHERE id = ? AND status = 'draft' LIMIT 1`).bind(entryId, entryId).first();
|
|
6564
6563
|
return draftExistsResult !== null;
|
|
6565
6564
|
} catch (error) {
|
|
6566
6565
|
throw this.mapError(error, `hasDraft(${seed.slug}, ${entryId})`);
|
|
@@ -6569,7 +6568,8 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6569
6568
|
/**
|
|
6570
6569
|
* Promotes a draft to the live entry: validates all relation targets referenced by the
|
|
6571
6570
|
* draft still exist, copies the draft's scalar fields into the live row, replaces the
|
|
6572
|
-
* live junction rows with the draft's, and deletes the draft — all as one batch.
|
|
6571
|
+
* live junction rows with the draft's, sets status to 'published', and deletes the draft — all as one batch.
|
|
6572
|
+
* If no mirror draft row exists but the live entry has status = 'draft', updates status to 'published'.
|
|
6573
6573
|
* A no-op if `seed.allowDrafts` is false.
|
|
6574
6574
|
*
|
|
6575
6575
|
* @throws EntryNotFoundError if no draft row exists for `entryId`.
|
|
@@ -6582,7 +6582,12 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6582
6582
|
const liveTableName = this.getTableName(seed.slug);
|
|
6583
6583
|
const draftRow = await this.database.prepare(`SELECT * FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId).first();
|
|
6584
6584
|
if (!draftRow) {
|
|
6585
|
-
|
|
6585
|
+
const liveRow = await this.database.prepare(`SELECT 1 FROM ${liveTableName} WHERE id = ? AND status = 'draft'`).bind(entryId).first();
|
|
6586
|
+
if (!liveRow) {
|
|
6587
|
+
throw new EntryNotFoundError9(`No draft found for ${entryId} in ${seed.slug}`);
|
|
6588
|
+
}
|
|
6589
|
+
await this.database.prepare(`UPDATE ${liveTableName} SET status = 'published', updated_at = (unixepoch()) WHERE id = ?`).bind(entryId).run();
|
|
6590
|
+
return;
|
|
6586
6591
|
}
|
|
6587
6592
|
let touchedFields = [];
|
|
6588
6593
|
if (draftRow && typeof draftRow["_touched_fields"] === "string") {
|
|
@@ -6606,15 +6611,9 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6606
6611
|
queryBindings.push(draftRow[branch.alias]);
|
|
6607
6612
|
}
|
|
6608
6613
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
updateSql = `UPDATE ${liveTableName} SET ${updateClauses.join(", ")} WHERE id = ?`;
|
|
6613
|
-
queryBindings.push(entryId);
|
|
6614
|
-
} else {
|
|
6615
|
-
updateSql = `UPDATE ${liveTableName} SET updated_at = (unixepoch()) WHERE id = ?`;
|
|
6616
|
-
queryBindings.push(entryId);
|
|
6617
|
-
}
|
|
6614
|
+
updateClauses.push("status = 'published'", "updated_at = (unixepoch())");
|
|
6615
|
+
const updateSql = `UPDATE ${liveTableName} SET ${updateClauses.join(", ")} WHERE id = ?`;
|
|
6616
|
+
queryBindings.push(entryId);
|
|
6618
6617
|
const batchStmts = [
|
|
6619
6618
|
this.database.prepare(updateSql).bind(...queryBindings),
|
|
6620
6619
|
this.database.prepare(`DELETE FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId)
|
|
@@ -6637,7 +6636,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6637
6636
|
}
|
|
6638
6637
|
await this.database.batch(batchStmts);
|
|
6639
6638
|
} catch (error) {
|
|
6640
|
-
if (error instanceof
|
|
6639
|
+
if (error instanceof EntryNotFoundError9) throw error;
|
|
6641
6640
|
if (error instanceof RelationTargetNotFoundError2) throw error;
|
|
6642
6641
|
throw this.mapError(error, `publishDraft(${seed.slug}, ${entryId})`);
|
|
6643
6642
|
}
|
|
@@ -6665,15 +6664,28 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6665
6664
|
try {
|
|
6666
6665
|
if (!seed.allowDrafts) return;
|
|
6667
6666
|
const draftTableName = this.getTableName(seed.slug, true);
|
|
6668
|
-
|
|
6667
|
+
const liveTableName = this.getTableName(seed.slug);
|
|
6668
|
+
const hasMirror = await this.database.prepare(`SELECT 1 FROM ${draftTableName} WHERE entry_id = ? LIMIT 1`).bind(entryId).first();
|
|
6669
|
+
if (hasMirror) {
|
|
6670
|
+
const batchStmts = [
|
|
6671
|
+
this.database.prepare(`DELETE FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId)
|
|
6672
|
+
];
|
|
6673
|
+
for (const branch of multiRelBranches(seed)) {
|
|
6674
|
+
const jdt = jDraftTable(seed.slug, branch.alias);
|
|
6675
|
+
batchStmts.push(this.database.prepare(`DELETE FROM ${jdt} WHERE entry_id = ?`).bind(entryId));
|
|
6676
|
+
}
|
|
6677
|
+
await this.database.batch(batchStmts);
|
|
6678
|
+
} else {
|
|
6679
|
+
await this.database.prepare(`DELETE FROM ${liveTableName} WHERE id = ? AND status = 'draft'`).bind(entryId).run();
|
|
6680
|
+
}
|
|
6669
6681
|
} catch (error) {
|
|
6670
6682
|
throw this.mapError(error, `deleteDraft(${seed.slug}, ${entryId})`);
|
|
6671
6683
|
}
|
|
6672
6684
|
}
|
|
6673
6685
|
/**
|
|
6674
6686
|
* Finds every pending draft across the given seeds, `UNION ALL`-ing one query per seed
|
|
6675
|
-
* so results span heterogeneous `content_{slug}_drafts` tables
|
|
6676
|
-
* against `activity_logs` to attribute the most recent
|
|
6687
|
+
* so results span heterogeneous `content_{slug}_drafts` tables and live draft entries,
|
|
6688
|
+
* then left-joins each against `activity_logs` to attribute the most recent action.
|
|
6677
6689
|
*
|
|
6678
6690
|
* @param seeds Seeds to search; drafts are looked up per-seed since draft tables aren't unified.
|
|
6679
6691
|
* @returns Draft summaries ordered by most-recently-updated first. `title` falls back to the
|
|
@@ -6689,17 +6701,18 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6689
6701
|
const liveTable = `content_${seed.slug}`;
|
|
6690
6702
|
const titleCol = seed.displayNameAlias;
|
|
6691
6703
|
const isScalar = seed.branches.some((b) => b.alias === titleCol && !(b.type === "relation" && b.multiple === true));
|
|
6692
|
-
const titleExpr = isScalar ? `COALESCE(d.${titleCol}, l.${titleCol})` : `COALESCE(l.slug, d.entry_id)`;
|
|
6704
|
+
const titleExpr = isScalar ? `COALESCE(d.${titleCol}, l.${titleCol}, l.slug, d.entry_id, l.id)` : `COALESCE(l.slug, d.entry_id, l.id)`;
|
|
6693
6705
|
const seedLabel = seed.labelPlural ?? seed.label;
|
|
6694
6706
|
unionSelects.push(`
|
|
6695
6707
|
SELECT
|
|
6696
6708
|
? AS seed_slug,
|
|
6697
6709
|
? AS seed_label,
|
|
6698
|
-
d.entry_id AS id,
|
|
6699
|
-
d.updated_at AS updated_at,
|
|
6710
|
+
COALESCE(d.entry_id, l.id) AS id,
|
|
6711
|
+
COALESCE(d.updated_at, l.updated_at) AS updated_at,
|
|
6700
6712
|
${titleExpr} AS title
|
|
6701
|
-
FROM ${
|
|
6702
|
-
LEFT JOIN ${
|
|
6713
|
+
FROM ${liveTable} l
|
|
6714
|
+
LEFT JOIN ${draftTable} d ON d.entry_id = l.id
|
|
6715
|
+
WHERE l.status = 'draft' OR d.entry_id IS NOT NULL
|
|
6703
6716
|
`);
|
|
6704
6717
|
bindings.push(seed.slug, seedLabel);
|
|
6705
6718
|
}
|
|
@@ -6721,8 +6734,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6721
6734
|
SELECT id FROM activity_logs
|
|
6722
6735
|
WHERE entity_id = ad.id
|
|
6723
6736
|
AND entity_slug = ad.seed_slug
|
|
6724
|
-
AND action
|
|
6725
|
-
AND json_extract(details, '$.note') = 'draft saved'
|
|
6737
|
+
AND action IN ('create', 'update')
|
|
6726
6738
|
ORDER BY created_at DESC
|
|
6727
6739
|
LIMIT 1
|
|
6728
6740
|
)
|
|
@@ -7196,10 +7208,13 @@ var D1NotificationRepository = class {
|
|
|
7196
7208
|
}
|
|
7197
7209
|
async create(record) {
|
|
7198
7210
|
const generatedId = this.idGenerator.uuid();
|
|
7211
|
+
const safeType = Object.hasOwn(record, "type") && record.type ? record.type : "info";
|
|
7212
|
+
const safeTitle = Object.hasOwn(record, "title") && typeof record.title === "string" ? record.title : "";
|
|
7213
|
+
const safeMessage = Object.hasOwn(record, "message") && typeof record.message === "string" ? record.message : "";
|
|
7199
7214
|
await this.database.prepare(
|
|
7200
7215
|
`INSERT INTO notifications (id, title, message, type)
|
|
7201
7216
|
VALUES (?, ?, ?, ?)`
|
|
7202
|
-
).bind(generatedId,
|
|
7217
|
+
).bind(generatedId, safeTitle, safeMessage, safeType).run();
|
|
7203
7218
|
return generatedId;
|
|
7204
7219
|
}
|
|
7205
7220
|
async markRead(notificationId) {
|
|
@@ -7216,13 +7231,14 @@ var D1NotificationRepository = class {
|
|
|
7216
7231
|
}
|
|
7217
7232
|
};
|
|
7218
7233
|
function mapRowToRecord2(row) {
|
|
7234
|
+
const safeType = Object.hasOwn(row, "type") && row.type ? row.type : "info";
|
|
7219
7235
|
return {
|
|
7220
|
-
id: row.id,
|
|
7221
|
-
title: row.title,
|
|
7222
|
-
message: row.message,
|
|
7223
|
-
type:
|
|
7224
|
-
isRead: row.is_read === 1,
|
|
7225
|
-
createdAt: row.created_at
|
|
7236
|
+
id: Object.hasOwn(row, "id") ? row.id : "",
|
|
7237
|
+
title: Object.hasOwn(row, "title") ? row.title : "",
|
|
7238
|
+
message: Object.hasOwn(row, "message") ? row.message : "",
|
|
7239
|
+
type: safeType,
|
|
7240
|
+
isRead: Object.hasOwn(row, "is_read") ? row.is_read === 1 : false,
|
|
7241
|
+
createdAt: Object.hasOwn(row, "created_at") ? row.created_at : 0
|
|
7226
7242
|
};
|
|
7227
7243
|
}
|
|
7228
7244
|
|
|
@@ -7256,12 +7272,12 @@ var D1WidgetRepository = class {
|
|
|
7256
7272
|
database;
|
|
7257
7273
|
async aggregate(seed, formula, window) {
|
|
7258
7274
|
const aggregateExpression = this.buildAggregateExpression(seed, formula);
|
|
7259
|
-
const { sql: timeWindowFilter, bindings } = this.buildTimeWindowFilter(window);
|
|
7275
|
+
const { sql: timeWindowFilter, bindings: timeBindings } = this.buildTimeWindowFilter(window);
|
|
7260
7276
|
const tableName = `content_${seed.slug}`;
|
|
7261
|
-
const sql = `SELECT ${aggregateExpression} as computed_value
|
|
7277
|
+
const sql = `SELECT ${aggregateExpression.sql} as computed_value
|
|
7262
7278
|
FROM ${tableName}
|
|
7263
7279
|
WHERE ${timeWindowFilter}`;
|
|
7264
|
-
const row = await this.database.prepare(sql).bind(...bindings).first();
|
|
7280
|
+
const row = await this.database.prepare(sql).bind(...aggregateExpression.bindings, ...timeBindings).first();
|
|
7265
7281
|
return row?.computed_value ?? 0;
|
|
7266
7282
|
}
|
|
7267
7283
|
async growth(seed, formula, window) {
|
|
@@ -7269,8 +7285,8 @@ var D1WidgetRepository = class {
|
|
|
7269
7285
|
const { currentFilter, previousFilter } = this.buildPreviousWindowFilter(window);
|
|
7270
7286
|
const tableName = `content_${seed.slug}`;
|
|
7271
7287
|
const [currentRow, previousRow] = await Promise.all([
|
|
7272
|
-
this.database.prepare(`SELECT ${aggregateExpression} as computed_value FROM ${tableName} WHERE ${currentFilter.sql}`).bind(...currentFilter.bindings).first(),
|
|
7273
|
-
this.database.prepare(`SELECT ${aggregateExpression} as computed_value FROM ${tableName} WHERE ${previousFilter.sql}`).bind(...previousFilter.bindings).first()
|
|
7288
|
+
this.database.prepare(`SELECT ${aggregateExpression.sql} as computed_value FROM ${tableName} WHERE ${currentFilter.sql}`).bind(...aggregateExpression.bindings, ...currentFilter.bindings).first(),
|
|
7289
|
+
this.database.prepare(`SELECT ${aggregateExpression.sql} as computed_value FROM ${tableName} WHERE ${previousFilter.sql}`).bind(...aggregateExpression.bindings, ...previousFilter.bindings).first()
|
|
7274
7290
|
]);
|
|
7275
7291
|
return {
|
|
7276
7292
|
currentValue: currentRow?.computed_value ?? 0,
|
|
@@ -7330,16 +7346,16 @@ var D1WidgetRepository = class {
|
|
|
7330
7346
|
async timeseries(seed, formula, window, groupColumn) {
|
|
7331
7347
|
const groupColumnExpression = this.resolveColumnExpression(seed, groupColumn);
|
|
7332
7348
|
const aggregateExpression = this.buildAggregateExpression(seed, formula);
|
|
7333
|
-
const { sql: timeWindowFilter, bindings } = this.buildTimeWindowFilter(window);
|
|
7349
|
+
const { sql: timeWindowFilter, bindings: timeBindings } = this.buildTimeWindowFilter(window);
|
|
7334
7350
|
const tableName = `content_${seed.slug}`;
|
|
7335
7351
|
const dateBucketExpression = groupColumnExpression === "created_at" ? `strftime('%Y-%m-%d', created_at, 'unixepoch')` : `strftime('%Y-%m-%d', CAST(${groupColumnExpression} AS INTEGER), 'unixepoch')`;
|
|
7336
7352
|
const sql = `SELECT ${dateBucketExpression} as bucket_label,
|
|
7337
|
-
${aggregateExpression} as bucket_value
|
|
7353
|
+
${aggregateExpression.sql} as bucket_value
|
|
7338
7354
|
FROM ${tableName}
|
|
7339
7355
|
WHERE ${timeWindowFilter}
|
|
7340
7356
|
GROUP BY bucket_label
|
|
7341
7357
|
ORDER BY bucket_label ASC`;
|
|
7342
|
-
const rows = await this.database.prepare(sql).bind(...bindings).all();
|
|
7358
|
+
const rows = await this.database.prepare(sql).bind(...aggregateExpression.bindings, ...timeBindings).all();
|
|
7343
7359
|
return (rows.results ?? []).map((row) => ({
|
|
7344
7360
|
label: row.bucket_label ?? "",
|
|
7345
7361
|
value: row.bucket_value ?? 0
|
|
@@ -7374,44 +7390,46 @@ var D1WidgetRepository = class {
|
|
|
7374
7390
|
buildAggregateExpression(seed, formula) {
|
|
7375
7391
|
switch (formula.op) {
|
|
7376
7392
|
case "count":
|
|
7377
|
-
return "COUNT(*)";
|
|
7393
|
+
return { sql: "COUNT(*)", bindings: [] };
|
|
7378
7394
|
case "sum": {
|
|
7379
7395
|
const column = this.resolveColumnExpression(seed, formula.column);
|
|
7380
|
-
return `SUM(CAST(${column} AS REAL))
|
|
7396
|
+
return { sql: `SUM(CAST(${column} AS REAL))`, bindings: [] };
|
|
7381
7397
|
}
|
|
7382
7398
|
case "avg": {
|
|
7383
7399
|
const column = this.resolveColumnExpression(seed, formula.column);
|
|
7384
|
-
return `AVG(CAST(${column} AS REAL))
|
|
7400
|
+
return { sql: `AVG(CAST(${column} AS REAL))`, bindings: [] };
|
|
7385
7401
|
}
|
|
7386
7402
|
case "min": {
|
|
7387
7403
|
const column = this.resolveColumnExpression(seed, formula.column);
|
|
7388
|
-
return `MIN(CAST(${column} AS REAL))
|
|
7404
|
+
return { sql: `MIN(CAST(${column} AS REAL))`, bindings: [] };
|
|
7389
7405
|
}
|
|
7390
7406
|
case "max": {
|
|
7391
7407
|
const column = this.resolveColumnExpression(seed, formula.column);
|
|
7392
|
-
return `MAX(CAST(${column} AS REAL))
|
|
7408
|
+
return { sql: `MAX(CAST(${column} AS REAL))`, bindings: [] };
|
|
7393
7409
|
}
|
|
7394
7410
|
case "countWhere":
|
|
7395
7411
|
return this.buildCountWhereExpression(seed, formula.column, formula.value);
|
|
7396
7412
|
case "percentageOf": {
|
|
7397
7413
|
const numerator = this.resolveColumnExpression(seed, formula.numeratorColumn);
|
|
7398
7414
|
const denominator = this.resolveColumnExpression(seed, formula.denominatorColumn);
|
|
7399
|
-
return
|
|
7415
|
+
return {
|
|
7416
|
+
sql: `CASE WHEN SUM(CAST(${denominator} AS REAL)) = 0 THEN 0 ELSE (SUM(CAST(${numerator} AS REAL)) * 100.0 / SUM(CAST(${denominator} AS REAL))) END`,
|
|
7417
|
+
bindings: []
|
|
7418
|
+
};
|
|
7400
7419
|
}
|
|
7401
7420
|
}
|
|
7402
7421
|
}
|
|
7403
7422
|
buildCountWhereExpression(seed, alias, value) {
|
|
7404
7423
|
const column = this.resolveColumnExpression(seed, alias);
|
|
7405
|
-
if (value === null) return `COUNT(CASE WHEN ${column} IS NULL THEN 1 END)
|
|
7424
|
+
if (value === null) return { sql: `COUNT(CASE WHEN ${column} IS NULL THEN 1 END)`, bindings: [] };
|
|
7406
7425
|
if (typeof value === "boolean") {
|
|
7407
7426
|
const numericValue = value ? 1 : 0;
|
|
7408
|
-
return `COUNT(CASE WHEN ${column} =
|
|
7427
|
+
return { sql: `COUNT(CASE WHEN ${column} = ? THEN 1 END)`, bindings: [numericValue] };
|
|
7409
7428
|
}
|
|
7410
7429
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
7411
|
-
return `COUNT(CASE WHEN CAST(${column} AS REAL) =
|
|
7430
|
+
return { sql: `COUNT(CASE WHEN CAST(${column} AS REAL) = ? THEN 1 END)`, bindings: [value] };
|
|
7412
7431
|
}
|
|
7413
|
-
|
|
7414
|
-
return `COUNT(CASE WHEN ${column} = '${escaped}' THEN 1 END)`;
|
|
7432
|
+
return { sql: `COUNT(CASE WHEN ${column} = ? THEN 1 END)`, bindings: [value] };
|
|
7415
7433
|
}
|
|
7416
7434
|
buildTimeWindowFilter(window) {
|
|
7417
7435
|
if (isDateRange(window)) {
|
|
@@ -7689,10 +7707,10 @@ var D1SiteSettingsRepository = class {
|
|
|
7689
7707
|
var nowSec = Math.floor(Date.now() / 1e3);
|
|
7690
7708
|
var DAY = 86400;
|
|
7691
7709
|
var t_c01 = nowSec - 25 * DAY;
|
|
7692
|
-
var t_c02 = nowSec -
|
|
7693
|
-
var t_c03 = nowSec -
|
|
7694
|
-
var t_c04 = nowSec -
|
|
7695
|
-
var t_c05 = nowSec -
|
|
7710
|
+
var t_c02 = nowSec - 18 * DAY;
|
|
7711
|
+
var t_c03 = nowSec - 12 * DAY;
|
|
7712
|
+
var t_c04 = nowSec - 6 * DAY;
|
|
7713
|
+
var t_c05 = nowSec - 1 * DAY;
|
|
7696
7714
|
function toIsoDate(sec) {
|
|
7697
7715
|
return new Date(sec * 1e3).toISOString().split("T")[0];
|
|
7698
7716
|
}
|
|
@@ -7723,11 +7741,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7723
7741
|
},
|
|
7724
7742
|
{
|
|
7725
7743
|
id: "c0200000-0000-4000-8000-000000000002",
|
|
7726
|
-
slug: "
|
|
7744
|
+
slug: "david-ferrari",
|
|
7727
7745
|
status: "published",
|
|
7728
7746
|
data: {
|
|
7729
|
-
name: "
|
|
7730
|
-
email: "
|
|
7747
|
+
name: "David Ferrari",
|
|
7748
|
+
email: "david.ferrari@innovasolutions.io",
|
|
7731
7749
|
company: "Innova Solutions",
|
|
7732
7750
|
tier: "pro",
|
|
7733
7751
|
account_status: "active",
|
|
@@ -7736,11 +7754,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7736
7754
|
},
|
|
7737
7755
|
{
|
|
7738
7756
|
id: "c0300000-0000-4000-8000-000000000003",
|
|
7739
|
-
slug: "
|
|
7757
|
+
slug: "philip-lombardi",
|
|
7740
7758
|
status: "published",
|
|
7741
7759
|
data: {
|
|
7742
|
-
name: "
|
|
7743
|
-
email: "
|
|
7760
|
+
name: "Philip Lombardi",
|
|
7761
|
+
email: "philip.lombardi@novalabs.dev",
|
|
7744
7762
|
company: "Nova Labs",
|
|
7745
7763
|
tier: "free",
|
|
7746
7764
|
account_status: "churned",
|
|
@@ -7749,11 +7767,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7749
7767
|
},
|
|
7750
7768
|
{
|
|
7751
7769
|
id: "c0400000-0000-4000-8000-000000000004",
|
|
7752
|
-
slug: "
|
|
7770
|
+
slug: "sarah-gallo",
|
|
7753
7771
|
status: "published",
|
|
7754
7772
|
data: {
|
|
7755
|
-
name: "
|
|
7756
|
-
email: "
|
|
7773
|
+
name: "Sarah Gallo",
|
|
7774
|
+
email: "sarah.gallo@alphadynamics.io",
|
|
7757
7775
|
company: "Alpha Dynamics",
|
|
7758
7776
|
tier: "enterprise",
|
|
7759
7777
|
account_status: "active",
|
|
@@ -7762,11 +7780,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7762
7780
|
},
|
|
7763
7781
|
{
|
|
7764
7782
|
id: "c0500000-0000-4000-8000-000000000005",
|
|
7765
|
-
slug: "
|
|
7783
|
+
slug: "matthew-conti",
|
|
7766
7784
|
status: "published",
|
|
7767
7785
|
data: {
|
|
7768
|
-
name: "
|
|
7769
|
-
email: "
|
|
7786
|
+
name: "Matthew Conti",
|
|
7787
|
+
email: "matthew.conti@cloudbase.app",
|
|
7770
7788
|
company: "CloudBase",
|
|
7771
7789
|
tier: "free",
|
|
7772
7790
|
account_status: "active",
|
|
@@ -7777,7 +7795,7 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7777
7795
|
var DEMO_ABBONAMENTI_FIXTURES = [
|
|
7778
7796
|
{
|
|
7779
7797
|
id: "a0100000-0000-4000-8000-000000000001",
|
|
7780
|
-
slug: "
|
|
7798
|
+
slug: "subscription-vertex-digital",
|
|
7781
7799
|
status: "published",
|
|
7782
7800
|
data: {
|
|
7783
7801
|
customer_id: "c0100000-0000-4000-8000-000000000001",
|
|
@@ -7788,7 +7806,7 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7788
7806
|
},
|
|
7789
7807
|
{
|
|
7790
7808
|
id: "a0200000-0000-4000-8000-000000000002",
|
|
7791
|
-
slug: "
|
|
7809
|
+
slug: "subscription-innova-solutions",
|
|
7792
7810
|
status: "published",
|
|
7793
7811
|
data: {
|
|
7794
7812
|
customer_id: "c0200000-0000-4000-8000-000000000002",
|
|
@@ -7799,7 +7817,7 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7799
7817
|
},
|
|
7800
7818
|
{
|
|
7801
7819
|
id: "a0300000-0000-4000-8000-000000000003",
|
|
7802
|
-
slug: "
|
|
7820
|
+
slug: "subscription-alpha-dynamics",
|
|
7803
7821
|
status: "published",
|
|
7804
7822
|
data: {
|
|
7805
7823
|
customer_id: "c0400000-0000-4000-8000-000000000004",
|
|
@@ -7812,10 +7830,10 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7812
7830
|
var DEMO_TICKET_FIXTURES = [
|
|
7813
7831
|
{
|
|
7814
7832
|
id: "t0100000-0000-4000-8000-000000000001",
|
|
7815
|
-
slug: "
|
|
7833
|
+
slug: "webhook-integration-failure",
|
|
7816
7834
|
status: "published",
|
|
7817
7835
|
data: {
|
|
7818
|
-
title: "
|
|
7836
|
+
title: "Webhook integration failure on invoice.paid event",
|
|
7819
7837
|
customer_id: "c0100000-0000-4000-8000-000000000001",
|
|
7820
7838
|
priority: "high",
|
|
7821
7839
|
category: "technical",
|
|
@@ -7824,10 +7842,10 @@ var DEMO_TICKET_FIXTURES = [
|
|
|
7824
7842
|
},
|
|
7825
7843
|
{
|
|
7826
7844
|
id: "t0200000-0000-4000-8000-000000000002",
|
|
7827
|
-
slug: "
|
|
7845
|
+
slug: "annual-billing-vat-invoicing",
|
|
7828
7846
|
status: "published",
|
|
7829
7847
|
data: {
|
|
7830
|
-
title: "
|
|
7848
|
+
title: "Annual billing VAT invoice code update request",
|
|
7831
7849
|
customer_id: "c0400000-0000-4000-8000-000000000004",
|
|
7832
7850
|
priority: "medium",
|
|
7833
7851
|
category: "billing",
|
|
@@ -7843,7 +7861,7 @@ var DEMO_CHANGELOG_FIXTURES = [
|
|
|
7843
7861
|
data: {
|
|
7844
7862
|
version: "v2.4.0",
|
|
7845
7863
|
release_date: toIsoDate(nowSec - 22 * DAY),
|
|
7846
|
-
features: toRichtextDoc("
|
|
7864
|
+
features: toRichtextDoc("Introduced 4-tier native privacy support, AES-256-GCM encryption, and Blind Indexing for secure search.")
|
|
7847
7865
|
}
|
|
7848
7866
|
},
|
|
7849
7867
|
{
|
|
@@ -7853,31 +7871,31 @@ var DEMO_CHANGELOG_FIXTURES = [
|
|
|
7853
7871
|
data: {
|
|
7854
7872
|
version: "v2.3.0",
|
|
7855
7873
|
release_date: toIsoDate(nowSec - 8 * DAY),
|
|
7856
|
-
features: toRichtextDoc("
|
|
7874
|
+
features: toRichtextDoc("Added advanced automation engine and dynamic Kanban card positioning.")
|
|
7857
7875
|
}
|
|
7858
7876
|
}
|
|
7859
7877
|
];
|
|
7860
7878
|
var DEMO_ARTICOLI_FIXTURES = [
|
|
7861
7879
|
{
|
|
7862
7880
|
id: "ar100000-0000-4000-8000-000000000001",
|
|
7863
|
-
slug: "
|
|
7881
|
+
slug: "guide-to-application-level-encryption",
|
|
7864
7882
|
status: "published",
|
|
7865
7883
|
data: {
|
|
7866
|
-
title: "
|
|
7884
|
+
title: "Guide to Application-Level Encryption in BeechCMS",
|
|
7867
7885
|
author: "Flavio De Musso",
|
|
7868
7886
|
cover_image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71.jpg?w=800&q=80",
|
|
7869
|
-
body: toRichtextDoc("
|
|
7887
|
+
body: toRichtextDoc("Learn how to protect confidential customer data directly inside the engine without sacrificing Cloudflare Workers edge performance.")
|
|
7870
7888
|
}
|
|
7871
7889
|
},
|
|
7872
7890
|
{
|
|
7873
7891
|
id: "ar200000-0000-4000-8000-000000000002",
|
|
7874
|
-
slug: "
|
|
7892
|
+
slug: "botanical-engine-and-cloudflare-d1",
|
|
7875
7893
|
status: "published",
|
|
7876
7894
|
data: {
|
|
7877
|
-
title: "
|
|
7895
|
+
title: "Botanical Engine Architecture & Cloudflare D1",
|
|
7878
7896
|
author: "Beech Core Team",
|
|
7879
7897
|
cover_image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f.jpg?w=800&q=80",
|
|
7880
|
-
body: toRichtextDoc("
|
|
7898
|
+
body: toRichtextDoc("An in-depth overview of the separation between @beechcms/core domain contracts and edge SQLite persistence.")
|
|
7881
7899
|
}
|
|
7882
7900
|
}
|
|
7883
7901
|
];
|
|
@@ -7978,9 +7996,13 @@ var D1SeedLayoutRepository = class {
|
|
|
7978
7996
|
await this.db.prepare("DELETE FROM seed_layouts WHERE slug = ?").bind(slug).run();
|
|
7979
7997
|
}
|
|
7980
7998
|
async getViewConfig(slug) {
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7999
|
+
try {
|
|
8000
|
+
const row = await this.db.prepare("SELECT view_config FROM seed_layouts WHERE slug = ?").bind(slug).first();
|
|
8001
|
+
if (!row?.view_config) return null;
|
|
8002
|
+
return seedViewConfigSchema2.parse(JSON.parse(row.view_config));
|
|
8003
|
+
} catch {
|
|
8004
|
+
return null;
|
|
8005
|
+
}
|
|
7984
8006
|
}
|
|
7985
8007
|
async setViewConfig(slug, config, updatedBy) {
|
|
7986
8008
|
const json = JSON.stringify(config);
|
|
@@ -9939,6 +9961,9 @@ function createBeechApp(config) {
|
|
|
9939
9961
|
apiPublic.use("*", publicRateLimitMiddleware());
|
|
9940
9962
|
apiPublic.use("*", apiKeyMiddleware());
|
|
9941
9963
|
apiPublic.route("/", publicRoutes);
|
|
9964
|
+
apiPublic.all("*", (c) => {
|
|
9965
|
+
return c.json({ error: "Not Found", message: `Public endpoint ${c.req.method} ${c.req.path} does not exist.` }, 404);
|
|
9966
|
+
});
|
|
9942
9967
|
app.route("/api/v1/public", apiPublic);
|
|
9943
9968
|
app.route("/api/webhooks", webhooksApp);
|
|
9944
9969
|
app.get("/api/media/:key{.+}", (context) => serveMediaHandler(context));
|
|
@@ -9967,7 +9992,7 @@ function createBeechApp(config) {
|
|
|
9967
9992
|
const headers = new Headers(assetResponse.headers);
|
|
9968
9993
|
headers.set(
|
|
9969
9994
|
"Content-Security-Policy",
|
|
9970
|
-
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self'
|
|
9995
|
+
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; connect-src 'self' http: https:; frame-ancestors 'none'"
|
|
9971
9996
|
);
|
|
9972
9997
|
headers.set("X-Frame-Options", "DENY");
|
|
9973
9998
|
headers.set("X-Content-Type-Options", "nosniff");
|