nodebb-plugin-calendar-onekite 11.1.32 → 11.1.33

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/admin.js CHANGED
@@ -1,249 +1,135 @@
1
+
1
2
  'use strict';
2
3
 
3
4
  const meta = require.main.require('./src/meta');
4
- const user = require.main.require('./src/user');
5
- const emailer = require.main.require('./src/emailer');
6
-
7
- const dbLayer = require('./db');
5
+ const User = require.main.require('./src/user');
8
6
  const helloasso = require('./helloasso');
7
+ const reservationDb = require('./db');
8
+ const { csvToList, toDateOnlyISO } = require('./utils');
9
9
 
10
- const ADMIN_PRIV = 'admin:settings';
11
-
12
- const admin = {};
13
-
14
- admin.renderAdmin = async function (req, res) {
15
- res.render('admin/plugins/calendar-onekite', {
16
- title: 'Calendar OneKite',
17
- });
18
- };
10
+ async function getSettings(req, res) {
11
+ const s = await meta.settings.get('calendar-onekite');
12
+ if (s && s.helloassoClientSecret) {
13
+ s.helloassoClientSecret = '***';
14
+ }
15
+ res.json({ ok:true, settings: s || {} });
16
+ }
17
+
18
+ async function saveSettings(req, res) {
19
+ const payload = req.body || {};
20
+ const normalizeCsv = (v) => String(v || '').split(',').map(s => s.trim()).filter(Boolean).join(',');
21
+
22
+ const current = await meta.settings.get('calendar-onekite') || {};
23
+ const toSave = {
24
+ helloassoEnv: payload.helloassoEnv || current.helloassoEnv || 'sandbox',
25
+ helloassoClientId: payload.helloassoClientId ?? current.helloassoClientId ?? '',
26
+ helloassoOrganizationSlug: payload.helloassoOrganizationSlug ?? current.helloassoOrganizationSlug ?? '',
27
+ helloassoFormType: payload.helloassoFormType || current.helloassoFormType || 'shop',
28
+ helloassoFormSlug: payload.helloassoFormSlug ?? current.helloassoFormSlug ?? '',
29
+
30
+ creatorGroups: normalizeCsv(payload.creatorGroups ?? current.creatorGroups),
31
+ validatorGroups: normalizeCsv(payload.validatorGroups ?? current.validatorGroups),
32
+ notifyGroups: normalizeCsv(payload.notifyGroups ?? current.notifyGroups),
33
+
34
+ holdMinutes: Math.max(1, parseInt(payload.holdMinutes ?? current.holdMinutes, 10) || 5),
35
+ };
19
36
 
20
- admin.getSettings = async function (req, res) {
21
- const settings = await meta.settings.get('calendar-onekite');
22
- res.json(settings || {});
23
- };
37
+ // secret only if provided and not masked
38
+ if (payload.helloassoClientSecret && payload.helloassoClientSecret !== '***') {
39
+ toSave.helloassoClientSecret = payload.helloassoClientSecret;
40
+ } else if (current.helloassoClientSecret) {
41
+ toSave.helloassoClientSecret = current.helloassoClientSecret;
42
+ }
24
43
 
25
- admin.saveSettings = async function (req, res) {
26
- await meta.settings.set('calendar-onekite', req.body || {});
27
- res.json({ ok: true });
28
- };
44
+ await meta.settings.set('calendar-onekite', toSave);
45
+ res.json({ ok:true });
46
+ }
29
47
 
30
- admin.listPending = async function (req, res) {
31
- const ids = await dbLayer.listAllReservationIds(5000);
48
+ async function listPending(req, res) {
49
+ // Fetch by broad range (last 2 years) and filter pending
50
+ const now = Date.now();
51
+ const ids = await reservationDb.listReservationIdsByStartRange(now - 86400000*365*2, now + 86400000*365*2);
32
52
  const pending = [];
33
53
  for (const rid of ids) {
34
- const r = await dbLayer.getReservation(rid);
35
- if (r && r.status === 'pending') {
36
- pending.push(r);
37
- }
38
- }
39
- pending.sort((a, b) => parseInt(a.start, 10) - parseInt(b.start, 10));
40
- res.json(pending);
41
- };
42
-
43
- admin.approveReservation = async function (req, res) {
44
- const rid = req.params.rid;
45
- const r = await dbLayer.getReservation(rid);
46
- if (!r) return res.status(404).json({ error: 'not-found' });
47
-
48
- r.status = 'approved';
49
-
50
- // Create HelloAsso payment link if configured
51
- const settings = await meta.settings.get('calendar-onekite');
52
- const env = settings.helloassoEnv || 'prod';
53
- const token = await helloasso.getAccessToken({
54
- env,
55
- clientId: settings.helloassoClientId,
56
- clientSecret: settings.helloassoClientSecret,
57
- });
58
-
59
- let paymentUrl = null;
60
- if (token) {
61
- const requester = await user.getUserData(r.uid);
62
- // Determine amount from HelloAsso items (pricing comes from HelloAsso)
63
- let totalAmount = 0;
64
- try {
65
- const items = await helloasso.listItems({
66
- env,
67
- token,
68
- organizationSlug: settings.helloassoOrganizationSlug,
69
- formType: settings.helloassoFormType,
70
- formSlug: settings.helloassoFormSlug,
71
- });
72
- const normalized = (items || []).map((it) => ({
73
- id: String(it.id || it.itemId || it.reference || it.name),
74
- price: it.price || it.amount || it.unitPrice || 0,
75
- })).filter(it => it.id);
76
- const match = normalized.find(it => it.id === String(r.itemId));
77
- totalAmount = match ? parseInt(match.price, 10) || 0 : 0;
78
- } catch (e) {
79
- totalAmount = 0;
80
- }
81
-
82
- if (!totalAmount) {
83
- return res.status(400).json({ error: 'item-price-not-found' });
84
- }
85
- paymentUrl = await helloasso.createCheckoutIntent({
86
- env,
87
- token,
88
- organizationSlug: settings.helloassoOrganizationSlug,
89
- formType: settings.helloassoFormType,
90
- formSlug: settings.helloassoFormSlug,
91
- totalAmount,
92
- payerEmail: requester && requester.email,
54
+ const r = await reservationDb.getReservation(rid);
55
+ if (!r) continue;
56
+ if (r.status !== 'pending') continue;
57
+ const user = await User.getUserFields(Number(r.uid), ['username']);
58
+ pending.push({
59
+ rid,
60
+ username: user?.username || '',
61
+ itemName: r.itemName,
62
+ startDate: toDateOnlyISO(Number(r.startTs)),
63
+ endDate: toDateOnlyISO(Number(r.endTs)),
64
+ status: r.status,
93
65
  });
94
66
  }
67
+ res.json({ ok:true, pending });
68
+ }
95
69
 
96
- if (paymentUrl) {
97
- r.paymentUrl = paymentUrl;
98
- }
70
+ async function approveReservation(req, res) {
71
+ const api = require('./api');
72
+ return api.approveReservation(req, res);
73
+ }
99
74
 
100
- await dbLayer.saveReservation(r);
75
+ async function refuseReservation(req, res) {
76
+ const api = require('./api');
77
+ return api.refuseReservation(req, res);
78
+ }
101
79
 
102
- // Email requester
103
- try {
104
- const requester = await user.getUserData(r.uid);
105
- if (requester && requester.email) {
106
- await emailer.send('calendar-onekite_approved', requester.email, {
107
- username: requester.username,
108
- itemName: r.itemName,
109
- start: new Date(parseInt(r.start, 10)).toISOString(),
110
- end: new Date(parseInt(r.end, 10)).toISOString(),
111
- paymentUrl: paymentUrl || '',
112
- });
113
- }
114
- } catch (e) {}
115
-
116
- res.json({ ok: true, paymentUrl: paymentUrl || null });
117
- };
118
-
119
- admin.refuseReservation = async function (req, res) {
120
- const rid = req.params.rid;
121
- const r = await dbLayer.getReservation(rid);
122
- if (!r) return res.status(404).json({ error: 'not-found' });
123
-
124
- r.status = 'refused';
125
- await dbLayer.saveReservation(r);
126
-
127
- try {
128
- const requester = await user.getUserData(r.uid);
129
- if (requester && requester.email) {
130
- await emailer.send('calendar-onekite_refused', requester.email, {
131
- username: requester.username,
132
- itemName: r.itemName,
133
- start: new Date(parseInt(r.start, 10)).toISOString(),
134
- end: new Date(parseInt(r.end, 10)).toISOString(),
135
- });
136
- }
137
- } catch (e) {}
138
-
139
- res.json({ ok: true });
140
- };
141
-
142
- admin.purgeByYear = async function (req, res) {
143
- const year = (req.body && req.body.year ? String(req.body.year) : '').trim();
80
+ async function purgeByYear(req, res) {
81
+ const year = String(req.body.year || '').trim();
144
82
  if (!/^\d{4}$/.test(year)) {
145
- return res.status(400).json({ error: 'invalid-year' });
83
+ return res.status(400).json({ ok:false, error:'invalid-year' });
146
84
  }
147
85
  const y = parseInt(year, 10);
148
- const startTs = new Date(Date.UTC(y, 0, 1)).getTime();
149
- const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
86
+ const start = Date.UTC(y,0,1,0,0,0,0);
87
+ const end = Date.UTC(y+1,0,1,0,0,0,0);
150
88
 
151
- const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 100000);
152
- let count = 0;
89
+ const ids = await reservationDb.listReservationIdsByStartRange(start, end, 100000);
153
90
  for (const rid of ids) {
154
- await dbLayer.removeReservation(rid);
155
- count++;
91
+ const r = await reservationDb.getReservation(rid);
92
+ if (!r) continue;
93
+ const st = Number(r.startTs);
94
+ if (st >= start && st < end) await reservationDb.deleteReservation(rid);
156
95
  }
157
- res.json({ ok: true, removed: count });
158
- };
159
-
160
- // Debug endpoint to validate HelloAsso connectivity and item loading
161
- admin.debugHelloAsso = async function (req, res) {
162
- const settings = await meta.settings.get('calendar-onekite');
163
- const env = (settings && settings.helloassoEnv) || 'prod';
164
-
165
- // Never expose secrets in debug output
166
- const safeSettings = {
167
- helloassoEnv: env,
168
- helloassoClientId: settings && settings.helloassoClientId ? String(settings.helloassoClientId) : '',
169
- helloassoClientSecret: settings && settings.helloassoClientSecret ? '***' : '',
170
- helloassoOrganizationSlug: settings && settings.helloassoOrganizationSlug ? String(settings.helloassoOrganizationSlug) : '',
171
- helloassoFormType: settings && settings.helloassoFormType ? String(settings.helloassoFormType) : '',
172
- helloassoFormSlug: settings && settings.helloassoFormSlug ? String(settings.helloassoFormSlug) : '',
173
- };
174
-
175
- const out = {
176
- ok: true,
177
- settings: safeSettings,
178
- token: { ok: false },
179
- // Catalog = what you actually want for a shop (available products/material)
180
- catalog: { ok: false, count: 0, sample: [], keys: [] },
181
- // Sold items = items present in orders (can be 0 if no sales yet)
182
- soldItems: { ok: false, count: 0, sample: [] },
183
- };
96
+ res.json({ ok:true, purged: ids.length });
97
+ }
184
98
 
99
+ async function debugHelloAsso(req, res) {
100
+ const s = await meta.settings.get('calendar-onekite') || {};
101
+ const safe = { ...s, helloassoClientSecret: s.helloassoClientSecret ? '***' : '' };
185
102
  try {
186
- const token = await helloasso.getAccessToken({
187
- env,
188
- clientId: settings.helloassoClientId,
189
- clientSecret: settings.helloassoClientSecret,
103
+ const tokenOk = !!(await helloasso.getToken(s));
104
+ const cat = await helloasso.getCatalog(s);
105
+ res.json({
106
+ ok:true,
107
+ settings: safe,
108
+ token: { ok: tokenOk },
109
+ items: { ok:true, count: (cat.items||[]).length, sample: (cat.items||[]).slice(0,10) }
190
110
  });
191
- if (!token) {
192
- out.token = { ok: false, error: 'token-null' };
193
- return res.json(out);
194
- }
195
- out.token = { ok: true };
196
-
197
- // Catalog items (via /public)
198
- try {
199
- const { publicForm, items } = await helloasso.listCatalogItems({
200
- env,
201
- token,
202
- organizationSlug: settings.helloassoOrganizationSlug,
203
- formType: settings.helloassoFormType,
204
- formSlug: settings.helloassoFormSlug,
205
- });
206
-
207
- const arr = Array.isArray(items) ? items : [];
208
- out.catalog.ok = true;
209
- out.catalog.count = arr.length;
210
- out.catalog.keys = publicForm && typeof publicForm === 'object' ? Object.keys(publicForm) : [];
211
- out.catalog.sample = arr.slice(0, 10).map((it) => ({
212
- id: it.id,
213
- name: it.name,
214
- price: it.price ?? null,
215
- }));
216
- } catch (e) {
217
- out.catalog = { ok: false, error: String(e && e.message ? e.message : e), count: 0, sample: [], keys: [] };
218
- }
219
-
220
- // Sold items
221
- try {
222
- const items = await helloasso.listItems({
223
- env,
224
- token,
225
- organizationSlug: settings.helloassoOrganizationSlug,
226
- formType: settings.helloassoFormType,
227
- formSlug: settings.helloassoFormSlug,
228
- });
229
- const arr = Array.isArray(items) ? items : [];
230
- out.soldItems.ok = true;
231
- out.soldItems.count = arr.length;
232
- out.soldItems.sample = arr.slice(0, 10).map((it) => ({
233
- id: it.id || it.itemId || it.reference || it.name,
234
- name: it.name || it.label || it.itemName,
235
- price: it.price || it.amount || it.unitPrice || null,
236
- }));
237
- } catch (e) {
238
- out.soldItems = { ok: false, error: String(e && e.message ? e.message : e), count: 0, sample: [] };
239
- }
240
-
241
- return res.json(out);
242
111
  } catch (e) {
243
- out.ok = false;
244
- out.token = { ok: false, error: String(e && e.message ? e.message : e) };
245
- return res.json(out);
112
+ res.json({
113
+ ok:true,
114
+ settings: safe,
115
+ token: { ok:false },
116
+ items: { ok:false, count: 0, sample: [] },
117
+ error: String(e && e.message || e),
118
+ });
246
119
  }
120
+ }
121
+
122
+ async function renderAdmin(req, res) {
123
+ res.render('admin/plugins/calendar-onekite', {});
124
+ }
125
+
126
+ module.exports = {
127
+ renderAdmin,
128
+ getSettings,
129
+ saveSettings,
130
+ listPending,
131
+ approveReservation,
132
+ refuseReservation,
133
+ purgeByYear,
134
+ debugHelloAsso,
247
135
  };
248
-
249
- module.exports = admin;