@beechcms/api 0.6.0 → 0.6.1
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-CspfxV2U.js} +1 -1
- package/assets/dashboard/assets/{index-BlehzIfd.js → index-DtDfJyaF.js} +3 -3
- package/assets/dashboard/assets/{pie-chart-recharts-iaYz4FeR.js → pie-chart-recharts-Bx2IXAjA.js} +1 -1
- package/assets/dashboard/assets/{timeseries-chart-recharts-C8Lcylom.js → timeseries-chart-recharts-BCWufwI1.js} +1 -1
- package/assets/dashboard/index.html +1 -1
- package/dist/index.js +47 -34
- 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);
|
|
@@ -336,9 +336,9 @@ async function buildRelationsMap(context, seed, entries) {
|
|
|
336
336
|
});
|
|
337
337
|
const map = {};
|
|
338
338
|
for (const item of items) {
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
const label =
|
|
339
|
+
const row = item;
|
|
340
|
+
const id = row.id;
|
|
341
|
+
const label = row[labelAlias];
|
|
342
342
|
map[id] = label != null && label !== "" ? String(label) : id;
|
|
343
343
|
}
|
|
344
344
|
relations[branch.alias] = map;
|
|
@@ -1150,7 +1150,7 @@ async function bulkHandler(context) {
|
|
|
1150
1150
|
}
|
|
1151
1151
|
|
|
1152
1152
|
// src/features/content/handlers/kanban-position.ts
|
|
1153
|
-
import { resolveKanbanConfig as resolveKanbanConfig2 } from "@beechcms/core";
|
|
1153
|
+
import { resolveKanbanConfig as resolveKanbanConfig2, EntryNotFoundError as EntryNotFoundError3 } from "@beechcms/core";
|
|
1154
1154
|
async function kanbanPositionHandler(context) {
|
|
1155
1155
|
const slug = context.req.param("slug");
|
|
1156
1156
|
const id = context.req.param("id");
|
|
@@ -1175,9 +1175,13 @@ async function kanbanPositionHandler(context) {
|
|
|
1175
1175
|
if (!compat.compatible || !compat.candidates.some((c) => c.branchId === axisBranchId)) {
|
|
1176
1176
|
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
1177
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1178
|
+
try {
|
|
1179
|
+
await context.get("repository").findById(seed, id);
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
if (error instanceof EntryNotFoundError3) {
|
|
1182
|
+
return publicProblem(context, { type: "content-not-found", title: "Not Found", status: 404, detail: CONTENT_ERRORS.NOT_FOUND });
|
|
1183
|
+
}
|
|
1184
|
+
throw error;
|
|
1181
1185
|
}
|
|
1182
1186
|
await context.get("kanbanPositionRepository").setPosition(slug, id, axisBranchId, position);
|
|
1183
1187
|
return context.json({ success: true });
|
|
@@ -1231,7 +1235,7 @@ async function kanbanMoveHandler(context) {
|
|
|
1231
1235
|
const currentTags = current[axisBranch.alias] ?? [];
|
|
1232
1236
|
const { oldValue, newValue } = body.axis;
|
|
1233
1237
|
const withoutOld = oldValue !== null ? currentTags.filter((t) => t !== oldValue) : currentTags;
|
|
1234
|
-
const nextTags = newValue !== null ? [...withoutOld, newValue] : withoutOld;
|
|
1238
|
+
const nextTags = newValue !== null && !withoutOld.includes(newValue) ? [...withoutOld, newValue] : withoutOld;
|
|
1235
1239
|
patch = { [axisBranch.alias]: nextTags };
|
|
1236
1240
|
}
|
|
1237
1241
|
const validation = validateAndSanitizeSeedPayload3(seed, patch, {
|
|
@@ -1504,7 +1508,7 @@ widgetApp.get("/distribution/:seed", async (context) => {
|
|
|
1504
1508
|
|
|
1505
1509
|
// src/features/rotate-field/rotate-field.handler.ts
|
|
1506
1510
|
import { Hono as Hono3 } from "hono";
|
|
1507
|
-
import { resolvePolicies as resolvePolicies4, verifyHashField, sha256hex as sha256hex2, validateAndSanitizeSeedPayload as validateAndSanitizeSeedPayload4, EntryNotFoundError as
|
|
1511
|
+
import { resolvePolicies as resolvePolicies4, verifyHashField, sha256hex as sha256hex2, validateAndSanitizeSeedPayload as validateAndSanitizeSeedPayload4, EntryNotFoundError as EntryNotFoundError4 } from "@beechcms/core";
|
|
1508
1512
|
|
|
1509
1513
|
// src/features/rotate-field/rotate-field.schema.ts
|
|
1510
1514
|
import { z } from "zod";
|
|
@@ -1571,7 +1575,7 @@ rotateFieldApp.post("/:slug/:id/rotate-field", async (context) => {
|
|
|
1571
1575
|
try {
|
|
1572
1576
|
contentRecord = await context.get("repository").findById(seed, entryId);
|
|
1573
1577
|
} catch (error) {
|
|
1574
|
-
if (error instanceof
|
|
1578
|
+
if (error instanceof EntryNotFoundError4) {
|
|
1575
1579
|
return publicProblem(context, {
|
|
1576
1580
|
type: "content-not-found",
|
|
1577
1581
|
title: "Not Found",
|
|
@@ -2274,7 +2278,7 @@ import {
|
|
|
2274
2278
|
|
|
2275
2279
|
// src/features/draft/draft.middleware.ts
|
|
2276
2280
|
import { createMiddleware } from "hono/factory";
|
|
2277
|
-
import { EntryNotFoundError as
|
|
2281
|
+
import { EntryNotFoundError as EntryNotFoundError5 } from "@beechcms/core";
|
|
2278
2282
|
var draftGuard = createMiddleware(async (context, next) => {
|
|
2279
2283
|
const slug = context.req.param("slug");
|
|
2280
2284
|
const id = context.req.param("id");
|
|
@@ -2307,7 +2311,7 @@ var draftGuard = createMiddleware(async (context, next) => {
|
|
|
2307
2311
|
try {
|
|
2308
2312
|
await repository.findById(seed, id);
|
|
2309
2313
|
} catch (error) {
|
|
2310
|
-
if (error instanceof
|
|
2314
|
+
if (error instanceof EntryNotFoundError5) {
|
|
2311
2315
|
return publicProblem(context, {
|
|
2312
2316
|
type: "content-not-found",
|
|
2313
2317
|
title: "Not Found",
|
|
@@ -5294,7 +5298,15 @@ function publicRateLimitMiddleware() {
|
|
|
5294
5298
|
return async (c, next) => {
|
|
5295
5299
|
const readMethod = isReadMethod2(c.req.method);
|
|
5296
5300
|
const limiterName = readMethod ? "publicApiRead" : "publicApiWrite";
|
|
5297
|
-
const
|
|
5301
|
+
const path = c.req.path;
|
|
5302
|
+
let seed = "no-seed";
|
|
5303
|
+
if (path.startsWith("/api/v1/public/")) {
|
|
5304
|
+
const remaining = path.slice("/api/v1/public/".length);
|
|
5305
|
+
const firstSegment = remaining.split("/")[0];
|
|
5306
|
+
if (firstSegment && firstSegment !== "health" && firstSegment !== "schema" && firstSegment !== "schema.html") {
|
|
5307
|
+
seed = firstSegment;
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5298
5310
|
const key = `${getClientIp(c.req)}:${seed}:${limiterName}`;
|
|
5299
5311
|
const result = await c.get("rateLimiters").getLimiter(limiterName).checkLimit(key);
|
|
5300
5312
|
if (!result.isAllowed) {
|
|
@@ -5364,7 +5376,7 @@ function withCachedResponse(edgeCache, cacheKey, response) {
|
|
|
5364
5376
|
}
|
|
5365
5377
|
|
|
5366
5378
|
// src/public/read-single.ts
|
|
5367
|
-
import { EntryNotFoundError as
|
|
5379
|
+
import { EntryNotFoundError as EntryNotFoundError6 } from "@beechcms/core";
|
|
5368
5380
|
|
|
5369
5381
|
// src/public/entry-projection.ts
|
|
5370
5382
|
import { resolvePolicies as resolvePolicies7 } from "@beechcms/core";
|
|
@@ -5427,7 +5439,7 @@ async function readSingleEntry(input) {
|
|
|
5427
5439
|
meta: buildPublicSingleMeta(seedSlug)
|
|
5428
5440
|
};
|
|
5429
5441
|
} catch (error) {
|
|
5430
|
-
if (error instanceof
|
|
5442
|
+
if (error instanceof EntryNotFoundError6) {
|
|
5431
5443
|
return { ok: false, detail: `Entry '${label}' not found for content type '${seedSlug}'.` };
|
|
5432
5444
|
}
|
|
5433
5445
|
throw error;
|
|
@@ -5554,6 +5566,7 @@ async function readListEntries(input) {
|
|
|
5554
5566
|
const sortDir = (cleanStr(query.orderDir) ?? "desc").toLowerCase() === "asc" ? "ASC" : "DESC";
|
|
5555
5567
|
const { items, total } = await repository.findMany(seed, {
|
|
5556
5568
|
filters: engineFilters,
|
|
5569
|
+
filterLogic: parsedFilter?.logic,
|
|
5557
5570
|
search: search || void 0,
|
|
5558
5571
|
status: publishedOnly ? "published" : null,
|
|
5559
5572
|
pagination: {
|
|
@@ -5761,7 +5774,7 @@ async function publicAddHandler(context) {
|
|
|
5761
5774
|
}
|
|
5762
5775
|
|
|
5763
5776
|
// src/public/public-edit.ts
|
|
5764
|
-
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as resolvePolicies8, EntryNotFoundError as
|
|
5777
|
+
import { isValidContentStatus as isValidContentStatus4, resolvePolicies as resolvePolicies8, EntryNotFoundError as EntryNotFoundError7 } from "@beechcms/core";
|
|
5765
5778
|
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
5779
|
function errorMessage(context, error) {
|
|
5767
5780
|
if (context.env.ENV !== "production" && error instanceof Error) return error.message;
|
|
@@ -5869,7 +5882,7 @@ async function publicEditHandler(context) {
|
|
|
5869
5882
|
});
|
|
5870
5883
|
return context.json({ success: true, id, slug: slugResult.value.nextSlug }, 200);
|
|
5871
5884
|
} catch (error) {
|
|
5872
|
-
if (error instanceof
|
|
5885
|
+
if (error instanceof EntryNotFoundError7) {
|
|
5873
5886
|
return publicProblem(context, { type: "entry-not-found", title: "Not Found", status: 404, detail: `Entry '${id}' not found for content type '${seedSlug}'.` });
|
|
5874
5887
|
}
|
|
5875
5888
|
console.error("Public edit error:", error);
|
|
@@ -6092,7 +6105,7 @@ import { createMiddleware as createMiddleware2 } from "hono/factory";
|
|
|
6092
6105
|
|
|
6093
6106
|
// src/shared/db/repositories/content.repository.d1.ts
|
|
6094
6107
|
import {
|
|
6095
|
-
EntryNotFoundError as
|
|
6108
|
+
EntryNotFoundError as EntryNotFoundError8,
|
|
6096
6109
|
RepositoryError as RepositoryError2,
|
|
6097
6110
|
SlugConflictError as SlugConflictError4,
|
|
6098
6111
|
RelationTargetNotFoundError as RelationTargetNotFoundError2,
|
|
@@ -6120,7 +6133,7 @@ var BaseD1Repository = class {
|
|
|
6120
6133
|
*/
|
|
6121
6134
|
mapError(error, context) {
|
|
6122
6135
|
const message = error?.message || "Unknown database error";
|
|
6123
|
-
if (message.includes("UNIQUE constraint failed
|
|
6136
|
+
if (message.includes("UNIQUE constraint failed") && message.includes("slug")) {
|
|
6124
6137
|
return new SlugConflictError3(`${context}: ${message}`);
|
|
6125
6138
|
}
|
|
6126
6139
|
return new RepositoryError(`${context}: ${message}`, error);
|
|
@@ -6274,13 +6287,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6274
6287
|
const tableName = this.getTableName(seed.slug);
|
|
6275
6288
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ? LIMIT 1`).bind(id).first();
|
|
6276
6289
|
if (!entryRow) {
|
|
6277
|
-
throw new
|
|
6290
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6278
6291
|
}
|
|
6279
6292
|
const data = this.rowToData(seed, entryRow);
|
|
6280
6293
|
await this.attachMultiRelations(seed, id, data);
|
|
6281
6294
|
return data;
|
|
6282
6295
|
} catch (error) {
|
|
6283
|
-
if (error instanceof
|
|
6296
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6284
6297
|
throw this.mapError(error, `findById(${seed.slug}, ${id})`);
|
|
6285
6298
|
}
|
|
6286
6299
|
}
|
|
@@ -6294,13 +6307,13 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6294
6307
|
const tableName = this.getTableName(seed.slug);
|
|
6295
6308
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE slug = ? LIMIT 1`).bind(slug).first();
|
|
6296
6309
|
if (!entryRow) {
|
|
6297
|
-
throw new
|
|
6310
|
+
throw new EntryNotFoundError8(`Entry with slug "${slug}" not found in ${seed.slug}`);
|
|
6298
6311
|
}
|
|
6299
6312
|
const data = this.rowToData(seed, entryRow);
|
|
6300
6313
|
await this.attachMultiRelations(seed, entryRow.id, data);
|
|
6301
6314
|
return data;
|
|
6302
6315
|
} catch (error) {
|
|
6303
|
-
if (error instanceof
|
|
6316
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6304
6317
|
throw this.mapError(error, `findBySlug(${seed.slug}, ${slug})`);
|
|
6305
6318
|
}
|
|
6306
6319
|
}
|
|
@@ -6616,16 +6629,16 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6616
6629
|
if (batchStmts.length === 1) {
|
|
6617
6630
|
const updateResult = await batchStmts[0].run();
|
|
6618
6631
|
if (updateResult.meta.changes === 0) {
|
|
6619
|
-
throw new
|
|
6632
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6620
6633
|
}
|
|
6621
6634
|
} else if (batchStmts.length > 1) {
|
|
6622
6635
|
const results = await this.database.batch(batchStmts);
|
|
6623
6636
|
if (stmt && (results[0].meta?.changes ?? 0) === 0) {
|
|
6624
|
-
throw new
|
|
6637
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6625
6638
|
}
|
|
6626
6639
|
}
|
|
6627
6640
|
} catch (error) {
|
|
6628
|
-
if (error instanceof
|
|
6641
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6629
6642
|
throw this.mapError(error, `update(${seed.slug}, ${id})`);
|
|
6630
6643
|
}
|
|
6631
6644
|
if (this.hooks?.afterUpdate) {
|
|
@@ -6650,12 +6663,12 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6650
6663
|
const tableName = this.getTableName(seed.slug);
|
|
6651
6664
|
const entryRow = await this.database.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).bind(id).first();
|
|
6652
6665
|
if (!entryRow) {
|
|
6653
|
-
throw new
|
|
6666
|
+
throw new EntryNotFoundError8(`Entry ${id} not found in ${seed.slug}`);
|
|
6654
6667
|
}
|
|
6655
6668
|
await this.database.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(id).run();
|
|
6656
6669
|
row = this.rowToData(seed, entryRow);
|
|
6657
6670
|
} catch (error) {
|
|
6658
|
-
if (error instanceof
|
|
6671
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6659
6672
|
throw this.mapError(error, `delete(${seed.slug}, ${id})`);
|
|
6660
6673
|
}
|
|
6661
6674
|
if (this.hooks?.afterDelete) {
|
|
@@ -6909,7 +6922,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6909
6922
|
const liveTableName = this.getTableName(seed.slug);
|
|
6910
6923
|
const draftRow = await this.database.prepare(`SELECT * FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId).first();
|
|
6911
6924
|
if (!draftRow) {
|
|
6912
|
-
throw new
|
|
6925
|
+
throw new EntryNotFoundError8(`No draft found for ${entryId} in ${seed.slug}`);
|
|
6913
6926
|
}
|
|
6914
6927
|
let touchedFields = [];
|
|
6915
6928
|
if (draftRow && typeof draftRow["_touched_fields"] === "string") {
|
|
@@ -6964,7 +6977,7 @@ var D1ContentRepository = class extends BaseD1Repository {
|
|
|
6964
6977
|
}
|
|
6965
6978
|
await this.database.batch(batchStmts);
|
|
6966
6979
|
} catch (error) {
|
|
6967
|
-
if (error instanceof
|
|
6980
|
+
if (error instanceof EntryNotFoundError8) throw error;
|
|
6968
6981
|
if (error instanceof RelationTargetNotFoundError2) throw error;
|
|
6969
6982
|
throw this.mapError(error, `publishDraft(${seed.slug}, ${entryId})`);
|
|
6970
6983
|
}
|
|
@@ -10172,8 +10185,8 @@ function createBeechApp(config) {
|
|
|
10172
10185
|
}
|
|
10173
10186
|
return null;
|
|
10174
10187
|
},
|
|
10175
|
-
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
10176
|
-
allowHeaders: ["Content-Type", "Authorization", "X-API-Key"],
|
|
10188
|
+
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
10189
|
+
allowHeaders: ["Content-Type", "Authorization", "X-API-Key", "Idempotency-Key"],
|
|
10177
10190
|
credentials: true
|
|
10178
10191
|
})(context, next);
|
|
10179
10192
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beechcms/api",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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.1",
|
|
24
24
|
"@upstash/qstash": "^2.11.0",
|
|
25
25
|
"bcryptjs": "^2.4.3",
|
|
26
26
|
"hono": "^4.12.21",
|