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 +9 -0
- package/lib/admin.js +10 -0
- package/lib/api.js +17 -0
- package/lib/helloassoWebhook.js +3 -15
- package/lib/realtime.js +36 -0
- package/lib/scheduler.js +7 -0
- package/package.json +1 -1
- package/pkg/package/CHANGELOG.md +3 -0
- package/pkg/package/package.json +1 -1
- package/pkg/package/plugin.json +1 -1
- package/pkg/package/public/client.js +3 -3
- package/plugin.json +1 -1
- package/public/client.js +11 -4
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
|
|
package/lib/helloassoWebhook.js
CHANGED
|
@@ -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
|
|
346
|
-
|
|
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);
|
package/lib/realtime.js
ADDED
|
@@ -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
package/pkg/package/CHANGELOG.md
CHANGED
|
@@ -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
|
|
package/pkg/package/package.json
CHANGED
package/pkg/package/plugin.json
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 (
|
|
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
|
|
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
|
|
1470
|
+
if (ymd < today) {
|
|
1471
1471
|
arg.el.classList.add('onekite-day-disabled');
|
|
1472
1472
|
}
|
|
1473
1473
|
} catch (e) {}
|
package/plugin.json
CHANGED
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 (
|
|
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
|
|
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
|
|
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
|
|
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;
|