2026-08-22 14:56:38 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
|
|
|
|
const { ocsRequest } = require('../lib/ocs');
|
|
|
|
|
const bridge = require('../lib/bridge');
|
|
|
|
|
const { resolveFileId } = bridge;
|
2026-08-22 22:50:47 +02:00
|
|
|
const { webdavGet, webdavPut, ensureFolder, webdavPropfindFileId } = require('../lib/webdav');
|
2026-08-22 14:56:38 +02:00
|
|
|
const { convertTo } = require('../lib/collabora');
|
|
|
|
|
const {
|
|
|
|
|
resolveDestNcPath,
|
|
|
|
|
assertSafeDestination,
|
|
|
|
|
contentTypeForFormat,
|
2026-08-22 22:50:47 +02:00
|
|
|
normalizeNcPath,
|
|
|
|
|
DEFAULT_OUTPUT_FOLDER
|
2026-08-22 14:56:38 +02:00
|
|
|
} = require('../lib/utils');
|
|
|
|
|
|
2026-08-22 22:50:47 +02:00
|
|
|
function hasExtension(p) {
|
|
|
|
|
return /\.[^/.]+$/.test(String(p).replace(/\/+$/, ''));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 14:56:38 +02:00
|
|
|
module.exports = function (RED) {
|
|
|
|
|
function NcOfficeNode(config) {
|
|
|
|
|
RED.nodes.createNode(this, config);
|
|
|
|
|
const node = this;
|
|
|
|
|
node.configNode = RED.nodes.getNode(config.config);
|
|
|
|
|
node.operation = config.operation || 'templates:list';
|
|
|
|
|
|
|
|
|
|
node.on('input', async (msg, send, done) => {
|
|
|
|
|
send = send || function () { node.send.apply(node, arguments); };
|
|
|
|
|
done = done || function (err) { if (err) { node.error(err, msg); } };
|
|
|
|
|
clearStatus(node);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cfg = getConfig(node);
|
|
|
|
|
const op = msg.operation || node.operation;
|
|
|
|
|
let result;
|
|
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
case 'templates:list': {
|
|
|
|
|
const type = pickMsgField(msg, 'templateType') || 'document';
|
|
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
path: `/ocs/v2.php/apps/richdocuments/api/v1/templates/${type}`
|
|
|
|
|
});
|
|
|
|
|
msg.payload = result.data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'templates:create': {
|
2026-08-22 22:50:47 +02:00
|
|
|
// richdocuments OCS#createFromTemplate($path, $template)
|
|
|
|
|
// POST /ocs/v2.php/apps/richdocuments/api/v1/templates/new
|
|
|
|
|
const template = pickMsgField(msg, 'template', 'templateId');
|
|
|
|
|
if (template === undefined || template === null || template === '') {
|
|
|
|
|
throw new Error('templates:create kräver msg.template (numeriskt id från templates:list)');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let path = normalizeNcPath(
|
|
|
|
|
pickMsgField(msg, 'path', 'destNcPath') ||
|
|
|
|
|
msg.templatePath ||
|
|
|
|
|
undefined
|
|
|
|
|
);
|
|
|
|
|
if (!path) {
|
|
|
|
|
const name = msg.destName || msg.fileName;
|
|
|
|
|
if (!name) {
|
|
|
|
|
throw new Error('templates:create kräver msg.path eller msg.destFolder+msg.destName');
|
|
|
|
|
}
|
|
|
|
|
const folder = normalizeNcPath(msg.destFolder || DEFAULT_OUTPUT_FOLDER);
|
|
|
|
|
path = normalizeNcPath((folder.endsWith('/') ? folder : folder + '/') + String(name).replace(/^\/+/, ''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ärv extension från mallen om målnamnet saknar en.
|
|
|
|
|
if (!hasExtension(path)) {
|
|
|
|
|
const ext = pickMsgField(msg, 'templateExtension', 'extension');
|
|
|
|
|
if (ext) {
|
|
|
|
|
path += '.' + String(ext).replace(/^\./, '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Idempotent: finns filen redan → återanvänd (ingen
|
|
|
|
|
// "namn (2)"-dubblett via direct editing).
|
|
|
|
|
try {
|
|
|
|
|
await webdavPropfindFileId(cfg, path);
|
|
|
|
|
msg.template = Number(template);
|
|
|
|
|
msg.path = path;
|
|
|
|
|
msg.createdPath = path;
|
|
|
|
|
msg.reused = true;
|
|
|
|
|
msg.payload = { path, reused: true };
|
|
|
|
|
setSuccess(node, msg, 'templates:create (återanvänd)');
|
|
|
|
|
send(msg);
|
|
|
|
|
done();
|
|
|
|
|
return;
|
|
|
|
|
} catch (e) { /* finns inte — skapa nedan */ }
|
|
|
|
|
|
|
|
|
|
// Mallifyllning via direct editing kräver att mappen finns.
|
|
|
|
|
await ensureFolder(cfg, path.substring(0, path.lastIndexOf('/')) || '/');
|
|
|
|
|
|
2026-08-22 14:56:38 +02:00
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: '/ocs/v2.php/apps/richdocuments/api/v1/templates/new',
|
|
|
|
|
body: {
|
2026-08-22 22:50:47 +02:00
|
|
|
path,
|
|
|
|
|
template: Number(template)
|
2026-08-22 14:56:38 +02:00
|
|
|
}
|
|
|
|
|
});
|
2026-08-22 22:50:47 +02:00
|
|
|
|
|
|
|
|
msg.template = Number(template);
|
|
|
|
|
msg.path = path;
|
|
|
|
|
msg.createdPath = path;
|
|
|
|
|
if (result.data && typeof result.data === 'object' && result.data.url) {
|
|
|
|
|
msg.directEditUrl = result.data.url;
|
|
|
|
|
}
|
|
|
|
|
msg.payload = { ...result.data, path };
|
2026-08-22 14:56:38 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'template:extractFields': {
|
|
|
|
|
const fileId = await resolveFileId(cfg, msg);
|
|
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/extract/${fileId}`
|
|
|
|
|
});
|
|
|
|
|
msg.fileId = fileId;
|
|
|
|
|
msg.payload = result.data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'template:fillFields': {
|
|
|
|
|
const fileId = await resolveFileId(cfg, msg);
|
|
|
|
|
const fields = msg.fields || msg.transform;
|
|
|
|
|
const body = { fields };
|
|
|
|
|
if (msg.destNcPath) {
|
|
|
|
|
body.destination = normalizeNcPath(msg.destNcPath).replace(/^\//, '');
|
|
|
|
|
}
|
|
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/fill/${fileId}`,
|
|
|
|
|
body
|
|
|
|
|
});
|
|
|
|
|
msg.fileId = fileId;
|
|
|
|
|
msg.payload = result.data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'document:direct': {
|
2026-08-22 22:50:47 +02:00
|
|
|
// richdocuments OCS#createDirect($fileId) binder parametern
|
|
|
|
|
// per metodnamn — skicka både fileId och fileid (Number).
|
2026-08-22 14:56:38 +02:00
|
|
|
const fileId = await resolveFileId(cfg, msg);
|
|
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
|
2026-08-22 22:50:47 +02:00
|
|
|
body: { fileId: Number(fileId), fileid: Number(fileId) }
|
2026-08-22 14:56:38 +02:00
|
|
|
});
|
|
|
|
|
msg.fileId = fileId;
|
|
|
|
|
msg.editorUrl = result.data?.url || result.data;
|
|
|
|
|
msg.payload = { url: msg.editorUrl, fileId };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'document:public': {
|
|
|
|
|
if (!msg.shareToken) {
|
|
|
|
|
throw new Error('document:public kräver msg.shareToken');
|
|
|
|
|
}
|
|
|
|
|
result = await ocsRequest(cfg, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
|
|
|
|
|
body: { shareToken: msg.shareToken }
|
|
|
|
|
});
|
|
|
|
|
msg.editorUrl = result.data?.url || result.data;
|
|
|
|
|
msg.payload = { url: msg.editorUrl };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'files:convert': {
|
|
|
|
|
const format = (pickMsgField(msg, 'format') || 'pdf').toLowerCase();
|
|
|
|
|
const source = msg.ncPath
|
|
|
|
|
? await webdavGet(cfg, msg.ncPath)
|
|
|
|
|
: { buffer: Buffer.from(msg.payload), filename: msg.filename || 'file.bin' };
|
|
|
|
|
const converted = await convertTo(cfg, {
|
|
|
|
|
buffer: source.buffer,
|
|
|
|
|
filename: source.filename,
|
|
|
|
|
format
|
|
|
|
|
});
|
|
|
|
|
const dest = assertSafeDestination(resolveDestNcPath(msg, msg.ncPath, format), msg);
|
|
|
|
|
const put = await webdavPut(cfg, dest, converted.buffer, contentTypeForFormat(format));
|
|
|
|
|
msg.destNcPath = put.destNcPath;
|
|
|
|
|
msg.payload = converted.buffer;
|
|
|
|
|
msg.format = format;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'assets:create': {
|
|
|
|
|
const link = await bridge.createAssetLink(cfg, msg);
|
|
|
|
|
msg.assetUrl = link.assetUrl;
|
|
|
|
|
msg.payload = { assetUrl: link.assetUrl, ncPath: link.ncPath };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Okänd nc-office operation: ${op}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setSuccess(node, msg, op);
|
|
|
|
|
send(msg);
|
|
|
|
|
done();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setError(node, msg, err);
|
|
|
|
|
send(msg);
|
|
|
|
|
done(err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RED.nodes.registerType('nc-office', NcOfficeNode);
|
|
|
|
|
};
|