master
commit
942fd8316d
@ -0,0 +1,164 @@
|
||||
// admin/newsroom.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const { SiteController, SiteError } = require('../../../lib/site-lib');
|
||||
|
||||
class NewsroomAdminController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
const upload = this.createMulter('newsroom-admin');
|
||||
|
||||
const router = express.Router();
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = 'admin';
|
||||
res.locals.adminView = 'newsroom';
|
||||
return next();
|
||||
});
|
||||
|
||||
router.param('feedId', this.populateFeedId.bind(this));
|
||||
router.param('feedEntryId', this.populateFeedEntryId.bind(this));
|
||||
|
||||
router.post('/resolve', upload.none(), this.postResolveFeed.bind(this));
|
||||
router.post('/:feedId', upload.none(), this.postUpdateFeed.bind(this));
|
||||
router.post('/', upload.none(), this.postCreateFeed.bind(this));
|
||||
|
||||
router.get('/create', this.getFeedEditor.bind(this));
|
||||
router.get('/:feedId', this.getFeedEditor.bind(this));
|
||||
|
||||
router.get('/', this.getHomeView.bind(this));
|
||||
|
||||
router.delete('/:feedId', this.deleteFeed.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async populateFeedId (req, res, next, feedId) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.feed = await feedService.getById(feedId);
|
||||
if (!res.locals.feed) {
|
||||
throw new SiteError(404, 'Feed not found');
|
||||
}
|
||||
return next();
|
||||
} catch (error) {
|
||||
this.log.error('failed to populate feedId', { feedId, error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async populateFeedEntryId (req, res, next, feedEntryId) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.feedEntry = await feedService.getEntryById(feedEntryId);
|
||||
if (!res.locals.feedEntry) {
|
||||
throw new SiteError(404, 'Feed entry not found');
|
||||
}
|
||||
return next();
|
||||
} catch (error) {
|
||||
this.log.error('failed to populate feed entry', { feedEntryId, error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async postResolveFeed (req, res) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
const feed = await feedService.load(req.body.feedUrl);
|
||||
this.log.info('request body', { body: req.body, feed });
|
||||
|
||||
const displayList = this.createDisplayList('resolve-feed');
|
||||
displayList.setInputValue(`input#title`, feed.title);
|
||||
displayList.setInputValue(`input#link`, feed.link);
|
||||
displayList.setInputValue(`textarea#description`, feed.description);
|
||||
|
||||
if (feed.generator) {
|
||||
displayList.setInputValue(`input[type="hidden"][name="generator"]`, feed.generator);
|
||||
}
|
||||
if (feed.language) {
|
||||
displayList.setInputValue(`input[type="hidden"][name="language"]`, feed.language);
|
||||
}
|
||||
if (feed.published) {
|
||||
displayList.setInputValue(`input[type="hidden"][name="published"]`, feed.published);
|
||||
}
|
||||
|
||||
res.status(200).json({ success: true, displayList });
|
||||
} catch (error) {
|
||||
this.log.error('failed to present the Newsroom Admin home', { error });
|
||||
return res.status(error.statusCode || 500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async postUpdateFeed (req, res, next) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
await feedService.update(res.locals.feed, req.body);
|
||||
res.redirect('/admin/newsroom');
|
||||
} catch (error) {
|
||||
this.log.error('failed to create feed', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async postCreateFeed (req, res, next) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.feed = await feedService.create(req.body);
|
||||
res.redirect(`/admin/newsroom/${res.locals.feed._id}`);
|
||||
} catch (error) {
|
||||
this.log.error('failed to create feed', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getFeedEditor (req, res) {
|
||||
res.render('admin/newsroom/editor');
|
||||
}
|
||||
|
||||
async getHomeView (req, res, next) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.pagination = this.getPaginationParameters(req, 20);
|
||||
res.locals.newsroom = await feedService.getFeeds(res.locals.pagination);
|
||||
res.render('admin/newsroom/index');
|
||||
} catch (error) {
|
||||
this.log.error('failed to present the Newsroom Admin home', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFeed (req, res) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
await feedService.remove(res.locals.feed);
|
||||
|
||||
const displayList = this.createDisplayList('delete-feed');
|
||||
displayList.navigateTo('/admin/newsroom');
|
||||
|
||||
res.status(200).json({ success: true, displayList });
|
||||
} catch (error) {
|
||||
this.log.error('failed to remove feed', { error });
|
||||
return res.status(error.statusCode || 500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'newsroomAdmin',
|
||||
slug: 'newsroom-admin',
|
||||
create: async (dtp) => { return new NewsroomAdminController(dtp); },
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
// newsroom.js
|
||||
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const { SiteController, SiteError } = require('../../lib/site-lib');
|
||||
|
||||
class NewsroomController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
const { dtp } = this;
|
||||
const { limiter: limiterService } = dtp.services;
|
||||
|
||||
const router = express.Router();
|
||||
dtp.app.use('/newsroom', router);
|
||||
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = module.exports.slug;
|
||||
return next();
|
||||
});
|
||||
|
||||
router.param('feedId', this.populateFeedId.bind(this));
|
||||
|
||||
router.get('/:feedId',
|
||||
limiterService.createMiddleware(limiterService.config.newsroom.getFeedView),
|
||||
this.getFeedView.bind(this),
|
||||
);
|
||||
|
||||
router.get('/',
|
||||
limiterService.createMiddleware(limiterService.config.newsletter.getIndex),
|
||||
this.getHome.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
async populateFeedId (req, res, next, feedId) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.feed = await feedService.getById(feedId);
|
||||
if (!res.locals.feed) {
|
||||
throw new SiteError(404, 'Feed not found');
|
||||
}
|
||||
return next();
|
||||
} catch (error) {
|
||||
this.log.error('failed to populate feedId', { feedId, error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getFeedView (req, res, next) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.pagination = this.getPaginationParameters(req, 10);
|
||||
res.locals.newsroom = await feedService.getFeedEntries(res.locals.feed, res.locals.pagination);
|
||||
res.render('newsroom/feed-view');
|
||||
} catch (error) {
|
||||
this.log.error('failed to present newsroom home', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getHome (req, res, next) {
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.pagination = this.getPaginationParameters(req, 10);
|
||||
res.locals.newsroom = await feedService.getFeeds(res.locals.pagination);
|
||||
res.render('newsroom/index');
|
||||
} catch (error) {
|
||||
this.log.error('failed to present newsroom home', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'newsroom',
|
||||
name: 'newsroom',
|
||||
create: (dtp) => { return new NewsroomController(dtp); },
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
// feed-entry.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const FeedEntrySchema = new Schema({
|
||||
feed: { type: Schema.ObjectId, required: true, index: 1, ref: 'Feed' },
|
||||
published: { type: Date },
|
||||
title: { type: String },
|
||||
description: { type: String },
|
||||
link: { type: String, index: 1 },
|
||||
});
|
||||
|
||||
FeedEntrySchema.index({
|
||||
feed: 1,
|
||||
link: 1,
|
||||
}, {
|
||||
name: 'feed_entry_by_feed_idx',
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('FeedEntry', FeedEntrySchema);
|
@ -0,0 +1,21 @@
|
||||
// feed.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const FeedSchema = new Schema({
|
||||
url: { type: String, required: true, unique: true },
|
||||
title: { type: String },
|
||||
link: { type: String },
|
||||
description: { type: String },
|
||||
language: { type: String },
|
||||
generator: { type: String },
|
||||
published: { type: Date },
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Feed', FeedSchema);
|
@ -0,0 +1,148 @@
|
||||
// announcement.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Feed = mongoose.model('Feed');
|
||||
const FeedEntry = mongoose.model('FeedEntry');
|
||||
|
||||
const { SiteService, SiteError } = require('../../lib/site-lib');
|
||||
const { read: feedReader } = require('feed-reader');
|
||||
|
||||
class FeedService extends SiteService {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
|
||||
this.populateFeedEntry = [
|
||||
{
|
||||
path: 'feed',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async create (feedDefinition) {
|
||||
feedDefinition.url = feedDefinition.url.trim();
|
||||
const feedContent = await this.load(feedDefinition.url);
|
||||
if (!feedContent) {
|
||||
throw new SiteError(404, 'Feed failed to load');
|
||||
}
|
||||
|
||||
const feed = new Feed();
|
||||
feed.url = feedDefinition.url;
|
||||
feed.title = feedDefinition.title || feedContent.title || 'New Feed';
|
||||
feed.link = feedDefinition.link || feedContent.link;
|
||||
feed.description = feedDefinition.description || feedContent.description;
|
||||
feed.language = feedContent.language;
|
||||
feed.generator = feedContent.generator;
|
||||
feed.published = feedContent.published;
|
||||
await feed.save();
|
||||
|
||||
return feed.toObject();
|
||||
}
|
||||
|
||||
async update (feed, feedDefinition) {
|
||||
feedDefinition.url = feedDefinition.url.trim();
|
||||
const feedContent = await this.load(feedDefinition.url);
|
||||
if (!feedContent) {
|
||||
throw new SiteError(404, 'Feed failed to load');
|
||||
}
|
||||
|
||||
const updateOp = { $set: { }, $unset: { } };
|
||||
|
||||
updateOp.$set.url = feedDefinition.url;
|
||||
updateOp.$set.title = feedDefinition.title || feedContent.title || 'New Feed';
|
||||
updateOp.$set.link = feedDefinition.link || feedContent.link;
|
||||
updateOp.$set.description = feedDefinition.description || feedContent.description;
|
||||
|
||||
updateOp.$set.language = feedDefinition.language || feedContent.language;
|
||||
updateOp.$set.generator = feedDefinition.generator || feedContent.generator;
|
||||
|
||||
await Feed.updateOne({ _id: feed._id }, updateOp);
|
||||
}
|
||||
|
||||
async getFeeds (pagination) {
|
||||
pagination = Object.assign({ skip: 0, cpp: 10 }, pagination);
|
||||
const feeds = await Feed
|
||||
.find()
|
||||
.sort({ title: 1 })
|
||||
.skip(pagination.skip)
|
||||
.limit(pagination.cpp)
|
||||
.lean();
|
||||
const totalFeedCount = await Feed.countDocuments();
|
||||
return { feeds, totalFeedCount };
|
||||
}
|
||||
|
||||
async getById (feedId) {
|
||||
const feed = await Feed.findOne({ _id: feedId }).lean();
|
||||
return feed;
|
||||
}
|
||||
|
||||
async getFeedEntries (feed, pagination) {
|
||||
pagination = Object.assign({ skip: 0, cpp: 10 }, pagination);
|
||||
const entries = await FeedEntry
|
||||
.find({ feed: feed._id })
|
||||
.sort({ published: -1 })
|
||||
.skip(pagination.skip)
|
||||
.limit(pagination.cpp)
|
||||
.populate(this.populateFeedEntry)
|
||||
.lean();
|
||||
const totalFeedEntryCount = await FeedEntry.countDocuments({ feed: feed._id });
|
||||
return { entries, totalFeedEntryCount };
|
||||
}
|
||||
|
||||
async getNewsfeed (pagination) {
|
||||
pagination = Object.assign({ skip: 0, cpp: 5 }, pagination);
|
||||
const entries = await FeedEntry
|
||||
.find()
|
||||
.sort({ published: -1 })
|
||||
.skip(pagination.skip)
|
||||
.limit(pagination.cpp)
|
||||
.populate(this.populateFeedEntry)
|
||||
.lean();
|
||||
const totalFeedEntryCount = await FeedEntry.estimatedDocumentCount();
|
||||
return { entries, totalFeedEntryCount };
|
||||
}
|
||||
|
||||
async remove (feed) {
|
||||
this.log.info('removing all feed entries', { feedId: feed._id, title: feed.title });
|
||||
await FeedEntry.deleteMany({ feed: feed._id });
|
||||
|
||||
this.log.info('removing feed', { feedId: feed._id, title: feed.title });
|
||||
await Feed.deleteOne({ _id: feed._id });
|
||||
}
|
||||
|
||||
async load (url) {
|
||||
const response = await feedReader(url);
|
||||
return response;
|
||||
}
|
||||
|
||||
async createEntry (feed, entryDefinition) {
|
||||
const NOW = new Date();
|
||||
const updateOp = { $setOnInsert: { }, $set: { }, $unset: { } };
|
||||
|
||||
updateOp.$setOnInsert.feed = feed._id;
|
||||
updateOp.$setOnInsert.link = entryDefinition.link.trim();
|
||||
updateOp.$setOnInsert.published = new Date(entryDefinition.published || NOW);
|
||||
|
||||
updateOp.$set.title = entryDefinition.title.trim();
|
||||
updateOp.$set.description = entryDefinition.description.trim();
|
||||
|
||||
await FeedEntry.updateOne(
|
||||
{
|
||||
feed: feed._id,
|
||||
link: updateOp.$setOnInsert.link,
|
||||
},
|
||||
updateOp,
|
||||
{ upsert: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'feed',
|
||||
name: 'feed',
|
||||
create: (dtp) => { return new FeedService(dtp); },
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
- var formActionUrl = feed ? `/admin/newsroom/${feed._id}` : '/admin/newsroom';
|
||||
|
||||
form#add-feed-form(method="POST", action= formActionUrl).uk-form
|
||||
|
||||
input(type="hidden", name="generator", value="DTP News")
|
||||
input(type="hidden", name="language", value="en")
|
||||
input(type="hidden", name="published")
|
||||
|
||||
.uk-card.uk-card-default.uk-card-small
|
||||
.uk-card-header
|
||||
h1.uk-card-title= feed ? 'Update Feed' : 'Add Feed'
|
||||
.uk-card-body
|
||||
.uk-margin
|
||||
label(for="url").uk-form-label Feed URL
|
||||
input(id="url", name="url", type="url", placeholder="Enter feed URL", value= feed ? feed.url : undefined).uk-input
|
||||
.uk-margin-small
|
||||
button(type="button", onclick="return dtp.adminApp.resolveNewsroomFeed(event);").uk-button.uk-button-default.uk-button-small.uk-border-rounded Resolve Feed
|
||||
span.uk-margin-small-left to load the following information automatically.
|
||||
|
||||
.uk-margin
|
||||
label(for="title").uk-form-label Title
|
||||
input(id="title", name="title", type="text", placeholder="Enter feed title", value= feed ? feed.title : undefined).uk-input
|
||||
|
||||
.uk-margin
|
||||
label(for="link").uk-form-label Link
|
||||
input(id="link", name="link", type="url", placeholder="Enter feed website link", value= feed ? feed.link : undefined).uk-input
|
||||
|
||||
.uk-margin
|
||||
label(for="description").uk-form-label Description
|
||||
textarea(id="description", name="description", rows="4", placeholder="Enter feed description").uk-textarea.uk-resize-vertical= feed ? feed.description : undefined
|
||||
|
||||
.uk-card-footer
|
||||
button(type="submit").uk-button.uk-button-primary.uk-border-rounded= feed ? 'Update Feed' : 'Add Feed'
|
||||
|
||||
if feed
|
||||
pre= JSON.stringify(feed, null, 2)
|
@ -0,0 +1,16 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
div(uk-grid)
|
||||
.uk-width-expand
|
||||
h1 Newsroom Feeds
|
||||
.uk-width-auto
|
||||
a(href='/admin/newsroom/create').uk-button.uk-button-primary #[i.fas.fa-plus]#[span.uk-margin-small-left Add Feed]
|
||||
|
||||
if Array.isArray(newsroom.feeds) && (newsroom.feeds.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each feed in newsroom.feeds
|
||||
li
|
||||
a(href=`/admin/newsroom/${feed._id}`)= feed.title
|
||||
else
|
||||
div There are no feeds.
|
@ -0,0 +1,9 @@
|
||||
mixin renderNewsroomFeedEntryListItem (entry)
|
||||
.uk-text-bold
|
||||
a(href= entry.link, target="_blank").uk-link-reset= entry.title
|
||||
.uk-text-small
|
||||
div(uk-grid).uk-grid-small
|
||||
.uk-width-expand
|
||||
a(href= entry.feed.link, target="_blank").uk-link-reset= entry.feed.title
|
||||
.uk-width-auto
|
||||
div= moment(entry.published).fromNow()
|
@ -0,0 +1,35 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
include ../components/pagination-bar
|
||||
|
||||
section.uk-section.uk-section-default.uk-section-small
|
||||
.uk-container
|
||||
|
||||
article.uk-article
|
||||
.uk-margin-large
|
||||
.uk-margin
|
||||
h1.uk-article-title.uk-margin-remove= feed.title
|
||||
.uk-article-meta
|
||||
div(uk-grid)
|
||||
.uk-width-auto
|
||||
a(href= feed.link, target="_blank") #[i.fas.fa-external-link-alt]#[span.uk-margin-small-left Visit site]
|
||||
.uk-width-auto
|
||||
div last updated #{moment(feed.published).fromNow()}
|
||||
|
||||
.uk-text-lead= feed.description
|
||||
|
||||
.uk-margin-large
|
||||
if Array.isArray(newsroom.entries) && (newsroom.entries.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each entry in newsroom.entries
|
||||
li
|
||||
.uk-text-bold
|
||||
a(href= entry.link, target="_blank")= entry.title
|
||||
.uk-text-small= entry.description
|
||||
//- pre= JSON.stringify(entry, null, 2)
|
||||
else
|
||||
div There are no news feed entries.
|
||||
|
||||
.uk-margin
|
||||
+renderPaginationBar(`/newsroom/${feed._id}`, newsroom.totalFeedEntryCount)
|
@ -0,0 +1,19 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
section.uk-section.uk-section-default.uk-section-small
|
||||
.uk-container
|
||||
|
||||
h1 #{site.name} Newsroom
|
||||
if Array.isArray(newsroom.feeds) && (newsroom.feeds.length > 0)
|
||||
div(uk-grid).uk-grid-match
|
||||
each feed in newsroom.feeds
|
||||
.uk-width-1-3
|
||||
.uk-tile.uk-tile-secondary.uk-padding-small.uk-border-rounded
|
||||
.uk-text-bold
|
||||
a(href=`/newsroom/${feed._id}`)= feed.title
|
||||
.uk-text-small.uk-text-muted
|
||||
div last update #{moment(feed.published).fromNow()}
|
||||
div= feed.description
|
||||
else
|
||||
div There are no configured news feeds.
|
@ -0,0 +1,60 @@
|
||||
// newsroom.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '..', '..', '.env') });
|
||||
|
||||
const {
|
||||
SiteLog,
|
||||
SiteWorker,
|
||||
} = require(path.join(__dirname, '..', '..', 'lib', 'site-lib'));
|
||||
|
||||
module.rootPath = path.resolve(__dirname, '..', '..');
|
||||
module.pkg = require(path.resolve(__dirname, '..', '..', 'package.json'));
|
||||
|
||||
module.config = {
|
||||
environment: process.env.NODE_ENV,
|
||||
root: module.rootPath,
|
||||
component: { name: 'newsroom', slug: 'newsroom' },
|
||||
};
|
||||
|
||||
module.config.site = require(path.join(module.rootPath, 'config', 'site'));
|
||||
module.config.http = require(path.join(module.rootPath, 'config', 'http'));
|
||||
|
||||
class NewsroomWorker extends SiteWorker {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, dtp.config.component);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
await super.start();
|
||||
|
||||
await this.loadProcessor(path.join(__dirname, 'newsroom', 'cron', 'update-feeds.js'));
|
||||
|
||||
await this.startProcessors();
|
||||
}
|
||||
|
||||
async stop ( ) {
|
||||
await super.stop();
|
||||
}
|
||||
}
|
||||
|
||||
(async ( ) => {
|
||||
try {
|
||||
module.log = new SiteLog(module, module.config.component);
|
||||
|
||||
module.worker = new NewsroomWorker(module);
|
||||
await module.worker.start();
|
||||
|
||||
module.log.info(`${module.pkg.name} v${module.pkg.version} ${module.config.component.name} started`);
|
||||
} catch (error) {
|
||||
module.log.error('failed to start worker', { component: module.config.component, error });
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,85 @@
|
||||
// newsroom/cron/update-feeds.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Feed = mongoose.model('Feed');
|
||||
|
||||
const { CronJob } = require('cron');
|
||||
const { read: feedReader } = require('feed-reader');
|
||||
const { SiteAsync } = require('../../../../lib/site-lib');
|
||||
|
||||
const { SiteWorkerProcess } = require(path.join(__dirname, '..', '..', '..', '..', 'lib', 'site-lib'));
|
||||
|
||||
class UpdateFeedsCron extends SiteWorkerProcess {
|
||||
|
||||
static get COMPONENT ( ) {
|
||||
return {
|
||||
name: 'updateFeeds',
|
||||
slug: 'update-feeds-cron',
|
||||
};
|
||||
}
|
||||
|
||||
constructor (worker) {
|
||||
super(worker, UpdateFeedsCron.COMPONENT);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
await super.start();
|
||||
|
||||
await this.updateFeeds();
|
||||
|
||||
this.job = new CronJob(
|
||||
'0 */15 * * * *',
|
||||
this.updateFeeds.bind(this),
|
||||
null,
|
||||
true,
|
||||
process.env.DTP_CRON_TIMEZONE || 'America/New_York',
|
||||
);
|
||||
}
|
||||
|
||||
async stop ( ) {
|
||||
if (this.job) {
|
||||
this.log.info('stopping feed update job');
|
||||
this.job.stop();
|
||||
delete this.job;
|
||||
}
|
||||
await super.stop();
|
||||
}
|
||||
|
||||
async updateFeeds ( ) {
|
||||
try {
|
||||
await Feed
|
||||
.find()
|
||||
.lean()
|
||||
.cursor()
|
||||
.eachAsync(async (feed) => {
|
||||
await this.updateFeed(feed);
|
||||
}, 4);
|
||||
} catch (error) {
|
||||
this.log.error('failed to update feeds', { error });
|
||||
}
|
||||
}
|
||||
|
||||
async updateFeed (feed) {
|
||||
const NOW = new Date();
|
||||
const { feed: feedService } = this.dtp.services;
|
||||
try {
|
||||
this.log.info('loading latest feed data', { feedId: feed._id, title: feed.title });
|
||||
const response = await feedReader(feed.url);
|
||||
await SiteAsync.each(response.entries, async (entry) => {
|
||||
await Feed.updateOne({ _id: feed._id }, { $set: { published: feed.published || NOW }});
|
||||
await feedService.createEntry(feed, entry);
|
||||
}, 4);
|
||||
this.log.info('feed updated', { entries: response.entries.length });
|
||||
} catch (error) {
|
||||
this.log.error('failed to update feed', { feedId: feed._id, title: feed.title, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateFeedsCron;
|
Loading…
Reference in new issue