@beechcms/api 0.6.0 → 0.6.2
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-FUiy4I7R.js → chart-CboAhzSV.js} +1 -1
- package/assets/dashboard/assets/{index-BlehzIfd.js → index-B19ig64T.js} +94 -94
- package/assets/dashboard/assets/{pie-chart-recharts-iaYz4FeR.js → pie-chart-recharts-B89zkqfa.js} +1 -1
- package/assets/dashboard/assets/{timeseries-chart-recharts-C8Lcylom.js → timeseries-chart-recharts-kD3eDNrV.js} +1 -1
- package/assets/dashboard/index.html +1 -1
- package/dist/index.js +100 -59
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -36,10 +36,10 @@ function parseLoginBody(body) {
|
|
|
36
36
|
const password = obj.password;
|
|
37
37
|
if (typeof email !== "string" || typeof password !== "string") return null;
|
|
38
38
|
if (!email.trim() || !password) return null;
|
|
39
|
-
return { email: email.trim(), password };
|
|
39
|
+
return { email: email.trim().toLowerCase(), password };
|
|
40
40
|
}
|
|
41
41
|
function validateLoginInput(email, password) {
|
|
42
|
-
return email.length <= MAX_EMAIL_LENGTH && EMAIL_REGEX.test(email) && password.
|
|
42
|
+
return email.length <= MAX_EMAIL_LENGTH && EMAIL_REGEX.test(email) && password.length >= MIN_PASSWORD_LENGTH && password.length <= MAX_PASSWORD_LENGTH && new TextEncoder().encode(password).length <= MAX_PASSWORD_BYTES;
|
|
43
43
|
}
|
|
44
44
|
async function verifyPassword(plainPassword, hash, hashProvider) {
|
|
45
45
|
return hashProvider.verify(plainPassword, hash);
|
|
@@ -188,7 +188,8 @@ async function applyPrivacy(data, seed) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
if (privacy === "hash" && value != null) {
|
|
191
|
-
|
|
191
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
192
|
+
result[alias] = await sha256hex(serialized);
|
|
192
193
|
} else {
|
|
193
194
|
result[alias] = value;
|
|
194
195
|
}
|
|
@@ -336,9 +337,9 @@ async function buildRelationsMap(context, seed, entries) {
|
|
|
336
337
|
});
|
|
337
338
|
const map = {};
|
|
338
339
|
for (const item of items) {
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
const label =
|
|
340
|
+
const row = item;
|
|
341
|
+
const id = row.id;
|
|
342
|
+
const label = row[labelAlias];
|
|
342
343
|
map[id] = label != null && label !== "" ? String(label) : id;
|
|
343
344
|
}
|
|
344
345
|
relations[branch.alias] = map;
|
|
@@ -885,11 +886,14 @@ var MEDIA_URL_PATTERN = /\/api\/media\/([^?#]+)/;
|
|
|
885
886
|
function extractMediaKey(mediaUrl, cdnUrl) {
|
|
886
887
|
const urlStr = String(mediaUrl);
|
|
887
888
|
if (cdnUrl) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
889
|
+
try {
|
|
890
|
+
const cdnOrigin = new URL(cdnUrl).origin;
|
|
891
|
+
const mediaUrlParsed = new URL(urlStr);
|
|
892
|
+
if (mediaUrlParsed.origin === cdnOrigin) {
|
|
893
|
+
const keyPart = mediaUrlParsed.pathname.replace(/^\//, "");
|
|
894
|
+
return keyPart ? decodeURIComponent(keyPart) : null;
|
|
895
|
+
}
|
|
896
|
+
} catch {
|
|
893
897
|
}
|
|
894
898
|
}
|
|
895
899
|
const match = MEDIA_URL_PATTERN.exec(urlStr);
|
|
@@ -1150,7 +1154,7 @@ async function bulkHandler(context) {
|
|
|
1150
1154
|
}
|
|
1151
1155
|
|
|
1152
1156
|
// src/features/content/handlers/kanban-position.ts
|
|
1153
|
-
import { resolveKanbanConfig as resolveKanbanConfig2 } from "@beechcms/core";
|
|
1157
|
+
import { resolveKanbanConfig as resolveKanbanConfig2, EntryNotFoundError as EntryNotFoundError3 } from "@beechcms/core";
|
|
1154
1158
|
async function kanbanPositionHandler(context) {
|
|
1155
1159
|
const slug = context.req.param("slug");
|
|
1156
1160
|
const id = context.req.param("id");
|
|
@@ -1175,9 +1179,13 @@ async function kanbanPositionHandler(context) {
|
|
|
1175
1179
|
if (!compat.compatible || !compat.candidates.some((c) => c.branchId === axisBranchId)) {
|
|
1176
1180
|
return publicProblem(context, { type: "content-invalid-kanban-axis", title: "Bad Request", status: 400, detail: "axisBranchId is not a valid kanban candidate for this seed" });
|
|
1177
1181
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1182
|
+
try {
|
|
1183
|
+
await context.get("repository").findById(seed, id);
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
if (error instanceof EntryNotFoundError3) {
|
|
1186
|
+
return publicProblem(context, { type: "content-not-found", title: "Not Found", status: 404, detail: CONTENT_ERRORS.NOT_FOUND });
|
|
1187
|
+
}
|
|
1188
|
+
throw error;
|
|
1181
1189
|
}
|
|
1182
1190
|
await context.get("kanbanPositionRepository").setPosition(slug, id, axisBranchId, position);
|
|
1183
1191
|
return context.json({ success: true });
|
|
@@ -1231,7 +1239,7 @@ async function kanbanMoveHandler(context) {
|
|
|
1231
1239
|
const currentTags = current[axisBranch.alias] ?? [];
|
|
1232
1240
|
const { oldValue, newValue } = body.axis;
|
|
1233
1241
|
const withoutOld = oldValue !== null ? currentTags.filter((t) => t !== oldValue) : currentTags;
|
|
1234
|
-
const nextTags = newValue !== null ? [...withoutOld, newValue] : withoutOld;
|
|
1242
|
+
const nextTags = newValue !== null && !withoutOld.includes(newValue) ? [...withoutOld, newValue] : withoutOld;
|
|
1235
1243
|
patch = { [axisBranch.alias]: nextTags };
|
|
1236
1244
|
}
|
|
1237
1245
|
const validation = validateAndSanitizeSeedPayload3(seed, patch, {
|
|
@@ -1504,7 +1512,7 @@ widgetApp.get("/distribution/:seed", async (context) => {
|
|
|
1504
1512
|
|
|
1505
1513
|
// src/features/rotate-field/rotate-field.handler.ts
|
|
1506
1514
|
import { Hono as Hono3 } from "hono";
|
|
1507
|
-
import { resolvePolicies as resolvePolicies4, verifyHashField, sha256hex as sha256hex2, validateAndSanitizeSeedPayload as validateAndSanitizeSeedPayload4, EntryNotFoundError as
|
|
1515
|
+
import { resolvePolicies as resolvePolicies4, verifyHashField, sha256hex as sha256hex2, validateAndSanitizeSeedPayload as validateAndSanitizeSeedPayload4, EntryNotFoundError as EntryNotFoundError4 } from "@beechcms/core";
|
|
1508
1516
|
|
|
1509
1517
|
// src/features/rotate-field/rotate-field.schema.ts
|
|
1510
1518
|
import { z } from "zod";
|
|
@@ -1571,7 +1579,7 @@ rotateFieldApp.post("/:slug/:id/rotate-field", async (context) => {
|
|
|
1571
1579
|
try {
|
|
1572
1580
|
contentRecord = await context.get("repository").findById(seed, entryId);
|
|
1573
1581
|
} catch (error) {
|
|
1574
|
-
if (error instanceof
|
|
1582
|
+
if (error instanceof EntryNotFoundError4) {
|
|
1575
1583
|
return publicProblem(context, {
|
|
1576
1584
|
type: "content-not-found",
|
|
1577
1585
|
title: "Not Found",
|
|
@@ -2274,7 +2282,7 @@ import {
|
|
|
2274
2282
|
|
|
2275
2283
|
// src/features/draft/draft.middleware.ts
|
|
2276
2284
|
import { createMiddleware } from "hono/factory";
|
|
2277
|
-
import { EntryNotFoundError as
|
|
2285
|
+
import { EntryNotFoundError as EntryNotFoundError5 } from "@beechcms/core";
|
|
2278
2286
|
var draftGuard = createMiddleware(async (context, next) => {
|
|
2279
2287
|
const slug = context.req.param("slug");
|
|
2280
2288
|
const id = context.req.param("id");
|
|
@@ -2307,7 +2315,7 @@ var draftGuard = createMiddleware(async (context, next) => {
|
|
|
2307
2315
|
try {
|
|
2308
2316
|
await repository.findById(seed, id);
|
|
2309
2317
|
} catch (error) {
|
|
2310
|
-
if (error instanceof
|
|
2318
|
+
if (error instanceof EntryNotFoundError5) {
|
|
2311
2319
|
return publicProblem(context, {
|
|
2312
2320
|
type: "content-not-found",
|
|
2313
2321
|
title: "Not Found",
|
|
@@ -5294,7 +5302,15 @@ function publicRateLimitMiddleware() {
|
|
|
5294
5302
|
return async (c, next) => {
|
|
5295
5303
|
const readMethod = isReadMethod2(c.req.method);
|
|
5296
5304
|
const limiterName = readMethod ? "publicApiRead" : "publicApiWrite";
|
|
5297
|
-
const
|
|
5305
|
+
const path = c.req.path;
|
|
5306
|
+
let seed = "no-seed";
|
|
5307
|
+
if (path.startsWith("/api/v1/public/")) {
|
|
5308
|
+
const remaining = path.slice("/api/v1/public/".length);
|
|
5309
|
+
const firstSegment = remaining.split("/")[0];
|
|
5310
|
+
if (firstSegment && firstSegment !== "health" && firstSegment !== "schema" && firstSegment !== "schema.html") {
|
|
5311
|
+
seed = firstSegment;
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5298
5314
|
const key = `${getClientIp(c.req)}:${seed}:${limiterName}`;
|
|
5299
5315
|
const result = await c.get("rateLimiters").getLimiter(limiterName).checkLimit(key);
|
|
5300
5316
|
if (!result.isAllowed) {
|
|
@@ -5364,7 +5380,7 @@ function withCachedResponse(edgeCache, cacheKey, response) {
|
|
|
5364
5380
|
}
|
|
5365
5381
|
|
|
5366
5382
|
// src/public/read-single.ts
|
|
5367
|
-
import { EntryNotFoundError as
|
|
5383
|
+
import { EntryNotFoundError as EntryNotFoundError6 } from "@beechcms/core";
|
|
5368
5384
|
|
|
5369
5385
|
// src/public/entry-projection.ts
|
|
5370
5386
|
import { resolvePolicies as resolvePolicies7 } from "@beechcms/core";
|
|
@@ -5380,7 +5396,11 @@ function applyPublicPolicies(data, seed) {
|
|
|
5380
5396
|
const { public: isPublic, visibility } = resolvePolicies7(branch);
|
|
5381
5397
|
if (!isPublic) continue;
|
|
5382
5398
|
if (visibility === "hidden") continue;
|
|
5383
|
-
|
|
5399
|
+
if (visibility === "masked") {
|
|
5400
|
+
result[branch.alias] = typeof value === "string" && value.length > 0 ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null;
|
|
5401
|
+
} else {
|
|
5402
|
+
result[branch.alias] = value;
|
|
5403
|
+
}
|
|
5384
5404
|
}
|
|
5385
5405
|
return result;
|
|
5386
5406
|
}
|
|
@@ -5427,7 +5447,7 @@ async function readSingleEntry(input) {
|
|
|
5427
5447
|
meta: buildPublicSingleMeta(seedSlug)
|
|
5428
5448
|
};
|
|
5429
5449
|
} catch (error) {
|
|
5430
|
-
if (error instanceof
|
|
5450
|
+
if (error instanceof EntryNotFoundError6) {
|
|
5431
5451
|
return { ok: false, detail: `Entry '${label}' not found for content type '${seedSlug}'.` };
|
|
5432
5452
|
}
|
|
5433
5453
|
throw error;
|
|
@@ -5435,6 +5455,7 @@ async function readSingleEntry(input) {
|
|
|
5435
5455
|
}
|
|
5436
5456
|
|
|
5437
5457
|
// src/public/query-builder.ts
|
|
5458
|
+
import { resolvePolicies as resolvePolicies8 } from "@beechcms/core";
|
|
5438
5459
|
var PUBLIC_FILTER_OPERATORS = /* @__PURE__ */ new Set([
|
|
5439
5460
|
"eq",
|
|
5440
5461
|
"neq",
|
|
@@ -5514,6 +5535,12 @@ function toEngineFilters2(seed, parsedFilter) {
|
|
|
5514
5535
|
if (!parsedFilter || parsedFilter.where.length === 0) return [];
|
|
5515
5536
|
return parsedFilter.where.map((cond) => {
|
|
5516
5537
|
const branch = seed.branches.find((b) => b.alias === cond.field);
|
|
5538
|
+
if (branch) {
|
|
5539
|
+
const { public: isPublic, filter: filterable } = resolvePolicies8(branch);
|
|
5540
|
+
if (!isPublic || !filterable) {
|
|
5541
|
+
throw new TypeError(`Invalid filter: field '${cond.field}' is not filterable`);
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5517
5544
|
const type = branch ? mapBranchToFilterType(branch.type) : SYSTEM_COLUMNS2.has(cond.field) ? "system" : "text";
|
|
5518
5545
|
return {
|
|
5519
5546
|
column: cond.field,
|
|
@@ -5554,6 +5581,7 @@ async function readListEntries(input) {
|
|
|
5554
5581
|
const sortDir = (cleanStr(query.orderDir) ?? "desc").toLowerCase() === "asc" ? "ASC" : "DESC";
|
|
5555
5582
|
const { items, total } = await repository.findMany(seed, {
|
|
5556
5583
|
filters: engineFilters,
|
|
5584
|
+
filterLogic: parsedFilter?.logic,
|
|
5557
5585
|
search: search || void 0,
|
|
5558
5586
|
status: publishedOnly ? "published" : null,
|
|
5559
5587
|
pagination: {
|
|
@@ -5620,7 +5648,7 @@ async function publicReadHandler(context) {
|
|
|
5620
5648
|
}
|
|
5621
5649
|
|
|
5622
5650
|
// src/public/public-add.ts
|
|
5623
|
-
import { isValidContentStatus as isValidContentStatus3, SlugConflictError as SlugConflictError2 } from "@beechcms/core";
|
|
5651
|
+
import { isValidContentStatus as isValidContentStatus3, resolvePolicies as resolvePolicies9, SlugConflictError as SlugConflictError2 } from "@beechcms/core";
|
|
5624
5652
|
|
|
5625
5653
|
// src/public/slug-utils.ts
|
|
5626
5654
|
import { slugify as slugify3, generateEntrySlug } from "@beechcms/core";
|
|
@@ -5705,6 +5733,13 @@ async function publicAddHandler(context) {
|
|
|
5705
5733
|
if (!isValidContentStatus3(statusValue)) {
|
|
5706
5734
|
return publicProblem(context, { type: "invalid-status", title: "Bad Request", status: 400, detail: "Invalid status. Allowed values are: draft, review, published" });
|
|
5707
5735
|
}
|
|
5736
|
+
const sensitiveAliases = Object.keys(rawData).filter((alias) => {
|
|
5737
|
+
const branch = seed.branches.find((b) => b.alias === alias);
|
|
5738
|
+
return branch != null && resolvePolicies9(branch).public === false;
|
|
5739
|
+
});
|
|
5740
|
+
if (sensitiveAliases.length > 0) {
|
|
5741
|
+
return publicProblem(context, { type: "sensitive-field-edit", title: "Unprocessable Entity", status: 422, detail: `Cannot write internal fields: ${sensitiveAliases.join(", ")}` });
|
|
5742
|
+
}
|
|
5708
5743
|
const sanitized = sanitizePublicPayload(seed, rawData, { operation: "create", allowNull: false, requireAtLeastOneValidField: true, enforceRequiredFields: true });
|
|
5709
5744
|
if (!sanitized.ok) {
|
|
5710
5745
|
if (sanitized.status === 422) {
|
|
@@ -5712,8 +5747,17 @@ async function publicAddHandler(context) {
|
|
|
5712
5747
|
}
|
|
5713
5748
|
return publicProblem(context, { type: sanitized.code, title: "Bad Request", status: 400, detail: sanitized.message, errors: sanitized.details });
|
|
5714
5749
|
}
|
|
5750
|
+
let privacyData;
|
|
5751
|
+
try {
|
|
5752
|
+
privacyData = await applyPrivacy(sanitized.data, seed);
|
|
5753
|
+
} catch (error) {
|
|
5754
|
+
if (error instanceof PrivacyPolicyError) {
|
|
5755
|
+
return publicProblem(context, { type: "policy-not-implemented", title: "Not Implemented", status: 501, detail: error.message });
|
|
5756
|
+
}
|
|
5757
|
+
throw error;
|
|
5758
|
+
}
|
|
5715
5759
|
const idempotencyKey = parseIdempotencyKey(context.req.header("Idempotency-Key"));
|
|
5716
|
-
const finalSlug = pickSlug(body,
|
|
5760
|
+
const finalSlug = pickSlug(body, privacyData) || context.get("idGenerator").uuid().slice(0, 8);
|
|
5717
5761
|
const repository = context.get("repository");
|
|
5718
5762
|
const idempotencyRepository = context.get("idempotencyRepository");
|
|
5719
5763
|
try {
|
|
@@ -5737,7 +5781,7 @@ async function publicAddHandler(context) {
|
|
|
5737
5781
|
}
|
|
5738
5782
|
const id = context.get("idGenerator").uuid();
|
|
5739
5783
|
try {
|
|
5740
|
-
await repository.create(seed, id, finalSlug, statusValue,
|
|
5784
|
+
await repository.create(seed, id, finalSlug, statusValue, privacyData);
|
|
5741
5785
|
} catch (error) {
|
|
5742
5786
|
if (error instanceof SlugConflictError2) {
|
|
5743
5787
|
return publicProblem(context, { type: "slug-conflict", title: "Conflict", status: 409, detail: `An entry with slug '${finalSlug}' already exists for content type '${seedSlug}'.` });
|
|
@@ -5750,7 +5794,7 @@ async function publicAddHandler(context) {
|
|
|
5750
5794
|
}
|
|
5751
5795
|
context.get("notificationService").notify({
|
|
5752
5796
|
title: `${seed.label}: New entry`,
|
|
5753
|
-
message: `A new entry ("${
|
|
5797
|
+
message: `A new entry ("${privacyData.title || privacyData.name || finalSlug}") has been added via the public API.`,
|
|
5754
5798
|
type: "success"
|
|
5755
5799
|
});
|
|
5756
5800
|
return context.json(responseBody, 201);
|
|
@@ -5761,7 +5805,7 @@ async function publicAddHandler(context) {
|
|
|
5761
5805
|
}
|
|
5762
5806
|
|
|
5763
5807
|
// src/public/public-edit.ts
|
|
5764
|
-
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as
|
|
5808
|
+
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as resolvePolicies10, EntryNotFoundError as EntryNotFoundError7 } from "@beechcms/core";
|
|
5765
5809
|
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;
|
|
5766
5810
|
function errorMessage(context, error) {
|
|
5767
5811
|
if (context.env.ENV !== "production" && error instanceof Error) return error.message;
|
|
@@ -5811,7 +5855,7 @@ function resolveData(context, seed, body) {
|
|
|
5811
5855
|
const sensitiveAliases = Object.keys(rawData).filter((alias) => {
|
|
5812
5856
|
const branch = seed.branches.find((b) => b.alias === alias);
|
|
5813
5857
|
if (!branch) return false;
|
|
5814
|
-
const policies =
|
|
5858
|
+
const policies = resolvePolicies10(branch);
|
|
5815
5859
|
return policies.privacy !== "plain" || policies.public === false;
|
|
5816
5860
|
});
|
|
5817
5861
|
if (sensitiveAliases.length > 0) {
|
|
@@ -5869,7 +5913,7 @@ async function publicEditHandler(context) {
|
|
|
5869
5913
|
});
|
|
5870
5914
|
return context.json({ success: true, id, slug: slugResult.value.nextSlug }, 200);
|
|
5871
5915
|
} catch (error) {
|
|
5872
|
-
if (error instanceof
|
|
5916
|
+
if (error instanceof EntryNotFoundError7) {
|
|
5873
5917
|
return publicProblem(context, { type: "entry-not-found", title: "Not Found", status: 404, detail: `Entry '${id}' not found for content type '${seedSlug}'.` });
|
|
5874
5918
|
}
|
|
5875
5919
|
console.error("Public edit error:", error);
|
|
@@ -6092,7 +6136,7 @@ import { createMiddleware as createMiddleware2 } from "hono/factory";
|
|
|
6092
6136
|
|
|
6093
6137
|
// src/shared/db/repositories/content.repository.d1.ts
|
|
6094
6138
|
import {
|
|
6095
|
-
EntryNotFoundError as
|
|
6139
|
+
EntryNotFoundError as EntryNotFoundError8,
|
|
6096
6140
|
RepositoryError as RepositoryError2,
|
|
6097
6141
|
SlugConflictError as SlugConflictError4,
|
|
6098
6142
|
RelationTargetNotFoundError as RelationTargetNotFoundError2,
|
|
@@ -6120,7 +6164,7 @@ var BaseD1Repository = class {
|
|
|
6120
6164
|
*/
|
|
6121
6165
|
mapError(error, context) {
|
|
6122
6166
|
const message = error?.message || "Unknown database error";
|
|
6123
|
-
if (message.includes("UNIQUE constraint failed
|
|
6167
|
+
if (message.includes("UNIQUE constraint failed") && message.includes("slug")) {
|
|
6124
6168
|
return new SlugConflictError3(`${context}: ${message}`);
|
|
6125
6169
|
}
|
|
6126
6170
|
return new RepositoryError(`${context}: ${message}`, error);
|
|
@@ -6274,13 +6318,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6274
6318
|
const tableName = this.getTableName(seed.slug);
|
|
6275
6319
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ? LIMIT 1`).bind(id).first();
|
|
6276
6320
|
if (!entryRow) {
|
|
6277
|
-
throw new
|
|
6321
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6278
6322
|
}
|
|
6279
6323
|
const data = this.rowToData(seed, entryRow);
|
|
6280
6324
|
await this.attachMultiRelations(seed, id, data);
|
|
6281
6325
|
return data;
|
|
6282
6326
|
} catch (error) {
|
|
6283
|
-
if (error instanceof
|
|
6327
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6284
6328
|
throw this.mapError(error, `findById(${seed.slug}, ${id})`);
|
|
6285
6329
|
}
|
|
6286
6330
|
}
|
|
@@ -6294,13 +6338,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6294
6338
|
const tableName = this.getTableName(seed.slug);
|
|
6295
6339
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE slug = ? LIMIT 1`).bind(slug).first();
|
|
6296
6340
|
if (!entryRow) {
|
|
6297
|
-
throw new
|
|
6341
|
+
throw new EntryNotFoundError8(`Entry with slug "${slug}" not found in ${seed.slug}`);
|
|
6298
6342
|
}
|
|
6299
6343
|
const data = this.rowToData(seed, entryRow);
|
|
6300
6344
|
await this.attachMultiRelations(seed, entryRow.id, data);
|
|
6301
6345
|
return data;
|
|
6302
6346
|
} catch (error) {
|
|
6303
|
-
if (error instanceof
|
|
6347
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6304
6348
|
throw this.mapError(error, `findBySlug(${seed.slug}, ${slug})`);
|
|
6305
6349
|
}
|
|
6306
6350
|
}
|
|
@@ -6616,16 +6660,16 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6616
6660
|
if (batchStmts.length === 1) {
|
|
6617
6661
|
const updateResult = await batchStmts[0].run();
|
|
6618
6662
|
if (updateResult.meta.changes === 0) {
|
|
6619
|
-
throw new
|
|
6663
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6620
6664
|
}
|
|
6621
6665
|
} else if (batchStmts.length > 1) {
|
|
6622
6666
|
const results = await this.database.batch(batchStmts);
|
|
6623
6667
|
if (stmt && (results[0].meta?.changes ?? 0) === 0) {
|
|
6624
|
-
throw new
|
|
6668
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6625
6669
|
}
|
|
6626
6670
|
}
|
|
6627
6671
|
} catch (error) {
|
|
6628
|
-
if (error instanceof
|
|
6672
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6629
6673
|
throw this.mapError(error, `update(${seed.slug}, ${id})`);
|
|
6630
6674
|
}
|
|
6631
6675
|
if (this.hooks?.afterUpdate) {
|
|
@@ -6650,12 +6694,12 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6650
6694
|
const tableName = this.getTableName(seed.slug);
|
|
6651
6695
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).bind(id).first();
|
|
6652
6696
|
if (!entryRow) {
|
|
6653
|
-
throw new
|
|
6697
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6654
6698
|
}
|
|
6655
6699
|
await this.database.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(id).run();
|
|
6656
6700
|
row = this.rowToData(seed, entryRow);
|
|
6657
6701
|
} catch (error) {
|
|
6658
|
-
if (error instanceof
|
|
6702
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6659
6703
|
throw this.mapError(error, `delete(${seed.slug}, ${id})`);
|
|
6660
6704
|
}
|
|
6661
6705
|
if (this.hooks?.afterDelete) {
|
|
@@ -6769,21 +6813,12 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6769
6813
|
const draftTableName = this.getTableName(seed.slug, true);
|
|
6770
6814
|
const mRelBranches = multiRelBranches(seed);
|
|
6771
6815
|
const mRelAliases = new Set(mRelBranches.map((b) => b.alias));
|
|
6772
|
-
|
|
6773
|
-
try {
|
|
6774
|
-
const existing = await this.database.prepare(`SELECT _touched_fields FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId).first();
|
|
6775
|
-
if (existing && existing._touched_fields) {
|
|
6776
|
-
existingTouched = JSON.parse(existing._touched_fields);
|
|
6777
|
-
}
|
|
6778
|
-
} catch {
|
|
6779
|
-
}
|
|
6780
|
-
const currentTouched = new Set(existingTouched);
|
|
6816
|
+
const thisCallTouched = [];
|
|
6781
6817
|
for (const branch of seed.branches) {
|
|
6782
6818
|
if (Object.hasOwn(data, branch.alias)) {
|
|
6783
|
-
|
|
6819
|
+
thisCallTouched.push(branch.alias);
|
|
6784
6820
|
}
|
|
6785
6821
|
}
|
|
6786
|
-
const updatedTouchedFields = Array.from(currentTouched);
|
|
6787
6822
|
const columnNames = ["entry_id"];
|
|
6788
6823
|
const placeholders = ["?"];
|
|
6789
6824
|
const queryBindings = [entryId];
|
|
@@ -6800,8 +6835,14 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6800
6835
|
}
|
|
6801
6836
|
columnNames.push("_touched_fields");
|
|
6802
6837
|
placeholders.push("?");
|
|
6803
|
-
queryBindings.push(JSON.stringify(
|
|
6804
|
-
updateClauses.push(`_touched_fields =
|
|
6838
|
+
queryBindings.push(JSON.stringify(thisCallTouched));
|
|
6839
|
+
updateClauses.push(`_touched_fields = (
|
|
6840
|
+
SELECT json_group_array(value) FROM (
|
|
6841
|
+
SELECT value FROM json_each(COALESCE(_touched_fields, '[]'))
|
|
6842
|
+
UNION
|
|
6843
|
+
SELECT value FROM json_each(excluded._touched_fields)
|
|
6844
|
+
)
|
|
6845
|
+
)`);
|
|
6805
6846
|
updateClauses.push("updated_at = (unixepoch())");
|
|
6806
6847
|
const sql = `
|
|
6807
6848
|
INSERT INTO ${draftTableName} (${columnNames.join(", ")})
|
|
@@ -6909,7 +6950,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6909
6950
|
const liveTableName = this.getTableName(seed.slug);
|
|
6910
6951
|
const draftRow = await this.database.prepare(`SELECT * FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId).first();
|
|
6911
6952
|
if (!draftRow) {
|
|
6912
|
-
throw new
|
|
6953
|
+
throw new EntryNotFoundError8(`No draft found for ${entryId} in ${seed.slug}`);
|
|
6913
6954
|
}
|
|
6914
6955
|
let touchedFields = [];
|
|
6915
6956
|
if (draftRow && typeof draftRow["_touched_fields"] === "string") {
|
|
@@ -6964,7 +7005,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6964
7005
|
}
|
|
6965
7006
|
await this.database.batch(batchStmts);
|
|
6966
7007
|
} catch (error) {
|
|
6967
|
-
if (error instanceof
|
|
7008
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6968
7009
|
if (error instanceof RelationTargetNotFoundError2) throw error;
|
|
6969
7010
|
throw this.mapError(error, `publishDraft(${seed.slug}, ${entryId})`);
|
|
6970
7011
|
}
|
|
@@ -10172,8 +10213,8 @@ function createBeechApp(config) {
|
|
|
10172
10213
|
}
|
|
10173
10214
|
return null;
|
|
10174
10215
|
},
|
|
10175
|
-
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
10176
|
-
allowHeaders: ["Content-Type", "Authorization", "X-API-Key"],
|
|
10216
|
+
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
10217
|
+
allowHeaders: ["Content-Type", "Authorization", "X-API-Key", "Idempotency-Key"],
|
|
10177
10218
|
credentials: true
|
|
10178
10219
|
})(context, next);
|
|
10179
10220
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beechcms/api",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/factory.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@aws-sdk/client-s3": "^3.995.0",
|
|
22
22
|
"@aws-sdk/s3-request-presigner": "^3.995.0",
|
|
23
|
-
"@beechcms/core": "^0.6.
|
|
23
|
+
"@beechcms/core": "^0.6.2",
|
|
24
24
|
"@upstash/qstash": "^2.11.0",
|
|
25
25
|
"bcryptjs": "^2.4.3",
|
|
26
26
|
"hono": "^4.12.21",
|