nodebb-plugin-calendar-onekite 11.1.11 → 11.1.12
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 +116 -109
- package/lib/api.js +126 -122
- package/lib/controllers.js +6 -12
- package/lib/db.js +20 -101
- package/lib/helloasso.js +111 -105
- package/lib/scheduler.js +32 -19
- package/library.js +49 -36
- package/package.json +6 -11
- package/plugin.json +3 -12
- package/public/admin.js +154 -177
- package/public/client.js +121 -138
- package/templates/admin/plugins/calendar-onekite.tpl +57 -83
package/lib/admin.js
CHANGED
|
@@ -1,130 +1,137 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
|
+
const user = require.main.require('./src/user');
|
|
3
5
|
const emailer = require.main.require('./src/emailer');
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const db = require('./db');
|
|
6
|
+
|
|
7
|
+
const dbLayer = require('./db');
|
|
7
8
|
const helloasso = require('./helloasso');
|
|
8
|
-
const { parseGroups, sendEmailToGroups } = require('./api');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const ADMIN_PRIV = 'admin:settings';
|
|
11
|
+
|
|
12
|
+
const admin = {};
|
|
13
|
+
|
|
14
|
+
admin.renderAdmin = async function (req, res) {
|
|
11
15
|
res.render('admin/plugins/calendar-onekite', {
|
|
12
16
|
title: 'Calendar OneKite',
|
|
13
17
|
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function getSettings(req, res) {
|
|
17
|
-
const s = await settings.get();
|
|
18
|
-
res.json({ settings: { ...defaults, ...s } });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function saveSettings(req, res) {
|
|
22
|
-
const body = req.body || {};
|
|
23
|
-
// Basic sanitization
|
|
24
|
-
const clean = {
|
|
25
|
-
allowedGroups: String(body.allowedGroups || '').trim(),
|
|
26
|
-
notifyGroups: String(body.notifyGroups || '').trim(),
|
|
27
|
-
pendingHoldMinutes: Math.max(1, parseInt(body.pendingHoldMinutes, 10) || 5),
|
|
28
|
-
cleanupIntervalSeconds: Math.max(10, parseInt(body.cleanupIntervalSeconds, 10) || 60),
|
|
29
|
-
|
|
30
|
-
helloassoEnv: (body.helloassoEnv === 'prod') ? 'prod' : 'sandbox',
|
|
31
|
-
helloassoClientId: String(body.helloassoClientId || '').trim(),
|
|
32
|
-
helloassoClientSecret: String(body.helloassoClientSecret || '').trim(),
|
|
33
|
-
helloassoOrganizationSlug: String(body.helloassoOrganizationSlug || '').trim(),
|
|
34
|
-
helloassoFormType: String(body.helloassoFormType || 'event').trim(),
|
|
35
|
-
helloassoFormSlug: String(body.helloassoFormSlug || '').trim(),
|
|
36
|
-
helloassoReturnUrl: String(body.helloassoReturnUrl || '').trim(),
|
|
37
|
-
|
|
38
|
-
showUsernamesOnCalendar: !!body.showUsernamesOnCalendar,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
await settings.set(clean);
|
|
42
|
-
res.json({ ok: true, settings: clean });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function listPending(req, res) {
|
|
46
|
-
const pending = await db.listPending(200);
|
|
47
|
-
res.json({ pending });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function approveReservation(req, res) {
|
|
51
|
-
const rid = req.params.rid;
|
|
52
|
-
const reservation = await db.getReservation(rid);
|
|
53
|
-
if (!reservation) return res.status(404).json({ error: 'Réservation introuvable' });
|
|
54
|
-
if (reservation.status !== 'pending') return res.status(400).json({ error: 'Statut incompatible' });
|
|
18
|
+
};
|
|
55
19
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
38
|
}
|
|
39
|
+
pending.sort((a, b) => parseInt(a.start, 10) - parseInt(b.start, 10));
|
|
40
|
+
res.json(pending);
|
|
41
|
+
};
|
|
67
42
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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,
|
|
73
57
|
});
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
let paymentUrl = null;
|
|
60
|
+
if (token) {
|
|
61
|
+
const requester = await user.getUserData(r.uid);
|
|
62
|
+
paymentUrl = await helloasso.createCheckoutIntent({
|
|
63
|
+
env,
|
|
64
|
+
token,
|
|
65
|
+
organizationSlug: settings.helloassoOrganizationSlug,
|
|
66
|
+
formType: settings.helloassoFormType,
|
|
67
|
+
formSlug: settings.helloassoFormSlug,
|
|
68
|
+
totalAmount: parseInt(settings.defaultAmount || '0', 10) || 0,
|
|
69
|
+
payerEmail: requester && requester.email,
|
|
82
70
|
});
|
|
83
|
-
}
|
|
71
|
+
}
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
if (paymentUrl) {
|
|
74
|
+
r.paymentUrl = paymentUrl;
|
|
75
|
+
}
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
const rid = req.params.rid;
|
|
90
|
-
const note = String((req.body && req.body.note) || '').trim();
|
|
77
|
+
await dbLayer.saveReservation(r);
|
|
91
78
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
// Email requester
|
|
80
|
+
try {
|
|
81
|
+
const requester = await user.getUserData(r.uid);
|
|
82
|
+
if (requester && requester.email) {
|
|
83
|
+
await emailer.send('calendar-onekite_approved', requester.email, {
|
|
84
|
+
username: requester.username,
|
|
85
|
+
itemName: r.itemName,
|
|
86
|
+
start: new Date(parseInt(r.start, 10)).toISOString(),
|
|
87
|
+
end: new Date(parseInt(r.end, 10)).toISOString(),
|
|
88
|
+
paymentUrl: paymentUrl || '',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {}
|
|
92
|
+
|
|
93
|
+
res.json({ ok: true, paymentUrl: paymentUrl || null });
|
|
94
|
+
};
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
admin.refuseReservation = async function (req, res) {
|
|
97
|
+
const rid = req.params.rid;
|
|
98
|
+
const r = await dbLayer.getReservation(rid);
|
|
99
|
+
if (!r) return res.status(404).json({ error: 'not-found' });
|
|
100
|
+
|
|
101
|
+
r.status = 'refused';
|
|
102
|
+
await dbLayer.saveReservation(r);
|
|
102
103
|
|
|
103
104
|
try {
|
|
104
|
-
await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const year = String((req.body && req.body.year) || '').trim();
|
|
117
|
-
if (!/^\d{4}$/.test(year)) return res.status(400).json({ error: 'Année invalide (YYYY)' });
|
|
118
|
-
const result = await db.purgeYear(year);
|
|
119
|
-
res.json({ ok: true, result });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
module.exports = {
|
|
123
|
-
renderAdmin,
|
|
124
|
-
getSettings,
|
|
125
|
-
saveSettings,
|
|
126
|
-
listPending,
|
|
127
|
-
approveReservation,
|
|
128
|
-
refuseReservation,
|
|
129
|
-
purgeByYear,
|
|
105
|
+
const requester = await user.getUserData(r.uid);
|
|
106
|
+
if (requester && requester.email) {
|
|
107
|
+
await emailer.send('calendar-onekite_refused', requester.email, {
|
|
108
|
+
username: requester.username,
|
|
109
|
+
itemName: r.itemName,
|
|
110
|
+
start: new Date(parseInt(r.start, 10)).toISOString(),
|
|
111
|
+
end: new Date(parseInt(r.end, 10)).toISOString(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
|
|
116
|
+
res.json({ ok: true });
|
|
130
117
|
};
|
|
118
|
+
|
|
119
|
+
admin.purgeByYear = async function (req, res) {
|
|
120
|
+
const year = (req.body && req.body.year ? String(req.body.year) : '').trim();
|
|
121
|
+
if (!/^\d{4}$/.test(year)) {
|
|
122
|
+
return res.status(400).json({ error: 'invalid-year' });
|
|
123
|
+
}
|
|
124
|
+
const y = parseInt(year, 10);
|
|
125
|
+
const startTs = new Date(Date.UTC(y, 0, 1)).getTime();
|
|
126
|
+
const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
|
|
127
|
+
|
|
128
|
+
const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 100000);
|
|
129
|
+
let count = 0;
|
|
130
|
+
for (const rid of ids) {
|
|
131
|
+
await dbLayer.removeReservation(rid);
|
|
132
|
+
count++;
|
|
133
|
+
}
|
|
134
|
+
res.json({ ok: true, removed: count });
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
module.exports = admin;
|
package/lib/api.js
CHANGED
|
@@ -1,154 +1,158 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const meta = require.main.require('./src/meta');
|
|
6
|
+
const user = require.main.require('./src/user');
|
|
3
7
|
const groups = require.main.require('./src/groups');
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const { settings } = require('./settings');
|
|
7
|
-
const db = require('./db');
|
|
8
|
+
|
|
9
|
+
const dbLayer = require('./db');
|
|
8
10
|
const helloasso = require('./helloasso');
|
|
9
11
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.filter(Boolean);
|
|
12
|
+
function toTs(v) {
|
|
13
|
+
if (!v) return NaN;
|
|
14
|
+
const d = new Date(v);
|
|
15
|
+
return d.getTime();
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
async function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const g of
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const ok = await groups.isMember(uid, g);
|
|
24
|
-
if (ok) return true;
|
|
25
|
-
} catch (e) { /* ignore */ }
|
|
26
|
-
}
|
|
27
|
-
// fallback
|
|
28
|
-
try {
|
|
29
|
-
const ug = await groups.getUserGroups([uid]);
|
|
30
|
-
const names = (ug && ug[uid] || []).map(x => x && (x.name || x.displayName)).filter(Boolean);
|
|
31
|
-
return names.some(n => groupNames.includes(n));
|
|
32
|
-
} catch (e) {
|
|
33
|
-
return false;
|
|
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;
|
|
34
24
|
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
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
|
+
};
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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));
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
res.json(out);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
api.getItems = async function (req, res) {
|
|
62
|
+
const settings = await meta.settings.get('calendar-onekite');
|
|
63
|
+
|
|
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([]);
|
|
58
73
|
}
|
|
59
|
-
}
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const list = await db.getReservationsBetween(startMs, endMs);
|
|
68
|
-
|
|
69
|
-
const events = list.map(r => {
|
|
70
|
-
const status = r.status || 'pending';
|
|
71
|
-
const icon = status === 'approved' ? '✅' : status === 'refused' ? '⛔' : status === 'expired' ? '⌛' : '⏳';
|
|
72
|
-
const title = `${icon} ${r.itemName || 'Matériel'}${r.username ? ` — ${r.username}` : ''}`;
|
|
73
|
-
return {
|
|
74
|
-
id: r.rid,
|
|
75
|
-
title,
|
|
76
|
-
start: new Date(Number(r.start)).toISOString(),
|
|
77
|
-
end: new Date(Number(r.end)).toISOString(),
|
|
78
|
-
extendedProps: {
|
|
79
|
-
status,
|
|
80
|
-
itemId: r.itemId,
|
|
81
|
-
itemName: r.itemName,
|
|
82
|
-
paymentUrl: r.paymentUrl || '',
|
|
83
|
-
},
|
|
84
|
-
};
|
|
75
|
+
const items = await helloasso.listItems({
|
|
76
|
+
env,
|
|
77
|
+
token,
|
|
78
|
+
organizationSlug: settings.helloassoOrganizationSlug,
|
|
79
|
+
formType: settings.helloassoFormType,
|
|
80
|
+
formSlug: settings.helloassoFormSlug,
|
|
85
81
|
});
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
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);
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const items = await helloasso.listFormItems();
|
|
93
|
-
res.json({ items });
|
|
94
|
-
} catch (e) {
|
|
95
|
-
res.status(500).json({ error: e.message });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
90
|
+
res.json(normalized);
|
|
91
|
+
};
|
|
98
92
|
|
|
99
|
-
async function
|
|
93
|
+
api.createReservation = async function (req, res) {
|
|
100
94
|
const uid = req.uid;
|
|
101
|
-
|
|
102
|
-
const allowedGroups = parseGroups(s.allowedGroups);
|
|
103
|
-
const notifyGroups = parseGroups(s.notifyGroups);
|
|
95
|
+
if (!uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
104
96
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
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' });
|
|
109
100
|
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
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();
|
|
113
105
|
|
|
114
|
-
if (!
|
|
115
|
-
return res.status(400).json({ error: '
|
|
106
|
+
if (!start || !end || !itemId) {
|
|
107
|
+
return res.status(400).json({ error: 'missing-fields' });
|
|
116
108
|
}
|
|
117
109
|
|
|
118
|
-
const
|
|
119
|
-
const
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const rid = crypto.randomUUID();
|
|
120
112
|
|
|
121
|
-
const
|
|
122
|
-
|
|
113
|
+
const resv = {
|
|
114
|
+
rid,
|
|
123
115
|
uid,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
itemName: body.itemName,
|
|
127
|
-
itemPrice: body.itemPrice,
|
|
116
|
+
itemId,
|
|
117
|
+
itemName: itemName || itemId,
|
|
128
118
|
start,
|
|
129
119
|
end,
|
|
130
120
|
status: 'pending',
|
|
131
|
-
|
|
132
|
-
}
|
|
121
|
+
createdAt: now,
|
|
122
|
+
};
|
|
133
123
|
|
|
134
|
-
//
|
|
135
|
-
await
|
|
136
|
-
subject: `[NodeBB] Nouvelle demande de réservation`,
|
|
137
|
-
reservation,
|
|
138
|
-
user,
|
|
139
|
-
url: `${req.protocol}://${req.get('host')}/admin/plugins/calendar-onekite`,
|
|
140
|
-
site_title: (req.app && req.app.locals && req.app.locals.config && req.app.locals.config.title) || 'NodeBB',
|
|
141
|
-
});
|
|
124
|
+
// Save
|
|
125
|
+
await dbLayer.saveReservation(resv);
|
|
142
126
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
154
|
|
|
146
|
-
|
|
147
|
-
getEvents,
|
|
148
|
-
getItems,
|
|
149
|
-
createReservation,
|
|
150
|
-
// exported for admin module reuse
|
|
151
|
-
parseGroups,
|
|
152
|
-
isUserInAnyGroup,
|
|
153
|
-
sendEmailToGroups,
|
|
155
|
+
res.json({ ok: true, rid });
|
|
154
156
|
};
|
|
157
|
+
|
|
158
|
+
module.exports = api;
|
package/lib/controllers.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const controllers = {};
|
|
4
4
|
|
|
5
|
-
async function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
title: 'Calendrier',
|
|
9
|
-
calendarOnekite: {
|
|
10
|
-
showUsernamesOnCalendar: !!s.showUsernamesOnCalendar,
|
|
11
|
-
},
|
|
5
|
+
controllers.renderCalendar = async function (req, res) {
|
|
6
|
+
res.render('calendar-onekite', {
|
|
7
|
+
title: 'Calendar',
|
|
12
8
|
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
module.exports = {
|
|
16
|
-
renderCalendar,
|
|
17
9
|
};
|
|
10
|
+
|
|
11
|
+
module.exports = controllers;
|