nodebb-plugin-calendar-onekite 11.1.21 → 11.1.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/lib/admin.js CHANGED
@@ -1,222 +1,76 @@
1
1
  'use strict';
2
2
 
3
3
  const meta = require.main.require('./src/meta');
4
- const user = require.main.require('./src/user');
5
- const emailer = require.main.require('./src/emailer');
4
+ const dbi = require('./db');
5
+ const api = require('./api');
6
6
 
7
- const dbLayer = require('./db');
8
- const helloasso = require('./helloasso');
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
- };
19
-
20
- admin.getSettings = async function (req, res) {
21
- const settings = await meta.settings.get('calendar-onekite');
22
- res.json(settings || {});
23
- };
24
-
25
- admin.saveSettings = async function (req, res) {
26
- await meta.settings.set('calendar-onekite', req.body || {});
27
- res.json({ ok: true });
28
- };
29
-
30
- admin.listPending = async function (req, res) {
31
- const ids = await dbLayer.listAllReservationIds(5000);
32
- const pending = [];
33
- 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
7
+ async function getSettings(req, res) {
51
8
  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,
93
- });
94
- }
95
-
96
- if (paymentUrl) {
97
- r.paymentUrl = paymentUrl;
9
+ // mask secret
10
+ if (settings && settings.helloassoClientSecret) settings.helloassoClientSecret = '***';
11
+ res.json({ ok: true, settings: settings || {} });
12
+ }
13
+
14
+ async function saveSettings(req, res) {
15
+ const body = req.body || {};
16
+ // If secret is '***', do not overwrite.
17
+ const current = await meta.settings.get('calendar-onekite') || {};
18
+ const next = { ...current, ...body };
19
+ if (body.helloassoClientSecret === '***') {
20
+ next.helloassoClientSecret = current.helloassoClientSecret;
98
21
  }
99
-
100
- await dbLayer.saveReservation(r);
101
-
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
-
22
+ await meta.settings.set('calendar-onekite', next);
23
+ // invalidate catalog cache
139
24
  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();
144
- if (!/^\d{4}$/.test(year)) {
145
- return res.status(400).json({ error: 'invalid-year' });
25
+ }
26
+
27
+ async function getPending(req, res) {
28
+ const ids = await dbi.listAllIds(5000);
29
+ const rows = await dbi.getReservations(ids);
30
+ const pending = rows.filter(r => (r.status || 'pending') === 'pending')
31
+ .sort((a,b)=> (b.createdAt||0)-(a.createdAt||0))
32
+ .slice(0, 200);
33
+ res.json({ ok: true, pending });
34
+ }
35
+
36
+ async function purgeByYear(req, res) {
37
+ const year = String((req.body||{}).year || '').trim();
38
+ if (!/^\d{4}$/.test(year)) return res.status(400).json({ status: { code: 'bad-request', message: 'Année invalide (YYYY)' } });
39
+ const y = Number(year);
40
+ const startTs = Date.UTC(y,0,1,0,0,0);
41
+ const endTs = Date.UTC(y+1,0,1,0,0,0);
42
+
43
+ const ids = await dbi.listReservationIdsByStartRange(startTs, endTs, 100000);
44
+ for (const id of ids) {
45
+ await dbi.deleteReservation(id);
146
46
  }
147
- 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;
150
-
151
- const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 100000);
152
- let count = 0;
153
- for (const rid of ids) {
154
- await dbLayer.removeReservation(rid);
155
- count++;
156
- }
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
- items: { ok: false, count: 0, sample: [] },
180
- };
181
-
47
+ res.json({ ok: true, deleted: ids.length });
48
+ }
49
+
50
+ async function debugHelloAsso(req, res) {
51
+ const settings = await meta.settings.get('calendar-onekite') || {};
52
+ const masked = { ...settings };
53
+ if (masked.helloassoClientSecret) masked.helloassoClientSecret = '***';
54
+ const out = { ok: true, settings: masked };
182
55
  try {
183
- const token = await helloasso.getAccessToken({
184
- env,
185
- clientId: settings.helloassoClientId,
186
- clientSecret: settings.helloassoClientSecret,
187
- });
188
- if (!token) {
189
- out.token = { ok: false, error: 'token-null' };
190
- return res.json(out);
191
- }
56
+ // token + catalog
57
+ const items = await api._getCatalogItems();
192
58
  out.token = { ok: true };
193
-
194
- try {
195
- const items = await helloasso.listItems({
196
- env,
197
- token,
198
- organizationSlug: settings.helloassoOrganizationSlug,
199
- formType: settings.helloassoFormType,
200
- formSlug: settings.helloassoFormSlug,
201
- });
202
- const arr = Array.isArray(items) ? items : [];
203
- out.items.ok = true;
204
- out.items.count = arr.length;
205
- out.items.sample = arr.slice(0, 10).map((it) => ({
206
- id: it.id || it.itemId || it.reference || it.name,
207
- name: it.name || it.label || it.itemName,
208
- price: it.price || it.amount || it.unitPrice || null,
209
- }));
210
- } catch (e) {
211
- out.items = { ok: false, error: String(e && e.message ? e.message : e), count: 0, sample: [] };
212
- }
213
-
214
- return res.json(out);
59
+ out.catalog = { ok: true, count: items.length, sample: items.slice(0, 10) };
60
+ // also expose cache error if any
61
+ const cache = api._catalogCache();
62
+ if (cache && cache.err) out.catalog.err = cache.err;
215
63
  } catch (e) {
216
- out.ok = false;
217
- out.token = { ok: false, error: String(e && e.message ? e.message : e) };
218
- return res.json(out);
64
+ out.token = { ok: false, message: e.message };
65
+ out.catalog = { ok: false, count: 0, sample: [], message: e.message };
219
66
  }
67
+ res.json(out);
68
+ }
69
+
70
+ module.exports = {
71
+ getSettings,
72
+ saveSettings,
73
+ getPending,
74
+ purgeByYear,
75
+ debugHelloAsso,
220
76
  };
221
-
222
- module.exports = admin;
package/lib/api.js CHANGED
@@ -1,158 +1,152 @@
1
1
  'use strict';
2
2
 
3
- const crypto = require('crypto');
4
-
5
3
  const meta = require.main.require('./src/meta');
6
- const user = require.main.require('./src/user');
7
- const groups = require.main.require('./src/groups');
8
-
9
- const dbLayer = require('./db');
10
- const helloasso = require('./helloasso');
11
-
12
- function toTs(v) {
13
- if (!v) return NaN;
14
- const d = new Date(v);
15
- return d.getTime();
4
+ const dbi = require('./db');
5
+ const hello = require('./helloasso');
6
+
7
+ function parseDateParam(s) {
8
+ // FullCalendar sends ISO date/time. Accept 'YYYY-MM-DD' too.
9
+ const d = new Date(s);
10
+ if (!s || Number.isNaN(d.getTime())) return null;
11
+ return d;
16
12
  }
17
13
 
18
- async function canRequest(uid, settings) {
19
- const allowed = (settings.allowedGroups || '').split(',').map(s => s.trim()).filter(Boolean);
20
- if (!allowed.length) return true; // if empty, allow all logged in users
21
- for (const g of allowed) {
22
- const isMember = await groups.isMember(uid, g);
23
- if (isMember) return true;
24
- }
25
- return false;
14
+ function toYMD(ts) {
15
+ const d = new Date(Number(ts));
16
+ const y = d.getUTCFullYear();
17
+ const m = String(d.getUTCMonth()+1).padStart(2,'0');
18
+ const da = String(d.getUTCDate()).padStart(2,'0');
19
+ return `${y}-${m}-${da}`;
26
20
  }
27
21
 
28
- function eventFor(resv) {
29
- const status = resv.status;
30
- const icons = { pending: '⏳', approved: '✅', refused: '⛔' };
31
- return {
32
- id: resv.rid,
33
- title: `${icons[status] || ''} ${resv.itemName || resv.itemId}`.trim(),
34
- start: new Date(parseInt(resv.start, 10)).toISOString(),
35
- end: new Date(parseInt(resv.end, 10)).toISOString(),
36
- extendedProps: {
37
- status,
38
- uid: resv.uid,
39
- itemId: resv.itemId,
40
- },
41
- };
22
+ function daysBetweenInclusive(startYMD, endYMDExclusive) {
23
+ // FullCalendar selection end is exclusive. We compute number of days selected.
24
+ const s = new Date(startYMD + 'T00:00:00Z');
25
+ const e = new Date(endYMDExclusive + 'T00:00:00Z');
26
+ const diff = Math.max(0, Math.round((e - s) / 86400000));
27
+ return diff || 1;
42
28
  }
43
29
 
44
- const api = {};
45
-
46
- api.getEvents = async function (req, res) {
47
- const startTs = toTs(req.query.start) || 0;
48
- const endTs = toTs(req.query.end) || (Date.now() + 365 * 24 * 3600 * 1000);
49
-
50
- const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 2000);
51
- const out = [];
52
- for (const rid of ids) {
53
- const r = await dbLayer.getReservation(rid);
54
- if (!r) continue;
55
- if (r.status !== 'pending' && r.status !== 'approved') continue;
56
- out.push(eventFor(r));
57
- }
58
- res.json(out);
59
- };
30
+ let catalogCache = { at: 0, items: [], raw: null, ok: false, err: null };
60
31
 
61
- api.getItems = async function (req, res) {
32
+ async function getSettings() {
62
33
  const settings = await meta.settings.get('calendar-onekite');
34
+ return settings || {};
35
+ }
63
36
 
64
- const env = settings.helloassoEnv || 'prod';
65
- const token = await helloasso.getAccessToken({
66
- env,
67
- clientId: settings.helloassoClientId,
68
- clientSecret: settings.helloassoClientSecret,
69
- });
70
-
71
- if (!token) {
72
- return res.json([]);
37
+ async function getCatalogItems() {
38
+ const settings = await getSettings();
39
+ const now = Date.now();
40
+ if (catalogCache.ok && (now - catalogCache.at) < 5*60*1000) {
41
+ return catalogCache.items;
42
+ }
43
+ if (!settings.helloassoClientId || !settings.helloassoClientSecret || !settings.helloassoOrganizationSlug || !settings.helloassoFormSlug || !settings.helloassoFormType) {
44
+ catalogCache = { at: now, items: [], raw: null, ok: true, err: null };
45
+ return [];
73
46
  }
47
+ try {
48
+ const pub = await hello.getShopCatalog(settings);
49
+ const items = hello.extractItemsFromPublic(pub);
50
+ catalogCache = { at: now, items, raw: pub, ok: true, err: null };
51
+ return items;
52
+ } catch (err) {
53
+ catalogCache = { at: now, items: [], raw: null, ok: false, err: { message: err.message, statusCode: err.statusCode, body: err.body } };
54
+ return [];
55
+ }
56
+ }
74
57
 
75
- const items = await helloasso.listItems({
76
- env,
77
- token,
78
- organizationSlug: settings.helloassoOrganizationSlug,
79
- formType: settings.helloassoFormType,
80
- formSlug: settings.helloassoFormSlug,
81
- });
58
+ async function getCatalog(req, res) {
59
+ const items = await getCatalogItems();
60
+ res.json({ ok: true, count: items.length, items });
61
+ }
82
62
 
83
- // Normalize minimal fields for client
84
- const normalized = (items || []).map((it) => ({
85
- id: it.id || it.itemId || it.reference || it.name,
86
- name: it.name || it.label || `Item ${it.id || ''}`,
87
- price: it.price || it.amount || it.unitPrice || 0,
88
- })).filter(it => it.id && it.name);
63
+ async function getEvents(req, res) {
64
+ const start = parseDateParam(req.query.start);
65
+ const end = parseDateParam(req.query.end);
66
+ const startTs = start ? start.getTime() : Date.now() - 365*86400000;
67
+ const endTs = end ? end.getTime() : Date.now() + 365*86400000;
68
+
69
+ const ids = await dbi.listReservationIdsByStartRange(startTs, endTs, 5000);
70
+ const rows = await dbi.getReservations(ids);
71
+
72
+ const events = rows.map(r => {
73
+ const itemNames = (r.items || []).map(it => it.name).join(', ');
74
+ const title = itemNames || 'Réservation';
75
+ const status = r.status || 'pending';
76
+ const icon = status === 'approved' ? '✅' : status === 'refused' ? '⛔' : '⏳';
77
+ return {
78
+ id: r.id,
79
+ title: `${icon} ${title}`,
80
+ start: toYMD(r.startTs),
81
+ end: toYMD(r.endTs), // end exclusive works for allDay
82
+ allDay: true,
83
+ extendedProps: {
84
+ status,
85
+ requesterUid: r.uid,
86
+ items: r.items || [],
87
+ totalCents: r.totalCents || 0,
88
+ days: r.days || 1,
89
+ }
90
+ };
91
+ });
89
92
 
90
- res.json(normalized);
91
- };
93
+ res.json(events);
94
+ }
92
95
 
93
- api.createReservation = async function (req, res) {
96
+ async function createReservation(req, res) {
94
97
  const uid = req.uid;
95
- if (!uid) return res.status(401).json({ error: 'not-logged-in' });
96
-
97
- const settings = await meta.settings.get('calendar-onekite');
98
- const ok = await canRequest(uid, settings);
99
- if (!ok) return res.status(403).json({ error: 'not-allowed' });
100
-
101
- const start = parseInt(toTs(req.body.start), 10);
102
- const end = parseInt(toTs(req.body.end), 10);
103
- const itemId = (req.body.itemId || '').toString();
104
- const itemName = (req.body.itemName || '').toString();
105
-
106
- if (!start || !end || !itemId) {
107
- return res.status(400).json({ error: 'missing-fields' });
98
+ if (!uid) return res.status(403).json({ status: { code: 'forbidden', message: 'Not logged in' } });
99
+
100
+ const body = req.body || {};
101
+ const startYMD = body.start;
102
+ const endYMD = body.end;
103
+ const itemIds = Array.isArray(body.itemIds) ? body.itemIds.map(String) : [];
104
+ if (!startYMD || !endYMD) return res.status(400).json({ status: { code: 'bad-request', message: 'Missing dates' } });
105
+ if (!itemIds.length) return res.status(400).json({ status: { code: 'bad-request', message: 'Missing itemIds' } });
106
+
107
+ const days = daysBetweenInclusive(startYMD, endYMD);
108
+
109
+ const catalog = await getCatalogItems();
110
+ const chosen = [];
111
+ let sumPerDay = 0;
112
+ for (const id of itemIds) {
113
+ const it = catalog.find(x => x.id === id);
114
+ if (it) {
115
+ chosen.push(it);
116
+ sumPerDay += Number(it.priceCents || 0);
117
+ } else {
118
+ // keep unknown item with 0 price to avoid hard fail
119
+ chosen.push({ id, name: `Item ${id}`, priceCents: 0 });
120
+ }
108
121
  }
122
+ const totalCents = sumPerDay * days;
109
123
 
110
- const now = Date.now();
111
- const rid = crypto.randomUUID();
124
+ const id = await dbi.nextId();
125
+ const startTs = new Date(startYMD + 'T00:00:00Z').getTime();
126
+ const endTs = new Date(endYMD + 'T00:00:00Z').getTime();
112
127
 
113
128
  const resv = {
114
- rid,
115
- uid,
116
- itemId,
117
- itemName: itemName || itemId,
118
- start,
119
- end,
129
+ id,
130
+ uid: String(uid),
131
+ startTs,
132
+ endTs,
133
+ startYMD,
134
+ endYMD,
135
+ items: chosen,
136
+ days,
137
+ totalCents,
120
138
  status: 'pending',
121
- createdAt: now,
139
+ createdAt: Date.now(),
122
140
  };
141
+ await dbi.saveReservation(resv);
123
142
 
124
- // Save
125
- await dbLayer.saveReservation(resv);
126
-
127
- // Notify groups by email
128
- try {
129
- const notifyGroups = (settings.notifyGroups || '').split(',').map(s => s.trim()).filter(Boolean);
130
- if (notifyGroups.length) {
131
- const emailer = require.main.require('./src/emailer');
132
- const u = await user.getUserData(uid);
133
- for (const g of notifyGroups) {
134
- const members = await groups.getMembers(g, 0, -1);
135
- for (const m of members) {
136
- const memberUid = typeof m === 'object' && m ? (m.uid || m.userId) : m;
137
- const md = await user.getUserData(memberUid);
138
- if (md && md.email) {
139
- await emailer.send('calendar-onekite_pending', md.email, {
140
- username: md.username,
141
- requester: u.username,
142
- itemName: resv.itemName,
143
- start: new Date(start).toISOString(),
144
- end: new Date(end).toISOString(),
145
- rid,
146
- });
147
- }
148
- }
149
- }
150
- }
151
- } catch (e) {
152
- // ignore email errors
153
- }
143
+ res.json({ ok: true, reservation: resv });
144
+ }
154
145
 
155
- res.json({ ok: true, rid });
146
+ module.exports = {
147
+ getEvents,
148
+ getCatalog,
149
+ createReservation,
150
+ _getCatalogItems: getCatalogItems,
151
+ _catalogCache: () => catalogCache,
156
152
  };
157
-
158
- module.exports = api;
package/lib/db.js CHANGED
@@ -2,41 +2,55 @@
2
2
 
3
3
  const db = require.main.require('./src/database');
4
4
 
5
- const KEY_ZSET = 'calendar-onekite:reservations';
6
- const KEY_OBJ = (rid) => `calendar-onekite:reservation:${rid}`;
5
+ const KEYS = {
6
+ Z_BY_START: 'calendar-onekite:reservations:byStart',
7
+ HASH: (id) => `calendar-onekite:reservation:${id}`,
8
+ NEXT_ID: 'calendar-onekite:reservation:nextId',
9
+ };
7
10
 
8
- async function getReservation(rid) {
9
- return await db.getObject(KEY_OBJ(rid));
11
+ async function nextId() {
12
+ const id = await db.incrObjectField('calendar-onekite:meta', 'nextId');
13
+ return String(id);
10
14
  }
11
15
 
12
16
  async function saveReservation(resv) {
13
- await db.setObject(KEY_OBJ(resv.rid), resv);
14
- // score = start timestamp
15
- await db.sortedSetAdd(KEY_ZSET, resv.start, resv.rid);
17
+ await db.setObject(KEYS.HASH(resv.id), resv);
18
+ await db.sortedSetAdd(KEYS.Z_BY_START, resv.startTs, resv.id);
19
+ }
20
+
21
+ async function getReservation(id) {
22
+ return await db.getObject(KEYS.HASH(id));
16
23
  }
17
24
 
18
- async function removeReservation(rid) {
19
- await db.sortedSetRemove(KEY_ZSET, rid);
20
- await db.delete(KEY_OBJ(rid));
25
+ async function getReservations(ids) {
26
+ if (!ids.length) return [];
27
+ const keys = ids.map(KEYS.HASH);
28
+ const objects = await db.getObjects(keys);
29
+ return objects.filter(Boolean);
21
30
  }
22
31
 
23
- async function listReservationIdsByStartRange(startTs, endTs, limit = 1000) {
24
- // NodeBB db method name is getSortedSetRangeByScore(set, start, stop, min, max)
25
- // (start/stop are index offsets, min/max are score range)
26
- const start = 0;
27
- const stop = Math.max(0, (parseInt(limit, 10) || 1000) - 1);
28
- return await db.getSortedSetRangeByScore(KEY_ZSET, start, stop, startTs, endTs);
32
+ async function listReservationIdsByStartRange(startTs, endTs, limit = 2000) {
33
+ // NodeBB db API: getSortedSetRangeByScore(set, start, stop, min, max)
34
+ const ids = await db.getSortedSetRangeByScore(KEYS.Z_BY_START, 0, limit - 1, startTs, endTs);
35
+ return ids || [];
29
36
  }
30
37
 
31
- async function listAllReservationIds(limit = 5000) {
32
- return await db.getSortedSetRange(KEY_ZSET, 0, limit - 1);
38
+ async function deleteReservation(id) {
39
+ await db.delete(KEYS.HASH(id));
40
+ await db.sortedSetRemove(KEYS.Z_BY_START, id);
41
+ }
42
+
43
+ async function listAllIds(limit=100000) {
44
+ return await db.getSortedSetRange(KEYS.Z_BY_START, 0, limit-1);
33
45
  }
34
46
 
35
47
  module.exports = {
36
- KEY_ZSET,
37
- getReservation,
48
+ KEYS,
49
+ nextId,
38
50
  saveReservation,
39
- removeReservation,
51
+ getReservation,
52
+ getReservations,
40
53
  listReservationIdsByStartRange,
41
- listAllReservationIds,
54
+ deleteReservation,
55
+ listAllIds,
42
56
  };