@communecter/cocolight-api-client 1.0.134 → 1.0.135

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communecter/cocolight-api-client",
3
- "version": "1.0.134",
3
+ "version": "1.0.135",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
package/src/api/Answer.ts CHANGED
@@ -167,6 +167,79 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
167
167
  "voteCount", "vote", "project"
168
168
  ];
169
169
 
170
+ /**
171
+ * {@inheritDoc BaseEntity#isAuthor}
172
+ *
173
+ * Override Answer : le champ d'autorité est `serverData.user` (pas `creator`
174
+ * comme la version par défaut de BaseEntity). Le `user` peut être :
175
+ * - un `string` (ID MongoDB brut renvoyé par le backend)
176
+ * - une instance `User` (après `_transformServerData` qui le link en entité)
177
+ *
178
+ * Les pré-conditions (connexion, id, serverData) sont vérifiées comme dans
179
+ * la version de base — `silent: true` (défaut) renvoie `false` au lieu de throw.
180
+ */
181
+ /**
182
+ * {@inheritDoc BaseEntity#_isAdminViaHierarchy}
183
+ *
184
+ * Override Answer : la hiérarchie d'Answer passe par son **parent instance
185
+ * Form** (pas par `serverData.parent` direct comme pour `events`/`projects`/`poi`).
186
+ *
187
+ * Chaîne : `Answer → parent (Form) → form.serverData.parent ({[orgId]: {type, name}}) → check userContext.links`.
188
+ *
189
+ * Si le parent n'est pas un Form (cas `api.answer({id})` direct sans contexte),
190
+ * renvoie `false` — impossible de vérifier la hiérarchie sans le Form.
191
+ *
192
+ * @protected
193
+ */
194
+ protected override _isAdminViaHierarchy(): boolean {
195
+ const form = this.parent;
196
+ if (!form || typeof (form as any).getEntityType !== "function"
197
+ || (form as any).getEntityType() !== "forms") {
198
+ return false;
199
+ }
200
+
201
+ const formParents = (form as { serverData?: { parent?: Record<string, unknown> } }).serverData?.parent;
202
+ if (!formParents || typeof formParents !== "object") {
203
+ return false;
204
+ }
205
+
206
+ for (const [parentId, parentRef] of Object.entries(formParents)) {
207
+ if (!parentRef || typeof parentRef !== "object") continue;
208
+ const ref = parentRef as { type?: string };
209
+ if (!ref.type) continue;
210
+ if (this._isAdminOfParent(parentId, ref.type)) {
211
+ return true;
212
+ }
213
+ }
214
+ return false;
215
+ }
216
+
217
+ override isAuthor(options?: { silent?: boolean }): boolean {
218
+ const silent = options?.silent ?? true;
219
+ try {
220
+ // Note : on utilise `isConnected` (user logged in) au lieu de `isMe`
221
+ // (l'entité IS le user logged in) qui est sémantiquement faux sur Answer.
222
+ // `_checkAccess` du parent est private et utilise isMe — non réutilisable ici.
223
+ if (!this.isConnected) throw new ApiError("Vous devez être connecté pour vérifier l'auteur.", 401);
224
+ if (!this.id) throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
225
+ if (!this.serverData) throw new ApiError("Aucune donnée serveur disponible.", 404);
226
+ if (!this.userId) throw new ApiError("Utilisateur non connecté.", 401);
227
+ } catch (e) {
228
+ if (silent) return false;
229
+ throw e;
230
+ }
231
+
232
+ const user = this._serverData.user;
233
+ if (!user) return false;
234
+
235
+ const authorId = typeof user === "string" ? user : (user as { id?: string }).id;
236
+ return Boolean(
237
+ this.userId
238
+ && typeof authorId === "string"
239
+ && authorId === this.userId
240
+ );
241
+ }
242
+
170
243
  /**
171
244
  * Transforme les champs imbriqués (user, context etc.) en instances d'entités.
172
245
  * @param data - Les données brutes du serveur.
@@ -269,12 +342,26 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
269
342
  * - `serverData` et `draftData` sont vidés (sans casser la réactivité)
270
343
  * - `_isDeleted = true` → toute mutation ultérieure (`save`, `get`, etc.) throw
271
344
  *
345
+ * **Guard** : autorisation côté client si l'utilisateur courant est :
346
+ * - l'**auteur** de l'Answer (`serverData.user === userId`), OU
347
+ * - **admin** d'un parent du Form (org/project) via la hiérarchie
348
+ * (`form.serverData.parent[orgId]` × `userContext.links.memberOf[orgId].isAdmin`).
349
+ *
350
+ * Le check utilise `isAuthorOrAdmin({checkHierarchy: true})` qui s'appuie sur
351
+ * l'override `Answer._isAdminViaHierarchy` (remonte via parent instance Form).
352
+ *
353
+ * **Pré-requis** :
354
+ * - `this.parent` doit être un Form (cas `form.answer({id})`)
355
+ * - `userContext` (User connecté) doit avoir `serverData.links` chargés (cas `api.me()`)
356
+ * Sans ces pré-requis, seul `isAuthor` peut autoriser.
357
+ *
272
358
  * @param reason - Raison libre transmise au backend (audit). Défaut : `"delete coform answer"`.
273
359
  * @throws {ApiError} 400 si l'Answer n'a pas d'id (jamais sauvegardée).
360
+ * @throws {ApiError} 403 si ni auteur ni admin du parent.
274
361
  *
275
362
  * @example
276
363
  * const answer = await form.answer({ id: "..." });
277
- * await answer.delete();
364
+ * await answer.delete(); // OK si auteur OU admin org/project parent du Form
278
365
  * answer._isDeleted; // true
279
366
  */
280
367
  async delete(reason: string = "delete coform answer"): Promise<void> {
@@ -282,6 +369,15 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
282
369
  throw new ApiError("Vous devez fournir un id pour supprimer une Answer.", 400);
283
370
  }
284
371
 
372
+ // Guard : auteur OU admin du parent Form (org/project).
373
+ if (!this.isAuthorOrAdmin({ checkHierarchy: true })) {
374
+ throw new ApiError(
375
+ "Suppression refusée — vous devez être l'auteur de l'Answer "
376
+ + "ou administrateur du parent (organisation/projet) du Form.",
377
+ 403
378
+ );
379
+ }
380
+
285
381
  const data: DeleteElementData = {
286
382
  reason,
287
383
  pathParams: { type: "answers", id: this.id },
@@ -296,6 +392,54 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
296
392
  this._isDeleted = true;
297
393
  }
298
394
 
395
+ /**
396
+ * {@inheritDoc BaseEntity#deleteFile}
397
+ *
398
+ * Override avec **cleanup local** : après suppression backend, retire le
399
+ * `docId` des structures uploader normalisées dans `serverData.answers` et
400
+ * `_draftData.answers` (format `{updateDate, files: {docId: docPath}}`
401
+ * produit par `processUploads()` et `_transformServerData`).
402
+ *
403
+ * Le caller n'a pas besoin de refetch — `answer.serverData.answers[*][*].files`
404
+ * reflète directement l'état post-suppression.
405
+ *
406
+ * @example
407
+ * const files = await answer.getFiles({ subKey: "sf.input", docType: "image" });
408
+ * await answer.deleteFile(files[0].docId);
409
+ * // answer.serverData.answers[sf][input].files[files[0].docId] === undefined
410
+ */
411
+ override async deleteFile(docId: string): Promise<void> {
412
+ await super.deleteFile(docId);
413
+
414
+ // Cleanup local : retire le docId des structures uploader normalisées
415
+ this._removeDocIdFromUploaderFields(this._serverData.answers, docId);
416
+ this._removeDocIdFromUploaderFields(this._draftData.answers as Record<string, unknown> | undefined, docId);
417
+ }
418
+
419
+ /**
420
+ * Walk `answers[subFormId][inputId]` et retire `docId` des structures
421
+ * `{updateDate, files: {docId: docPath}}` (format canonique uploader).
422
+ * Mutation in-place pour préserver la réactivité.
423
+ * @private
424
+ */
425
+ private _removeDocIdFromUploaderFields(
426
+ answers: Record<string, unknown> | undefined,
427
+ docId: string,
428
+ ): void {
429
+ if (!answers || typeof answers !== "object") return;
430
+
431
+ for (const subFormData of Object.values(answers)) {
432
+ if (!this._isObjectRecord(subFormData)) continue;
433
+ for (const inputValue of Object.values(subFormData)) {
434
+ if (!this._isObjectRecord(inputValue)) continue;
435
+ const files = (inputValue as { files?: unknown }).files;
436
+ if (this._isObjectRecord(files) && docId in files) {
437
+ delete (files as Record<string, unknown>)[docId];
438
+ }
439
+ }
440
+ }
441
+ }
442
+
299
443
  /**
300
444
  * Récupère la liste des fichiers attachés à un input précis de cette Answer
301
445
  * via `COFORM_GET_ANSWER_FILES`.
@@ -376,6 +520,13 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
376
520
  /**
377
521
  * Upload un fichier rattaché à cette Answer via `COFORM_UPLOAD_ANSWER_FILE`.
378
522
  *
523
+ * **Building block du pipeline** : conçu pour être utilisé via
524
+ * `processUploads()` suivi de `save()`. L'Answer créée par le premier upload
525
+ * est en état "placeholder" côté backend (champ `user` non posé) — `save()`
526
+ * finalise. Appeler `get()` ou `delete()` (via l'entité) sur une Answer
527
+ * upload-only sans save échouera : le backend la considère comme orpheline
528
+ * (`editDeniedReason: "not_owner"`, stub sans `data.id`).
529
+ *
379
530
  * **Cas premier upload (Answer sans id)** : le backend crée l'Answer et
380
531
  * renvoie `answerId`. La méthode pose alors `this._draftData.id = answerId`
381
532
  * — les uploads suivants utiliseront cet id, et un `save()` ultérieur passera
@@ -58,7 +58,8 @@ import type {
58
58
  LinkMediawikiAccountData,
59
59
  UnlinkMediawikiAccountData,
60
60
  GetMediawikiContributionsData,
61
- CoremuOperationData
61
+ CoremuOperationData,
62
+ DeleteDocumentByIdData
62
63
  } from "./EndpointApi.types.js";
63
64
  import type { GetElementsKeyResponse } from "../types/api-responses.js";
64
65
  import type { TransformsMap } from "../types/entities.js";
@@ -1225,6 +1226,39 @@ export class BaseEntity<TServerData = any> {
1225
1226
  throw new ApiError("Type de fichier non reconnu pour l'upload.", 400);
1226
1227
  }
1227
1228
 
1229
+ /**
1230
+ * Supprime un document (fichier ou image) par son `docId` MongoDB via
1231
+ * `DELETE_DOCUMENT_BY_ID`.
1232
+ *
1233
+ * Méthode générique exposée sur toutes les entités — le endpoint backend
1234
+ * opère par docId seul, sans contexte parent requis. Disponible ici pour
1235
+ * être découvrable et **override-able** par les entités qui veulent un
1236
+ * cleanup local après suppression (ex: `News.deleteFile` pourrait retirer
1237
+ * l'id de `mediaImg.images` côté serverData).
1238
+ *
1239
+ * Ne met PAS à jour `serverData` après suppression — le caller doit
1240
+ * invalider son cache local s'il en a un (cas typique : React Query).
1241
+ *
1242
+ * @param docId - ID MongoDB du document (24 hex)
1243
+ * @throws {ApiError} 400 si `docId` invalide.
1244
+ *
1245
+ * @example
1246
+ * // Depuis une Answer (suppression d'un fichier uploader)
1247
+ * const files = await answer.getFiles({ subKey: "sf.input", docType: "image" });
1248
+ * await answer.deleteFile(files[0].docId);
1249
+ *
1250
+ * @example
1251
+ * // Depuis n'importe quelle entité qui possède un docId
1252
+ * await news.deleteFile(imageDocId);
1253
+ */
1254
+ async deleteFile(docId: string): Promise<void> {
1255
+ if (typeof docId !== "string" || docId.length !== 24) {
1256
+ throw new ApiError("docId requis (24 hex) pour supprimer un document.", 400);
1257
+ }
1258
+ const payload: DeleteDocumentByIdData = { pathParams: { id: docId } };
1259
+ await this.callIsConnected(() => this.endpointApi.deleteDocumentById(payload));
1260
+ }
1261
+
1228
1262
  /**
1229
1263
  * Valide les entrées d'upload de fichiers.
1230
1264
  *
@@ -2505,10 +2539,15 @@ export class BaseEntity<TServerData = any> {
2505
2539
  }
2506
2540
 
2507
2541
  /**
2508
- * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste)
2509
- * @private
2542
+ * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste).
2543
+ *
2544
+ * Note : passé `protected` pour permettre l'override par les entités dont la
2545
+ * hiérarchie n'est pas un simple champ `serverData.parent` ou `.organizer`
2546
+ * (cf. `Answer._isAdminViaHierarchy` qui remonte via son parent instance Form).
2547
+ *
2548
+ * @protected
2510
2549
  */
2511
- private _isAdminViaHierarchy(): boolean {
2550
+ protected _isAdminViaHierarchy(): boolean {
2512
2551
  const entityType = this.getEntityType();
2513
2552
  const entityData = this.serverData as any;
2514
2553
 
@@ -2545,10 +2584,14 @@ export class BaseEntity<TServerData = any> {
2545
2584
  }
2546
2585
 
2547
2586
  /**
2548
- * Vérifie si l'utilisateur est admin d'un parent spécifique
2549
- * @private
2587
+ * Vérifie si l'utilisateur est admin d'un parent spécifique.
2588
+ *
2589
+ * Note : passé `protected` pour être utilisable par les overrides de
2590
+ * `_isAdminViaHierarchy()` (cf. `Answer._isAdminViaHierarchy`).
2591
+ *
2592
+ * @protected
2550
2593
  */
2551
- private _isAdminOfParent(parentId: string, parentType: string): boolean {
2594
+ protected _isAdminOfParent(parentId: string, parentType: string): boolean {
2552
2595
  const userLinks = this.userContext?.serverData?.links;
2553
2596
  if (!userLinks) return false;
2554
2597
 
@@ -4152,10 +4195,13 @@ export class BaseEntity<TServerData = any> {
4152
4195
  *
4153
4196
  * @param options - Options de vérification.
4154
4197
  * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
4198
+ * @param options.checkHierarchy - Si `true`, propagé à `isAdmin` pour vérifier
4199
+ * également la hiérarchie parent (ex: `Answer.isAdmin({checkHierarchy: true})`
4200
+ * remonte vers org/project du Form parent).
4155
4201
  * @returns - `true` si l'utilisateur est l'auteur ou administrateur, `false` sinon.
4156
4202
  * @throws {ApiError} - Si `silent` est `false` et que les préconditions ne sont pas remplies.
4157
4203
  */
4158
- isAuthorOrAdmin(options?: { silent?: boolean }): boolean {
4204
+ isAuthorOrAdmin(options?: { silent?: boolean; checkHierarchy?: boolean }): boolean {
4159
4205
  return this.isAuthor() || this.isAdmin(options);
4160
4206
  }
4161
4207
 
@@ -136,6 +136,34 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
136
136
  * Les autres sont calculés/posés par le backend.
137
137
  */
138
138
  removeFields: string[];
139
+ /**
140
+ * {@inheritDoc BaseEntity#isAuthor}
141
+ *
142
+ * Override Answer : le champ d'autorité est `serverData.user` (pas `creator`
143
+ * comme la version par défaut de BaseEntity). Le `user` peut être :
144
+ * - un `string` (ID MongoDB brut renvoyé par le backend)
145
+ * - une instance `User` (après `_transformServerData` qui le link en entité)
146
+ *
147
+ * Les pré-conditions (connexion, id, serverData) sont vérifiées comme dans
148
+ * la version de base — `silent: true` (défaut) renvoie `false` au lieu de throw.
149
+ */
150
+ /**
151
+ * {@inheritDoc BaseEntity#_isAdminViaHierarchy}
152
+ *
153
+ * Override Answer : la hiérarchie d'Answer passe par son **parent instance
154
+ * Form** (pas par `serverData.parent` direct comme pour `events`/`projects`/`poi`).
155
+ *
156
+ * Chaîne : `Answer → parent (Form) → form.serverData.parent ({[orgId]: {type, name}}) → check userContext.links`.
157
+ *
158
+ * Si le parent n'est pas un Form (cas `api.answer({id})` direct sans contexte),
159
+ * renvoie `false` — impossible de vérifier la hiérarchie sans le Form.
160
+ *
161
+ * @protected
162
+ */
163
+ protected _isAdminViaHierarchy(): boolean;
164
+ isAuthor(options?: {
165
+ silent?: boolean;
166
+ }): boolean;
139
167
  /**
140
168
  * Transforme les champs imbriqués (user, context etc.) en instances d'entités.
141
169
  * @param data - Les données brutes du serveur.
@@ -183,15 +211,53 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
183
211
  * - `serverData` et `draftData` sont vidés (sans casser la réactivité)
184
212
  * - `_isDeleted = true` → toute mutation ultérieure (`save`, `get`, etc.) throw
185
213
  *
214
+ * **Guard** : autorisation côté client si l'utilisateur courant est :
215
+ * - l'**auteur** de l'Answer (`serverData.user === userId`), OU
216
+ * - **admin** d'un parent du Form (org/project) via la hiérarchie
217
+ * (`form.serverData.parent[orgId]` × `userContext.links.memberOf[orgId].isAdmin`).
218
+ *
219
+ * Le check utilise `isAuthorOrAdmin({checkHierarchy: true})` qui s'appuie sur
220
+ * l'override `Answer._isAdminViaHierarchy` (remonte via parent instance Form).
221
+ *
222
+ * **Pré-requis** :
223
+ * - `this.parent` doit être un Form (cas `form.answer({id})`)
224
+ * - `userContext` (User connecté) doit avoir `serverData.links` chargés (cas `api.me()`)
225
+ * Sans ces pré-requis, seul `isAuthor` peut autoriser.
226
+ *
186
227
  * @param reason - Raison libre transmise au backend (audit). Défaut : `"delete coform answer"`.
187
228
  * @throws {ApiError} 400 si l'Answer n'a pas d'id (jamais sauvegardée).
229
+ * @throws {ApiError} 403 si ni auteur ni admin du parent.
188
230
  *
189
231
  * @example
190
232
  * const answer = await form.answer({ id: "..." });
191
- * await answer.delete();
233
+ * await answer.delete(); // OK si auteur OU admin org/project parent du Form
192
234
  * answer._isDeleted; // true
193
235
  */
194
236
  delete(reason?: string): Promise<void>;
237
+ /**
238
+ * {@inheritDoc BaseEntity#deleteFile}
239
+ *
240
+ * Override avec **cleanup local** : après suppression backend, retire le
241
+ * `docId` des structures uploader normalisées dans `serverData.answers` et
242
+ * `_draftData.answers` (format `{updateDate, files: {docId: docPath}}`
243
+ * produit par `processUploads()` et `_transformServerData`).
244
+ *
245
+ * Le caller n'a pas besoin de refetch — `answer.serverData.answers[*][*].files`
246
+ * reflète directement l'état post-suppression.
247
+ *
248
+ * @example
249
+ * const files = await answer.getFiles({ subKey: "sf.input", docType: "image" });
250
+ * await answer.deleteFile(files[0].docId);
251
+ * // answer.serverData.answers[sf][input].files[files[0].docId] === undefined
252
+ */
253
+ deleteFile(docId: string): Promise<void>;
254
+ /**
255
+ * Walk `answers[subFormId][inputId]` et retire `docId` des structures
256
+ * `{updateDate, files: {docId: docPath}}` (format canonique uploader).
257
+ * Mutation in-place pour préserver la réactivité.
258
+ * @private
259
+ */
260
+ private _removeDocIdFromUploaderFields;
195
261
  /**
196
262
  * Récupère la liste des fichiers attachés à un input précis de cette Answer
197
263
  * via `COFORM_GET_ANSWER_FILES`.
@@ -223,6 +289,13 @@ export declare class Answer extends BaseEntity<AnswerItemNormalized> {
223
289
  /**
224
290
  * Upload un fichier rattaché à cette Answer via `COFORM_UPLOAD_ANSWER_FILE`.
225
291
  *
292
+ * **Building block du pipeline** : conçu pour être utilisé via
293
+ * `processUploads()` suivi de `save()`. L'Answer créée par le premier upload
294
+ * est en état "placeholder" côté backend (champ `user` non posé) — `save()`
295
+ * finalise. Appeler `get()` ou `delete()` (via l'entité) sur une Answer
296
+ * upload-only sans save échouera : le backend la considère comme orpheline
297
+ * (`editDeniedReason: "not_owner"`, stub sans `data.id`).
298
+ *
226
299
  * **Cas premier upload (Answer sans id)** : le backend crée l'Answer et
227
300
  * renvoie `answerId`. La méthode pose alors `this._draftData.id = answerId`
228
301
  * — les uploads suivants utiliseront cet id, et un `save()` ultérieur passera
@@ -562,6 +562,32 @@ export declare class BaseEntity<TServerData = any> {
562
562
  qqfilename: string;
563
563
  qqtotalfilesize: number;
564
564
  }>;
565
+ /**
566
+ * Supprime un document (fichier ou image) par son `docId` MongoDB via
567
+ * `DELETE_DOCUMENT_BY_ID`.
568
+ *
569
+ * Méthode générique exposée sur toutes les entités — le endpoint backend
570
+ * opère par docId seul, sans contexte parent requis. Disponible ici pour
571
+ * être découvrable et **override-able** par les entités qui veulent un
572
+ * cleanup local après suppression (ex: `News.deleteFile` pourrait retirer
573
+ * l'id de `mediaImg.images` côté serverData).
574
+ *
575
+ * Ne met PAS à jour `serverData` après suppression — le caller doit
576
+ * invalider son cache local s'il en a un (cas typique : React Query).
577
+ *
578
+ * @param docId - ID MongoDB du document (24 hex)
579
+ * @throws {ApiError} 400 si `docId` invalide.
580
+ *
581
+ * @example
582
+ * // Depuis une Answer (suppression d'un fichier uploader)
583
+ * const files = await answer.getFiles({ subKey: "sf.input", docType: "image" });
584
+ * await answer.deleteFile(files[0].docId);
585
+ *
586
+ * @example
587
+ * // Depuis n'importe quelle entité qui possède un docId
588
+ * await news.deleteFile(imageDocId);
589
+ */
590
+ deleteFile(docId: string): Promise<void>;
565
591
  /**
566
592
  * Valide les entrées d'upload de fichiers.
567
593
  *
@@ -938,15 +964,24 @@ export declare class BaseEntity<TServerData = any> {
938
964
  */
939
965
  private _extractUserContext;
940
966
  /**
941
- * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste)
942
- * @private
967
+ * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste).
968
+ *
969
+ * Note : passé `protected` pour permettre l'override par les entités dont la
970
+ * hiérarchie n'est pas un simple champ `serverData.parent` ou `.organizer`
971
+ * (cf. `Answer._isAdminViaHierarchy` qui remonte via son parent instance Form).
972
+ *
973
+ * @protected
943
974
  */
944
- private _isAdminViaHierarchy;
975
+ protected _isAdminViaHierarchy(): boolean;
945
976
  /**
946
- * Vérifie si l'utilisateur est admin d'un parent spécifique
947
- * @private
977
+ * Vérifie si l'utilisateur est admin d'un parent spécifique.
978
+ *
979
+ * Note : passé `protected` pour être utilisable par les overrides de
980
+ * `_isAdminViaHierarchy()` (cf. `Answer._isAdminViaHierarchy`).
981
+ *
982
+ * @protected
948
983
  */
949
- private _isAdminOfParent;
984
+ protected _isAdminOfParent(parentId: string, parentType: string): boolean;
950
985
  /**
951
986
  * Construit dynamiquement des filtres sur un lien entre entités
952
987
  *
@@ -1503,11 +1538,15 @@ export declare class BaseEntity<TServerData = any> {
1503
1538
  *
1504
1539
  * @param options - Options de vérification.
1505
1540
  * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
1541
+ * @param options.checkHierarchy - Si `true`, propagé à `isAdmin` pour vérifier
1542
+ * également la hiérarchie parent (ex: `Answer.isAdmin({checkHierarchy: true})`
1543
+ * remonte vers org/project du Form parent).
1506
1544
  * @returns - `true` si l'utilisateur est l'auteur ou administrateur, `false` sinon.
1507
1545
  * @throws {ApiError} - Si `silent` est `false` et que les préconditions ne sont pas remplies.
1508
1546
  */
1509
1547
  isAuthorOrAdmin(options?: {
1510
1548
  silent?: boolean;
1549
+ checkHierarchy?: boolean;
1511
1550
  }): boolean;
1512
1551
  /**
1513
1552
  * Vérifie si l'utilisateur est membre de l'entité.