Files

295 lines
8.7 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
const { webdavGet, webdavPut, webdavPropfindFileId, listUserExamples } = 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;
}
// Bridge-op "pickDemoSource": slå upp en fil som FAKTISKT finns hos användaren
// (/Dokument först, sedan /Documents) och lägg den på msg.ncPath. Endast för
// demo/generator-flöden — ingen silent fallback inne i convert-operationer.
async function pickDemoSource(config, msg) {
const examples = await listUserExamples(config);
if (!examples.length) {
throw new Error(
'Ingen exempelfil hittad i /Dokument eller /Documents — ladda upp en .md/.odt/.docx ' +
'eller ange msg.ncPath manuellt.'
);
}
msg.demoSources = examples.map((e) => e.ncPath);
if (!pickMsgField(msg, 'ncPath')) {
msg.ncPath = examples[0].ncPath;
}
return {
payload: examples,
picked: msg.ncPath
};
}
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,
pickDemoSource,
loadSourceBuffer,
saveResult,
resolveFileId
};