6e02c2d31e
- lib/bridge.js createAssetLink: POST <nextcloudUrl>/apps/richdocuments/assets (utan index.php), Content-Type application/x-www-form-urlencoded med body path=<urlencoded>, path trimmad utan ledande slash (userFolder->get relativt filrot), OCS-APIRequest borttagen. Fel inkluderar statusCode + svarskropp. - Intern collaboraUrl är HTTP: http://code:9980 (fallback http://<bridge-ip>:9980) i nc-collab-config default, .env.example, example-flows.json, docker-compose, install.sh och integrate-into-nodered.sh resolve_collabora_url. - Publik WOPI-URL dokumenterad som https://docs.domain.name. - package-lock.json för deterministisk form-data-install i Docker.
273 lines
7.9 KiB
JavaScript
273 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
const { webdavGet, webdavPut, webdavPropfindFileId } = require('./webdav');
|
|
const { ocsRequest } = require('./ocs');
|
|
const { requestBuffer } = require('./http-client');
|
|
const { convertTo, extractStructure, transformStructure } = require('./collabora');
|
|
const {
|
|
resolveDestNcPath,
|
|
assertSafeDestination,
|
|
contentTypeForFormat,
|
|
replaceExtension,
|
|
basenameFromNcPath,
|
|
normalizeNcPath,
|
|
pickMsgField
|
|
} = require('./utils');
|
|
|
|
async function resolveFileId(config, msg) {
|
|
if (msg.fileId) {
|
|
return parseInt(msg.fileId, 10);
|
|
}
|
|
const ncPath = pickMsgField(msg, 'ncPath');
|
|
if (ncPath) {
|
|
return webdavPropfindFileId(config, ncPath);
|
|
}
|
|
throw new Error('Ange msg.fileId eller msg.ncPath');
|
|
}
|
|
|
|
async function loadSourceBuffer(config, msg) {
|
|
if (msg.payload !== undefined && msg.payload !== null && msg.payload !== '') {
|
|
if (Buffer.isBuffer(msg.payload)) {
|
|
return {
|
|
buffer: msg.payload,
|
|
filename: msg.filename || 'payload.bin',
|
|
ncPath: msg.ncPath
|
|
};
|
|
}
|
|
const text = typeof msg.payload === 'string' ? msg.payload : JSON.stringify(msg.payload);
|
|
return {
|
|
buffer: Buffer.from(text, 'utf8'),
|
|
filename: msg.filename || 'payload.txt',
|
|
ncPath: msg.ncPath
|
|
};
|
|
}
|
|
|
|
const ncPath = pickMsgField(msg, 'ncPath');
|
|
if (!ncPath) {
|
|
throw new Error('Ange msg.ncPath eller msg.payload');
|
|
}
|
|
const file = await webdavGet(config, ncPath);
|
|
return file;
|
|
}
|
|
|
|
async function saveResult(config, msg, buffer, format, sourceNcPath) {
|
|
const destNcPath = assertSafeDestination(
|
|
resolveDestNcPath(msg, sourceNcPath, format),
|
|
msg
|
|
);
|
|
const contentType = contentTypeForFormat(format);
|
|
const putResult = await webdavPut(config, destNcPath, buffer, contentType);
|
|
return {
|
|
destNcPath: putResult.destNcPath,
|
|
filename: putResult.filename,
|
|
contentType,
|
|
statusCode: putResult.statusCode
|
|
};
|
|
}
|
|
|
|
async function ncFileConvert(config, msg, format) {
|
|
const source = await loadSourceBuffer(config, msg);
|
|
const extraFields = [];
|
|
|
|
if (msg.templateNcPath) {
|
|
const tpl = await webdavGet(config, msg.templateNcPath);
|
|
extraFields.push({
|
|
name: 'template',
|
|
buffer: tpl.buffer,
|
|
filename: tpl.filename
|
|
});
|
|
}
|
|
|
|
if (msg.compareNcPath) {
|
|
const cmp = await webdavGet(config, msg.compareNcPath);
|
|
extraFields.push({
|
|
name: 'compare',
|
|
buffer: cmp.buffer,
|
|
filename: cmp.filename
|
|
});
|
|
}
|
|
|
|
const converted = await convertTo(config, {
|
|
buffer: source.buffer,
|
|
filename: source.filename,
|
|
format,
|
|
extraFields
|
|
});
|
|
|
|
const saved = await saveResult(config, msg, converted.buffer, format, source.ncPath);
|
|
return {
|
|
...saved,
|
|
format,
|
|
sourceNcPath: source.ncPath,
|
|
payload: converted.buffer
|
|
};
|
|
}
|
|
|
|
async function ncFileToPdf(config, msg) {
|
|
return ncFileConvert(config, msg, 'pdf');
|
|
}
|
|
|
|
async function ncFileToPng(config, msg) {
|
|
return ncFileConvert(config, msg, 'png');
|
|
}
|
|
|
|
async function ncFileToHtml(config, msg) {
|
|
return ncFileConvert(config, msg, 'html');
|
|
}
|
|
|
|
async function htmlToOffice(config, msg) {
|
|
const format = (pickMsgField(msg, 'format') || 'docx').toLowerCase();
|
|
const source = await loadSourceBuffer(config, msg);
|
|
let buffer = source.buffer;
|
|
let filename = source.filename || 'content.html';
|
|
|
|
if (!filename.endsWith('.html') && !filename.endsWith('.htm')) {
|
|
filename = 'content.html';
|
|
}
|
|
|
|
const extraFields = [];
|
|
if (msg.templateNcPath) {
|
|
const tpl = await webdavGet(config, msg.templateNcPath);
|
|
extraFields.push({
|
|
name: 'template',
|
|
buffer: tpl.buffer,
|
|
filename: tpl.filename
|
|
});
|
|
}
|
|
|
|
const converted = await convertTo(config, {
|
|
buffer,
|
|
filename,
|
|
format,
|
|
extraFields
|
|
});
|
|
|
|
const saved = await saveResult(config, msg, converted.buffer, format, msg.ncPath || filename);
|
|
return {
|
|
...saved,
|
|
format,
|
|
payload: converted.buffer
|
|
};
|
|
}
|
|
|
|
async function fillTemplateSave(config, msg) {
|
|
const fileId = await resolveFileId(config, msg);
|
|
const fields = msg.fields || msg.transform;
|
|
if (!fields) {
|
|
throw new Error('Ange msg.fields eller msg.transform för mallifyllning');
|
|
}
|
|
|
|
const destination = msg.destNcPath
|
|
? assertSafeDestination(normalizeNcPath(msg.destNcPath), msg)
|
|
: undefined;
|
|
|
|
const body = { fields };
|
|
if (destination) {
|
|
body.destination = destination.startsWith('/') ? destination.slice(1) : destination;
|
|
}
|
|
|
|
await ocsRequest(config, {
|
|
method: 'POST',
|
|
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/fill/${fileId}`,
|
|
body
|
|
});
|
|
|
|
const destNcPath = destination || resolveDestNcPath(msg, msg.ncPath, 'docx');
|
|
return {
|
|
fileId,
|
|
destNcPath: normalizeNcPath('/' + (body.destination || destNcPath.replace(/^\//, ''))),
|
|
statusCode: 200
|
|
};
|
|
}
|
|
|
|
async function compareVersionsPdf(config, msg) {
|
|
if (!msg.compareNcPath) {
|
|
throw new Error('compareVersionsPdf kräver msg.compareNcPath (äldre version)');
|
|
}
|
|
msg.format = 'pdf';
|
|
return ncFileToPdf(config, msg);
|
|
}
|
|
|
|
async function extractToPayload(config, msg) {
|
|
if (msg.fileId) {
|
|
const res = await ocsRequest(config, {
|
|
method: 'GET',
|
|
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/extract/${msg.fileId}`
|
|
});
|
|
return { fields: res.data, fileId: msg.fileId };
|
|
}
|
|
|
|
const source = await loadSourceBuffer(config, msg);
|
|
const filter = msg.filter || 'contentcontrol';
|
|
const structure = await extractStructure(config, {
|
|
buffer: source.buffer,
|
|
filename: source.filename,
|
|
filter
|
|
});
|
|
return {
|
|
structure,
|
|
sourceNcPath: source.ncPath
|
|
};
|
|
}
|
|
|
|
async function createAssetLink(config, msg) {
|
|
const assetPath = pickMsgField(msg, 'ncPath', 'path');
|
|
if (!assetPath) {
|
|
throw new Error('createAssetLink kräver msg.ncPath eller msg.path');
|
|
}
|
|
|
|
// richdocuments AssetsController::create($path) läser form-parametern 'path'
|
|
// och slår upp filen relativt användarens filrot → ingen ledande slash.
|
|
const relPath = String(assetPath).trim().replace(/^\/+/, '').replace(/\/{2,}/g, '/');
|
|
const base = String(config.nextcloudUrl || '').replace(/\/+$/, '');
|
|
const token = Buffer.from(`${config.username}:${config.password}`).toString('base64');
|
|
|
|
const res = await requestBuffer({
|
|
url: `${base}/apps/richdocuments/assets`,
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Basic ${token}`,
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: Buffer.from(`path=${encodeURIComponent(relPath)}`, 'utf8'),
|
|
rejectUnauthorized: config.verifyTls !== false && config.verifyTls !== 'false'
|
|
});
|
|
|
|
const bodyText = res.body.toString('utf8').trim();
|
|
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
throw new Error(
|
|
`assets:create misslyckades (${res.statusCode}): ${bodyText || '<tom body>'} — ` +
|
|
`path=${relPath}, url=${base}/apps/richdocuments/assets. ` +
|
|
'404 = filen saknas i användarens rot eller fel nextcloudUrl.'
|
|
);
|
|
}
|
|
|
|
let json;
|
|
try {
|
|
json = JSON.parse(bodyText);
|
|
} catch (e) {
|
|
throw new Error(`assets:create: ogiltigt JSON-svar (${res.statusCode}): ${bodyText.slice(0, 200)}`);
|
|
}
|
|
|
|
return {
|
|
assetUrl: json.url || json.assetUrl || json,
|
|
ncPath: relPath
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
ncFileToPdf,
|
|
ncFileToPng,
|
|
ncFileToHtml,
|
|
htmlToOffice,
|
|
fillTemplateSave,
|
|
compareVersionsPdf,
|
|
extractToPayload,
|
|
createAssetLink,
|
|
loadSourceBuffer,
|
|
saveResult,
|
|
resolveFileId
|
|
};
|