nodebb-plugin-equipment-calendar 0.8.2 → 0.8.6

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.
Files changed (3) hide show
  1. package/library.js +60 -19
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/library.js CHANGED
@@ -207,38 +207,75 @@ function isCheckoutPaid(checkout) {
207
207
  return false;
208
208
  }
209
209
 
210
+
210
211
  async function fetchHelloAssoItems(settings) {
211
212
  const org = String(settings.ha_organizationSlug || '').trim();
212
213
  const formType = String(settings.ha_itemsFormType || '').trim();
213
214
  const formSlug = String(settings.ha_itemsFormSlug || '').trim();
214
215
  if (!org || !formType || !formSlug) return [];
215
216
 
217
+ const token = await getHelloAssoAccessToken(settings);
218
+ const base = (String(settings.ha_apiBaseUrl || '').trim() || 'https://api.helloasso.com').replace(/\/$/, '');
216
219
  const cacheKey = `equipmentCalendar:ha:items:${org}:${formType}:${formSlug}`;
217
- const cache = await db.getObject(cacheKey);
220
+
221
+ // Cache 10 minutes
222
+ const cached = await db.getObject(cacheKey);
218
223
  const now = Date.now();
219
- if (cache && cache.payload && cache.expiresAt && now < parseInt(cache.expiresAt, 10)) {
220
- try {
221
- return JSON.parse(cache.payload);
222
- } catch (e) {}
224
+ if (cached && cached.itemsJson && cached.expMs && now < parseInt(cached.expMs, 10)) {
225
+ try { return JSON.parse(cached.itemsJson); } catch (e) {}
223
226
  }
224
227
 
225
- const token = await getHelloAssoAccessToken(settings);
226
- const url = `https://api.helloasso.com/v5/organizations/${encodeURIComponent(org)}/forms/${encodeURIComponent(formType)}/${encodeURIComponent(formSlug)}/items`;
227
- const resp = await fetchFn(url, { headers: { authorization: `Bearer ${token}` } });
228
+ // IMPORTANT:
229
+ // - /forms/.../items = "articles vendus" (liés aux commandes) => peut renvoyer 0 si aucune commande
230
+ // - /forms/.../public = données publiques détaillées => contient la structure (tiers / products) du formulaire
231
+ const publicUrl = `${base}/v5/organizations/${encodeURIComponent(org)}/forms/${encodeURIComponent(formType)}/${encodeURIComponent(formSlug)}/public`;
232
+ const resp = await fetchFn(publicUrl, { headers: { authorization: `Bearer ${token}`, accept: 'application/json' } });
228
233
  if (!resp.ok) {
229
234
  const t = await resp.text();
230
- throw new Error(`HelloAsso items error: ${resp.status} ${t}`);
235
+ throw new Error(`HelloAsso public form error: ${resp.status} ${t}`);
236
+ }
237
+ const data = await resp.json();
238
+
239
+ // Extract catalog items:
240
+ // structure differs by formType; common pattern: data.tiers[] or data.tiers[].products[] / items[]
241
+ const out = [];
242
+ const tiers = (data && (data.tiers || data.tiersList || data.prices || data.priceCategories)) || [];
243
+ const tierArr = Array.isArray(tiers) ? tiers : [];
244
+
245
+ function pushItem(it, tierName) {
246
+ if (!it) return;
247
+ const id = String(it.id || it.itemId || it.reference || it.slug || it.code || it.name || '').trim();
248
+ const name = String(it.name || it.label || it.title || '').trim() || (tierName ? String(tierName) : id);
249
+ if (!id || !name) return;
250
+ const amount = it.amount || it.price || it.unitPrice || it.totalAmount || it.initialAmount;
251
+ const priceCents = (typeof amount === 'number' ? amount : parseInt(amount, 10)) || 0;
252
+ out.push({ id, name, priceCents: String(priceCents), location: '' });
231
253
  }
232
- const json = await resp.json();
233
- // API responses are usually { data: [...] } but keep it flexible
234
- const list = Array.isArray(json) ? json : (Array.isArray(json.data) ? json.data : []);
235
254
 
236
- // cache 15 minutes
237
- try {
238
- await db.setObject(cacheKey, { payload: JSON.stringify(list), expiresAt: String(now + 15 * 60 * 1000) });
239
- } catch (e) {}
255
+ // Try a few known layouts
256
+ for (const t of tierArr) {
257
+ const tierName = t && (t.name || t.label || t.title);
258
+ const products = (t && (t.items || t.products || t.prices || t.options)) || [];
259
+ const arr = Array.isArray(products) ? products : [];
260
+ if (arr.length) {
261
+ arr.forEach(p => pushItem(p, tierName));
262
+ } else {
263
+ // sometimes tier itself is the product
264
+ pushItem(t, tierName);
265
+ }
266
+ }
267
+
268
+ // Fallback: some forms expose items directly
269
+ if (!out.length && data && Array.isArray(data.items)) {
270
+ data.items.forEach(p => pushItem(p, ''));
271
+ }
272
+
273
+ await db.setObject(cacheKey, {
274
+ itemsJson: JSON.stringify(out),
275
+ expMs: String(Date.now() + 10 * 60 * 1000),
276
+ });
240
277
 
241
- return list;
278
+ return out;
242
279
  }
243
280
 
244
281
  function parseItems(itemsJson) {
@@ -794,6 +831,8 @@ async function handleHelloAssoTest(req, res) {
794
831
  if (!isAdmin) return helpers.notAllowed(req, res);
795
832
 
796
833
  const settings = await getSettings();
834
+ let sampleItems = [];
835
+ let hasSampleItems = false;
797
836
  let ok = false;
798
837
  let message = '';
799
838
  let count = 0;
@@ -807,13 +846,14 @@ async function handleHelloAssoTest(req, res) {
807
846
  const items = await fetchHelloAssoItems(settings);
808
847
  const list = Array.isArray(items) ? items : (Array.isArray(items.data) ? items.data : []);
809
848
  count = list.length;
810
- const sampleItems = list.slice(0, 10).map(it => ({
849
+ sampleItems = list.slice(0, 10).map(it => ({
811
850
  id: String(it.id || it.itemId || it.reference || it.slug || it.name || '').trim(),
812
851
  name: String(it.name || it.label || it.title || '').trim(),
813
852
  rawName: String(it.name || it.label || it.title || it.id || '').trim(),
814
853
  }));
854
+ hasSampleItems = sampleItems && sampleItems.length > 0;
815
855
  ok = true;
816
- message = `OK: token valide. Items récupérés: ${count}.`;
856
+ message = `OK: token valide. Catalogue récupéré via /public : ${count} item(s).`;
817
857
  } catch (e) {
818
858
  ok = false;
819
859
  message = (e && e.message) ? e.message : String(e);
@@ -826,6 +866,7 @@ async function handleHelloAssoTest(req, res) {
826
866
  count,
827
867
  settings,
828
868
  sampleItems,
869
+ hasSampleItems,
829
870
  hasSampleItems: sampleItems && sampleItems.length > 0,
830
871
  });
831
872
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "0.8.2",
3
+ "version": "0.8.6",
4
4
  "description": "Equipment reservation calendar for NodeBB (FullCalendar, approvals, HelloAsso payments)",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/plugin.json CHANGED
@@ -25,6 +25,6 @@
25
25
  "scripts": [
26
26
  "public/js/client.js"
27
27
  ],
28
- "version": "0.4.8",
28
+ "version": "0.5.0",
29
29
  "minver": "4.7.1"
30
30
  }