@beechcms/api 0.6.5 → 0.6.7
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-IiMhdPtU.js → chart-D_uST30B.js} +2 -2
- package/assets/dashboard/assets/index-B58kBLrh.css +1 -0
- package/assets/dashboard/assets/{index-Culul_0c.js → index-DjniT5Z9.js} +93 -93
- package/assets/dashboard/assets/{pie-chart-recharts-BZAzxTy0.js → pie-chart-recharts-CnVomiSi.js} +1 -1
- package/assets/dashboard/assets/{timeseries-chart-recharts-BFQVPxPT.js → timeseries-chart-recharts-CTqi8qnV.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 +115 -103
- 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/package.json +2 -2
- package/assets/dashboard/assets/index-DWm-ERoT.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
|
)
|
|
@@ -7664,13 +7676,13 @@ var D1SiteSettingsRepository = class {
|
|
|
7664
7676
|
const { results } = await this.db.prepare("SELECT key, value FROM site_settings").all();
|
|
7665
7677
|
const map = new Map(results.map((r) => [r.key, r.value]));
|
|
7666
7678
|
return {
|
|
7667
|
-
siteTitle: map.get("siteTitle")
|
|
7668
|
-
defaultLanguage: map.get("defaultLanguage")
|
|
7669
|
-
timezone: map.get("timezone")
|
|
7670
|
-
currency: map.get("currency")
|
|
7671
|
-
companyName: map.get("companyName")
|
|
7672
|
-
companyWebsite: map.get("companyWebsite")
|
|
7673
|
-
companyAbbreviation: map.get("companyAbbreviation")
|
|
7679
|
+
siteTitle: map.get("siteTitle") || DEFAULTS.siteTitle,
|
|
7680
|
+
defaultLanguage: map.get("defaultLanguage") || DEFAULTS.defaultLanguage,
|
|
7681
|
+
timezone: map.get("timezone") || DEFAULTS.timezone,
|
|
7682
|
+
currency: map.get("currency") || DEFAULTS.currency,
|
|
7683
|
+
companyName: map.get("companyName") || null,
|
|
7684
|
+
companyWebsite: map.get("companyWebsite") || null,
|
|
7685
|
+
companyAbbreviation: map.get("companyAbbreviation") || null
|
|
7674
7686
|
};
|
|
7675
7687
|
}
|
|
7676
7688
|
async setMany(values) {
|
|
@@ -7679,7 +7691,7 @@ var D1SiteSettingsRepository = class {
|
|
|
7679
7691
|
const stmts = entries.map(
|
|
7680
7692
|
([key, value]) => this.db.prepare(
|
|
7681
7693
|
"INSERT INTO site_settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
7682
|
-
).bind(key, value)
|
|
7694
|
+
).bind(key, value ?? "")
|
|
7683
7695
|
);
|
|
7684
7696
|
await this.db.batch(stmts);
|
|
7685
7697
|
}
|
|
@@ -7689,10 +7701,10 @@ var D1SiteSettingsRepository = class {
|
|
|
7689
7701
|
var nowSec = Math.floor(Date.now() / 1e3);
|
|
7690
7702
|
var DAY = 86400;
|
|
7691
7703
|
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 -
|
|
7704
|
+
var t_c02 = nowSec - 18 * DAY;
|
|
7705
|
+
var t_c03 = nowSec - 12 * DAY;
|
|
7706
|
+
var t_c04 = nowSec - 6 * DAY;
|
|
7707
|
+
var t_c05 = nowSec - 1 * DAY;
|
|
7696
7708
|
function toIsoDate(sec) {
|
|
7697
7709
|
return new Date(sec * 1e3).toISOString().split("T")[0];
|
|
7698
7710
|
}
|
|
@@ -7723,11 +7735,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7723
7735
|
},
|
|
7724
7736
|
{
|
|
7725
7737
|
id: "c0200000-0000-4000-8000-000000000002",
|
|
7726
|
-
slug: "
|
|
7738
|
+
slug: "david-ferrari",
|
|
7727
7739
|
status: "published",
|
|
7728
7740
|
data: {
|
|
7729
|
-
name: "
|
|
7730
|
-
email: "
|
|
7741
|
+
name: "David Ferrari",
|
|
7742
|
+
email: "david.ferrari@innovasolutions.io",
|
|
7731
7743
|
company: "Innova Solutions",
|
|
7732
7744
|
tier: "pro",
|
|
7733
7745
|
account_status: "active",
|
|
@@ -7736,11 +7748,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7736
7748
|
},
|
|
7737
7749
|
{
|
|
7738
7750
|
id: "c0300000-0000-4000-8000-000000000003",
|
|
7739
|
-
slug: "
|
|
7751
|
+
slug: "philip-lombardi",
|
|
7740
7752
|
status: "published",
|
|
7741
7753
|
data: {
|
|
7742
|
-
name: "
|
|
7743
|
-
email: "
|
|
7754
|
+
name: "Philip Lombardi",
|
|
7755
|
+
email: "philip.lombardi@novalabs.dev",
|
|
7744
7756
|
company: "Nova Labs",
|
|
7745
7757
|
tier: "free",
|
|
7746
7758
|
account_status: "churned",
|
|
@@ -7749,11 +7761,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7749
7761
|
},
|
|
7750
7762
|
{
|
|
7751
7763
|
id: "c0400000-0000-4000-8000-000000000004",
|
|
7752
|
-
slug: "
|
|
7764
|
+
slug: "sarah-gallo",
|
|
7753
7765
|
status: "published",
|
|
7754
7766
|
data: {
|
|
7755
|
-
name: "
|
|
7756
|
-
email: "
|
|
7767
|
+
name: "Sarah Gallo",
|
|
7768
|
+
email: "sarah.gallo@alphadynamics.io",
|
|
7757
7769
|
company: "Alpha Dynamics",
|
|
7758
7770
|
tier: "enterprise",
|
|
7759
7771
|
account_status: "active",
|
|
@@ -7762,11 +7774,11 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7762
7774
|
},
|
|
7763
7775
|
{
|
|
7764
7776
|
id: "c0500000-0000-4000-8000-000000000005",
|
|
7765
|
-
slug: "
|
|
7777
|
+
slug: "matthew-conti",
|
|
7766
7778
|
status: "published",
|
|
7767
7779
|
data: {
|
|
7768
|
-
name: "
|
|
7769
|
-
email: "
|
|
7780
|
+
name: "Matthew Conti",
|
|
7781
|
+
email: "matthew.conti@cloudbase.app",
|
|
7770
7782
|
company: "CloudBase",
|
|
7771
7783
|
tier: "free",
|
|
7772
7784
|
account_status: "active",
|
|
@@ -7777,7 +7789,7 @@ var DEMO_CLIENTI_FIXTURES = [
|
|
|
7777
7789
|
var DEMO_ABBONAMENTI_FIXTURES = [
|
|
7778
7790
|
{
|
|
7779
7791
|
id: "a0100000-0000-4000-8000-000000000001",
|
|
7780
|
-
slug: "
|
|
7792
|
+
slug: "subscription-vertex-digital",
|
|
7781
7793
|
status: "published",
|
|
7782
7794
|
data: {
|
|
7783
7795
|
customer_id: "c0100000-0000-4000-8000-000000000001",
|
|
@@ -7788,7 +7800,7 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7788
7800
|
},
|
|
7789
7801
|
{
|
|
7790
7802
|
id: "a0200000-0000-4000-8000-000000000002",
|
|
7791
|
-
slug: "
|
|
7803
|
+
slug: "subscription-innova-solutions",
|
|
7792
7804
|
status: "published",
|
|
7793
7805
|
data: {
|
|
7794
7806
|
customer_id: "c0200000-0000-4000-8000-000000000002",
|
|
@@ -7799,7 +7811,7 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7799
7811
|
},
|
|
7800
7812
|
{
|
|
7801
7813
|
id: "a0300000-0000-4000-8000-000000000003",
|
|
7802
|
-
slug: "
|
|
7814
|
+
slug: "subscription-alpha-dynamics",
|
|
7803
7815
|
status: "published",
|
|
7804
7816
|
data: {
|
|
7805
7817
|
customer_id: "c0400000-0000-4000-8000-000000000004",
|
|
@@ -7812,10 +7824,10 @@ var DEMO_ABBONAMENTI_FIXTURES = [
|
|
|
7812
7824
|
var DEMO_TICKET_FIXTURES = [
|
|
7813
7825
|
{
|
|
7814
7826
|
id: "t0100000-0000-4000-8000-000000000001",
|
|
7815
|
-
slug: "
|
|
7827
|
+
slug: "webhook-integration-failure",
|
|
7816
7828
|
status: "published",
|
|
7817
7829
|
data: {
|
|
7818
|
-
title: "
|
|
7830
|
+
title: "Webhook integration failure on invoice.paid event",
|
|
7819
7831
|
customer_id: "c0100000-0000-4000-8000-000000000001",
|
|
7820
7832
|
priority: "high",
|
|
7821
7833
|
category: "technical",
|
|
@@ -7824,10 +7836,10 @@ var DEMO_TICKET_FIXTURES = [
|
|
|
7824
7836
|
},
|
|
7825
7837
|
{
|
|
7826
7838
|
id: "t0200000-0000-4000-8000-000000000002",
|
|
7827
|
-
slug: "
|
|
7839
|
+
slug: "annual-billing-vat-invoicing",
|
|
7828
7840
|
status: "published",
|
|
7829
7841
|
data: {
|
|
7830
|
-
title: "
|
|
7842
|
+
title: "Annual billing VAT invoice code update request",
|
|
7831
7843
|
customer_id: "c0400000-0000-4000-8000-000000000004",
|
|
7832
7844
|
priority: "medium",
|
|
7833
7845
|
category: "billing",
|
|
@@ -7843,7 +7855,7 @@ var DEMO_CHANGELOG_FIXTURES = [
|
|
|
7843
7855
|
data: {
|
|
7844
7856
|
version: "v2.4.0",
|
|
7845
7857
|
release_date: toIsoDate(nowSec - 22 * DAY),
|
|
7846
|
-
features: toRichtextDoc("
|
|
7858
|
+
features: toRichtextDoc("Introduced 4-tier native privacy support, AES-256-GCM encryption, and Blind Indexing for secure search.")
|
|
7847
7859
|
}
|
|
7848
7860
|
},
|
|
7849
7861
|
{
|
|
@@ -7853,31 +7865,31 @@ var DEMO_CHANGELOG_FIXTURES = [
|
|
|
7853
7865
|
data: {
|
|
7854
7866
|
version: "v2.3.0",
|
|
7855
7867
|
release_date: toIsoDate(nowSec - 8 * DAY),
|
|
7856
|
-
features: toRichtextDoc("
|
|
7868
|
+
features: toRichtextDoc("Added advanced automation engine and dynamic Kanban card positioning.")
|
|
7857
7869
|
}
|
|
7858
7870
|
}
|
|
7859
7871
|
];
|
|
7860
7872
|
var DEMO_ARTICOLI_FIXTURES = [
|
|
7861
7873
|
{
|
|
7862
7874
|
id: "ar100000-0000-4000-8000-000000000001",
|
|
7863
|
-
slug: "
|
|
7875
|
+
slug: "guide-to-application-level-encryption",
|
|
7864
7876
|
status: "published",
|
|
7865
7877
|
data: {
|
|
7866
|
-
title: "
|
|
7878
|
+
title: "Guide to Application-Level Encryption in BeechCMS",
|
|
7867
7879
|
author: "Flavio De Musso",
|
|
7868
7880
|
cover_image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71.jpg?w=800&q=80",
|
|
7869
|
-
body: toRichtextDoc("
|
|
7881
|
+
body: toRichtextDoc("Learn how to protect confidential customer data directly inside the engine without sacrificing Cloudflare Workers edge performance.")
|
|
7870
7882
|
}
|
|
7871
7883
|
},
|
|
7872
7884
|
{
|
|
7873
7885
|
id: "ar200000-0000-4000-8000-000000000002",
|
|
7874
|
-
slug: "
|
|
7886
|
+
slug: "botanical-engine-and-cloudflare-d1",
|
|
7875
7887
|
status: "published",
|
|
7876
7888
|
data: {
|
|
7877
|
-
title: "
|
|
7889
|
+
title: "Botanical Engine Architecture & Cloudflare D1",
|
|
7878
7890
|
author: "Beech Core Team",
|
|
7879
7891
|
cover_image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f.jpg?w=800&q=80",
|
|
7880
|
-
body: toRichtextDoc("
|
|
7892
|
+
body: toRichtextDoc("An in-depth overview of the separation between @beechcms/core domain contracts and edge SQLite persistence.")
|
|
7881
7893
|
}
|
|
7882
7894
|
}
|
|
7883
7895
|
];
|
|
@@ -6,7 +6,7 @@ export declare function checkPublicOperation(seed: Seed, operation: PublicOperat
|
|
|
6
6
|
} | {
|
|
7
7
|
readonly ok: false;
|
|
8
8
|
readonly error: {
|
|
9
|
-
readonly error:
|
|
9
|
+
readonly error: "Forbidden";
|
|
10
10
|
readonly message: `Public ${string} is not allowed for content type '${string}'.`;
|
|
11
11
|
};
|
|
12
12
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
type EdgeCache = {
|
|
3
|
+
cache: Cache;
|
|
4
|
+
executionCtx: {
|
|
5
|
+
waitUntil: (p: Promise<unknown>) => void;
|
|
6
|
+
};
|
|
7
|
+
} | null;
|
|
8
|
+
export declare function resolveEdgeCache(c: Context): EdgeCache;
|
|
9
|
+
export declare function withCachedResponse(edgeCache: EdgeCache, cacheKey: Request, response: Response): Response;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a Problem+JSON response when the error is a SQLite FK violation,
|
|
4
|
+
* or null when the error is unrelated (let the caller handle it).
|
|
5
|
+
*
|
|
6
|
+
* @param c Hono context
|
|
7
|
+
* @param error The caught error
|
|
8
|
+
* @param method HTTP method of the current request (e.g. 'DELETE', 'POST')
|
|
9
|
+
*/
|
|
10
|
+
export declare function fkProblemOrNull(c: Context, error: unknown, method: string): Response | null;
|
|
11
|
+
export interface PublicProblemDetailItem {
|
|
12
|
+
field: string;
|
|
13
|
+
expected: string;
|
|
14
|
+
received: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
type PublicProblemInput = {
|
|
18
|
+
type: string;
|
|
19
|
+
title: string;
|
|
20
|
+
status: 400 | 401 | 403 | 404 | 405 | 409 | 422 | 429 | 500 | 501;
|
|
21
|
+
detail: string;
|
|
22
|
+
errors?: PublicProblemDetailItem[];
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
export declare function internalErrorDetail(env: {
|
|
26
|
+
ENV?: string;
|
|
27
|
+
}, error: unknown): string;
|
|
28
|
+
/**
|
|
29
|
+
* Restituisce errori API in formato Problem Details (RFC 9457).
|
|
30
|
+
*/
|
|
31
|
+
export declare function publicProblem(c: Context, input: PublicProblemInput): Response;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error constants for Public API responses.
|
|
3
|
+
*/
|
|
4
|
+
export declare const PUBLIC_ERRORS: {
|
|
5
|
+
readonly API_KEY_UNAUTHORIZED: {
|
|
6
|
+
readonly error: "Unauthorized";
|
|
7
|
+
readonly message: "Missing or invalid API key. Provide a valid key via X-API-Key header.";
|
|
8
|
+
};
|
|
9
|
+
readonly API_KEY_FORBIDDEN: {
|
|
10
|
+
readonly error: "Forbidden";
|
|
11
|
+
readonly message: "Public API access is not configured for this instance.";
|
|
12
|
+
};
|
|
13
|
+
};
|