nodebb-plugin-onekite-calendar 2.0.21 → 2.0.23

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,5 +1,14 @@
1
1
  # Changelog – calendar-onekite
2
2
 
3
+ ## 1.3.18
4
+ - Fix HelloAsso webhook : suppression d'une double déclaration `realtime` qui empêchait le plugin de se charger (SyntaxError).
5
+
6
+ ## 1.3.17
7
+ - Temps réel : les **créations** (locations / évènements), **changements de statut** (validation, refus, annulation, paiement reçu) et **expirations** se répercutent automatiquement sur le calendrier pour **tous les utilisateurs connectés**, sans rechargement de page.
8
+
9
+ ## 1.3.16
10
+ - Calendrier : le **jour actuel** n’est plus affiché comme indisponible (gris + panneau sens interdit). Seules les **dates passées** restent désactivées visuellement.
11
+
3
12
  ## 1.3.15
4
13
  - Permissions : les **modérateurs globaux / gestionnaires** (groupe NodeBB « Global Moderators », selon le slug) sont désormais considérés comme validateurs pour les actions de modération (dont l’**annulation**).
5
14
 
package/lib/admin.js CHANGED
@@ -73,6 +73,7 @@ function normalizeReturnUrl(meta) {
73
73
 
74
74
 
75
75
  const dbLayer = require('./db');
76
+ const realtime = require('./realtime');
76
77
  const helloasso = require('./helloasso');
77
78
 
78
79
  const ADMIN_PRIV = 'admin:settings';
@@ -207,6 +208,12 @@ admin.approveReservation = async function (req, res) {
207
208
 
208
209
  await dbLayer.saveReservation(r);
209
210
 
211
+ // Real-time refresh for all viewers
212
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'refused', rid: String(rid), status: r.status });
213
+
214
+ // Real-time refresh for all viewers
215
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'approved', rid: String(rid), status: r.status });
216
+
210
217
  // Email requester
211
218
  try {
212
219
  const requesterUid = parseInt(r.uid, 10);
@@ -249,6 +256,9 @@ admin.refuseReservation = async function (req, res) {
249
256
  r.refusedReason = String((req.body && (req.body.reason || req.body.refusedReason || req.body.refuseReason)) || '').trim();
250
257
  await dbLayer.saveReservation(r);
251
258
 
259
+ // Real-time refresh for all viewers
260
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'refused', rid: String(rid), status: r.status });
261
+
252
262
  try {
253
263
  const requesterUid = parseInt(r.uid, 10);
254
264
  const requester = await user.getUserFields(requesterUid, ['username']);
package/lib/api.js CHANGED
@@ -112,6 +112,7 @@ function normalizeAllowedGroups(raw) {
112
112
 
113
113
  const helloasso = require('./helloasso');
114
114
  const discord = require('./discord');
115
+ const realtime = require('./realtime');
115
116
 
116
117
  // Email helper (NodeBB 4.x): always send by uid.
117
118
  // Subject must be provided inside params.subject.
@@ -772,6 +773,8 @@ api.createSpecialEvent = async function (req, res) {
772
773
  createdAt: String(Date.now()),
773
774
  };
774
775
  await dbLayer.saveSpecialEvent(ev);
776
+ // Real-time refresh for all viewers
777
+ realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'created', eid: ev.eid });
775
778
  res.json({ ok: true, eid });
776
779
  };
777
780
 
@@ -783,6 +786,7 @@ api.deleteSpecialEvent = async function (req, res) {
783
786
  const eid = String(req.params.eid || '').replace(/^special:/, '').trim();
784
787
  if (!eid) return res.status(400).json({ error: 'bad-id' });
785
788
  await dbLayer.removeSpecialEvent(eid);
789
+ realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'deleted', eid });
786
790
  res.json({ ok: true });
787
791
  };
788
792
 
@@ -982,6 +986,9 @@ api.createReservation = async function (req, res) {
982
986
  // Save
983
987
  await dbLayer.saveReservation(resv);
984
988
 
989
+ // Real-time refresh for all viewers
990
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'created', rid: resv.rid, status: resv.status });
991
+
985
992
  // Audit
986
993
  await auditLog(isValidatorFree ? 'reservation_self_checked' : 'reservation_requested', uid, {
987
994
  targetType: 'reservation',
@@ -1184,6 +1191,9 @@ api.approveReservation = async function (req, res) {
1184
1191
 
1185
1192
  await dbLayer.saveReservation(r);
1186
1193
 
1194
+ // Real-time refresh for all viewers
1195
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'approved', rid: String(rid), status: r.status });
1196
+
1187
1197
  await auditLog('reservation_approved', uid, {
1188
1198
  targetType: 'reservation',
1189
1199
  targetId: String(rid),
@@ -1251,6 +1261,8 @@ api.refuseReservation = async function (req, res) {
1251
1261
  }
1252
1262
  await dbLayer.saveReservation(r);
1253
1263
 
1264
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'refused', rid: String(rid), status: r.status });
1265
+
1254
1266
  await auditLog('reservation_refused', uid, {
1255
1267
  targetType: 'reservation',
1256
1268
  targetId: String(rid),
@@ -1320,6 +1332,8 @@ api.cancelReservation = async function (req, res) {
1320
1332
 
1321
1333
  await dbLayer.saveReservation(r);
1322
1334
 
1335
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'cancelled', rid: String(rid), status: r.status });
1336
+
1323
1337
  await auditLog('reservation_cancelled', uid, {
1324
1338
  targetType: 'reservation',
1325
1339
  targetId: String(rid),
@@ -1395,6 +1409,8 @@ api.setMaintenance = async function (req, res) {
1395
1409
  targetType: 'item',
1396
1410
  targetId: itemId,
1397
1411
  });
1412
+ // Maintenance impacts availability; ask clients to refetch.
1413
+ realtime.emitCalendarUpdated({ kind: 'maintenance', action: enabled ? 'on' : 'off', itemId });
1398
1414
  return res.json({ ok: true, itemId, enabled });
1399
1415
  };
1400
1416
 
@@ -1437,6 +1453,7 @@ api.setMaintenanceAll = async function (req, res) {
1437
1453
  targetId: enabled ? 'all_on' : 'all_off',
1438
1454
  count: result && typeof result.count === 'number' ? result.count : (enabled ? catalogIds.length : 0),
1439
1455
  });
1456
+ realtime.emitCalendarUpdated({ kind: 'maintenance', action: enabled ? 'all_on' : 'all_off' });
1440
1457
  return res.json(Object.assign({ ok: true, enabled }, result || {}));
1441
1458
  };
1442
1459
 
@@ -5,19 +5,11 @@ const crypto = require('crypto');
5
5
  const db = require.main.require('./src/database');
6
6
  const meta = require.main.require('./src/meta');
7
7
  const user = require.main.require('./src/user');
8
- // Real-time updates
9
- let io;
10
- try {
11
- const socketIO = require.main.require('./src/socket.io');
12
- io = socketIO.server || socketIO;
13
- } catch (e) {
14
- io = null;
15
- }
16
-
17
8
 
18
9
  const dbLayer = require('./db');
19
10
  const helloasso = require('./helloasso');
20
11
  const discord = require('./discord');
12
+ const realtime = require('./realtime');
21
13
 
22
14
  async function auditLog(action, actorUid, payload) {
23
15
  try {
@@ -342,12 +334,8 @@ async function handler(req, res, next) {
342
334
  paymentId: r.paymentId || '',
343
335
  });
344
336
 
345
- // Real-time notify: refresh calendars for all viewers (owner + validators/admins)
346
- try {
347
- if (io && io.sockets && typeof io.sockets.emit === 'function') {
348
- io.sockets.emit('event:calendar-onekite.reservationUpdated', { rid: r.rid, status: r.status });
349
- }
350
- } catch (e) {}
337
+ // Real-time notify: refresh calendars for all viewers
338
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'paid', rid: String(r.rid), status: r.status });
351
339
 
352
340
  // Notify requester
353
341
  const requesterUid = parseInt(r.uid, 10);
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ // Broadcast real-time calendar updates to all connected clients.
4
+ // NodeBB exposes socket.io via ./src/socket.io (either the server itself or an
5
+ // object with a .server property depending on version).
6
+
7
+ let io;
8
+
9
+ function getIO() {
10
+ if (io) return io;
11
+ try {
12
+ const socketIO = require.main.require('./src/socket.io');
13
+ io = socketIO && socketIO.server ? socketIO.server : socketIO;
14
+ } catch (e) {
15
+ io = null;
16
+ }
17
+ return io;
18
+ }
19
+
20
+ function emitCalendarUpdated(payload) {
21
+ try {
22
+ const server = getIO();
23
+ if (!server || !server.sockets || typeof server.sockets.emit !== 'function') return;
24
+
25
+ // New event name (generic).
26
+ server.sockets.emit('event:calendar-onekite.calendarUpdated', payload || {});
27
+
28
+ // Backwards compatible event name (older clients only listened to this).
29
+ // Keep it emitted for *any* calendar mutation, not just reservation status.
30
+ server.sockets.emit('event:calendar-onekite.reservationUpdated', payload || {});
31
+ } catch (e) {}
32
+ }
33
+
34
+ module.exports = {
35
+ emitCalendarUpdated,
36
+ };
package/lib/scheduler.js CHANGED
@@ -4,6 +4,7 @@ const meta = require.main.require('./src/meta');
4
4
  const db = require.main.require('./src/database');
5
5
  const dbLayer = require('./db');
6
6
  const discord = require('./discord');
7
+ const realtime = require('./realtime');
7
8
  const nconf = require.main.require('nconf');
8
9
  const groups = require.main.require('./src/groups');
9
10
 
@@ -219,6 +220,9 @@ async function expirePending() {
219
220
  }
220
221
 
221
222
  await dbLayer.removeReservation(rid);
223
+
224
+ // Real-time refresh for all viewers
225
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'expired', rid: String(rid), status: 'expired' });
222
226
  }
223
227
  }
224
228
  }
@@ -380,6 +384,9 @@ async function processAwaitingPayment() {
380
384
  }
381
385
 
382
386
  await dbLayer.removeReservation(rid);
387
+
388
+ // Real-time refresh for all viewers
389
+ realtime.emitCalendarUpdated({ kind: 'reservation', action: 'expired', rid: String(rid), status: 'expired' });
383
390
  }
384
391
  }
385
392
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.21",
3
+ "version": "2.0.23",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -1,5 +1,8 @@
1
1
  # Changelog – calendar-onekite
2
2
 
3
+ ## 1.3.16
4
+ - Calendrier : le **jour actuel** n’est plus affiché comme indisponible (gris + panneau sens interdit). Seules les **dates passées** restent désactivées visuellement.
5
+
3
6
  ## 1.3.15
4
7
  - Permissions : les **modérateurs globaux / gestionnaires** (groupe NodeBB « Global Moderators », selon le slug) sont désormais considérés comme validateurs pour les actions de modération (dont l’**annulation**).
5
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "1.3.15",
3
+ "version": "1.3.16",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "1.3.13"
42
+ "version": "1.3.16"
43
43
  }
@@ -31,7 +31,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
31
31
  touch-action: manipulation;
32
32
  }
33
33
 
34
- /* Show days that cannot be selected for new reservations (today & past) */
34
+ /* Show days that cannot be selected for new reservations (past only) */
35
35
  .fc .onekite-day-disabled {
36
36
  background: rgba(0, 0, 0, 0.03);
37
37
  }
@@ -1460,14 +1460,14 @@ function toDatetimeLocalValue(date) {
1460
1460
  longPressDelay: 300,
1461
1461
  selectLongPressDelay: 300,
1462
1462
  dayCellDidMount: function (arg) {
1463
- // Visually disable today and past days for reservation creation rules,
1463
+ // Visually disable past days for reservation creation rules,
1464
1464
  // without breaking event clicks.
1465
1465
  try {
1466
1466
  const cellDate = arg && arg.date ? new Date(arg.date) : null;
1467
1467
  if (!cellDate) return;
1468
1468
  const ymd = toLocalYmd(cellDate);
1469
1469
  const today = toLocalYmd(new Date());
1470
- if (ymd <= today) {
1470
+ if (ymd < today) {
1471
1471
  arg.el.classList.add('onekite-day-disabled');
1472
1472
  }
1473
1473
  } catch (e) {}
package/plugin.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "2.0.21"
42
+ "version": "2.0.23"
43
43
  }
package/public/client.js CHANGED
@@ -31,7 +31,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
31
31
  touch-action: manipulation;
32
32
  }
33
33
 
34
- /* Show days that cannot be selected for new reservations (today & past) */
34
+ /* Show days that cannot be selected for new reservations (past only) */
35
35
  .fc .onekite-day-disabled {
36
36
  background: rgba(0, 0, 0, 0.03);
37
37
  }
@@ -1470,14 +1470,14 @@ function toDatetimeLocalValue(date) {
1470
1470
  longPressDelay: 300,
1471
1471
  selectLongPressDelay: 300,
1472
1472
  dayCellDidMount: function (arg) {
1473
- // Visually disable today and past days for reservation creation rules,
1473
+ // Visually disable past days for reservation creation rules,
1474
1474
  // without breaking event clicks.
1475
1475
  try {
1476
1476
  const cellDate = arg && arg.date ? new Date(arg.date) : null;
1477
1477
  if (!cellDate) return;
1478
1478
  const ymd = toLocalYmd(cellDate);
1479
1479
  const today = toLocalYmd(new Date());
1480
- if (ymd <= today) {
1480
+ if (ymd < today) {
1481
1481
  arg.el.classList.add('onekite-day-disabled');
1482
1482
  }
1483
1483
  } catch (e) {}
@@ -2369,10 +2369,17 @@ function autoInit(data) {
2369
2369
 
2370
2370
 
2371
2371
 
2372
- // Live refresh when a reservation changes (e.g., payment confirmed by webhook)
2372
+ // Live refresh when the calendar changes (reservation status, new reservation,
2373
+ // new special event, maintenance toggles, webhook payment, etc.)
2373
2374
  try {
2374
2375
  if (!window.__oneKiteSocketBound && typeof socket !== 'undefined' && socket && typeof socket.on === 'function') {
2375
2376
  window.__oneKiteSocketBound = true;
2377
+ socket.on('event:calendar-onekite.calendarUpdated', function () {
2378
+ try {
2379
+ const cal = window.oneKiteCalendar;
2380
+ scheduleRefetch(cal);
2381
+ } catch (e) {}
2382
+ });
2376
2383
  socket.on('event:calendar-onekite.reservationUpdated', function () {
2377
2384
  try {
2378
2385
  const cal = window.oneKiteCalendar;