98 lines
3.2 KiB
JavaScript
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
|
|
};
|