this.db = db;
this.options = options;
this.communication = new Communication(logger, db, options);
-
- // Precalculate the invariant root GET metadata.
- this.getRootContent = Template.rootHTML(undefined, options);
- const now = new Date();
- this.startTimeString = now.toGMTString();
- this.startTimeMs = now.getTime();
- this.getRootETag = common.generateETag(undefined, undefined, this.getRootContent);
}
const _scope = _fileScope('getRoot');
this.logger.debug(_scope, 'called', { ctx });
- res.setHeader(Enum.Header.LastModified, this.startTimeString);
- res.setHeader(Enum.Header.ETag, this.getRootETag);
-
- if (common.isClientCached(req, this.startTimeMs, this.getRootETag)) {
- this.logger.debug(_scope, 'client cached response', { ctx });
- res.statusCode = 304;
- res.end();
- return;
- }
- res.end(this.getRootContent);
+ const content = Template.rootHTML(ctx, this.options);
+ res.end(content);
this.logger.info(_scope, 'finished', { ctx });
}
});
res.end(this.infoContent(ctx));
- this.logger.info(_scope, 'finished', { ...ctx });
+ this.logger.info(_scope, 'finished', { ctx });
+ }
+
+
+ /**
+ * label the bars of the topic update history graph
+ * @param {Number} index
+ * @param {Number} value
+ * @returns {String}
+ */
+ static _historyBarCaption(index, value) {
+ let when;
+ switch (index) {
+ case 0:
+ when ='today';
+ break;
+ case 1:
+ when = 'yesterday';
+ break;
+ default:
+ when = `${index} days ago`;
+ }
+ return `${when}, ${value ? value : 'no'} update${value === 1 ? '': 's'}`;
}
+ /**
+ * GET SVG chart of topic update history
+ * @param {http.ServerResponse} res
+ * @param {object} ctx
+ */
+ async getHistorySVG(res, ctx) {
+ const _scope = _fileScope('getHist');
+ this.logger.debug(_scope, 'called', { ctx });
+
+ const days = Math.min(parseInt(ctx.queryParams.days) || this.options.manager.publishHistoryDays, 365);
+ const histOptions = {
+ title: 'Topic Publish History',
+ description: 'Updates per Day',
+ labelZero: '^ Today',
+ labelX: 'Days Ago',
+ maxItems: days,
+ minItems: days,
+ tickEvery: 7,
+ barWidth: 25,
+ barHeight: 40,
+ labelHeight: 12,
+ barCaptionFn: Manager._historyBarCaption,
+ };
+
+ let publishHistory;
+ await this.db.context(async (dbCtx) => {
+ publishHistory = await this.db.topicPublishHistory(dbCtx, ctx.params.topicId, days);
+ });
+
+ res.end(Template.histogramSVG(publishHistory, histOptions));
+ this.logger.info(_scope, 'finished', { ctx });
+ }
+
/**
* GET request for authorized /admin information.
* @param {http.ServerResponse} res
});
this.logger.debug(_scope, 'got topics', { topics: ctx.topics });
+ // Profile users can only see related topics.
+ if (ctx.session && ctx.session.authenticatedProfile) {
+ const profileUrlObj = new URL(ctx.session.authenticatedProfile);
+ ctx.topics = ctx.topics.filter((topic) => {
+ const topicUrlObj = new URL(topic.url);
+ return (topicUrlObj.hostname === profileUrlObj.hostname);
+ });
+ }
+
res.end(Template.adminOverviewHTML(ctx, this.options));
- this.logger.info(_scope, 'finished', { ...ctx, topics: ctx.topics.length })
+ this.logger.info(_scope, 'finished', { ctx, topics: ctx.topics.length });
}
const _scope = _fileScope('getTopicDetails');
this.logger.debug(_scope, 'called', { ctx });
+
+ ctx.publishSpan = 60;
const topicId = ctx.params.topicId;
+ let publishHistory;
await this.db.context(async (dbCtx) => {
ctx.topic = await this.db.topicGetById(dbCtx, topicId);
ctx.subscriptions = await this.db.subscriptionsByTopicId(dbCtx, topicId);
+ publishHistory = await this.db.topicPublishHistory(dbCtx, topicId, ctx.publishSpan);
});
- this.logger.debug(_scope, 'got topic details', { topic: ctx.topic, subscriptions: ctx.subscriptions });
+ ctx.publishCount = publishHistory.reduce((a, b) => a + b, 0);
+ this.logger.debug(_scope, 'got topic details', { topic: ctx.topic, subscriptions: ctx.subscriptions, updates: ctx.publishCount });
+
+ // Profile users can only see related topics.
+ if (ctx.session && ctx.session.authenticatedProfile) {
+ const profileUrlObj = new URL(ctx.session.authenticatedProfile);
+ const topicUrlObj = new URL(ctx.topic.url);
+ if (topicUrlObj.hostname !== profileUrlObj.hostname) {
+ ctx.topic = null;
+ ctx.subscriptions = [];
+ }
+ }
res.end(Template.adminTopicDetailsHTML(ctx, this.options));
- this.logger.info(_scope, 'finished', { ...ctx, subscriptions: ctx.subscriptions.length, topic: ctx.topic.id });
+ this.logger.info(_scope, 'finished', { ctx, subscriptions: ctx.subscriptions.length, topic: ctx.topic && ctx.topic.id || ctx.topic });
}
* @param {object} ctx
*/
async processTasks(res, ctx) {
- const _scope = _fileScope('getTopicDetails');
+ const _scope = _fileScope('processTasks');
this.logger.debug(_scope, 'called', { ctx });
// N.B. no await on this