@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.
@@ -1,144 +1,145 @@
1
- ◂ Retour à [README](./README.md)
2
- # InWink APIs — Champs de type fichier (guide d'utilisation de l'API)
1
+ ◂ Back to [README](./README.md)
3
2
 
4
- > **But.** Décrire comment **envoyer**, **lire**, **remplacer** et **supprimer** un fichier
5
- > porté par un champ d'entité, via les APIs d'administration InWink (EventAPI / CommunityAPI /
6
- > CustomerAPI). Tout est décrit au niveau HTTP : verbe + route + corps JSON + réponse JSON.
3
+ # InWink APIs — File-type fields (API usage guide)
4
+
5
+ > **Purpose.** Describe how to **upload**, **read**, **replace** and **delete** a file
6
+ > carried by an entity field, through the InWink admin APIs (EventAPI / CommunityAPI /
7
+ > CustomerAPI). Everything is described at the HTTP level: verb + route + JSON body + JSON response.
7
8
  >
8
- > Auth : jeton porteur (Bearer) avec les claims du scope. La FrontAPI publique (participant,
9
- > anonyme) a des routes différentes et n'est pas couverte (note en fin de document).
9
+ > Auth: Bearer token with the scope claims. The public FrontAPI (participant,
10
+ > anonymous) has different routes and is not covered (note at the end of the document).
10
11
  >
11
- > Convention JSON : les corps de requête sont acceptés en casse insensible ; les réponses
12
- > sont en `camelCase`. Les exemples ci-dessous utilisent `camelCase`.
12
+ > JSON convention: request bodies are accepted case-insensitively; responses
13
+ > are in `camelCase`. The examples below use `camelCase`.
13
14
 
14
15
  ---
15
16
 
16
- ## 1. Principe (à lire en premier)
17
+ ## 1. Principle (read first)
17
18
 
18
- Un « champ de type fichier » est un champ d'entité (ex. `prop3` sur `person`,
19
- `videofile` sur `vod`, `audiofile` sur `podcast`…) dont le type déclaré dans le template
20
- est l'un de :
19
+ A "file-type field" is an entity field (e.g. `prop3` on `person`,
20
+ `videofile` on `vod`, `audiofile` on `podcast`…) whose type declared in the template
21
+ is one of:
21
22
 
22
- | Type de champ | Effet |
23
+ | Field type | Effect |
23
24
  |---------------|-------|
24
- | `file` | fichier générique (document, etc.) |
25
- | `image` | image (miniatures générées automatiquement) |
26
- | `filevideo` | vidéo (encodage + transcripts déclenchés) |
25
+ | `file` | generic file (document, etc.) |
26
+ | `image` | image (thumbnails generated automatically) |
27
+ | `filevideo` | video (encoding + transcripts triggered) |
27
28
 
28
- **La valeur du champ change de forme selon le sens :**
29
+ **The field value changes shape depending on direction:**
29
30
 
30
- - **En écriture** : une simple **chaîne = le nom du blob temporaire** (`blobName`) obtenu à
31
- l'étape d'upload.
32
- - **En lecture** : un **objet fichier** JSON complet
31
+ - **On write**: a plain **string = the temporary blob name** (`blobName`) obtained at
32
+ the upload step.
33
+ - **On read**: a full JSON **file object**
33
34
  (`{ blobName, container, url, fileName, mimeType, size, ... }`).
34
35
 
35
- **L'upload suit un pattern « lien d'upload direct » en 4 temps.** L'API n'est jamais dans le
36
- chemin des octets : elle délivre une URL d'upload signée (SAS), et **le client envoie les
37
- octets directement sur le stockage**.
36
+ **The upload follows a 4-step "direct upload link" pattern.** The API is never in the
37
+ bytes path: it issues a signed upload URL (SAS), and **the client sends the
38
+ bytes directly to storage**.
38
39
 
39
40
  ```
40
- 1. POST .../file → l'API crée un emplacement temporaire, renvoie une URL signée
41
- 2. PUT des octets → le client téléverse directement sur l'URL signée
42
- 3. POST .../file/finalize-tmp → l'API valide et fige le fichier temporaire
43
- 4. PATCH/PUT de l'entité → on met la valeur du champ = nom du blob ; l'API le déplace
44
- vers son emplacement définitif et réécrit la valeur en objet fichier
41
+ 1. POST .../file → the API creates a temporary location, returns a signed URL
42
+ 2. PUT the bytes → the client uploads directly to the signed URL
43
+ 3. POST .../file/finalize-tmp → the API validates and freezes the temporary file
44
+ 4. PATCH/PUT the entity → set the field value = blob name; the API moves it
45
+ to its final location and rewrites the value as a file object
45
46
  ```
46
47
 
47
- > ⚠️ **Point crucial** : il n'existe **aucun endpoint « attacher un fichier »**. On référence
48
- > le nom du blob temporaire dans le champ, puis **on sauvegarde l'entité via son endpoint
49
- > d'édition habituel**. C'est cette sauvegarde qui matérialise le fichier.
48
+ > ⚠️ **Crucial point**: there is **no "attach a file" endpoint**. You reference
49
+ > the temporary blob name in the field, then **save the entity through its usual
50
+ > edit endpoint**. That save is what materializes the file.
50
51
 
51
- ### Forme des routes
52
+ ### Route shape
52
53
 
53
- Le point d'entrée fichier d'une entité est `{scopeId}/{entity}/file`. Exemples :
54
+ An entity's file entry point is `{scopeId}/{entity}/file`. Examples:
54
55
  `{eventId}/person/file`, `{eventId}/session/file`, `{communityId}/vod/file`.
55
56
 
56
- `{scopeId}` selon l'API :
57
- - EventAPI : `{eventId}`
58
- - CommunityAPI : `{communityId}`
59
- - CustomerAPI : `{customerId}/audience/{tenantId}` (ou `{customerId}`)
57
+ `{scopeId}` depending on the API:
58
+ - EventAPI: `{eventId}`
59
+ - CommunityAPI: `{communityId}`
60
+ - CustomerAPI: `{customerId}/audience/{tenantId}` (or `{customerId}`)
60
61
 
61
62
  ---
62
63
 
63
- ## 2. Envoyer un fichier dans un champ d'entité (flux complet)
64
+ ## 2. Upload a file into an entity field (full flow)
64
65
 
65
- ### Étape 1 — Créer l'emplacement temporaire
66
+ ### Step 1 — Create the temporary location
66
67
 
67
68
  ```
68
69
  POST {scopeId}/{entity}/file
69
70
  Content-Type: application/json
70
71
 
71
72
  {
72
- "entity": "person", // nom de l'entité
73
- "field": "prop3", // clé du champ fichier
74
- "type": "image/png", // type MIME du fichier
75
- "name": "photo.png", // nom de fichier
76
- "blobType": "Block" // "Block" (défaut) | "append" | "page"
73
+ "entity": "person", // entity name
74
+ "field": "prop3", // file field key
75
+ "type": "image/png", // file MIME type
76
+ "name": "photo.png", // file name
77
+ "blobType": "Block" // "Block" (default) | "append" | "page"
77
78
  }
78
79
  ```
79
80
 
80
- Réponse `200` — objet d'accès temporaire :
81
+ Response `200` — temporary access object:
81
82
 
82
83
  ```json
83
84
  {
84
- "url": "https://<stockage>.blob.core.windows.net",
85
+ "url": "https://<storage>.blob.core.windows.net",
85
86
  "container": "tmp",
86
87
  "blobName": "a7f3c1e0-....png",
87
88
  "sas": "?sv=...&sig=..."
88
89
  }
89
90
  ```
90
91
 
91
- ### Étape 2 — Téléverser les octets directement (hors API)
92
+ ### Step 2 — Upload the bytes directly (outside the API)
92
93
 
93
- Le client construit l'URL cible et **envoie les octets** dessus (droits lecture/écriture sur
94
- l'emplacement temporaire) :
94
+ The client builds the target URL and **sends the bytes** to it (read/write rights on
95
+ the temporary location):
95
96
 
96
97
  ```
97
98
  PUT {url}/{container}/{blobName}{sas}
98
99
  x-ms-blob-type: BlockBlob
99
100
  Content-Type: image/png
100
101
 
101
- <octets du fichier>
102
+ <file bytes>
102
103
  ```
103
104
 
104
- Pour un upload long, renouveler le lien signé si besoin :
105
+ For a long upload, renew the signed link if needed:
105
106
 
106
107
  ```
107
- POST {scopeId}/{entity}/file/renew { "blobName": "...", "sas": "..." } → nouvel objet d'accès
108
- POST {scopeId}/{entity}/file/try-renew { "blobName": "...", "sas": "..." } → renouvelé si expiré
108
+ POST {scopeId}/{entity}/file/renew { "blobName": "...", "sas": "..." } → new access object
109
+ POST {scopeId}/{entity}/file/try-renew { "blobName": "...", "sas": "..." } → renewed if expired
109
110
  ```
110
111
 
111
- ### Étape 3 — Finaliser le fichier temporaire
112
+ ### Step 3 — Finalize the temporary file
112
113
 
113
114
  ```
114
115
  POST {scopeId}/{entity}/file/finalize-tmp
115
116
  { "blobName": "a7f3c1e0-....png", "sas": "?sv=...&sig=..." }
116
117
  ```
117
118
 
118
- - `200` : le contenu correspond au type MIME déclaré ; le fichier temporaire est figé.
119
- - `400` avec corps `101` : le contenu ne correspond pas au MIME annoncé (rejet).
120
- - `400` avec corps `102` : type de fichier non supporté.
119
+ - `200`: the content matches the declared MIME type; the temporary file is frozen.
120
+ - `400` with body `101`: the content does not match the announced MIME (rejected).
121
+ - `400` with body `102`: unsupported file type.
121
122
 
122
- ### Étape 4 — Sauvegarder l'entité en référençant le fichier
123
+ ### Step 4 — Save the entity referencing the file
123
124
 
124
- Via l'**endpoint d'édition habituel de l'entité**, mettre la valeur du champ = le `blobName` :
125
+ Through the **entity's usual edit endpoint**, set the field value = the `blobName`:
125
126
 
126
127
  ```
127
128
  PATCH {scopeId}/person/{personId}
128
129
  { "prop3": "a7f3c1e0-....png" }
129
130
  ```
130
131
 
131
- À la sauvegarde, l'API valide (taille, MIME autorisés, cohérence du contenu), **déplace le
132
- fichier vers son emplacement définitif**, **réécrit la valeur** du champ en objet fichier
133
- complet, et déclenche les traitements associés (encodage vidéo, miniatures, analyse de document).
132
+ On save, the API validates (size, allowed MIMEs, content consistency), **moves the
133
+ file to its final location**, **rewrites the field value** as a full file
134
+ object, and triggers the associated processing (video encoding, thumbnails, document analysis).
134
135
 
135
- La valeur relue devient par exemple :
136
+ The value read back then becomes, for example:
136
137
 
137
138
  ```json
138
139
  {
139
140
  "blobName": "prop3/9f...png",
140
141
  "container": "3fa85f64-...._person-prop3",
141
- "url": "https://<stockage>.blob.core.windows.net/...",
142
+ "url": "https://<storage>.blob.core.windows.net/...",
142
143
  "fileName": "photo.png",
143
144
  "mimeType": "image/png",
144
145
  "size": 20481,
@@ -148,17 +149,17 @@ La valeur relue devient par exemple :
148
149
 
149
150
  ---
150
151
 
151
- ## 3. Lire / télécharger un fichier
152
+ ## 3. Read / download a file
152
153
 
153
154
  ```
154
155
  GET {scopeId}/{entity}/file/{entityId}/{field}?lang=fr&withoutExpiration=false
155
156
  ```
156
157
 
157
- Réponse `200` — objet fichier avec lien d'accès :
158
+ Response `200` — file object with access link:
158
159
 
159
160
  ```json
160
161
  {
161
- "url": "https://<stockage>.blob.core.windows.net",
162
+ "url": "https://<storage>.blob.core.windows.net",
162
163
  "container": "3fa85f64-...._person-prop3",
163
164
  "blobName": "prop3/9f...png",
164
165
  "fileName": "photo.png",
@@ -169,44 +170,44 @@ Réponse `200` — objet fichier avec lien d'accès :
169
170
  }
170
171
  ```
171
172
 
172
- Le client télécharge ensuite les octets directement sur **`url` + `/{container}/{blobName}` + `sas`**.
173
+ The client then downloads the bytes directly from **`url` + `/{container}/{blobName}` + `sas`**.
173
174
 
174
- - `404` si le champ n'a pas de fichier.
175
- - `lang` : langue pour un champ localisé (défaut = langue par défaut du scope).
176
- - `withoutExpiration=true` : lien d'accès de longue durée.
175
+ - `404` if the field has no file.
176
+ - `lang`: language for a localized field (default = the scope's default language).
177
+ - `withoutExpiration=true`: long-lived access link.
177
178
 
178
- **Fichier d'une sous-entité (collection)** :
179
+ **File of a sub-entity (collection)**:
179
180
  ```
180
181
  GET {scopeId}/{entity}/file/{entityId}/{nestedField}/{index}/{field}?lang=&withoutExpiration=
181
182
  ```
182
183
 
183
- **Champ vidéo** : la réponse est enrichie (voir §5).
184
+ **Video field**: the response is enriched (see §5).
184
185
 
185
186
  ---
186
187
 
187
- ## 4. Remplacer / ré-éditer / supprimer un fichier
188
+ ## 4. Replace / re-edit / delete a file
188
189
 
189
- **Récupérer une copie éditable** du fichier actuel dans l'espace temporaire, puis reprendre
190
- le flux finalize + save :
190
+ **Get an editable copy** of the current file in the temporary space, then resume
191
+ the finalize + save flow:
191
192
  ```
192
193
  GET {scopeId}/{entity}/file/{entityId}/{field}/copytotmp?lang=fr
193
194
  → 200 { "blobName": "...", "container": "tmp", ... }
194
195
  ```
195
196
 
196
- **Remplacer** : téléverser un nouveau fichier temporaire (§2) et ré-éditer l'entité avec le
197
- nouveau `blobName`. L'ancien fichier est automatiquement retiré.
197
+ **Replace**: upload a new temporary file (§2) and re-edit the entity with the
198
+ new `blobName`. The old file is removed automatically.
198
199
 
199
- **Supprimer** : éditer l'entité en mettant le champ à `null`.
200
+ **Delete**: edit the entity setting the field to `null`.
200
201
 
201
202
  ---
202
203
 
203
- ## 5. Champs vidéo — fonctionnalités avancées
204
+ ## 5. Video fields — advanced features
204
205
 
205
- Les champs `filevideo` déclenchent un encodage et exposent transcripts et analyse de document.
206
- Toutes les routes sont sous `{scopeId}/{entity}/file/...`.
206
+ `filevideo` fields trigger encoding and expose transcripts and document analysis.
207
+ All routes are under `{scopeId}/{entity}/file/...`.
207
208
 
208
- ### Lecture d'un champ vidéo
209
- `GET {scopeId}/{entity}/file/{entityId}/{field}` renvoie, quand la vidéo est prête :
209
+ ### Reading a video field
210
+ `GET {scopeId}/{entity}/file/{entityId}/{field}` returns, when the video is ready:
210
211
 
211
212
  ```json
212
213
  {
@@ -220,94 +221,94 @@ Toutes les routes sont sous `{scopeId}/{entity}/file/...`.
220
221
  }
221
222
  ```
222
223
 
223
- ### Encodage
224
+ ### Encoding
224
225
  ```
225
- POST {scopeId}/{entity}/file/video/{entityId}/{field}/encoding/retry?lang=fr → relance l'encodage
226
- GET {scopeId}/{entity}/file/{entityId}/{field}/status?lang=fr → statut d'encodage
226
+ POST {scopeId}/{entity}/file/video/{entityId}/{field}/encoding/retry?lang=fr → retries the encoding
227
+ GET {scopeId}/{entity}/file/{entityId}/{field}/status?lang=fr → encoding status
227
228
  ```
228
229
 
229
- ### Transcripts (requiert une licence IA)
230
+ ### Transcripts (requires an AI license)
230
231
  ```
231
- POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/enabled → active le traitement
232
+ POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/enabled → enables processing
232
233
  GET {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/status → { status, progress, duration }
233
234
  GET {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/manifest?language=fr
234
- POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/manifest?language=fr (corps = manifeste)
235
+ POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/manifest?language=fr (body = manifest)
235
236
  GET {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/{language}
236
- POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/{language} (corps = transcript)
237
- POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/{language}/delete (corps = transcript)
237
+ POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/{language} (body = transcript)
238
+ POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/{language}/delete (body = transcript)
238
239
  POST {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/reset/from-artifacts
239
- GET {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/artifacts/{data} (redirection 302 vers le fichier)
240
+ GET {scopeId}/{entity}/file/video/{entityId}/{field}/transcripts/artifacts/{data} (302 redirect to the file)
240
241
  ```
241
- `status` ∈ `NotSet`, `Pending`/`Processing`, `Ready`. Un `Ready` non enregistré est
242
- persisté automatiquement au premier appel de `status`/`manifest`/`get`.
242
+ `status` ∈ `NotSet`, `Pending`/`Processing`, `Ready`. An unsaved `Ready` is
243
+ persisted automatically on the first call to `status`/`manifest`/`get`.
243
244
 
244
- ### Analyse de document (requiert une licence IA)
245
+ ### Document analysis (requires an AI license)
245
246
  ```
246
- GET {scopeId}/{entity}/file/document/{entityId}/{field}/analysis → statut de l'opération
247
- POST {scopeId}/{entity}/file/document/{entityId}/{field}/analysis/start → démarre l'analyse
247
+ GET {scopeId}/{entity}/file/document/{entityId}/{field}/analysis → operation status
248
+ POST {scopeId}/{entity}/file/document/{entityId}/{field}/analysis/start → starts the analysis
248
249
  ```
249
250
 
250
251
  ---
251
252
 
252
- ## 6. Formes JSON de référence
253
+ ## 6. Reference JSON shapes
253
254
 
254
- **Corps de requête**
255
- - Créer l'emplacement temporaire : `{ entity, field, type, name, blobType }`
256
- - Renouveler / finaliser : `{ blobName, sas }`
255
+ **Request bodies**
256
+ - Create the temporary location: `{ entity, field, type, name, blobType }`
257
+ - Renew / finalize: `{ blobName, sas }`
257
258
 
258
- **Réponses**
259
- - Objet d'accès temporaire (create/renew) : `{ url, container, blobName, sas }`
260
- - Objet fichier avec lien d'accès (get/download) :
259
+ **Responses**
260
+ - Temporary access object (create/renew): `{ url, container, blobName, sas }`
261
+ - File object with access link (get/download):
261
262
  `{ url, container, blobName, fileName, size, mimeType, sas, lastModified }`
262
- - Objet fichier persisté (valeur du champ) :
263
+ - Persisted file object (field value):
263
264
  `{ url, container, blobName, fileName, size, mimeType, linkedBlobs, contentMD5, properties }`
264
- - Image : ajoute `thumb`, `thumbSmall`, `thumbLarge` (chacun un objet fichier).
265
- - Vidéo : ajoute `video { id, isReady, transcriptsEnabled, transcripts, transcriptData }`
266
- et `encodings { <provider>: { hlsUrl, dashUrl, status, progress, timestamp } }`.
265
+ - Image: adds `thumb`, `thumbSmall`, `thumbLarge` (each a file object).
266
+ - Video: adds `video { id, isReady, transcriptsEnabled, transcripts, transcriptData }`
267
+ and `encodings { <provider>: { hlsUrl, dashUrl, status, progress, timestamp } }`.
267
268
 
268
- **Codes d'erreur de transfert** (corps de la réponse `400`) : `101` = fichier invalide
269
- (contenu ≠ MIME), `102` = type de fichier non supporté.
269
+ **Transfer error codes** (body of the `400` response): `101` = invalid file
270
+ (content ≠ MIME), `102` = unsupported file type.
270
271
 
271
272
  ---
272
273
 
273
- ## 7. Exemple de bout en bout
274
+ ## 7. End-to-end example
274
275
 
275
- Entité `person`, champ `prop3` de type `file`.
276
+ Entity `person`, field `prop3` of type `file`.
276
277
 
277
- 1. Créer l'emplacement temporaire :
278
+ 1. Create the temporary location:
278
279
  ```
279
280
  POST {eventId}/person/file
280
281
  { "entity": "person", "field": "prop3", "name": "photo.jpg", "type": "image/jpeg" }
281
282
  → 200 { "blobName": "<blobName>", "container": "tmp", "url": "...", "sas": "..." }
282
283
  ```
283
- 2. Téléverser les octets sur `url + /tmp/<blobName> + sas`.
284
- 3. Finaliser : `POST {eventId}/person/file/finalize-tmp { "blobName": "<blobName>", "sas": "..." }` → `200`.
285
- 4. Éditer la personne : `PATCH {eventId}/person/<personId> { "prop3": "<blobName>" }` → `200`.
286
- → le fichier est déplacé de `tmp` vers le conteneur `{eventId}_person-prop3`, et la valeur
287
- du champ est relue comme objet fichier complet.
284
+ 2. Upload the bytes to `url + /tmp/<blobName> + sas`.
285
+ 3. Finalize: `POST {eventId}/person/file/finalize-tmp { "blobName": "<blobName>", "sas": "..." }` → `200`.
286
+ 4. Edit the person: `PATCH {eventId}/person/<personId> { "prop3": "<blobName>" }` → `200`.
287
+ → the file is moved from `tmp` to the `{eventId}_person-prop3` container, and the field
288
+ value is read back as a full file object.
288
289
 
289
290
  ---
290
291
 
291
- ## 8. Points d'attention
292
+ ## 8. Caveats
292
293
 
293
- - **Double validation MIME** : au `finalize-tmp` (contenu vs MIME déclaré) **et** à la
294
- sauvegarde de l'entité (taille max, MIME autorisés du champ, cohérence du contenu, sûreté
295
- SVG). Un fichier invalide fait échouer/vider le champ.
296
- - **Emplacement définitif** = conteneur `{scopeId}_{entity}-{field}` (minuscules) — utile
297
- pour interpréter les URLs retournées.
298
- - **Langue** : pour un champ localisé, transmettre `lang` en lecture.
299
- - **`withoutExpiration=true`** : lien de longue durée (à réserver aux usages qui le justifient).
300
- - **Remplacement** : ré-uploader et ré-éditer suffit ; l'ancien fichier est nettoyé automatiquement.
294
+ - **Double MIME validation**: at `finalize-tmp` (content vs declared MIME) **and** at
295
+ entity save (max size, field's allowed MIMEs, content consistency, SVG
296
+ safety). An invalid file makes the field fail/be emptied.
297
+ - **Final location** = container `{scopeId}_{entity}-{field}` (lowercase) — useful
298
+ for interpreting the returned URLs.
299
+ - **Language**: for a localized field, pass `lang` on read.
300
+ - **`withoutExpiration=true`**: long-lived link (reserve for uses that justify it).
301
+ - **Replacement**: re-uploading and re-editing is enough; the old file is cleaned up automatically.
301
302
 
302
303
  ---
303
304
 
304
- ## Note — FrontAPI (hors périmètre)
305
+ ## Note — FrontAPI (out of scope)
305
306
 
306
- Côté public/participant, un flux équivalent existe mais avec des routes et une auth
307
- différentes : `POST {scopeId}/file` (+ `/renew`, `/try-renew`, `/finalize-tmp`), lecture
308
- `GET community/{communityId}/file/{Entity}/{entityId}/{field}`, en accès anonyme avec
309
- contrôles par droits/groupes (peut renvoyer `403` selon l'appartenance).
307
+ On the public/participant side, an equivalent flow exists but with different routes and
308
+ auth: `POST {scopeId}/file` (+ `/renew`, `/try-renew`, `/finalize-tmp`), read
309
+ `GET community/{communityId}/file/{Entity}/{entityId}/{field}`, with anonymous access and
310
+ rights/group checks (may return `403` depending on membership).
310
311
 
311
312
  ---
312
313
 
313
- *Voir aussi : `assets` (bibliothèque médias — mêmes primitives d'upload, avec identité propre et `inwinkasset://`), `mutations`.*
314
+ *See also: [assets.md](./assets.md) (media library — same upload primitives, with its own identity and `inwinkasset://`), [update.md](./update.md).*
@@ -0,0 +1,68 @@
1
+ ◂ Back to [README](./README.md)
2
+
3
+ # History: past versions and deleted entities
4
+
5
+ "With History" entities (see [entities.md](./entities.md)) are stored in **SQL Server temporal tables**: every modification archives the previous version in a history table. Three read endpoints, same request body as `query` / `count`:
6
+
7
+ | Action | Route | Purpose |
8
+ |---|---|---|
9
+ | Past versions | `POST {api}/{shardId}/{entity}/history/query` | Successive snapshots of an entity (still existing or not) |
10
+ | Deleted entities | `POST {api}/{shardId}/{entity}/history/removed/query` | Last snapshot of entities that no longer exist |
11
+ | Count deleted | `POST {api}/{shardId}/{entity}/history/removed/count` | Integer |
12
+
13
+ ## Anatomy of a history row
14
+
15
+ A row is a **full snapshot** of the entity over a period, not a diff:
16
+
17
+ - `validFrom` — start of validity of this version (UTC). On the current table, it is the last modification date.
18
+ - `validTo` — end of validity, i.e. the instant of the next modification (UTC).
19
+ - `modifiedById` (UUID) and `modifiedByDisplayName` (string) — author of this version, on entities that track their author (Person, Event…). Set to `"anonymous"`, an application id or `"unknown"` depending on the context of the modification.
20
+ - All other fields hold the value they had during the period, custom fields included.
21
+
22
+ To find out **what changed**, compare two consecutive snapshots (sorted by `validFrom`).
23
+
24
+ ## The current version
25
+
26
+ The history holds the versions that were **replaced**. While the entity exists, its current version stays in the current table: read it with [query](./query.md). The most recent history row then ends at its `validTo`, which is the `validFrom` of the current row, i.e. the date of the last modification. A deleted entity has no current row: its last snapshot records the deletion (see "Deleted entities").
27
+
28
+ To find out whether an entity changed since a date, compare that date with the `validFrom` of the current row. The history alone cannot tell: when no version starts after that date, the last modification may still be later, because its result is the current row. When [query](./query.md) finds no row for the id, the entity may have been deleted: `history/removed/query` returns its last snapshot.
29
+
30
+ ## Deleted entities
31
+
32
+ Before deleting a row, the API writes one last version: on the last snapshot of a deleted entity, `validFrom` is the instant of deletion and `modifiedBy*` identifies who deleted it.
33
+
34
+ ## Examples
35
+
36
+ Versions of an attendee, most recent first:
37
+
38
+ ```json
39
+ {
40
+ "filters": { "id": "3f2a…" },
41
+ "orders": [ { "desc": true, "value": { "validFrom": {} } } ],
42
+ "selects": { "$all": false, "mail": true, "company": true, "validFrom": true, "validTo": true, "modifiedByDisplayName": true },
43
+ "page": { "index": 0, "size": 50 }
44
+ }
45
+ ```
46
+
47
+ Attendees deleted since a date, and by whom:
48
+
49
+ ```json
50
+ {
51
+ "filters": { "validFrom": { "$gte": "2024-06-01T00:00:00Z" } },
52
+ "orders": [ { "desc": true, "value": { "validFrom": {} } } ],
53
+ "selects": { "$all": false, "mail": true, "firstname": true, "lastname": true, "validFrom": true, "modifiedByDisplayName": true },
54
+ "page": { "index": 0, "size": 50 }
55
+ }
56
+ ```
57
+
58
+ ## Limits
59
+
60
+ - **Cost**: history tables are large and history is never purged. Always filter on `id` (versions) or bound `validFrom` (deleted entities).
61
+ - **Flat projection only**: related entities cannot be expanded in `selects` on these endpoints.
62
+ - **Sorting**: no default sort; offset pagination is only stable with an explicit `orders`.
63
+ - **Coverage**: only "With History" entities expose these routes. A 404 means the entity has no history.
64
+ - `page` is required, `size` ≤ 300.
65
+
66
+ ---
67
+
68
+ *See also: [query.md](./query.md), [query-language.md](./query-language.md), [entities.md](./entities.md).*
@@ -1,4 +1,4 @@
1
- ◂ Retour à [README](./README.md)
1
+ ◂ Back to [README](./README.md)
2
2
 
3
3
  # Import (bulk create/update)
4
4
 
@@ -19,6 +19,6 @@ POST {api}/{shardId}/{entity}/import
19
19
  }
20
20
  ```
21
21
 
22
- - If an entity includes `id`, it's treated as an **update**.
23
- - If no `id` is present, the `identifiers` map is used as a unique constraint to determine create vs. update. In the example above, `mail` is used to match existing records.
24
- - This is much more performant than individual create/edit calls.
22
+ - If an entity includes `id`, it is treated as an **update**.
23
+ - Without `id`, the `identifiers` map acts as a uniqueness constraint to decide create vs. update. Above, `mail` is used to match existing records.
24
+ - Much faster than individual create/edit calls.