nodebb-plugin-onekite-calendar 2.0.63 → 2.0.65

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/api.js CHANGED
@@ -2,12 +2,9 @@
2
2
 
3
3
  const crypto = require('crypto');
4
4
 
5
- const meta = require.main.require('./src/meta');
6
5
  const nconf = require.main.require('nconf');
7
6
  const user = require.main.require('./src/user');
8
7
  const groups = require.main.require('./src/groups');
9
- const db = require.main.require('./src/database');
10
- const logger = require.main.require('./src/logger');
11
8
 
12
9
  const dbLayer = require('./db');
13
10
  const { getSettings } = require('./settings');
@@ -251,25 +248,26 @@ async function canCreateSpecial(uid, settings) {
251
248
  }
252
249
 
253
250
 
251
+ /**
252
+ * Determines if a user can join or leave special events as a participant.
253
+ *
254
+ * This permission is intentionally permissive: any authenticated user can participate
255
+ * in special events, regardless of group membership. This differs from creation/deletion
256
+ * permissions which remain restricted to specific groups.
257
+ *
258
+ * @param {number|string} uid - The user ID. Falsy values (0, null, undefined, '') indicate guest/unauthenticated users.
259
+ * @param {Object} settings - Plugin settings (unused but kept for API consistency with other permission functions).
260
+ * @returns {boolean} True if the user can join/leave special events (i.e., is authenticated).
261
+ *
262
+ * @since 1.0.0 Modified to allow all authenticated users (previously restricted to specific groups)
263
+ */
254
264
  async function canJoinSpecial(uid, settings) {
255
- if (!uid) return false;
256
- // Admins always allowed
257
- try {
258
- const isAdmin = await groups.isMember(uid, 'administrators');
259
- if (isAdmin) return true;
260
- } catch (e) {}
261
-
262
- // Special-event creators can join/leave
263
- if (await canCreateSpecial(uid, settings)) return true;
264
-
265
- // Location creator groups can also participate (even if they can't create events)
266
- const allowed = normalizeAllowedGroups(settings.creatorGroups || '');
267
- if (!allowed.length) return false;
268
- return userInAnyGroup(uid, allowed);
265
+ // Any authenticated user (non-zero uid) can participate in special events.
266
+ // The !! coercion converts truthy values to true, falsy to false.
267
+ return !!uid;
269
268
  }
270
269
 
271
270
 
272
-
273
271
  async function canDeleteSpecial(uid, settings) {
274
272
  if (!uid) return false;
275
273
  try {
@@ -838,11 +836,19 @@ api.joinSpecialEvent = async function (req, res) {
838
836
  ev.participants = JSON.stringify(list);
839
837
  await dbLayer.saveSpecialEvent(ev);
840
838
 
839
+ const names = await usernamesByUids(list);
840
+
841
841
  realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'participants', eid });
842
+ realtime.emitParticipantsUpdated({
843
+ type: 'special',
844
+ id: eid,
845
+ participants: list,
846
+ participantsUsernames: names,
847
+ });
842
848
  return res.json({
843
849
  ok: true,
844
850
  participants: list,
845
- participantsUsernames: await usernamesByUids(list),
851
+ participantsUsernames: names,
846
852
  });
847
853
  };
848
854
 
@@ -865,8 +871,16 @@ api.leaveSpecialEvent = async function (req, res) {
865
871
  ev.participants = JSON.stringify(next);
866
872
  await dbLayer.saveSpecialEvent(ev);
867
873
 
874
+ const names = await usernamesByUids(next);
875
+
868
876
  realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'participants', eid });
869
- return res.json({ ok: true, participants: next, participantsUsernames: await usernamesByUids(next) });
877
+ realtime.emitParticipantsUpdated({
878
+ type: 'special',
879
+ id: eid,
880
+ participants: next,
881
+ participantsUsernames: names,
882
+ });
883
+ return res.json({ ok: true, participants: next, participantsUsernames: names });
870
884
  };
871
885
 
872
886
  api.getCapabilities = async function (req, res) {
@@ -1032,8 +1046,16 @@ api.joinOuting = async function (req, res) {
1032
1046
  o.participants = JSON.stringify(list);
1033
1047
  await dbLayer.saveOuting(o);
1034
1048
 
1049
+ const names = await usernamesByUids(list);
1050
+
1035
1051
  realtime.emitCalendarUpdated({ kind: 'outing', action: 'participants', oid });
1036
- return res.json({ ok: true, participants: list, participantsUsernames: await usernamesByUids(list) });
1052
+ realtime.emitParticipantsUpdated({
1053
+ type: 'outing',
1054
+ id: oid,
1055
+ participants: list,
1056
+ participantsUsernames: names,
1057
+ });
1058
+ return res.json({ ok: true, participants: list, participantsUsernames: names });
1037
1059
  };
1038
1060
 
1039
1061
  api.leaveOuting = async function (req, res) {
@@ -1056,8 +1078,16 @@ api.leaveOuting = async function (req, res) {
1056
1078
  o.participants = JSON.stringify(next);
1057
1079
  await dbLayer.saveOuting(o);
1058
1080
 
1081
+ const names = await usernamesByUids(next);
1082
+
1059
1083
  realtime.emitCalendarUpdated({ kind: 'outing', action: 'participants', oid });
1060
- return res.json({ ok: true, participants: next, participantsUsernames: await usernamesByUids(next) });
1084
+ realtime.emitParticipantsUpdated({
1085
+ type: 'outing',
1086
+ id: oid,
1087
+ participants: next,
1088
+ participantsUsernames: names,
1089
+ });
1090
+ return res.json({ ok: true, participants: next, participantsUsernames: names });
1061
1091
  };
1062
1092
 
1063
1093
  api.createOuting = async function (req, res) {
package/lib/realtime.js CHANGED
@@ -27,6 +27,16 @@ function emitCalendarUpdated(payload) {
27
27
  } catch (e) {}
28
28
  }
29
29
 
30
+ function emitParticipantsUpdated(payload) {
31
+ try {
32
+ const server = getIO();
33
+ if (!server || !server.sockets || typeof server.sockets.emit !== 'function') return;
34
+
35
+ server.sockets.emit('event:calendar-onekite.participantsUpdated', payload || {});
36
+ } catch (e) {}
37
+ }
38
+
30
39
  module.exports = {
31
40
  emitCalendarUpdated,
41
+ emitParticipantsUpdated,
32
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.63",
3
+ "version": "2.0.65",
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
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "2.0.63"
42
+ "version": "2.0.65"
43
43
  }
package/public/client.js CHANGED
@@ -1705,7 +1705,7 @@ function toDatetimeLocalValue(date) {
1705
1705
  const leaveBtn = (canJoinHere && p.isParticipant)
1706
1706
  ? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-special" data-eid="${eidPlain}" title="Se retirer" aria-label="Se retirer">−</button>`
1707
1707
  : '';
1708
- const participantsHtml = `<div class="mb-2" id="onekite-participants-special"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1708
+ const participantsHtml = `<div class="mb-2" id="onekite-participants-special" data-eid="${eidPlain}"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1709
1709
  (participants.length
1710
1710
  ? participants.map((name) => {
1711
1711
  const u = String(name || '').trim();
@@ -1855,7 +1855,7 @@ function toDatetimeLocalValue(date) {
1855
1855
  const leaveBtn = (canJoinHere && p.isParticipant)
1856
1856
  ? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-outing" data-oid="${oidPlain}" title="Se retirer" aria-label="Se retirer">−</button>`
1857
1857
  : '';
1858
- const participantsHtml = `<div class="mb-2" id="onekite-participants-outing"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1858
+ const participantsHtml = `<div class="mb-2" id="onekite-participants-outing" data-oid="${oidPlain}"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1859
1859
  (participants.length
1860
1860
  ? participants.map((name) => {
1861
1861
  const u = String(name || '').trim();
@@ -2846,6 +2846,63 @@ try {
2846
2846
  scheduleRefetch(cal);
2847
2847
  } catch (e) {}
2848
2848
  });
2849
+
2850
+ // Live update of participants list in open modals (special events & outings)
2851
+ socket.on('event:calendar-onekite.participantsUpdated', function (payload) {
2852
+ try {
2853
+ const p = payload || {};
2854
+ const type = String(p.type || '').trim();
2855
+ const id = String(p.id || '').trim();
2856
+ if (!type || !id) return;
2857
+
2858
+ const names = Array.isArray(p.participantsUsernames) ? p.participantsUsernames : [];
2859
+ const uids = Array.isArray(p.participants) ? p.participants.map(String) : [];
2860
+
2861
+ const uidNow = String(
2862
+ (window.config && window.config.uid) ||
2863
+ (typeof app !== 'undefined' && app && app.user && app.user.uid) ||
2864
+ (typeof ajaxify !== 'undefined' && ajaxify && ajaxify.data && ajaxify.data.uid) ||
2865
+ ''
2866
+ );
2867
+ const isParticipant = uidNow && uids.includes(String(uidNow));
2868
+
2869
+ const sel = (type === 'special') ? '#onekite-participants-special' : (type === 'outing') ? '#onekite-participants-outing' : '';
2870
+ if (!sel) return;
2871
+ const candidates = document.querySelectorAll(sel);
2872
+ if (!candidates || !candidates.length) return;
2873
+ let box = null;
2874
+ for (const el of candidates) {
2875
+ if (!el) continue;
2876
+ const matchId = type === 'special' ? String(el.getAttribute('data-eid') || '') : String(el.getAttribute('data-oid') || '');
2877
+ if (matchId === id) { box = el; break; }
2878
+ }
2879
+ if (!box) return;
2880
+
2881
+ const hasControls = !!box.querySelector('.onekite-join-special, .onekite-leave-special, .onekite-join-outing, .onekite-leave-outing');
2882
+ let controlsHtml = '';
2883
+ if (hasControls && uidNow) {
2884
+ if (type === 'special') {
2885
+ controlsHtml = isParticipant
2886
+ ? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-special" data-eid="${escapeHtml(id)}" title="Se retirer" aria-label="Se retirer">−</button>`
2887
+ : `<button type="button" class="btn btn-sm btn-success ms-2 onekite-join-special" data-eid="${escapeHtml(id)}" title="S'ajouter" aria-label="S'ajouter">+</button>`;
2888
+ } else if (type === 'outing') {
2889
+ controlsHtml = isParticipant
2890
+ ? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-outing" data-oid="${escapeHtml(id)}" title="Se retirer" aria-label="Se retirer">−</button>`
2891
+ : `<button type="button" class="btn btn-sm btn-success ms-2 onekite-join-outing" data-oid="${escapeHtml(id)}" title="S'ajouter" aria-label="S'ajouter">+</button>`;
2892
+ }
2893
+ }
2894
+
2895
+ const links = names.length
2896
+ ? names.map((name) => {
2897
+ const u = String(name || '').trim();
2898
+ if (!u) return '';
2899
+ return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
2900
+ }).filter(Boolean).join('')
2901
+ : `<span class="text-muted">Aucun</span>`;
2902
+
2903
+ box.innerHTML = `<strong>Participants</strong>${controlsHtml}<br>${links}`;
2904
+ } catch (e) {}
2905
+ });
2849
2906
  }
2850
2907
  } catch (e) {}
2851
2908