122 lines
4.0 KiB
JavaScript
122 lines
4.0 KiB
JavaScript
import config from '../../config.js';
|
|
import log4js from 'log4js';
|
|
import { getModelByName } from './model.js';
|
|
import { listObjectsOData } from '../../database/odata.js';
|
|
import { getFilter } from '../../utils.js';
|
|
import { getModelFilterFields, parseOrderBy } from './export.js';
|
|
|
|
const logger = log4js.getLogger('RSS');
|
|
logger.level = config.server.logLevel;
|
|
|
|
function escapeXml(str) {
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function getItemTitle(objectType, row) {
|
|
const preferredTitle = row.name || row.title || row.label || row.code;
|
|
if (preferredTitle) return String(preferredTitle);
|
|
return `${objectType} ${row._id || 'entry'}`;
|
|
}
|
|
|
|
function getItemDescription(row) {
|
|
const ignoredKeys = new Set(['_id', 'createdAt', 'updatedAt']);
|
|
const summary = Object.entries(row || {})
|
|
.filter(
|
|
([key, val]) => !ignoredKeys.has(key) && val !== null && val !== undefined && val !== ''
|
|
)
|
|
.slice(0, 10)
|
|
.map(([key, val]) => `${key}: ${typeof val === 'object' ? JSON.stringify(val) : String(val)}`)
|
|
.join(' | ');
|
|
|
|
if (summary) return summary;
|
|
return JSON.stringify(row || {});
|
|
}
|
|
|
|
function buildRssXml({ objectType, feedUrl, rows }) {
|
|
const now = new Date().toUTCString();
|
|
const itemsXml = rows
|
|
.map((row) => {
|
|
const rowId = row?._id ? String(row._id) : `${objectType}-${Date.now()}`;
|
|
const pubDateSource = row?.updatedAt || row?.createdAt;
|
|
const pubDate = pubDateSource ? new Date(pubDateSource).toUTCString() : now;
|
|
const itemLink = `${feedUrl}#${encodeURIComponent(rowId)}`;
|
|
return [
|
|
'<item>',
|
|
` <title>${escapeXml(getItemTitle(objectType, row || {}))}</title>`,
|
|
` <link>${escapeXml(itemLink)}</link>`,
|
|
` <guid isPermaLink="false">${escapeXml(`${objectType}:${rowId}`)}</guid>`,
|
|
` <description>${escapeXml(getItemDescription(row || {}))}</description>`,
|
|
` <pubDate>${escapeXml(pubDate)}</pubDate>`,
|
|
'</item>',
|
|
].join('\n');
|
|
})
|
|
.join('\n');
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>${escapeXml(`FarmControl ${objectType} RSS Feed`)}</title>
|
|
<link>${escapeXml(feedUrl)}</link>
|
|
<description>${escapeXml(`Recent ${objectType} updates from FarmControl`)}</description>
|
|
<lastBuildDate>${escapeXml(now)}</lastBuildDate>
|
|
${itemsXml}
|
|
</channel>
|
|
</rss>`;
|
|
}
|
|
|
|
/**
|
|
* Route handler for GET /rss/:objectType
|
|
* Supports OData-style query options: $top, $skip, $orderby and allowed filters.
|
|
*/
|
|
export const listRssRouteHandler = async (req, res) => {
|
|
const objectType = req.params.objectType;
|
|
const entry = getModelByName(objectType);
|
|
|
|
if (!entry?.model) {
|
|
logger.warn(`RSS: unknown object type "${objectType}"`);
|
|
return res.status(404).send({ error: `Unknown object type: ${objectType}` });
|
|
}
|
|
|
|
const { $top, $skip, $orderby } = req.query;
|
|
const queryWithoutAuth = { ...req.query };
|
|
delete queryWithoutAuth.u;
|
|
delete queryWithoutAuth.p;
|
|
const limit = $top ? Math.min(parseInt($top, 10) || 25, 1000) : 25;
|
|
const skip = $skip ? Math.max(0, parseInt($skip, 10) || 0) : 0;
|
|
const page = Math.floor(skip / limit) + 1;
|
|
const { sort, order } = parseOrderBy($orderby);
|
|
const allowedFilters = getModelFilterFields(objectType);
|
|
const filter = getFilter(queryWithoutAuth, allowedFilters);
|
|
|
|
const result = await listObjectsOData({
|
|
model: entry.model,
|
|
populate: [],
|
|
page,
|
|
limit,
|
|
filter,
|
|
sort,
|
|
order,
|
|
pagination: true,
|
|
project: '-__v',
|
|
count: false,
|
|
});
|
|
|
|
if (result?.error) {
|
|
logger.error('RSS list error:', result.error);
|
|
return res.status(result.code || 500).send(result);
|
|
}
|
|
|
|
const baseUrl = config.app?.urlApi || '';
|
|
const feedUrl = `${baseUrl}/rss/${objectType}`;
|
|
const rows = result?.value || [];
|
|
const xml = buildRssXml({ objectType, feedUrl, rows });
|
|
|
|
res.set('Content-Type', 'application/rss+xml; charset=utf-8');
|
|
res.send(xml);
|
|
};
|