'use strict'; const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils'); const { ocsRequest } = require('../lib/ocs'); const bridge = require('../lib/bridge'); const { resolveFileId } = bridge; const { webdavGet, webdavPut, ensureFolder, webdavPropfindFileId } = require('../lib/webdav'); const { convertTo } = require('../lib/collabora'); const { resolveDestNcPath, assertSafeDestination, contentTypeForFormat, normalizeNcPath, DEFAULT_OUTPUT_FOLDER } = require('../lib/utils'); function hasExtension(p) { return /\.[^/.]+$/.test(String(p).replace(/\/+$/, '')); } 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': { // 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('/')) || '/'); result = await ocsRequest(cfg, { method: 'POST', path: '/ocs/v2.php/apps/richdocuments/api/v1/templates/new', body: { path, template: Number(template) } }); 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 }; 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': { // richdocuments OCS#createDirect($fileId) binder parametern // per metodnamn — skicka både fileId och fileid (Number). const fileId = await resolveFileId(cfg, msg); result = await ocsRequest(cfg, { method: 'POST', path: '/ocs/v2.php/apps/richdocuments/api/v1/document', body: { fileId: Number(fileId), fileid: Number(fileId) } }); 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); };