40021580ce1d665eeb1e7cffe9341e1ef7a28328
[squeep-indie-auther] / src / chores.js
1 'use strict';
2
3 const common = require('./common');
4 const Enum = require('./enum');
5 const { Chores: BaseChores } = require('@squeep/chores');
6 const _fileScope = common.fileScope(__filename);
7
8 /**
9 * Wrangle periodic tasks what need doing.
10 */
11
12 class Chores extends BaseChores {
13 constructor(logger, db, queuePublisher, options) {
14 super(logger);
15 this.options = options;
16 this.db = db;
17 this.queuePublisher = queuePublisher;
18
19 this.establishChore(Enum.Chore.CleanTokens, this.cleanTokens.bind(this), options?.chores?.tokenCleanupMs);
20 this.establishChore(Enum.Chore.CleanScopes, this.cleanScopes.bind(this), options?.chores?.scopeCleanupMs);
21 this.establishChore(Enum.Chore.PublishTickets, this.publishTickets.bind(this), options?.chores?.publishTicketsMs);
22 }
23
24 /**
25 * Attempt to remove tokens which are expired or otherwise no longer valid.
26 * @param {Number} atLeastMsSinceLast
27 */
28 async cleanTokens(atLeastMsSinceLast = this.options?.chores?.tokenCleanupMs || 0) {
29 const _scope = _fileScope('cleanTokens');
30 this.logger.debug(_scope, 'called', { atLeastMsSinceLast });
31
32 let tokensCleaned;
33 try {
34 await this.db.context(async (dbCtx) => {
35 const codeValidityTimeoutSeconds = Math.ceil(this.options.manager.codeValidityTimeoutMs / 1000);
36 tokensCleaned = await this.db.tokenCleanup(dbCtx, codeValidityTimeoutSeconds, atLeastMsSinceLast);
37 }); // dbCtx
38 if (tokensCleaned) {
39 this.logger.info(_scope, 'finished', { tokensCleaned });
40 }
41 } catch (e) {
42 this.logger.error(_scope, 'failed', { error: e });
43 throw e;
44 }
45 }
46
47
48 /**
49 * Attempt to remove ephemeral scopes which are no longer referenced by tokens.
50 * @param {Number} atLeastMsSinceLast
51 */
52 async cleanScopes(atLeastMsSinceLast = this.options?.chores?.scopeCleanupMs || 0) {
53 const _scope = _fileScope('cleanScopes');
54 this.logger.debug(_scope, 'called', { atLeastMsSinceLast });
55
56 let scopesCleaned;
57 try {
58 await this.db.context(async (dbCtx) => {
59 scopesCleaned = await this.db.scopeCleanup(dbCtx, atLeastMsSinceLast);
60 }); // dbCtx
61 if (scopesCleaned) {
62 this.logger.info(_scope, 'finished', { scopesCleaned });
63 }
64 } catch (e) {
65 this.logger.error(_scope, 'failed', { error: e });
66 throw e;
67 }
68 }
69
70
71 /**
72 * Attempt to deliver any redeemed but un-delivered ticket tokens.
73 */
74 async publishTickets() {
75 const _scope = _fileScope('publishTickets');
76 this.logger.debug(_scope, 'called');
77
78 try {
79 const queueName = this.options.queues.ticketRedeemedName;
80 await this.db.context(async (dbCtx) => {
81 const ticketTokens = await this.db.ticketTokenGetUnpublished(dbCtx);
82 for await (const data of ticketTokens) {
83 try {
84 const result = await this.queuePublisher.publish(queueName, data);
85 this.logger.info(_scope, 'published ticket token', { queueName, result, ...data });
86 const redeemedData = common.pick(data, ['resource', 'subject', 'iss', 'ticket', 'token']);
87 await this.db.ticketTokenPublished(dbCtx, redeemedData);
88 } catch (e) {
89 this.logger.error(_scope, 'publish failed', { error: e, data });
90 }
91 }
92 }); // dbCtx
93 } catch (e) {
94 this.logger.error(_scope, 'failed', { error: e });
95 throw e;
96 }
97 }
98 } // Chores
99
100 module.exports = Chores;