nodebb-plugin-onekite-calendar 1.0.2 → 1.0.4
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 +31 -2
- package/lib/admin.js +15 -155
- package/lib/api.js +158 -103
- package/lib/constants.js +7 -0
- package/lib/controllers.js +2 -2
- package/lib/db.js +5 -5
- package/lib/discord.js +6 -5
- package/lib/email.js +26 -0
- package/lib/helloasso.js +4 -3
- package/lib/helloassoWebhook.js +8 -7
- package/lib/log.js +29 -0
- package/lib/scheduler.js +8 -11
- package/lib/widgets.js +128 -8
- package/library.js +39 -63
- package/package.json +1 -1
- package/plugin.json +5 -11
- package/public/admin.js +20 -92
- package/public/client.js +23 -23
- package/templates/admin/plugins/{calendar-onekite.tpl → onekite-calendar.tpl} +4 -23
- package/templates/{calendar-onekite.tpl → onekite-calendar.tpl} +11 -0
- /package/templates/emails/{calendar-onekite_approved.tpl → onekite-calendar_approved.tpl} +0 -0
- /package/templates/emails/{calendar-onekite_expired.tpl → onekite-calendar_expired.tpl} +0 -0
- /package/templates/emails/{calendar-onekite_paid.tpl → onekite-calendar_paid.tpl} +0 -0
- /package/templates/emails/{calendar-onekite_pending.tpl → onekite-calendar_pending.tpl} +0 -0
- /package/templates/emails/{calendar-onekite_refused.tpl → onekite-calendar_refused.tpl} +0 -0
- /package/templates/emails/{calendar-onekite_reminder.tpl → onekite-calendar_reminder.tpl} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
# Changelog
|
|
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 »
|
|
27
|
+
|
|
28
|
+
## 1.0.3
|
|
29
|
+
- Suppression du texte d’archivage dans le toast de purge (plus de « 0 archivés »)
|
|
30
|
+
- Renommage du plugin : nodebb-plugin-onekite-calendar
|
|
2
31
|
|
|
3
32
|
## 1.0.2
|
|
4
33
|
- Purge calendrier : suppression réelle des réservations (aucune logique d’archivage)
|
|
@@ -14,7 +43,7 @@
|
|
|
14
43
|
- Correctifs divers de stabilité/performance (dont crash au démarrage)
|
|
15
44
|
|
|
16
45
|
## 1.0.0
|
|
17
|
-
- Première version stable du plugin calendar
|
|
46
|
+
- Première version stable du plugin onekite-calendar
|
|
18
47
|
- Gestion des réservations de matériel avec contrôle de disponibilité
|
|
19
48
|
- Calendrier FullCalendar via CDN
|
|
20
49
|
- Validation / refus des demandes depuis l’ACP
|
package/lib/admin.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
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');
|
|
6
5
|
const nconf = require.main.require('nconf');
|
|
7
6
|
|
|
7
|
+
const { sendEmail } = require('./email');
|
|
8
|
+
const log = require('./log');
|
|
9
|
+
|
|
8
10
|
function forumBaseUrl() {
|
|
9
11
|
const base = String(nconf.get('url') || '').trim().replace(/\/$/, '');
|
|
10
12
|
return base;
|
|
@@ -18,53 +20,7 @@ function formatFR(tsOrIso) {
|
|
|
18
20
|
return `${dd}/${mm}/${yyyy}`;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
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
|
-
}
|
|
23
|
+
// Email helper is centralized in lib/email.js.
|
|
68
24
|
|
|
69
25
|
function normalizeCallbackUrl(configured, meta) {
|
|
70
26
|
const base = (meta && meta.config && (meta.config.url || meta.config['url'])) ? (meta.config.url || meta.config['url']) : '';
|
|
@@ -94,18 +50,18 @@ const ADMIN_PRIV = 'admin:settings';
|
|
|
94
50
|
const admin = {};
|
|
95
51
|
|
|
96
52
|
admin.renderAdmin = async function (req, res) {
|
|
97
|
-
res.render('admin/plugins/calendar
|
|
98
|
-
title: 'Calendar
|
|
53
|
+
res.render('admin/plugins/onekite-calendar', {
|
|
54
|
+
title: 'Calendar Onekite',
|
|
99
55
|
});
|
|
100
56
|
};
|
|
101
57
|
|
|
102
58
|
admin.getSettings = async function (req, res) {
|
|
103
|
-
const settings = await meta.settings.get('calendar
|
|
59
|
+
const settings = await meta.settings.get('onekite-calendar');
|
|
104
60
|
res.json(settings || {});
|
|
105
61
|
};
|
|
106
62
|
|
|
107
63
|
admin.saveSettings = async function (req, res) {
|
|
108
|
-
await meta.settings.set('calendar
|
|
64
|
+
await meta.settings.set('onekite-calendar', req.body || {});
|
|
109
65
|
res.json({ ok: true });
|
|
110
66
|
};
|
|
111
67
|
|
|
@@ -141,17 +97,13 @@ admin.approveReservation = async function (req, res) {
|
|
|
141
97
|
}
|
|
142
98
|
|
|
143
99
|
// Create HelloAsso payment link if configured
|
|
144
|
-
const settings = await meta.settings.get('calendar
|
|
100
|
+
const settings = await meta.settings.get('onekite-calendar');
|
|
145
101
|
const env = settings.helloassoEnv || 'prod';
|
|
146
102
|
const token = await helloasso.getAccessToken({
|
|
147
103
|
env,
|
|
148
104
|
clientId: settings.helloassoClientId,
|
|
149
105
|
clientSecret: settings.helloassoClientSecret,
|
|
150
106
|
});
|
|
151
|
-
if (!token) {
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
107
|
let paymentUrl = null;
|
|
156
108
|
if (token) {
|
|
157
109
|
const requester = await user.getUserFields(r.uid, ['username', 'email']);
|
|
@@ -159,7 +111,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
159
111
|
const totalAmount = Math.max(0, Math.round((Number(r.total) || 0) * 100));
|
|
160
112
|
const base = forumBaseUrl();
|
|
161
113
|
const returnUrl = base ? `${base}/calendar` : '';
|
|
162
|
-
const webhookUrl = base ? `${base}/plugins/calendar
|
|
114
|
+
const webhookUrl = base ? `${base}/plugins/onekite-calendar/helloasso` : '';
|
|
163
115
|
const year = new Date(Number(r.start)).getFullYear();
|
|
164
116
|
const intent = await helloasso.createCheckoutIntent({
|
|
165
117
|
env,
|
|
@@ -173,7 +125,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
173
125
|
// User return/back/error URLs must be real pages; webhook uses the plugin endpoint.
|
|
174
126
|
callbackUrl: returnUrl,
|
|
175
127
|
webhookUrl: webhookUrl,
|
|
176
|
-
itemName: 'Réservation matériel
|
|
128
|
+
itemName: 'Réservation matériel Onekite',
|
|
177
129
|
containsDonation: false,
|
|
178
130
|
metadata: { reservationId: String(rid) },
|
|
179
131
|
});
|
|
@@ -188,7 +140,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
188
140
|
if (paymentUrl) {
|
|
189
141
|
r.paymentUrl = paymentUrl;
|
|
190
142
|
} else {
|
|
191
|
-
|
|
143
|
+
log.warn('HelloAsso payment link not created (approve ACP)', { rid });
|
|
192
144
|
}
|
|
193
145
|
|
|
194
146
|
await dbLayer.saveReservation(r);
|
|
@@ -202,7 +154,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
202
154
|
const mapUrl = (Number.isFinite(latNum) && Number.isFinite(lonNum))
|
|
203
155
|
? `https://www.openstreetmap.org/?mlat=${encodeURIComponent(String(latNum))}&mlon=${encodeURIComponent(String(lonNum))}#map=18/${encodeURIComponent(String(latNum))}/${encodeURIComponent(String(lonNum))}`
|
|
204
156
|
: '';
|
|
205
|
-
await sendEmail('
|
|
157
|
+
await sendEmail('onekite-calendar_approved', requester.email, 'Location matériel - Réservation validée', {
|
|
206
158
|
uid: parseInt(r.uid, 10),
|
|
207
159
|
username: requester.username,
|
|
208
160
|
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
@@ -237,7 +189,7 @@ admin.refuseReservation = async function (req, res) {
|
|
|
237
189
|
try {
|
|
238
190
|
const requester = await user.getUserFields(r.uid, ['username', 'email']);
|
|
239
191
|
if (requester && requester.email) {
|
|
240
|
-
await sendEmail('
|
|
192
|
+
await sendEmail('onekite-calendar_refused', requester.email, 'Location matériel - Réservation refusée', {
|
|
241
193
|
uid: parseInt(r.uid, 10),
|
|
242
194
|
username: requester.username,
|
|
243
195
|
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
@@ -292,98 +244,6 @@ admin.purgeSpecialEventsByYear = async function (req, res) {
|
|
|
292
244
|
return res.json({ ok: true, removed: count });
|
|
293
245
|
};
|
|
294
246
|
|
|
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
|
-
};
|
|
387
247
|
|
|
388
248
|
// Accounting endpoint: aggregates paid reservations so you can contabilize what was rented.
|
|
389
249
|
// Query params:
|
|
@@ -500,7 +360,7 @@ admin.exportAccountingCsv = async function (req, res) {
|
|
|
500
360
|
}
|
|
501
361
|
const csv = lines.join('\n');
|
|
502
362
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
503
|
-
res.setHeader('Content-Disposition', 'attachment; filename="calendar-
|
|
363
|
+
res.setHeader('Content-Disposition', 'attachment; filename="onekite-calendar-accounting.csv"');
|
|
504
364
|
return res.send(csv);
|
|
505
365
|
};
|
|
506
366
|
|