nodebb-plugin-calendar-onekite 11.1.75 → 11.1.77

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 CHANGED
@@ -21,27 +21,39 @@ function formatFR(tsOrIso) {
21
21
  async function sendEmail(template, toEmail, subject, data) {
22
22
  if (!toEmail) return;
23
23
  try {
24
+ const dataWithSubject = Object.assign({}, data || {}, subject ? { subject, _subject: subject } : {});
25
+
26
+ // Same compatibility strategy as lib/api.js
27
+ const attempts = [];
24
28
  if (typeof emailer.sendToEmail === 'function') {
25
- if (emailer.sendToEmail.length >= 4) {
26
- await emailer.sendToEmail(template, toEmail, subject, data);
29
+ if (emailer.sendToEmail.length === 3) {
30
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject));
27
31
  } else {
28
- const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
29
- await emailer.sendToEmail(template, toEmail, dataWithSubject);
32
+ attempts.push(() => emailer.sendToEmail(template, toEmail, subject, dataWithSubject));
33
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject, subject));
34
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject));
30
35
  }
31
- return;
32
36
  }
33
37
  if (typeof emailer.send === 'function') {
34
- if (emailer.send.length >= 4) {
35
- await emailer.send(template, toEmail, subject, data);
36
- return;
37
- }
38
38
  if (emailer.send.length === 3) {
39
- const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
40
- await emailer.send(template, toEmail, dataWithSubject);
39
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject));
40
+ } else {
41
+ attempts.push(() => emailer.send(template, toEmail, subject, dataWithSubject));
42
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject, subject));
43
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject));
44
+ }
45
+ }
46
+
47
+ let lastErr = null;
48
+ for (const fn of attempts) {
49
+ try {
50
+ await fn();
41
51
  return;
52
+ } catch (e) {
53
+ lastErr = e;
42
54
  }
43
- await emailer.send(template, toEmail, subject, data);
44
55
  }
56
+ if (lastErr) throw lastErr;
45
57
  } catch (err) {
46
58
  console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err && err.message || err) });
47
59
  }
@@ -86,7 +98,12 @@ admin.getSettings = async function (req, res) {
86
98
  };
87
99
 
88
100
  admin.saveSettings = async function (req, res) {
89
- await meta.settings.set('calendar-onekite', req.body || {});
101
+ // Merge instead of overwrite so a missing/empty body (eg. due to body parser
102
+ // differences across NodeBB versions) does not wipe existing settings.
103
+ const current = (await meta.settings.get('calendar-onekite')) || {};
104
+ const incoming = req.body && typeof req.body === 'object' ? req.body : {};
105
+ const merged = Object.assign({}, current, incoming);
106
+ await meta.settings.set('calendar-onekite', merged);
90
107
  res.json({ ok: true });
91
108
  };
92
109
 
package/lib/api.js CHANGED
@@ -15,27 +15,43 @@ const helloasso = require('./helloasso');
15
15
  async function sendEmail(template, toEmail, subject, data) {
16
16
  if (!toEmail) return;
17
17
  const emailer = require.main.require('./src/emailer');
18
- const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
18
+ const dataWithSubject = Object.assign({}, data || {}, subject ? { subject, _subject: subject } : {});
19
19
  try {
20
- // NodeBB's Emailer API differs across versions; the most reliable approach is to pass `subject` inside `data`.
20
+ // NodeBB Emailer signatures vary across versions/plugins.
21
+ // IMPORTANT: some implementations accept (template, email, data) and will NOT throw if we pass a string as `data`,
22
+ // which can lead to "sent but empty subject". So we prefer the (template, email, data) call first when arity hints it.
23
+ const attempts = [];
24
+
21
25
  if (typeof emailer.sendToEmail === 'function') {
22
- // Prefer 3-args (template, email, data) when possible
23
- if (emailer.sendToEmail.length >= 3) {
24
- await emailer.sendToEmail(template, toEmail, dataWithSubject);
25
- return;
26
+ if (emailer.sendToEmail.length === 3) {
27
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject));
28
+ } else {
29
+ // Try the common forms that include subject
30
+ attempts.push(() => emailer.sendToEmail(template, toEmail, subject, dataWithSubject));
31
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject, subject));
32
+ attempts.push(() => emailer.sendToEmail(template, toEmail, dataWithSubject));
26
33
  }
27
- // Fallback
28
- await emailer.sendToEmail(template, toEmail, subject, data);
29
- return;
30
34
  }
31
35
  if (typeof emailer.send === 'function') {
32
- if (emailer.send.length >= 3) {
33
- await emailer.send(template, toEmail, dataWithSubject);
36
+ if (emailer.send.length === 3) {
37
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject));
38
+ } else {
39
+ attempts.push(() => emailer.send(template, toEmail, subject, dataWithSubject));
40
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject, subject));
41
+ attempts.push(() => emailer.send(template, toEmail, dataWithSubject));
42
+ }
43
+ }
44
+
45
+ let lastErr = null;
46
+ for (const fn of attempts) {
47
+ try {
48
+ await fn();
34
49
  return;
50
+ } catch (e) {
51
+ lastErr = e;
35
52
  }
36
- await emailer.send(template, toEmail, subject, data);
37
- return;
38
53
  }
54
+ if (lastErr) throw lastErr;
39
55
  } catch (err) {
40
56
  // eslint-disable-next-line no-console
41
57
  console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String((err && err.message) || err) });
package/library.js CHANGED
@@ -126,6 +126,13 @@ Plugin.renderMiniWidget = async function (hookData, callback) {
126
126
  Plugin.init = async function (params) {
127
127
  const { router, middleware } = params;
128
128
 
129
+ // Parse JSON bodies for our API routes.
130
+ // NodeBB core parses bodies for many built-in routes, but custom plugin
131
+ // routes are not guaranteed to have body parsing enabled across versions.
132
+ const jsonBody = bodyParser.json({
133
+ type: ['application/json', 'application/*+json'],
134
+ });
135
+
129
136
  // Build middleware arrays safely and always spread them into Express route methods.
130
137
  // Express will throw if any callback is undefined, so we filter strictly.
131
138
  const publicExpose = mw(middleware && middleware.exposeUid);
@@ -173,12 +180,12 @@ Plugin.init = async function (params) {
173
180
  });
174
181
 
175
182
  ['/api/v3/plugins/calendar-onekite/reservations', '/api/plugins/calendar-onekite/reservations'].forEach((p) => {
176
- router.post(p, ...publicAuth, api.createReservation);
183
+ router.post(p, jsonBody, ...publicAuth, api.createReservation);
177
184
  });
178
185
 
179
186
  // Special events (other colour) - created/deleted by configured groups
180
187
  ['/api/v3/plugins/calendar-onekite/special-events', '/api/plugins/calendar-onekite/special-events'].forEach((p) => {
181
- router.post(p, ...publicAuth, api.createSpecialEvent);
188
+ router.post(p, jsonBody, ...publicAuth, api.createSpecialEvent);
182
189
  });
183
190
  ['/api/v3/plugins/calendar-onekite/special-events/:eid', '/api/plugins/calendar-onekite/special-events/:eid'].forEach((p) => {
184
191
  router.delete(p, ...publicAuth, api.deleteSpecialEvent);
@@ -186,14 +193,14 @@ Plugin.init = async function (params) {
186
193
 
187
194
  // Validator actions from the calendar popup (requires login + validatorGroups)
188
195
  ['/api/v3/plugins/calendar-onekite/reservations/:rid/approve', '/api/plugins/calendar-onekite/reservations/:rid/approve'].forEach((p) => {
189
- router.put(p, ...publicAuth, api.approveReservation);
196
+ router.put(p, jsonBody, ...publicAuth, api.approveReservation);
190
197
  });
191
198
  ['/api/v3/plugins/calendar-onekite/reservations/:rid/refuse', '/api/plugins/calendar-onekite/reservations/:rid/refuse'].forEach((p) => {
192
- router.put(p, ...publicAuth, api.refuseReservation);
199
+ router.put(p, jsonBody, ...publicAuth, api.refuseReservation);
193
200
  });
194
201
  // Cancellation by requester (or staff): owner can cancel even if not in validator group
195
202
  ['/api/v3/plugins/calendar-onekite/reservations/:rid/cancel', '/api/plugins/calendar-onekite/reservations/:rid/cancel'].forEach((p) => {
196
- router.put(p, ...publicAuth, api.cancelReservation);
203
+ router.put(p, jsonBody, ...publicAuth, api.cancelReservation);
197
204
  });
198
205
 
199
206
 
@@ -202,21 +209,21 @@ Plugin.init = async function (params) {
202
209
 
203
210
  adminBases.forEach((base) => {
204
211
  router.get(`${base}/settings`, ...adminMws, admin.getSettings);
205
- router.put(`${base}/settings`, ...adminMws, admin.saveSettings);
212
+ router.put(`${base}/settings`, jsonBody, ...adminMws, admin.saveSettings);
206
213
 
207
214
  router.get(`${base}/pending`, ...adminMws, admin.listPending);
208
- router.put(`${base}/reservations/:rid/approve`, ...adminMws, admin.approveReservation);
209
- router.put(`${base}/reservations/:rid/refuse`, ...adminMws, admin.refuseReservation);
215
+ router.put(`${base}/reservations/:rid/approve`, jsonBody, ...adminMws, admin.approveReservation);
216
+ router.put(`${base}/reservations/:rid/refuse`, jsonBody, ...adminMws, admin.refuseReservation);
210
217
 
211
- router.post(`${base}/purge`, ...adminMws, admin.purgeByYear);
218
+ router.post(`${base}/purge`, jsonBody, ...adminMws, admin.purgeByYear);
212
219
  router.get(`${base}/debug`, ...adminMws, admin.debugHelloAsso);
213
220
  // Accounting / exports
214
221
  router.get(`${base}/accounting`, ...adminMws, admin.getAccounting);
215
222
  router.get(`${base}/accounting.csv`, ...adminMws, admin.exportAccountingCsv);
216
- router.post(`${base}/accounting/purge`, ...adminMws, admin.purgeAccounting);
223
+ router.post(`${base}/accounting/purge`, jsonBody, ...adminMws, admin.purgeAccounting);
217
224
 
218
225
  // Purge special events by year
219
- router.post(`${base}/special-events/purge`, ...adminMws, admin.purgeSpecialEventsByYear);
226
+ router.post(`${base}/special-events/purge`, jsonBody, ...adminMws, admin.purgeSpecialEventsByYear);
220
227
  });
221
228
 
222
229
  // HelloAsso callback endpoint (hardened)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.75",
3
+ "version": "11.1.77",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -28,7 +28,6 @@
28
28
  },
29
29
  "templates": "./templates",
30
30
  "modules": {
31
- "../admin/plugins/calendar-onekite.js": "./public/admin.js",
32
31
  "admin/plugins/calendar-onekite": "./public/admin.js"
33
32
  },
34
33
  "scripts": [
@@ -37,5 +36,5 @@
37
36
  "acpScripts": [
38
37
  "public/admin.js"
39
38
  ],
40
- "version": "1.0.49"
41
- }
39
+ "version": "1.0.54"
40
+ }