You are on page 1of 3

const fs = require('fs').

promises;
const RSS = require('rss');
const moment = require('moment');
const _ = require('lodash');

const { GENERATED_FILES_PATHS } = require('../../constants/paths');

const { getModel } = require('$models/helpers');


const ImageService = require('../ImageService');

class RSSFeedsService {
constructor(site, options = {}) {
const { baseTargetPath, ttl } = {
baseTargetPath: GENERATED_FILES_PATHS.RSS,
ttl: 30,
...options,
};

if (!site) {
throw new Error('RSSFeedsService: Missing site');
}

if (!site.config) {
throw new Error('RSSFeedsService: Missing site config');
}

if (!site.config.options) {
throw new Error('RSSFeedsService: Missing site options');
}

this.site = site;
this.siteId = site._id;
this.domain = site.domain;
this.siteOptions = site.config.options;
this.rootDirectory = baseTargetPath;
this.targetPath = `${baseTargetPath}/${site._id}.xml`;
this.rssConfiguration = {
isActive: false,
managingEditor: this.siteOptions.siteTitle,
copyright: `${this.siteOptions?.siteTitle}. L’utilisation des flux RSS du
site ${this.domain} est réservée à un usage strictement personnel, non
professionnel et non collectif. Toute autre exploitation doit faire l’objet d’une
autorisation. Contact : ${this.siteOptions?.contactEmail}`,
categories: [],
contentTypes: [],
...(site.config.rssConfiguration || {}),
};

this.contents = [];

this.feed = new RSS({


title: this.siteOptions.siteTitle,
description: this.siteOptions.siteDescription,
generator: 'Plateforme UFullVideo',
feed_url: `https://${site.domain}/rss`,
site_url: `https://${site.domain}`,
image_url: this.siteOptions.favicon
? ImageService.getUrl(this.site, this.siteOptions.favicon.file)
: null,
managingEditor: this.rssConfiguration.managingEditor,
webMaster: 'Magasin Numérique',
copyright: this.rssConfiguration.copyright,
language: 'fr',
categories: this.rssConfiguration.categories,
ttl: ttl,
pubDate: moment(),
});
}

async generate() {
if (!this.rssConfiguration.isActive) return;

try {
await fs.access(this.rootDirectory);
} catch (e) {
await fs.mkdir(this.rootDirectory, { recursive: true });
}

let contents = [];

for (const modelId of this.rssConfiguration.contentTypes) {


const Model = getModel(modelId)(this.siteId);
contents = [
...contents,
...(await Model.find()
.isIndexed()
.isPublished()
.sort({ publishStartTs: -1 })
.limit(20)),
];
}

contents = _.orderBy(
contents,
['publishStartTs', 'createTs'],
['desc', 'desc']
).slice(0, 20);

for (const content of contents) {


await this.#generateContentRSSFeedsItem(content);
}

await this.#writeRSSFeedsFile();

console.log(
`RSS feed for ${this.siteId} written at path : ${this.targetPath}`
);
}

async #generateContentRSSFeedsItem(content) {
const { label, publishStartTs, image } = content;

const path = await content.getFrontendFullPath();

const categoryLabels = _.map(await content.getCategories(), 'label');

const authorsLabel = content.getAuthors


? _.map(await content.getAuthors(), 'label').join(', ')
: '';

this.feed.item({
title: label,
description: content.getDescriptionFormattedHtml
? content.getDescriptionFormattedHtml({ forHeading: true })
: content.description?.html,
url: `${this.site.getFrontUrl()}${path}?utm_medium=rss`,
guid: content._id,
categories: categoryLabels,
author: authorsLabel,
date: moment.unix(publishStartTs),
enclosure: image
? { url: ImageService.getUrl(this.site, image.file) }
: null,
});
}

async #writeRSSFeedsFile() {
try {
await fs.writeFile(this.targetPath, this.feed.xml());

console.log(
`RSS feeds written for ${this.siteId} at path : ${this.targetPath}`
);

return true;
} catch (err) {
console.error(err);
}
}
}

module.exports = RSSFeedsService;

You might also like