@inwink-mcp/backoffice-extension-helpers 1.0.11 → 1.0.13

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/README.md CHANGED
@@ -75,7 +75,21 @@ Contrôles effectués :
75
75
  - `extension.manifest.json` présent, `name` (slug) et `version` (semver) valides ;
76
76
  - `dist/` contient `index.html` + `extension.manifest.json` à la racine + `assets/` ;
77
77
  - le zip contient `index.html` et `extension.manifest.json` à la racine, sans `node_modules/`, `src/` ni `dist/` ;
78
- - avertissements : `base: './'` absent de `vite.config.ts`, usage de `BrowserRouter`.
78
+ - avertissements : `base: './'` absent de `vite.config.ts`, usage de `BrowserRouter`, `docs/inwink-api/` différent de la doc de cette version de la CLI.
79
+
80
+ ### `update-docs`
81
+
82
+ Met à jour la doc API d'une extension **déjà générée** : `docs/inwink-api/` est remplacé en miroir par celui de cette version (fichiers ajoutés, modifiés, et supprimés s'ils ont disparu du template). Rien d'autre n'est touché, la commande peut être relancée sans risque.
83
+
84
+ ```bash
85
+ npx @inwink-mcp/backoffice-extension-helpers@latest update-docs
86
+ ```
87
+
88
+ | Option | Description |
89
+ |--------|-------------|
90
+ | `--dir <path>` | Dossier du projet (défaut : dossier courant) |
91
+
92
+ Ce dossier appartient à la CLI : une modification locale est écrasée au prochain `update-docs`. `AGENTS.md` et le code du squelette ne sont pas mis à jour.
79
93
 
80
94
  ## Templates
81
95
 
package/dist/cli.js CHANGED
@@ -340,12 +340,70 @@ function bootstrap(opts) {
340
340
  }
341
341
 
342
342
  // src/commands/pack.ts
343
- import path7 from "path";
343
+ import path8 from "path";
344
344
 
345
345
  // src/lib/verify.ts
346
+ import fs5 from "fs";
347
+ import path6 from "path";
348
+ import AdmZip from "adm-zip";
349
+
350
+ // src/lib/docs.ts
346
351
  import fs4 from "fs";
347
352
  import path5 from "path";
348
- import AdmZip from "adm-zip";
353
+ var DOCS_REL_DIR = "docs/inwink-api";
354
+ var DOCS_TEMPLATE = "vite-extension";
355
+ var UPDATE_DOCS_COMMAND = "npx @inwink-mcp/backoffice-extension-helpers@latest update-docs";
356
+ function templateDocsDir() {
357
+ return path5.join(templatesRoot, DOCS_TEMPLATE, DOCS_REL_DIR);
358
+ }
359
+ function listFiles(root, current = root) {
360
+ if (!fs4.existsSync(current)) return [];
361
+ const files = [];
362
+ for (const entry of fs4.readdirSync(current, { withFileTypes: true })) {
363
+ const full = path5.join(current, entry.name);
364
+ if (entry.isDirectory()) files.push(...listFiles(root, full));
365
+ else files.push(path5.relative(root, full).split(path5.sep).join("/"));
366
+ }
367
+ return files.sort();
368
+ }
369
+ function readNormalized(file) {
370
+ return fs4.readFileSync(file, "utf8").replaceAll("\r\n", "\n");
371
+ }
372
+ function diffDocs(projectDir) {
373
+ const src = templateDocsDir();
374
+ const dest = path5.join(projectDir, DOCS_REL_DIR);
375
+ const srcFiles = listFiles(src);
376
+ const destFiles = new Set(listFiles(dest));
377
+ const diff = { added: [], changed: [], removed: [] };
378
+ for (const rel of srcFiles) {
379
+ if (!destFiles.has(rel)) diff.added.push(rel);
380
+ else if (readNormalized(path5.join(src, rel)) !== readNormalized(path5.join(dest, rel))) {
381
+ diff.changed.push(rel);
382
+ }
383
+ destFiles.delete(rel);
384
+ }
385
+ diff.removed = [...destFiles];
386
+ return diff;
387
+ }
388
+ function isDocsUpToDate(diff) {
389
+ return diff.added.length + diff.changed.length + diff.removed.length === 0;
390
+ }
391
+ function syncDocs(projectDir) {
392
+ const diff = diffDocs(projectDir);
393
+ const src = templateDocsDir();
394
+ const dest = path5.join(projectDir, DOCS_REL_DIR);
395
+ for (const rel of [...diff.added, ...diff.changed]) {
396
+ const target = path5.join(dest, rel);
397
+ fs4.mkdirSync(path5.dirname(target), { recursive: true });
398
+ fs4.copyFileSync(path5.join(src, rel), target);
399
+ }
400
+ for (const rel of diff.removed) {
401
+ fs4.rmSync(path5.join(dest, rel));
402
+ }
403
+ return diff;
404
+ }
405
+
406
+ // src/lib/verify.ts
349
407
  function verifyExtension(options) {
350
408
  const { projectDir, requireDist = false } = options;
351
409
  const errors = [];
@@ -366,40 +424,49 @@ function verifyExtension(options) {
366
424
  } catch (err) {
367
425
  errors.push(err.message);
368
426
  }
369
- const distDir = path5.join(projectDir, "dist");
370
- if (!fs4.existsSync(distDir)) {
427
+ const distDir = path6.join(projectDir, "dist");
428
+ if (!fs5.existsSync(distDir)) {
371
429
  if (requireDist) {
372
430
  errors.push("Dossier dist/ introuvable apr\xE8s le build.");
373
431
  }
374
432
  } else {
375
- if (!fs4.existsSync(path5.join(distDir, "index.html"))) {
433
+ if (!fs5.existsSync(path6.join(distDir, "index.html"))) {
376
434
  errors.push("dist/index.html manquant.");
377
435
  }
378
- if (!fs4.existsSync(path5.join(distDir, "extension.manifest.json"))) {
436
+ if (!fs5.existsSync(path6.join(distDir, "extension.manifest.json"))) {
379
437
  errors.push("dist/extension.manifest.json manquant (le plugin Vite doit le copier).");
380
438
  }
381
- const assetsDir = path5.join(distDir, "assets");
382
- if (!fs4.existsSync(assetsDir) || !fs4.statSync(assetsDir).isDirectory()) {
439
+ const assetsDir = path6.join(distDir, "assets");
440
+ if (!fs5.existsSync(assetsDir) || !fs5.statSync(assetsDir).isDirectory()) {
383
441
  warnings.push("dist/assets/ introuvable \u2014 build Vite incomplet ?");
384
442
  }
385
443
  }
386
444
  const zipPath = resolveZipPath(options, projectDir, name, version);
387
- if (zipPath && fs4.existsSync(zipPath)) {
445
+ if (zipPath && fs5.existsSync(zipPath)) {
388
446
  verifyZip(zipPath, errors);
389
447
  } else if (options.zipPath) {
390
448
  errors.push(`Zip introuvable : ${options.zipPath}`);
391
449
  }
392
450
  checkViteBase(projectDir, warnings);
393
451
  checkRouter(projectDir, warnings);
452
+ checkDocs(projectDir, warnings);
394
453
  return { errors, warnings };
395
454
  }
455
+ function checkDocs(projectDir, warnings) {
456
+ const diff = diffDocs(projectDir);
457
+ if (isDocsUpToDate(diff)) return;
458
+ const count = diff.added.length + diff.changed.length + diff.removed.length;
459
+ warnings.push(
460
+ `${DOCS_REL_DIR}/ diff\xE8re de la doc API actuelle (${count} fichier(s)). Mettez-la \xE0 jour avec : ${UPDATE_DOCS_COMMAND}`
461
+ );
462
+ }
396
463
  function resolveZipPath(options, projectDir, name, version) {
397
464
  if (options.zipPath) {
398
- return path5.isAbsolute(options.zipPath) ? options.zipPath : path5.join(projectDir, options.zipPath);
465
+ return path6.isAbsolute(options.zipPath) ? options.zipPath : path6.join(projectDir, options.zipPath);
399
466
  }
400
467
  if (name && version) {
401
- const candidate = path5.join(projectDir, `${name}-${version}.zip`);
402
- return fs4.existsSync(candidate) ? candidate : void 0;
468
+ const candidate = path6.join(projectDir, `${name}-${version}.zip`);
469
+ return fs5.existsSync(candidate) ? candidate : void 0;
403
470
  }
404
471
  return void 0;
405
472
  }
@@ -408,7 +475,7 @@ function verifyZip(zipPath, errors) {
408
475
  try {
409
476
  entries = new AdmZip(zipPath).getEntries();
410
477
  } catch (err) {
411
- errors.push(`Impossible de lire le zip ${path5.basename(zipPath)} : ${err.message}`);
478
+ errors.push(`Impossible de lire le zip ${path6.basename(zipPath)} : ${err.message}`);
412
479
  return;
413
480
  }
414
481
  const names = entries.map((e) => e.entryName.split("\\").join("/"));
@@ -429,60 +496,60 @@ function verifyZip(zipPath, errors) {
429
496
  }
430
497
  }
431
498
  function checkViteBase(projectDir, warnings) {
432
- const file = path5.join(projectDir, "vite.config.ts");
433
- if (!fs4.existsSync(file)) return;
434
- const content = fs4.readFileSync(file, "utf8");
499
+ const file = path6.join(projectDir, "vite.config.ts");
500
+ if (!fs5.existsSync(file)) return;
501
+ const content = fs5.readFileSync(file, "utf8");
435
502
  if (!/base:\s*['"]\.\/['"]/.test(content)) {
436
503
  warnings.push("vite.config.ts : `base: './'` introuvable \u2014 les assets risquent de casser apr\xE8s d\xE9ploiement.");
437
504
  }
438
505
  }
439
506
  function checkRouter(projectDir, warnings) {
440
- const srcDir = path5.join(projectDir, "src");
441
- if (!fs4.existsSync(srcDir)) return;
507
+ const srcDir = path6.join(projectDir, "src");
508
+ if (!fs5.existsSync(srcDir)) return;
442
509
  if (sourceUsesBrowserRouter(srcDir)) {
443
510
  warnings.push("Usage de BrowserRouter d\xE9tect\xE9 \u2014 utilisez HashRouter pour l'h\xE9bergement versionn\xE9.");
444
511
  }
445
512
  }
446
513
  function sourceUsesBrowserRouter(dir) {
447
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
448
- const full = path5.join(dir, entry.name);
514
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
515
+ const full = path6.join(dir, entry.name);
449
516
  if (entry.isDirectory()) {
450
517
  if (sourceUsesBrowserRouter(full)) return true;
451
518
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
452
- if (/\bBrowserRouter\b/.test(fs4.readFileSync(full, "utf8"))) return true;
519
+ if (/\bBrowserRouter\b/.test(fs5.readFileSync(full, "utf8"))) return true;
453
520
  }
454
521
  }
455
522
  return false;
456
523
  }
457
524
 
458
525
  // src/lib/zip.ts
459
- import fs5 from "fs";
460
- import path6 from "path";
526
+ import fs6 from "fs";
527
+ import path7 from "path";
461
528
  import AdmZip2 from "adm-zip";
462
529
  function createSourceZip(projectDir) {
463
530
  const sourceZip = new AdmZip2();
464
- const rootFiles = fs5.readdirSync(projectDir);
531
+ const rootFiles = fs6.readdirSync(projectDir);
465
532
  for (const file of rootFiles) {
466
533
  if (file === "node_modules" || file === "dist" || file.endsWith(".zip")) {
467
534
  continue;
468
535
  }
469
- const filePath = path6.join(projectDir, file);
470
- const stat = fs5.statSync(filePath);
536
+ const filePath = path7.join(projectDir, file);
537
+ const stat = fs6.statSync(filePath);
471
538
  if (stat.isDirectory()) {
472
539
  sourceZip.addLocalFolder(filePath, file);
473
540
  } else {
474
- sourceZip.addFile(file, fs5.readFileSync(filePath));
541
+ sourceZip.addFile(file, fs6.readFileSync(filePath));
475
542
  }
476
543
  }
477
544
  return sourceZip.toBuffer();
478
545
  }
479
546
  function zipDist(projectDir, name, version) {
480
- const distDir = path6.join(projectDir, "dist");
481
- if (!fs5.existsSync(distDir)) {
547
+ const distDir = path7.join(projectDir, "dist");
548
+ if (!fs6.existsSync(distDir)) {
482
549
  throw new Error("Dossier dist/ introuvable. Lancez le build avant de zipper.");
483
550
  }
484
551
  const zipFileName = `${name}-${version}.zip`;
485
- const zipFilePath = path6.join(projectDir, zipFileName);
552
+ const zipFilePath = path7.join(projectDir, zipFileName);
486
553
  const zip = new AdmZip2();
487
554
  zip.addLocalFolder(distDir);
488
555
  const sourceZipBuffer = createSourceZip(projectDir);
@@ -493,7 +560,7 @@ function zipDist(projectDir, name, version) {
493
560
 
494
561
  // src/commands/pack.ts
495
562
  function pack(opts) {
496
- const projectDir = path7.resolve(opts.dir ?? process.cwd());
563
+ const projectDir = path8.resolve(opts.dir ?? process.cwd());
497
564
  let name;
498
565
  let version;
499
566
  try {
@@ -515,7 +582,7 @@ function pack(opts) {
515
582
  error(err.message);
516
583
  return 1;
517
584
  }
518
- success(`Zip cr\xE9\xE9 : ${path7.basename(zipPath)}`);
585
+ success(`Zip cr\xE9\xE9 : ${path8.basename(zipPath)}`);
519
586
  info(`Version : ${version}`);
520
587
  info("Mise \xE0 jour de la version avec la date du jour\u2026");
521
588
  try {
@@ -531,10 +598,39 @@ function pack(opts) {
531
598
  return ok ? 0 : 1;
532
599
  }
533
600
 
601
+ // src/commands/update-docs.ts
602
+ import path9 from "path";
603
+ import pc4 from "picocolors";
604
+ function updateDocs(opts) {
605
+ const projectDir = path9.resolve(opts.dir ?? process.cwd());
606
+ try {
607
+ readManifest(projectDir);
608
+ } catch (err) {
609
+ error(`Projet cible invalide : ${err.message}`);
610
+ return 1;
611
+ }
612
+ let diff;
613
+ try {
614
+ diff = syncDocs(projectDir);
615
+ } catch (err) {
616
+ error(`\xC9chec de la mise \xE0 jour : ${err.message}`);
617
+ return 1;
618
+ }
619
+ if (isDocsUpToDate(diff)) {
620
+ success(`${DOCS_REL_DIR}/ est d\xE9j\xE0 \xE0 jour.`);
621
+ return 0;
622
+ }
623
+ success(`${DOCS_REL_DIR}/ mis \xE0 jour :`);
624
+ for (const file of diff.added) info(` ${pc4.green("+")} ${file}`);
625
+ for (const file of diff.changed) info(` ${pc4.dim("~")} ${file}`);
626
+ for (const file of diff.removed) info(` ${pc4.red("-")} ${file}`);
627
+ return 0;
628
+ }
629
+
534
630
  // src/commands/verify.ts
535
- import path8 from "path";
631
+ import path10 from "path";
536
632
  function verify(opts) {
537
- const projectDir = path8.resolve(opts.dir ?? process.cwd());
633
+ const projectDir = path10.resolve(opts.dir ?? process.cwd());
538
634
  info("V\xE9rification de conformit\xE9\u2026");
539
635
  const result = verifyExtension({ projectDir, zipPath: opts.zip });
540
636
  const ok = printVerifyResult(result);
@@ -550,6 +646,9 @@ program.command("bootstrap").description("G\xE9n\xE8re le squelette d'une extens
550
646
  program.command("apply").description("Applique un template par-dessus un projet existant (overlay).").argument("<template>", "template \xE0 appliquer (sous-dossier de templates/)").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((template, options) => {
551
647
  process.exitCode = apply(template, options);
552
648
  });
649
+ program.command("update-docs").description("Remplace la doc API (docs/inwink-api/) d'une extension existante par celle de cette version.").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((options) => {
650
+ process.exitCode = updateDocs(options);
651
+ });
553
652
  program.command("package").description("Build + zip + v\xE9rification de conformit\xE9 de l'extension.").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((options) => {
554
653
  process.exitCode = pack(options);
555
654
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inwink-mcp/backoffice-extension-helpers",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "author": "inwink",
5
5
  "license": "ISC",
6
6
  "description": "CLI pour bootstrapper, packager et vérifier les extensions backoffice inwink.",
@@ -56,19 +56,23 @@ Avant d'écrire **tout** appel API, utilise les outils MCP inwink pour découvri
56
56
 
57
57
  ### Référence des patterns d'API (`docs/inwink-api/`)
58
58
 
59
- Les outils MCP donnent les endpoints/champs **exacts** ; la doc statique dans `docs/inwink-api/` décrit les **patterns d'appel** (verbes, forme des payloads, langage de requête). Lis uniquement le fichier correspondant à ta tâche :
59
+ Les outils MCP donnent les endpoints/champs **exacts** ; la doc statique dans `docs/inwink-api/` décrit les **patterns d'appel** (verbes, forme des payloads, langage de requête). Ce dossier est une copie de la CLI helper, figée à la génération : mets-le à jour une fois par session de travail (pas avant chaque lecture) avec `npx @inwink-mcp/backoffice-extension-helpers@latest update-docs` (ne réécrit que `docs/inwink-api/`), et ne le modifie pas à la main. Lis uniquement le fichier correspondant à ta tâche :
60
60
 
61
61
  | Tâche | Fichier |
62
62
  |-------|---------|
63
+ | URLs de base, shardId, routes d'audience, codes HTTP & règles générales | [docs/inwink-api/README.md](docs/inwink-api/README.md) |
63
64
  | Lire / lister des entités (query, count, pagination, projection, joins) | [docs/inwink-api/query.md](docs/inwink-api/query.md) |
64
65
  | Construire un filtre / tri / projection (langage de requête JSON) | [docs/inwink-api/query-language.md](docs/inwink-api/query-language.md) |
66
+ | Regrouper / agréger (GROUP BY, statistiques, facettes) | [docs/inwink-api/aggregate.md](docs/inwink-api/aggregate.md) |
65
67
  | Créer une entité | [docs/inwink-api/create.md](docs/inwink-api/create.md) |
66
68
  | Modifier (patch / massupdate) | [docs/inwink-api/update.md](docs/inwink-api/update.md) |
67
69
  | Supprimer (delete / massdelete) | [docs/inwink-api/delete.md](docs/inwink-api/delete.md) |
68
70
  | Import en masse (bulk create/update) | [docs/inwink-api/import.md](docs/inwink-api/import.md) |
71
+ | Versions passées et entités supprimées | [docs/inwink-api/history.md](docs/inwink-api/history.md) |
72
+ | Contrats d'entités, endpoints divers | [docs/inwink-api/entities.md](docs/inwink-api/entities.md) |
73
+ | Déclarer un champ custom (template d'entité) | [docs/inwink-api/entity-template.md](docs/inwink-api/entity-template.md) |
69
74
  | Champs de type `file` (upload / download) | [docs/inwink-api/files.md](docs/inwink-api/files.md) |
70
- | Contrats & schémas d'entités, endpoints divers | [docs/inwink-api/entities.md](docs/inwink-api/entities.md) |
71
- | Codes HTTP & règles générales | [docs/inwink-api/README.md](docs/inwink-api/README.md) |
75
+ | Manipuler les `assets` (galerie media) | [docs/inwink-api/assets.md](docs/inwink-api/assets.md) |
72
76
 
73
77
  ## Appeler l'API inwink
74
78
 
@@ -90,7 +94,7 @@ function MonComposant() {
90
94
  }
91
95
  ```
92
96
 
93
- Méthodes disponibles sur chaque hook : `request<T>(path, init?)`, `get<T>`, `post<T>(path, body?)`, `put<T>(path, body?)`, `del<T>`.
97
+ Méthodes disponibles sur chaque hook : `request<T>(path, init?)`, `get<T>`, `post<T>(path, body?)`, `put<T>(path, body?)`, `patch<T>(path, body?)`, `del<T>`.
94
98
 
95
99
  Pour appeler un **hôte externe déclaré** (hors API inwink), utilise le hook générique `useInwinkApi(baseUrl)`.
96
100
 
@@ -130,10 +134,12 @@ import { useInwinkContext } from './hooks/useInwinkContext';
130
134
  function MonComposant() {
131
135
  const { scope, eventId, communityId, tenantId } = useInwinkContext();
132
136
  // scope vaut 'event' | 'community' | 'tenant' | null (priorité event > community > tenant)
133
- // Utilise l'id pertinent comme shardId dans les chemins d'API.
137
+ // En scope 'event' ou 'community', l'id correspondant est le shardId des chemins d'API.
134
138
  }
135
139
  ```
136
140
 
141
+ En scope `tenant`, le `tenantId` **n'est pas un shardId** : les entités d'audience (`authtenant*`) vivent sous `{customerId}/audience/{tenantId}/{entity}/...` sur l'API Customer, où le shardId reste le `customerId` (cf. [docs/inwink-api/README.md](docs/inwink-api/README.md#audience-scoped-entities-customerapi)).
142
+
137
143
  Pour laisser l'utilisateur **choisir** un contexte, monte un picker (`EventPicker`, `CommunityPicker`, `AudiencePicker`) — typiquement en **surchargeant le `Header`** (le `Header` par défaut est minimal et prévu pour ça). Les pickers ne sont pas montés par défaut : décide selon le besoin de l'extension.
138
144
 
139
145
  ## Recettes
@@ -217,6 +223,7 @@ Packaging et vérification via la CLI helper :
217
223
  ```powershell
218
224
  npx @inwink-mcp/backoffice-extension-helpers@latest package # build + zip + vérification de conformité
219
225
  npx @inwink-mcp/backoffice-extension-helpers@latest verify # vérification de conformité seule
226
+ npx @inwink-mcp/backoffice-extension-helpers@latest update-docs # met à jour docs/inwink-api/
220
227
  ```
221
228
 
222
229
  ## Conventions de code
@@ -232,5 +239,6 @@ npx @inwink-mcp/backoffice-extension-helpers@latest verify # vérification de
232
239
 
233
240
  > Ces consignes s'appliquent à **tout** assistant IA / outil agentique qui manipule cette extension, quel qu'il soit.
234
241
 
242
+ - **Langue de l'extension.** Ce guide est rédigé en français et la référence API (`docs/inwink-api/`) en anglais : cela ne dit rien de la langue de l'extension. Échange avec l'utilisateur dans sa langue. Pour le code, les commentaires et les textes d'interface, utilise la langue qu'il demande ou, à défaut, celle dans laquelle il t'écrit — pas celle de cette documentation ni des commentaires du squelette.
235
243
  - **Ne jamais publier sans validation explicite de l'utilisateur.** Le build, le packaging et la vérification **en local** sont autorisés sans demander. En revanche, toute étape qui **expose l'extension côté inwink** (upload d'une version, `publish`, promotion en `latest`) doit être **confirmée explicitement par l'utilisateur à chaque fois**. En cas de doute, demande avant d'agir.
236
244
  - **Tester dans un vrai navigateur, pas dans un aperçu intégré.** Ne lance pas l'extension dans un panneau d'aperçu ou une webview embarquée de l'outil agentique : le flux OIDC y échoue. L'authentification exige que l'origine soit le serveur de dev local — le `redirect_uri` vaut `${window.location.origin}${window.location.pathname}` et n'est enregistré côté IdP que pour `http://localhost:<port>`, alors qu'un aperçu intégré sert l'application depuis une autre origine. **Recommandé** : lancer `npm run dev` et ouvrir un **vrai onglet de navigateur** (Chrome de préférence) sur l'URL locale affichée (ex. `http://localhost:5173`).
@@ -1,43 +1,84 @@
1
- # inwink API Reference
1
+ # inwink API reference
2
2
 
3
- This document explains how to consume inwink APIs. It is intended as a reference for building API calls programmatically.
3
+ Reference for consuming the inwink APIs from the extension. Calls always go through the `useInwink*Api` hooks, which set the Bearer token and the base URL: the paths below are relative to that base.
4
4
 
5
- > **Découverte dynamique d'abord.** Cette doc décrit les *patterns* d'appel (verbes, forme des payloads, langage de requête). Les **endpoints exacts, champs et entités** se découvrent via les outils MCP (`inwink_mcp_find_api_endpoint`, `inwink_mcp_get_entity_description`) — ne devine jamais une route ou un schéma.
5
+ > **Keep this folder current.** It is a snapshot of the helper CLI taken when the extension was generated, and it is never edited by hand. Refresh it once per working session, not before each read, with `npx @inwink-mcp/backoffice-extension-helpers@latest update-docs` (rewrites `docs/inwink-api/` only).
6
6
 
7
- ## Sommaire
7
+ > **Dynamic discovery first.** This documentation describes call *patterns* (verbs, payload shapes, query language). The **exact endpoints, fields and entities** are discovered with the MCP tools (`inwink_mcp_find_api_endpoint`, `inwink_mcp_get_entity_description`) — never guess a route or a schema.
8
8
 
9
- | Tâche | Fichier |
9
+ ## Contents
10
+
11
+ | Task | File |
10
12
  |---|---|
11
- | Lire / lister des entités (query, count, pagination, projection, joins) | [query.md](./query.md) |
12
- | Construire un filtre / tri / projection (langage de requête JSON) | [query-language.md](./query-language.md) |
13
- | Créer une entité | [create.md](./create.md) |
14
- | Modifier une entité (patch / massupdate) | [update.md](./update.md) |
15
- | Supprimer une entité (delete / massdelete) | [delete.md](./delete.md) |
16
- | Import en masse (bulk create/update) | [import.md](./import.md) |
17
- | Contrats & schémas d'entités, endpoints divers | [entities.md](./entities.md) |
18
- | Champs de type `file` (upload / download) | [files.md](./files.md) |
19
- | Manipuler les `assets` (galerie media) | [assets.md](./assets.md) |
13
+ | Read / list entities (query, count, pagination, projection, joins) | [query.md](./query.md) |
14
+ | Build a filter / sort / projection (JSON query language) | [query-language.md](./query-language.md) |
15
+ | Group / aggregate (GROUP BY, statistics, facets) | [aggregate.md](./aggregate.md) |
16
+ | Create an entity | [create.md](./create.md) |
17
+ | Edit an entity (patch / massupdate) | [update.md](./update.md) |
18
+ | Delete an entity (delete / massdelete) | [delete.md](./delete.md) |
19
+ | Bulk import (bulk create/update) | [import.md](./import.md) |
20
+ | Past versions and deleted entities | [history.md](./history.md) |
21
+ | Entity contracts, other endpoints | [entities.md](./entities.md) |
22
+ | Declare a custom field (entity template) | [entity-template.md](./entity-template.md) |
23
+ | `file` fields (upload / download) | [files.md](./files.md) |
24
+ | Handle `assets` (media library) | [assets.md](./assets.md) |
20
25
 
21
26
  ---
22
27
 
23
- ## General Rules
28
+ ## API Base URLs
29
+
30
+ | API | Base URL | Hook | Scope |
31
+ |---|---|---|---|
32
+ | EventAPI | `https://api-event.inwink.com` | `useInwinkEventApi` | Entities tied to an event (persons, sessions, exhibitors…) |
33
+ | CommunityAPI | `https://api-community.inwink.com` | `useInwinkCommunityApi` | Entities tied to a community (members, forums, forumsubjects…) |
34
+ | CustomerAPI | `https://api-customer.inwink.com` | `useInwinkCustomerApi` | Entities tied to the customer (events, contacts…) |
35
+
36
+ ## URL pattern
37
+
38
+ - EventAPI: `{eventAPI}/{eventId}/{entity}/...`
39
+ - CommunityAPI: `{communityAPI}/{communityId}/{entity}/...`
40
+ - CustomerAPI: `{customerAPI}/{customerId}/{entity}/...`
41
+
42
+ The `{eventId}`, `{communityId}` or `{customerId}` is called the **shardId** — it scopes each request to one specific event, community or customer.
43
+
44
+ ### Audience-scoped entities (CustomerAPI)
45
+
46
+ A customer holds one or more **audiences** (also called *tenants*). Entities that belong to an audience rather than to the whole customer have a name starting with `authtenant` (`authtenantcommunity`, `authtenantcontact`, `authtenantcompany`, `authtenantmailtemplate`…) and live on a two-segment route:
47
+
48
+ ```
49
+ {customerAPI}/{customerId}/audience/{tenantId}/{entity}/...
50
+ ```
51
+
52
+ The `{customerId}` remains the shardId; the `{tenantId}` identifies the audience. On these entities, `{shardId}` reads `{customerId}/audience/{tenantId}` in every route of this documentation. An `authtenant*` entity called without the audience segment returns 404.
53
+
54
+ ## Common headers
55
+
56
+ Every request requires (set by the hooks):
57
+
58
+ ```
59
+ Authorization: Bearer {access_token}
60
+ Content-Type: application/json
61
+ ```
62
+
63
+ ---
64
+
65
+ ## General rules
24
66
 
25
67
  - Property names are **case-insensitive**.
26
68
  - Enum values are **case-insensitive**.
27
69
  - All IDs are UUIDs.
28
70
  - Dates are ISO 8601 / UTC.
29
- - All write operations (create, edit, delete, import) use **POST** method.
30
-
31
- ---
71
+ - Write operations on entities (create, edit, delete, import) use the **POST** method. Only the partial edit of an entity's template uses `PATCH` (see [entity-template.md](./entity-template.md)).
32
72
 
33
- ## HTTP Status Codes
73
+ ## HTTP codes
34
74
 
35
75
  | Code | Meaning |
36
76
  |---|---|
37
- | 200 | Success — parse body as JSON |
77
+ | 200 | Success — parse the body as JSON (some writes, such as entity template writes, return 200 with no body) |
38
78
  | 204 | Success — no content |
39
- | 400 | Bad request — body contains error details |
40
- | 401 | Unauthorized — token expired or invalid |
41
- | 403 | Forbidden — missing rights, contact CSM |
42
- | 500 | Server error — may be transient, contact support if persistent |
79
+ | 400 | Bad request — the body holds the error details |
80
+ | 401 | Unauthorized — token expired or invalid (handled by the hooks) |
81
+ | 403 | Forbidden — missing rights, contact the CSM |
82
+ | 404 | Entity not found, empty body: entity name does not exist, entity exists but under another name on another API, or `authtenant*` entity called without the audience segment. Do not guess another name: run `inwink_mcp_find_api_endpoint` again with a natural-language description of the need |
83
+ | 500 | Server error — possibly transient, contact support if it persists. Also returned for a `shardId` that does not exist: check the identifier before concluding to an outage |
43
84
  | 503 | Transient infrastructure error — retry later |