nodebb-plugin-onekite-calendar 1.0.4 → 1.0.5

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/CHANGELOG.md CHANGED
@@ -1,29 +1,4 @@
1
- # Changelog
2
-
3
- ## 1.1.0
4
- - Ultra perf clean : JS chargé uniquement sur les pages/plugin concernées (plus de scripts globaux)
5
- - Suppression legacy supplémentaire : clés DB renommées en onekite-calendar:* (aucune donnée conservée)
6
- - Nettoyages internes (variables globales JS, routes/paramètres uniformisés)
7
-
8
-
9
- ## 1.0.8
10
- - Suppression du legacy (namespace unique onekite-calendar, ACP/API/widgets)
11
- - Suppression des fallbacks inutiles
12
-
13
-
14
- ## 1.0.6
15
- - Refactor interne : centralisation de l’envoi d’emails et du logging (opt-in)
16
- - Nettoyage : suppression des alias legacy (API + ACP)
17
-
18
- ## 1.0.5
19
- - Widget : points colorés selon le type (mêmes couleurs que le calendrier)
20
- - Widget mobile : swipe horizontal pour naviguer semaine par semaine
21
-
22
- ## 1.0.4
23
- - Widget : affichage des évènements sous forme de points (sans texte)
24
- - Widget : survol/clic sur un point affiche le contenu (tooltip + popup)
25
- - Widget : renommage « Calendrier (2 semaines) » en « Calendrier »
26
- - Texte : remplacement de toutes les occurrences « OneKite » par « Onekite »
1
+ # Changelog – calendar-onekite
27
2
 
28
3
  ## 1.0.3
29
4
  - Suppression du texte d’archivage dans le toast de purge (plus de « 0 archivés »)
@@ -43,7 +18,7 @@
43
18
  - Correctifs divers de stabilité/performance (dont crash au démarrage)
44
19
 
45
20
  ## 1.0.0
46
- - Première version stable du plugin onekite-calendar
21
+ - Première version stable du plugin calendar-onekite
47
22
  - Gestion des réservations de matériel avec contrôle de disponibilité
48
23
  - Calendrier FullCalendar via CDN
49
24
  - Validation / refus des demandes depuis l’ACP
package/lib/admin.js CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  const meta = require.main.require('./src/meta');
4
4
  const user = require.main.require('./src/user');
5
+ const emailer = require.main.require('./src/emailer');
5
6
  const nconf = require.main.require('nconf');
6
7
 
7
- const { sendEmail } = require('./email');
8
- const log = require('./log');
9
-
10
8
  function forumBaseUrl() {
11
9
  const base = String(nconf.get('url') || '').trim().replace(/\/$/, '');
12
10
  return base;
@@ -20,7 +18,53 @@ function formatFR(tsOrIso) {
20
18
  return `${dd}/${mm}/${yyyy}`;
21
19
  }
22
20
 
23
- // Email helper is centralized in lib/email.js.
21
+ async function sendEmail(template, toEmail, subject, data) {
22
+ // Prefer sending by uid (NodeBB core expects uid in various places)
23
+ const uid = data && Number.isInteger(data.uid) ? data.uid : null;
24
+ if (!toEmail && !uid) return;
25
+
26
+ const settings = await meta.settings.get('calendar-onekite').catch(() => ({}));
27
+ const lang = (settings && settings.defaultLang) || (meta && meta.config && meta.config.defaultLang) || 'fr';
28
+ const params = Object.assign({}, data || {}, subject ? { subject } : {});
29
+
30
+ // If we have a uid, use the native uid-based sender first.
31
+ try {
32
+ if (uid && typeof emailer.send === 'function') {
33
+ // NodeBB: send(template, uid, params)
34
+ if (emailer.send.length >= 3) {
35
+ await emailer.send(template, uid, params);
36
+ } else {
37
+ await emailer.send(template, uid, params);
38
+ }
39
+ return;
40
+ }
41
+ } catch (err) {
42
+ console.warn('[calendar-onekite] Failed to send email', {
43
+ template,
44
+ toEmail,
45
+ err: err && err.message ? err.message : String(err),
46
+ });
47
+ }
48
+
49
+ try {
50
+ if (typeof emailer.sendToEmail === 'function') {
51
+ // NodeBB: sendToEmail(template, email, language, params)
52
+ if (emailer.sendToEmail.length >= 4) {
53
+ await emailer.sendToEmail(template, toEmail, lang, params);
54
+ } else {
55
+ // Older signature: sendToEmail(template, email, params)
56
+ await emailer.sendToEmail(template, toEmail, params);
57
+ }
58
+ return;
59
+ }
60
+ } catch (err) {
61
+ console.warn('[calendar-onekite] Failed to send email', {
62
+ template,
63
+ toEmail,
64
+ err: err && err.message ? err.message : String(err),
65
+ });
66
+ }
67
+ }
24
68
 
25
69
  function normalizeCallbackUrl(configured, meta) {
26
70
  const base = (meta && meta.config && (meta.config.url || meta.config['url'])) ? (meta.config.url || meta.config['url']) : '';
@@ -50,18 +94,18 @@ const ADMIN_PRIV = 'admin:settings';
50
94
  const admin = {};
51
95
 
52
96
  admin.renderAdmin = async function (req, res) {
53
- res.render('admin/plugins/onekite-calendar', {
54
- title: 'Calendar Onekite',
97
+ res.render('admin/plugins/calendar-onekite', {
98
+ title: 'Calendar OneKite',
55
99
  });
56
100
  };
57
101
 
58
102
  admin.getSettings = async function (req, res) {
59
- const settings = await meta.settings.get('onekite-calendar');
103
+ const settings = await meta.settings.get('calendar-onekite');
60
104
  res.json(settings || {});
61
105
  };
62
106
 
63
107
  admin.saveSettings = async function (req, res) {
64
- await meta.settings.set('onekite-calendar', req.body || {});
108
+ await meta.settings.set('calendar-onekite', req.body || {});
65
109
  res.json({ ok: true });
66
110
  };
67
111
 
@@ -97,13 +141,17 @@ admin.approveReservation = async function (req, res) {
97
141
  }
98
142
 
99
143
  // Create HelloAsso payment link if configured
100
- const settings = await meta.settings.get('onekite-calendar');
144
+ const settings = await meta.settings.get('calendar-onekite');
101
145
  const env = settings.helloassoEnv || 'prod';
102
146
  const token = await helloasso.getAccessToken({
103
147
  env,
104
148
  clientId: settings.helloassoClientId,
105
149
  clientSecret: settings.helloassoClientSecret,
106
150
  });
151
+ if (!token) {
152
+
153
+ }
154
+
107
155
  let paymentUrl = null;
108
156
  if (token) {
109
157
  const requester = await user.getUserFields(r.uid, ['username', 'email']);
@@ -111,7 +159,7 @@ admin.approveReservation = async function (req, res) {
111
159
  const totalAmount = Math.max(0, Math.round((Number(r.total) || 0) * 100));
112
160
  const base = forumBaseUrl();
113
161
  const returnUrl = base ? `${base}/calendar` : '';
114
- const webhookUrl = base ? `${base}/plugins/onekite-calendar/helloasso` : '';
162
+ const webhookUrl = base ? `${base}/plugins/calendar-onekite/helloasso` : '';
115
163
  const year = new Date(Number(r.start)).getFullYear();
116
164
  const intent = await helloasso.createCheckoutIntent({
117
165
  env,
@@ -125,7 +173,7 @@ admin.approveReservation = async function (req, res) {
125
173
  // User return/back/error URLs must be real pages; webhook uses the plugin endpoint.
126
174
  callbackUrl: returnUrl,
127
175
  webhookUrl: webhookUrl,
128
- itemName: 'Réservation matériel Onekite',
176
+ itemName: 'Réservation matériel OneKite',
129
177
  containsDonation: false,
130
178
  metadata: { reservationId: String(rid) },
131
179
  });
@@ -140,7 +188,7 @@ admin.approveReservation = async function (req, res) {
140
188
  if (paymentUrl) {
141
189
  r.paymentUrl = paymentUrl;
142
190
  } else {
143
- log.warn('HelloAsso payment link not created (approve ACP)', { rid });
191
+ console.warn('[calendar-onekite] HelloAsso payment link not created (approve ACP)', { rid });
144
192
  }
145
193
 
146
194
  await dbLayer.saveReservation(r);
@@ -154,7 +202,7 @@ admin.approveReservation = async function (req, res) {
154
202
  const mapUrl = (Number.isFinite(latNum) && Number.isFinite(lonNum))
155
203
  ? `https://www.openstreetmap.org/?mlat=${encodeURIComponent(String(latNum))}&mlon=${encodeURIComponent(String(lonNum))}#map=18/${encodeURIComponent(String(latNum))}/${encodeURIComponent(String(lonNum))}`
156
204
  : '';
157
- await sendEmail('onekite-calendar_approved', requester.email, 'Location matériel - Réservation validée', {
205
+ await sendEmail('calendar-onekite_approved', requester.email, 'Location matériel - Réservation validée', {
158
206
  uid: parseInt(r.uid, 10),
159
207
  username: requester.username,
160
208
  itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
@@ -189,7 +237,7 @@ admin.refuseReservation = async function (req, res) {
189
237
  try {
190
238
  const requester = await user.getUserFields(r.uid, ['username', 'email']);
191
239
  if (requester && requester.email) {
192
- await sendEmail('onekite-calendar_refused', requester.email, 'Location matériel - Réservation refusée', {
240
+ await sendEmail('calendar-onekite_refused', requester.email, 'Location matériel - Réservation refusée', {
193
241
  uid: parseInt(r.uid, 10),
194
242
  username: requester.username,
195
243
  itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
@@ -244,6 +292,98 @@ admin.purgeSpecialEventsByYear = async function (req, res) {
244
292
  return res.json({ ok: true, removed: count });
245
293
  };
246
294
 
295
+ // Debug endpoint to validate HelloAsso connectivity and item loading
296
+
297
+
298
+ admin.debugHelloAsso = async function (req, res) {
299
+ const settings = await meta.settings.get('calendar-onekite');
300
+ const env = (settings && settings.helloassoEnv) || 'prod';
301
+
302
+ // Never expose secrets in debug output
303
+ const safeSettings = {
304
+ helloassoEnv: env,
305
+ helloassoClientId: settings && settings.helloassoClientId ? String(settings.helloassoClientId) : '',
306
+ helloassoClientSecret: settings && settings.helloassoClientSecret ? '***' : '',
307
+ helloassoOrganizationSlug: settings && settings.helloassoOrganizationSlug ? String(settings.helloassoOrganizationSlug) : '',
308
+ helloassoFormType: settings && settings.helloassoFormType ? String(settings.helloassoFormType) : '',
309
+ helloassoFormSlug: settings && settings.helloassoFormSlug ? String(settings.helloassoFormSlug) : '',
310
+ };
311
+
312
+ const out = {
313
+ ok: true,
314
+ settings: safeSettings,
315
+ token: { ok: false },
316
+ // Catalog = what you actually want for a shop (available products/material)
317
+ catalog: { ok: false, count: 0, sample: [], keys: [] },
318
+ // Sold items = items present in orders (can be 0 if no sales yet)
319
+ soldItems: { ok: false, count: 0, sample: [] },
320
+ };
321
+
322
+ try {
323
+ const token = await helloasso.getAccessToken({
324
+ env,
325
+ clientId: settings.helloassoClientId,
326
+ clientSecret: settings.helloassoClientSecret,
327
+ });
328
+ if (!token) {
329
+ out.token = { ok: false, error: 'token-null' };
330
+ return res.json(out);
331
+ }
332
+ out.token = { ok: true };
333
+
334
+ // Catalog items (via /public)
335
+ try {
336
+ const y = new Date().getFullYear();
337
+ const { publicForm, items } = await helloasso.listCatalogItems({
338
+ env,
339
+ token,
340
+ organizationSlug: settings.helloassoOrganizationSlug,
341
+ formType: settings.helloassoFormType,
342
+ formSlug: `locations-materiel-${y}`,
343
+ });
344
+
345
+ const arr = Array.isArray(items) ? items : [];
346
+ out.catalog.ok = true;
347
+ out.catalog.count = arr.length;
348
+ out.catalog.keys = publicForm && typeof publicForm === 'object' ? Object.keys(publicForm) : [];
349
+ out.catalog.sample = arr.slice(0, 10).map((it) => ({
350
+ id: it.id,
351
+ name: it.name,
352
+ price: it.price ?? null,
353
+ }));
354
+ } catch (e) {
355
+ out.catalog = { ok: false, error: String(e && e.message ? e.message : e), count: 0, sample: [], keys: [] };
356
+ }
357
+
358
+ // Sold items
359
+ try {
360
+ const y2 = new Date().getFullYear();
361
+ const items = await helloasso.listItems({
362
+ env,
363
+ token,
364
+ organizationSlug: settings.helloassoOrganizationSlug,
365
+ formType: settings.helloassoFormType,
366
+ formSlug: `locations-materiel-${y2}`,
367
+ });
368
+ const arr = Array.isArray(items) ? items : [];
369
+ out.soldItems.ok = true;
370
+ out.soldItems.count = arr.length;
371
+ out.soldItems.sample = arr.slice(0, 10).map((it) => ({
372
+ id: it.id || it.itemId || it.reference || it.name,
373
+ name: it.name || it.label || it.itemName,
374
+ price: it.price || it.amount || it.unitPrice || null,
375
+ }));
376
+ } catch (e) {
377
+ out.soldItems = { ok: false, error: String(e && e.message ? e.message : e), count: 0, sample: [] };
378
+ }
379
+
380
+ return res.json(out);
381
+ } catch (e) {
382
+ out.ok = false;
383
+ out.token = { ok: false, error: String(e && e.message ? e.message : e) };
384
+ return res.json(out);
385
+ }
386
+ };
247
387
 
248
388
  // Accounting endpoint: aggregates paid reservations so you can contabilize what was rented.
249
389
  // Query params:
@@ -360,7 +500,7 @@ admin.exportAccountingCsv = async function (req, res) {
360
500
  }
361
501
  const csv = lines.join('\n');
362
502
  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
363
- res.setHeader('Content-Disposition', 'attachment; filename="onekite-calendar-accounting.csv"');
503
+ res.setHeader('Content-Disposition', 'attachment; filename="calendar-onekite-accounting.csv"');
364
504
  return res.send(csv);
365
505
  };
366
506