@cardinali-studio/adminkit 2.2.6 → 2.2.8
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/dist/functions/api/content.js +27 -7
- package/dist/persistence.js +4 -1
- package/package.json +1 -1
|
@@ -16,6 +16,12 @@ function diagnosticId() {
|
|
|
16
16
|
return `diag_${crypto.randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function invalid(code, reason) {
|
|
20
|
+
const error = new Error(code);
|
|
21
|
+
error.reason = reason;
|
|
22
|
+
return error;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
function repository(env) {
|
|
20
26
|
if (!env.CONTENT_DB) throw new Error("content_unavailable");
|
|
21
27
|
return new D1ContentRepository(env.CONTENT_DB);
|
|
@@ -28,12 +34,20 @@ function safeError(error, stage) {
|
|
|
28
34
|
const status = safeCode === "unauthenticated" ? 401
|
|
29
35
|
: safeCode === "stale_revision" || safeCode === "duplicate_request" ? 409
|
|
30
36
|
: safeCode === "invalid_request" || safeCode === "invalid_document" ? 400 : 503;
|
|
37
|
+
const invalidMessages = {
|
|
38
|
+
request_shape: "La richiesta di salvataggio ha una forma non valida.",
|
|
39
|
+
schema_validation: "Il contenuto non rispetta lo schema del sito.",
|
|
40
|
+
document_identity: "Il documento validato non conserva la propria identità.",
|
|
41
|
+
payload_checksum: "Il checksum del contenuto non coincide con il documento validato.",
|
|
42
|
+
request_checksum: "Il checksum del comando non coincide con il salvataggio validato.",
|
|
43
|
+
};
|
|
44
|
+
const reason = typeof error?.reason === "string" && invalidMessages[error.reason] ? error.reason : undefined;
|
|
31
45
|
const message = safeCode === "unauthenticated" ? "Sessione non valida: ricarica la pagina per rientrare."
|
|
32
46
|
: safeCode === "stale_revision" ? "Questo contenuto è stato modificato altrove: ricarica e risolvi il conflitto."
|
|
33
47
|
: code === "duplicate_request" ? "Questo identificatore di salvataggio è già associato a dati diversi."
|
|
34
|
-
: safeCode === "invalid_request" || safeCode === "invalid_document" ? "Il documento non è valido."
|
|
48
|
+
: safeCode === "invalid_request" || safeCode === "invalid_document" ? (invalidMessages[reason] || "Il documento non è valido.")
|
|
35
49
|
: "La persistenza contenuti non è disponibile.";
|
|
36
|
-
return response({ ok: false, code: safeCode, stage, error: message, diagnosticId: diagnosticId(), retryable: status >= 500 }, status);
|
|
50
|
+
return response({ ok: false, code: safeCode, stage, ...(reason ? { reason } : {}), error: message, diagnosticId: diagnosticId(), retryable: status >= 500 }, status);
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
function author(data) {
|
|
@@ -117,14 +131,20 @@ export async function onRequestPost({ request, env, data }) {
|
|
|
117
131
|
}
|
|
118
132
|
if (!new URL(request.url).pathname.endsWith("/save")) return response({ ok: false, code: "invalid_request", stage: "validation", error: "Metodo non ammesso.", diagnosticId: diagnosticId(), retryable: false }, 405);
|
|
119
133
|
const editor = author(data);
|
|
120
|
-
if (!id.test(body.revisionId) || !id.test(body.documentId) || (body.baseRevisionId !== null && !id.test(body.baseRevisionId)) || (body.expectedDraftRevisionId !== undefined && body.expectedDraftRevisionId !== null && !id.test(body.expectedDraftRevisionId)) || (body.translationGroupId !== null && body.translationGroupId !== undefined && !id.test(body.translationGroupId)) || !checksum.test(body.requestChecksum || "") || !Number.isInteger(body.schemaVersion) || body.schemaVersion < 1 || typeof body.kind !== "string" || body.kind.length > 120 || typeof body.locale !== "string" || body.locale.length > 35 || (body.payload !== null && (typeof body.payload !== "object" || Array.isArray(body.payload)))) throw
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
if (!id.test(body.revisionId) || !id.test(body.documentId) || (body.baseRevisionId !== null && !id.test(body.baseRevisionId)) || (body.expectedDraftRevisionId !== undefined && body.expectedDraftRevisionId !== null && !id.test(body.expectedDraftRevisionId)) || (body.translationGroupId !== null && body.translationGroupId !== undefined && !id.test(body.translationGroupId)) || !checksum.test(body.requestChecksum || "") || !Number.isInteger(body.schemaVersion) || body.schemaVersion < 1 || typeof body.kind !== "string" || body.kind.length > 120 || typeof body.locale !== "string" || body.locale.length > 35 || (body.payload !== null && (typeof body.payload !== "object" || Array.isArray(body.payload)))) throw invalid("invalid_request", "request_shape");
|
|
135
|
+
let document;
|
|
136
|
+
try {
|
|
137
|
+
document = validateContentDocument({ documentId: body.documentId, kind: body.kind, locale: body.locale, translationGroupId: body.translationGroupId ?? null, payload: body.payload, tombstone: body.tombstone === true, schemaVersion: body.schemaVersion });
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error?.message === "invalid_document") throw invalid("invalid_document", "schema_validation");
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
if (document.documentId !== body.documentId || document.kind !== body.kind || document.locale !== body.locale || document.schemaVersion !== body.schemaVersion || document.tombstone !== (body.tombstone === true)) throw invalid("invalid_document", "document_identity");
|
|
123
143
|
const expectedPayloadChecksum = await canonicalChecksum(document.tombstone ? null : document.payload);
|
|
124
|
-
if (body.payloadChecksum !== expectedPayloadChecksum) throw
|
|
144
|
+
if (body.payloadChecksum !== expectedPayloadChecksum) throw invalid("invalid_document", "payload_checksum");
|
|
125
145
|
const normalized = { ...body, ...document, expectedDraftRevisionId: body.expectedDraftRevisionId === undefined ? body.baseRevisionId : body.expectedDraftRevisionId, payloadChecksum: expectedPayloadChecksum };
|
|
126
146
|
const expectedRequestChecksum = await contentSaveRequestChecksum(normalized);
|
|
127
|
-
if (body.requestChecksum !== expectedRequestChecksum) throw
|
|
147
|
+
if (body.requestChecksum !== expectedRequestChecksum) throw invalid("invalid_document", "request_checksum");
|
|
128
148
|
const repo = repository(env);
|
|
129
149
|
try {
|
|
130
150
|
const outcome = await repo.save({ ...normalized, now, authorSubject: editor.subject, authorLabel: editor.label });
|
package/dist/persistence.js
CHANGED
|
@@ -158,7 +158,10 @@ export class D1ContentRepository {
|
|
|
158
158
|
return "repeated";
|
|
159
159
|
if ((input.payload === null) === !input.tombstone)
|
|
160
160
|
throw new Error("invalid_document");
|
|
161
|
-
|
|
161
|
+
/* `kind` and `locale` are part of the immutable identity check below, so
|
|
162
|
+
* they must be projected by the real D1 query. Test doubles used to return
|
|
163
|
+
* extra object properties and accidentally hid their absence in SQL. */
|
|
164
|
+
const document = await this.db.prepare("SELECT document_id, kind, locale, draft_revision_id, abandoned_at FROM content_documents WHERE document_id = ?").bind(input.documentId).first();
|
|
162
165
|
if (document && (document.kind !== input.kind || document.locale !== input.locale))
|
|
163
166
|
throw new Error("invalid_document");
|
|
164
167
|
const expectedDraftRevisionId = input.expectedDraftRevisionId === undefined ? input.baseRevisionId : input.expectedDraftRevisionId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardinali-studio/adminkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
|
|
6
6
|
"license": "UNLICENSED",
|