sc-dev-deploy/lib/payloadRefs.js
2026-06-01 16:43:43 -05:00

98 lines
3.2 KiB
JavaScript

// Recursive walker for op payloads: translate file references between this
// instance's local form (numeric id or relative path) and a portable
// placeholder ("__dd_file_ref::<uuid>"). Promote-time wraps call
// toPlaceholders to journal portable payloads; apply-time handlers call
// fromPlaceholders to resolve back to the target's local file path.
//
// Saltcorn's /files/serve route accepts either a numeric file id or a
// relative path, so writing the path back on apply works regardless of which
// form the source used.
const db = require("@saltcorn/data/db");
const { lookupByUuid } = require("./entityIds");
// Common keys in page/view layout JSON that reference a file.
const FILE_REF_KEYS = new Set([
"fileid",
"file_id",
"bgFileId",
"image_id"
]);
const PLACEHOLDER_PREFIX = "__dd_file_ref::";
// Look up a file entity by value (string path or numeric id). Returns the
// entity_ids row or null.
const lookupFileByValue = async (v) => {
if (v === null || v === undefined || v === "") return null;
// Match by current_name (relative path string)
let row = await db.selectMaybeOne("_dd_entity_ids", { kind: "file", current_name: String(v) });
if (row) return row;
// Also try numeric id, in case the source stored an integer id
const asNum = Number(v);
if (Number.isFinite(asNum)) {
row = await db.selectMaybeOne("_dd_entity_ids", { kind: "file", current_id: asNum });
if (row) return row;
}
return null;
};
// Walk obj, mutating in place. For each key in FILE_REF_KEYS with a non-empty
// value, replace with the result of `transform(key, value)`. Async-aware.
const transformFileRefs = async (obj, transform) => {
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
if (obj[i] && typeof obj[i] === "object") {
await transformFileRefs(obj[i], transform);
}
}
return;
}
if (!obj || typeof obj !== "object") return;
for (const k of Object.keys(obj)) {
const v = obj[k];
if (FILE_REF_KEYS.has(k) && v !== null && v !== undefined && v !== "" && typeof v !== "object") {
obj[k] = await transform(k, v);
} else if (v && typeof v === "object") {
await transformFileRefs(v, transform);
}
}
};
// Convert local file refs (id or path) -> portable placeholders.
const toPlaceholders = async (payload) => {
await transformFileRefs(payload, async (k, v) => {
const ent = await lookupFileByValue(v);
if (ent) return PLACEHOLDER_PREFIX + ent.uuid;
return v;
});
return payload;
};
// Convert portable placeholders -> local file refs (write back as relative
// path, which Saltcorn's /files/serve accepts).
const fromPlaceholders = async (payload) => {
await transformFileRefs(payload, async (k, v) => {
if (typeof v !== "string") return v;
if (!v.startsWith(PLACEHOLDER_PREFIX)) return v;
const uuid = v.substring(PLACEHOLDER_PREFIX.length);
const ent = await lookupByUuid(uuid);
return ent ? ent.current_name : v;
});
return payload;
};
module.exports = {
toPlaceholders,
fromPlaceholders,
PLACEHOLDER_PREFIX,
FILE_REF_KEYS
};