nodebb-plugin-onekite-calendar 2.0.66 → 2.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/admin.js +17 -115
- package/lib/api.js +57 -186
- package/lib/db.js +6 -3
- package/lib/discord.js +2 -7
- package/lib/helloassoWebhook.js +20 -49
- package/lib/nodebb-helpers.js +11 -174
- package/lib/scheduler.js +33 -22
- package/lib/shared.js +427 -0
- package/lib/utils.js +10 -65
- package/lib/widgets.js +2 -6
- package/package.json +1 -1
package/lib/nodebb-helpers.js
CHANGED
|
@@ -1,179 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const groups = require.main.require('./src/groups');
|
|
7
|
-
const user = require.main.require('./src/user');
|
|
8
|
-
|
|
9
|
-
const { getGroupNameBySlug } = require('./group-helpers');
|
|
10
|
-
|
|
11
|
-
function normalizeAllowedGroups(raw) {
|
|
12
|
-
if (!raw) return [];
|
|
13
|
-
if (Array.isArray(raw)) {
|
|
14
|
-
return raw.map((v) => String(v || '').trim()).filter(Boolean);
|
|
15
|
-
}
|
|
16
|
-
const s = String(raw || '').trim();
|
|
17
|
-
if (!s) return [];
|
|
18
|
-
|
|
19
|
-
if (s.startsWith('[') && s.endsWith(']')) {
|
|
20
|
-
try {
|
|
21
|
-
const parsed = JSON.parse(s);
|
|
22
|
-
if (Array.isArray(parsed)) {
|
|
23
|
-
return parsed.map((v) => String(v || '').trim()).filter(Boolean);
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// fall through
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return s
|
|
31
|
-
.split(',')
|
|
32
|
-
.map((v) => String(v || '').trim().replace(/^"+|"+$/g, ''))
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeUids(members) {
|
|
37
|
-
if (!Array.isArray(members)) return [];
|
|
38
|
-
const out = [];
|
|
39
|
-
for (const m of members) {
|
|
40
|
-
if (Number.isInteger(m)) {
|
|
41
|
-
out.push(m);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (typeof m === 'string' && m.trim()) {
|
|
45
|
-
const n = parseInt(m, 10);
|
|
46
|
-
if (Number.isFinite(n)) out.push(n);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (m && typeof m === 'object' && m.uid != null) {
|
|
50
|
-
const n = Number.isInteger(m.uid) ? m.uid : parseInt(String(m.uid).trim(), 10);
|
|
51
|
-
if (Number.isFinite(n)) out.push(n);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return Array.from(new Set(out)).filter((u) => Number.isInteger(u) && u > 0);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function getMembersByGroupIdentifier(groupIdentifier) {
|
|
58
|
-
const id = String(groupIdentifier || '').trim();
|
|
59
|
-
if (!id) return [];
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const members = await groups.getMembers(id, 0, -1);
|
|
63
|
-
if (Array.isArray(members) && members.length) return members;
|
|
64
|
-
} catch (e) {
|
|
65
|
-
// ignore
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const groupName = await getGroupNameBySlug(id);
|
|
69
|
-
if (groupName && String(groupName).trim() && String(groupName).trim() !== id) {
|
|
70
|
-
try {
|
|
71
|
-
const members = await groups.getMembers(String(groupName).trim(), 0, -1);
|
|
72
|
-
return Array.isArray(members) ? members : [];
|
|
73
|
-
} catch (e) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function userInAnyGroup(uid, allowedGroups) {
|
|
82
|
-
const allowed = normalizeAllowedGroups(allowedGroups);
|
|
83
|
-
if (!uid || !allowed.length) return false;
|
|
84
|
-
|
|
85
|
-
const ug = await groups.getUserGroups([uid]);
|
|
86
|
-
const list = (ug && ug[0]) ? ug[0] : [];
|
|
87
|
-
|
|
88
|
-
const seen = new Set();
|
|
89
|
-
for (const g of list) {
|
|
90
|
-
if (!g) continue;
|
|
91
|
-
if (g.slug) seen.add(String(g.slug));
|
|
92
|
-
if (g.name) seen.add(String(g.name));
|
|
93
|
-
if (g.groupName) seen.add(String(g.groupName));
|
|
94
|
-
if (g.displayName) seen.add(String(g.displayName));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return allowed.some((v) => seen.has(String(v)));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function forumBaseUrl() {
|
|
101
|
-
let base = (meta && meta.config && (meta.config.url || meta.config['url'])) ? (meta.config.url || meta.config['url']) : '';
|
|
102
|
-
if (!base) base = String(nconf.get('url') || '').trim();
|
|
103
|
-
base = String(base || '').trim().replace(/\/$/, '');
|
|
104
|
-
if (base && !/^https?:\/\//i.test(base)) {
|
|
105
|
-
base = `https://${base.replace(/^\/\//, '')}`;
|
|
106
|
-
}
|
|
107
|
-
return base;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function sendEmail(template, uid, subject, params) {
|
|
111
|
-
const toUid = Number.isInteger(uid) ? uid : parseInt(String(uid || '').trim(), 10);
|
|
112
|
-
if (!Number.isFinite(toUid) || toUid <= 0) return;
|
|
113
|
-
|
|
114
|
-
const payload = Object.assign({}, params || {}, subject ? { subject } : {});
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
if (typeof emailer.send !== 'function') return;
|
|
118
|
-
await emailer.send(template, toUid, payload);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
|
-
console.warn('[calendar-onekite] Failed to send email', { template, uid: toUid, err: String((err && err.message) || err) });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function normalizeUidList(uids) {
|
|
126
|
-
if (!uids) return [];
|
|
127
|
-
const arr = Array.isArray(uids) ? uids : (() => {
|
|
128
|
-
const s = String(uids || '').trim();
|
|
129
|
-
if (!s) return [];
|
|
130
|
-
try {
|
|
131
|
-
const parsed = JSON.parse(s);
|
|
132
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
133
|
-
} catch (e) {
|
|
134
|
-
return s.split(',').map((x) => String(x).trim()).filter(Boolean);
|
|
135
|
-
}
|
|
136
|
-
})();
|
|
137
|
-
|
|
138
|
-
const out = [];
|
|
139
|
-
for (const u of arr) {
|
|
140
|
-
const n = Number.isInteger(u) ? u : parseInt(String(u || '').trim(), 10);
|
|
141
|
-
if (Number.isFinite(n) && n > 0) out.push(String(n));
|
|
142
|
-
}
|
|
143
|
-
return Array.from(new Set(out));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function usernamesByUids(uids) {
|
|
147
|
-
const ids = normalizeUidList(uids);
|
|
148
|
-
if (!ids.length) return [];
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
if (typeof user.getUsersFields === 'function') {
|
|
152
|
-
const rows = await user.getUsersFields(ids, ['username']);
|
|
153
|
-
return (rows || []).map((r) => (r && r.username ? String(r.username) : '')).filter(Boolean);
|
|
154
|
-
}
|
|
155
|
-
} catch (e) {
|
|
156
|
-
// fall through
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const rows = await Promise.all(ids.map(async (uid) => {
|
|
160
|
-
try {
|
|
161
|
-
const r = await user.getUserFields(uid, ['username']);
|
|
162
|
-
return r && r.username ? String(r.username) : '';
|
|
163
|
-
} catch (e) {
|
|
164
|
-
return '';
|
|
165
|
-
}
|
|
166
|
-
}));
|
|
167
|
-
return rows.filter(Boolean);
|
|
168
|
-
}
|
|
3
|
+
// Thin re-export layer for backward compatibility.
|
|
4
|
+
// All helpers now live in ./shared.js.
|
|
5
|
+
const shared = require('./shared');
|
|
169
6
|
|
|
170
7
|
module.exports = {
|
|
171
|
-
normalizeAllowedGroups,
|
|
172
|
-
normalizeUids,
|
|
173
|
-
getMembersByGroupIdentifier,
|
|
174
|
-
userInAnyGroup,
|
|
175
|
-
forumBaseUrl,
|
|
176
|
-
sendEmail,
|
|
177
|
-
normalizeUidList,
|
|
178
|
-
usernamesByUids,
|
|
8
|
+
normalizeAllowedGroups: shared.normalizeAllowedGroups,
|
|
9
|
+
normalizeUids: shared.normalizeUids,
|
|
10
|
+
getMembersByGroupIdentifier: shared.getMembersByGroupIdentifier,
|
|
11
|
+
userInAnyGroup: shared.userInAnyGroup,
|
|
12
|
+
forumBaseUrl: shared.forumBaseUrl,
|
|
13
|
+
sendEmail: shared.sendEmail,
|
|
14
|
+
normalizeUidList: shared.normalizeUidList,
|
|
15
|
+
usernamesByUids: shared.usernamesByUids,
|
|
179
16
|
};
|
package/lib/scheduler.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const nconf = require.main.require('nconf');
|
|
4
4
|
const db = require.main.require('./src/database');
|
|
5
5
|
const dbLayer = require('./db');
|
|
6
6
|
const discord = require('./discord');
|
|
7
7
|
const realtime = require('./realtime');
|
|
8
|
-
const nconf = require.main.require('nconf');
|
|
9
|
-
const utils = require("./utils");
|
|
10
|
-
const nb = require("./nodebb-helpers");
|
|
11
8
|
const { getSettings } = require('./settings');
|
|
12
9
|
|
|
10
|
+
const shared = require('./shared');
|
|
11
|
+
const {
|
|
12
|
+
getSetting,
|
|
13
|
+
formatFR,
|
|
14
|
+
arrayifyNames,
|
|
15
|
+
forumBaseUrl,
|
|
16
|
+
normalizeAllowedGroups,
|
|
17
|
+
normalizeUids,
|
|
18
|
+
getMembersByGroupIdentifier,
|
|
19
|
+
sendEmail,
|
|
20
|
+
} = shared;
|
|
13
21
|
let timer = null;
|
|
14
22
|
|
|
15
23
|
// Some NodeBB database adapters don't expose setAdd/setRemove helpers.
|
|
@@ -53,8 +61,7 @@ async function addOnce(key, value) {
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
const { forumBaseUrl, normalizeAllowedGroups, normalizeUids, getMembersByGroupIdentifier, sendEmail } = nb;
|
|
64
|
+
// Helpers imported from shared.js above
|
|
58
65
|
async function getValidatorUids(settings) {
|
|
59
66
|
const out = new Set();
|
|
60
67
|
// Always include administrators
|
|
@@ -96,7 +103,7 @@ async function getNotifiedValidatorUids(settings) {
|
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
// Pending holds: short lock after a user creates a request (defaults to 5 minutes)
|
|
99
|
-
async function expirePending() {
|
|
106
|
+
async function expirePending(preIds, preReservations) {
|
|
100
107
|
const settings = await getSettings();
|
|
101
108
|
const holdMins = parseInt(getSetting(settings, 'pendingHoldMinutes', '5'), 10) || 5;
|
|
102
109
|
const validatorReminderMins = parseInt(getSetting(settings, 'validatorReminderMinutesPending', '0'), 10) || 0;
|
|
@@ -111,11 +118,10 @@ async function expirePending() {
|
|
|
111
118
|
|
|
112
119
|
const validatorUids = validatorReminderMins > 0 ? await getNotifiedValidatorUids(settings) : [];
|
|
113
120
|
|
|
114
|
-
const ids = await dbLayer.listAllReservationIds(5000);
|
|
121
|
+
const ids = preIds || await dbLayer.listAllReservationIds(5000);
|
|
115
122
|
if (!ids || !ids.length) return;
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
const reservations = await dbLayer.getReservations(ids);
|
|
124
|
+
const reservations = preReservations || await dbLayer.getReservations(ids);
|
|
119
125
|
|
|
120
126
|
for (let i = 0; i < ids.length; i += 1) {
|
|
121
127
|
const rid = ids[i];
|
|
@@ -197,7 +203,7 @@ async function expirePending() {
|
|
|
197
203
|
// - When a reservation is validated it becomes awaiting_payment
|
|
198
204
|
// - We send a reminder after `paymentHoldMinutes` (default 60)
|
|
199
205
|
// - We expire (and remove) after `2 * paymentHoldMinutes`
|
|
200
|
-
async function processAwaitingPayment() {
|
|
206
|
+
async function processAwaitingPayment(preIds, preReservations) {
|
|
201
207
|
const settings = await getSettings();
|
|
202
208
|
const holdMins = parseInt(
|
|
203
209
|
getSetting(settings, 'paymentHoldMinutes', getSetting(settings, 'holdMinutes', '60')),
|
|
@@ -205,7 +211,7 @@ async function processAwaitingPayment() {
|
|
|
205
211
|
) || 60;
|
|
206
212
|
const now = Date.now();
|
|
207
213
|
|
|
208
|
-
const ids = await dbLayer.listAllReservationIds(5000);
|
|
214
|
+
const ids = preIds || await dbLayer.listAllReservationIds(5000);
|
|
209
215
|
if (!ids || !ids.length) return;
|
|
210
216
|
|
|
211
217
|
const user = require.main.require('./src/user');
|
|
@@ -215,7 +221,7 @@ async function processAwaitingPayment() {
|
|
|
215
221
|
return base ? `${base}/admin/plugins/calendar-onekite` : '/admin/plugins/calendar-onekite';
|
|
216
222
|
})();
|
|
217
223
|
|
|
218
|
-
const reservations = await dbLayer.getReservations(ids);
|
|
224
|
+
const reservations = preReservations || await dbLayer.getReservations(ids);
|
|
219
225
|
|
|
220
226
|
for (let i = 0; i < ids.length; i += 1) {
|
|
221
227
|
const rid = ids[i];
|
|
@@ -339,15 +345,20 @@ function start() {
|
|
|
339
345
|
if (timer) return;
|
|
340
346
|
// eslint-disable-next-line no-console
|
|
341
347
|
console.info('[calendar-onekite] Scheduler enabled');
|
|
342
|
-
timer = setInterval(() => {
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
timer = setInterval(async () => {
|
|
349
|
+
try {
|
|
350
|
+
// Single DB fetch shared between both jobs (avoids duplicate listAll + getReservations)
|
|
351
|
+
const ids = await dbLayer.listAllReservationIds(5000);
|
|
352
|
+
const reservations = ids && ids.length ? await dbLayer.getReservations(ids) : [];
|
|
353
|
+
await expirePending(ids, reservations).catch((err) => {
|
|
354
|
+
console.warn('[calendar-onekite] Scheduler error in expirePending', err && err.message ? err.message : err);
|
|
355
|
+
});
|
|
356
|
+
await processAwaitingPayment(ids, reservations).catch((err) => {
|
|
357
|
+
console.warn('[calendar-onekite] Scheduler error in processAwaitingPayment', err && err.message ? err.message : err);
|
|
358
|
+
});
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.warn('[calendar-onekite] Scheduler tick error', err && err.message ? err.message : err);
|
|
361
|
+
}
|
|
351
362
|
}, 60 * 1000);
|
|
352
363
|
}
|
|
353
364
|
|