@cardinali-studio/adminkit 2.2.5 → 2.2.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/dist/app/lib/home.js +5 -1
- package/dist/app/site.d.ts +4 -1
- package/dist/functions/api/content.js +27 -7
- package/package.json +1 -1
package/dist/app/lib/home.js
CHANGED
|
@@ -60,7 +60,11 @@ export function useHome(active) {
|
|
|
60
60
|
const mapped = site().homeDocument;
|
|
61
61
|
if (contentPersistenceMode() === "d1" && mapped && revision) {
|
|
62
62
|
const revisionId = crypto.randomUUID();
|
|
63
|
-
|
|
63
|
+
/* The Worker validates and may normalize a document before checking its signed
|
|
64
|
+
* representation. Sign the site-owned canonical payload here too, otherwise an
|
|
65
|
+
* obsolete/unknown client field becomes a misleading "documento non valido". */
|
|
66
|
+
const payload = mapped.toPayload ? mapped.toPayload(data) : data;
|
|
67
|
+
await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false });
|
|
64
68
|
setRevision({ base: revisionId, expected: revisionId });
|
|
65
69
|
setNotice("Bozza D1 salvata");
|
|
66
70
|
setDirty(false);
|
package/dist/app/site.d.ts
CHANGED
|
@@ -61,12 +61,15 @@ export interface SiteAdminConfig {
|
|
|
61
61
|
modules?: Record<string, boolean>;
|
|
62
62
|
/** Git remains the compatibility default. D1 is an explicit, later site cutover. */
|
|
63
63
|
contentPersistence?: "github" | "d1";
|
|
64
|
-
/** Lossless D1 identity for the singleton home; required before its editor may leave Git.
|
|
64
|
+
/** Lossless D1 identity for the singleton home; required before its editor may leave Git.
|
|
65
|
+
* A site can canonicalize its editor value before the client signs it: this keeps the
|
|
66
|
+
* checksum aligned with the same schema normalization performed by the Worker. */
|
|
65
67
|
homeDocument?: {
|
|
66
68
|
documentId: string;
|
|
67
69
|
kind: string;
|
|
68
70
|
locale: string;
|
|
69
71
|
schemaVersion: number;
|
|
72
|
+
toPayload?: (home: Record<string, unknown>) => unknown;
|
|
70
73
|
};
|
|
71
74
|
/** Identity plus the deliberately site-owned conversion between the generic
|
|
72
75
|
* settings form and an immutable D1 document payload. A site may persist the
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardinali-studio/adminkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
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",
|