@communecter/cocolight-api-client 1.0.103 → 1.0.105

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.103",
3
+ "version": "1.0.105",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -548,7 +548,7 @@ export class BaseEntity<TServerData = any> {
548
548
  */
549
549
  protected _setData(newData: TServerData, { forceInitialDraftReset = false }: { forceInitialDraftReset?: boolean } = {}): void {
550
550
  if (this.userContext && this.userContext !== (this as object)) {
551
- this.apiClient._logger?.info?.(`[${this.__entityTag}] Mise à jour liée à userContext : ${this.userContext.id}`);
551
+ this.apiClient._logger?.debug?.(`[${this.__entityTag}] Mise à jour liée à userContext : ${this.userContext.id}`);
552
552
  }
553
553
 
554
554
  const transformed = this._transformServerData(newData);
@@ -718,13 +718,31 @@ export class BaseEntity<TServerData = any> {
718
718
  *
719
719
  * @returns Représentation JSON de l'instance.
720
720
  */
721
- toJSON(): { __entityTag: string; __isSerializedEntity: boolean; serverData: any; parent: { id?: string; type?: string; __entityTag?: string; slug?: string } | null } {
722
- const parentMeta: { id?: string; type?: string; __entityTag?: string; slug?: string } = {};
721
+ toJSON(): { __entityTag: string; __isSerializedEntity: boolean; serverData: any; parent: any } {
722
+ const parentMeta: any = {};
723
723
  if (this.parent?.id) parentMeta.id = this.parent.id;
724
- if (typeof this.parent?.getEntityType === "function") parentMeta.type = this.parent.getEntityType();
724
+ if (typeof this.parent?.getEntityType === "function") {
725
+ parentMeta.type = this.parent.getEntityType();
726
+ }
725
727
  if (this.parent?.__entityTag) parentMeta.__entityTag = this.parent.__entityTag;
726
728
  if (this.parent?.slug) parentMeta.slug = this.parent.slug;
727
729
 
730
+ // Ajouter serverData minimales si présentes
731
+ if (this.parent?.serverData) {
732
+ parentMeta.serverData = {
733
+ id: this.parent.serverData.id,
734
+ collection: this.parent.serverData.collection,
735
+ slug: this.parent.serverData.slug,
736
+ // Inclure le name si présent
737
+ ...(this.parent.serverData.name && { name: this.parent.serverData.name }),
738
+ // Inclure parent et organizer si présents (pour la hiérarchie)
739
+ ...(this.parent.serverData.parent && { parent: this.parent.serverData.parent }),
740
+ ...(this.parent.serverData.organizer && { organizer: this.parent.serverData.organizer }),
741
+ // Inclure les liens nécessaires pour isAdmin()
742
+ ...(this.parent.serverData.links && { links: this.parent.serverData.links })
743
+ };
744
+ }
745
+
728
746
  return {
729
747
  __entityTag: this.__entityTag,
730
748
  __isSerializedEntity: true,
@@ -2139,6 +2157,10 @@ export class BaseEntity<TServerData = any> {
2139
2157
 
2140
2158
  if(data.contextId && data.contextType) {
2141
2159
  const entity = await this.entity(data.contextType, { id: data.contextId });
2160
+
2161
+ // Corriger le userContext si nécessaire
2162
+ await this._fixEntityUserContext(entity);
2163
+
2142
2164
  return entity;
2143
2165
  } else {
2144
2166
  throw new ApiResponseError(`Le slug ${slug} n'est pas valide`, 200, data as object);
@@ -2152,6 +2174,202 @@ export class BaseEntity<TServerData = any> {
2152
2174
  }
2153
2175
  }
2154
2176
 
2177
+ /**
2178
+ * Corriger le userContext d'une entité si elle a un vrai parent différent
2179
+ * @param entity - L'entité à corriger
2180
+ * @private
2181
+ */
2182
+ private async _fixEntityUserContext(entity: AnyEntity): Promise<void> {
2183
+ const entityType = entity.getEntityType();
2184
+
2185
+ // Seulement pour les entités qui peuvent avoir des parents
2186
+ if (!["events", "projects", "poi"].includes(entityType)) {
2187
+ return;
2188
+ }
2189
+
2190
+ const realParentInfo = this._extractRealParent(entity);
2191
+ if (!realParentInfo) {
2192
+ return; // Pas de parent réel détecté, garder le parent actuel
2193
+ }
2194
+
2195
+ // Essayer de trouver le vrai parent dans le cache/contexte actuel
2196
+ const realParent = await this._findOrCreateParent(realParentInfo);
2197
+ if (realParent && realParent !== entity.parent) {
2198
+ // Corriger le userContext sans recréer l'entité
2199
+ entity.userContext = this._extractUserContext(realParent);
2200
+ entity.parent = realParent;
2201
+ }
2202
+ }
2203
+
2204
+ /**
2205
+ * Extraire l'info du vrai parent depuis les serverData
2206
+ * @param entity - L'entité à analyser
2207
+ * @private
2208
+ */
2209
+ private _extractRealParent(entity: AnyEntity): { type: string; id: string } | null {
2210
+ const serverData = entity.serverData;
2211
+
2212
+ // Pour les events : chercher dans organizer avec priorité project > organization > user
2213
+ if (entity.getEntityType() === "events") {
2214
+ const eventData = serverData as { organizer?: Record<string, { type: string; name?: string }> };
2215
+ if (eventData.organizer) {
2216
+ const organizers = Object.entries(eventData.organizer);
2217
+
2218
+ // Priorité 1: projects
2219
+ const projectOrganizer = organizers.find(([_, org]) => org.type === "projects");
2220
+ if (projectOrganizer) {
2221
+ return { type: "projects", id: projectOrganizer[0] };
2222
+ }
2223
+
2224
+ // Priorité 2: organizations
2225
+ const orgOrganizer = organizers.find(([_, org]) => org.type === "organizations");
2226
+ if (orgOrganizer) {
2227
+ return { type: "organizations", id: orgOrganizer[0] };
2228
+ }
2229
+
2230
+ // Priorité 3: users
2231
+ const userOrganizer = organizers.find(([_, org]) => org.type === "citoyens");
2232
+ if (userOrganizer) {
2233
+ return { type: "citoyens", id: userOrganizer[0] };
2234
+ }
2235
+
2236
+ // Fallback: premier organizer trouvé (autre type)
2237
+ const firstOrganizer = organizers[0];
2238
+ if (firstOrganizer && firstOrganizer[1]) {
2239
+ return { type: firstOrganizer[1].type, id: firstOrganizer[0] };
2240
+ }
2241
+ }
2242
+ }
2243
+
2244
+ // Pour projects, poi : chercher dans parent
2245
+ const entityDataWithParent = serverData as { parent?: Record<string, { type: string; name?: string }> };
2246
+ if (entityDataWithParent.parent) {
2247
+ const firstParent = Object.entries(entityDataWithParent.parent)[0];
2248
+ if (firstParent && firstParent[1]) {
2249
+ return { type: firstParent[1].type, id: firstParent[0] };
2250
+ }
2251
+ }
2252
+
2253
+ return null;
2254
+ }
2255
+
2256
+ /**
2257
+ * Trouver ou créer le parent réel avec ses données complètes
2258
+ * @param parentInfo - Info du parent à créer
2259
+ * @private
2260
+ */
2261
+ private async _findOrCreateParent(parentInfo: { type: string; id: string }): Promise<AnyEntity | null> {
2262
+ try {
2263
+ // Vérifier si le parent existe déjà dans la hiérarchie actuelle
2264
+ let current = this.parent;
2265
+ while (current) {
2266
+ if (current.id === parentInfo.id && current.getEntityType() === parentInfo.type) {
2267
+ return current as AnyEntity;
2268
+ }
2269
+ current = current.parent;
2270
+ }
2271
+
2272
+ // Créer une instance du parent et appeler get() pour avoir les données complètes
2273
+ const entityTypeMap: Record<string, string> = {
2274
+ "citoyens": "citoyens",
2275
+ "organizations": "organizations",
2276
+ "projects": "projects"
2277
+ };
2278
+
2279
+ const entityType = entityTypeMap[parentInfo.type];
2280
+ if (entityType) {
2281
+ const parentEntity = this._entityInstanceData(entityType as any, { id: parentInfo.id });
2282
+ await parentEntity.get(); // Récupérer les données complètes du parent
2283
+ return parentEntity;
2284
+ }
2285
+
2286
+ return null;
2287
+ } catch (error) {
2288
+ this.apiClient._logger?.warn?.("Impossible de créer le parent réel:", error);
2289
+ return null;
2290
+ }
2291
+ }
2292
+
2293
+ /**
2294
+ * Extraire le bon userContext du parent
2295
+ * @param parent - Le parent dont on veut extraire le userContext
2296
+ * @private
2297
+ */
2298
+ private _extractUserContext(parent: AnyEntity): User | null {
2299
+ return this.getEntityTag(parent?.__entityTag) === "User" ? (parent as User) : (parent.userContext || null);
2300
+ }
2301
+
2302
+ /**
2303
+ * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste)
2304
+ * @private
2305
+ */
2306
+ private _isAdminViaHierarchy(): boolean {
2307
+ const entityType = this.getEntityType();
2308
+ const entityData = this.serverData as any;
2309
+
2310
+ // Déterminer le champ de hiérarchie selon le type d'entité
2311
+ let hierarchyField: Record<string, any> | undefined;
2312
+ if (entityType === "events") {
2313
+ hierarchyField = entityData?.organizer;
2314
+ } else if (entityType === "projects" || entityType === "poi") {
2315
+ hierarchyField = entityData?.parent;
2316
+ } else {
2317
+ // Organizations et autres types n'ont pas de hiérarchie parent
2318
+ return false;
2319
+ }
2320
+
2321
+ if (!hierarchyField || typeof hierarchyField !== "object") {
2322
+ return false;
2323
+ }
2324
+
2325
+ // Vérifier chaque parent dans la hiérarchie
2326
+ for (const [parentId, parentInfo] of Object.entries(hierarchyField)) {
2327
+ if (!parentInfo || typeof parentInfo !== "object") continue;
2328
+
2329
+ const parentRef = parentInfo as { type: string; name?: string };
2330
+ const parentType = parentRef.type;
2331
+ if (!parentType) continue;
2332
+
2333
+ // Vérifier les droits admin selon le type de parent
2334
+ if (this._isAdminOfParent(parentId, parentType)) {
2335
+ return true;
2336
+ }
2337
+ }
2338
+
2339
+ return false;
2340
+ }
2341
+
2342
+ /**
2343
+ * Vérifie si l'utilisateur est admin d'un parent spécifique
2344
+ * @private
2345
+ */
2346
+ private _isAdminOfParent(parentId: string, parentType: string): boolean {
2347
+ const userLinks = this.userContext?.serverData?.links;
2348
+ if (!userLinks) return false;
2349
+
2350
+ // Déterminer le type de lien selon le type de parent
2351
+ let linkType: string;
2352
+ if (parentType === "citoyens") {
2353
+ // Les citoyens ne peuvent pas être des parents admin (logique métier)
2354
+ return false;
2355
+ } else if (parentType === "organizations") {
2356
+ linkType = "memberOf";
2357
+ } else if (parentType === "projects") {
2358
+ linkType = "projects";
2359
+ } else {
2360
+ // Type de parent non supporté
2361
+ return false;
2362
+ }
2363
+
2364
+ const parentLinks = userLinks[linkType] as Record<string, any> | undefined;
2365
+ if (!parentLinks) return false;
2366
+
2367
+ const parentLink = parentLinks[parentId];
2368
+ return this._validateUserLink(parentLink) &&
2369
+ parentLink?.isAdmin === true &&
2370
+ !parentLink?.isAdminPending;
2371
+ }
2372
+
2155
2373
  /**
2156
2374
  * Construit dynamiquement des filtres sur un lien entre entités
2157
2375
  *
@@ -3063,8 +3281,7 @@ export class BaseEntity<TServerData = any> {
3063
3281
  "$or": {
3064
3282
  [`links.attendees.${this.id}`]: { "$exists": true },
3065
3283
  [`organizer.${this.id}`]: { "$exists": true }
3066
- },
3067
- [`links.attendees.${this.id}`]: { "$exists": true }
3284
+ }
3068
3285
  };
3069
3286
 
3070
3287
  return this.endpointApi.getEvents(finalData);
@@ -3538,8 +3755,15 @@ export class BaseEntity<TServerData = any> {
3538
3755
  isAdmin(): boolean {
3539
3756
  this._checkAccess("vérifier l'administrateur.");
3540
3757
  this._assertEntityType("organizations", "projects", "events");
3758
+
3759
+ // 1. Vérifier lien direct sur l'entité courante
3541
3760
  const userLink = this._getLinkFromConnectedUser();
3542
- return this._validateUserLink(userLink) && userLink?.isAdmin === true && !userLink?.isAdminPending;
3761
+ if (this._validateUserLink(userLink) && userLink?.isAdmin === true && !userLink?.isAdminPending) {
3762
+ return true;
3763
+ }
3764
+
3765
+ // 2. Si pas de lien direct, remonter la hiérarchie parent
3766
+ return this._isAdminViaHierarchy();
3543
3767
  }
3544
3768
 
3545
3769
  /**
@@ -57,6 +57,27 @@ export function fromEntityJSON(json: unknown, parent: ApiClient | BaseEntity | n
57
57
  // On gère null / non-objet / pas une entité → on renvoie tel quel (ou null si tu préfères)
58
58
  if (!isEntityJSON(json)) return json;
59
59
 
60
+ // Vérifier si l'entité a son propre parent sérialisé
61
+ let actualParent = parent;
62
+ const entityJson = json as any;
63
+ if (entityJson.parent && typeof entityJson.parent === "object") {
64
+ // Vérifier si le parent passé correspond déjà au parent sérialisé
65
+ const serializedParent = entityJson.parent;
66
+ const parentId = serializedParent.serverData?.id || serializedParent.id;
67
+
68
+ if (parent && "id" in parent && parent.id === parentId) {
69
+ // Le parent passé est déjà le bon, on l'utilise directement
70
+ actualParent = parent as BaseEntity;
71
+ } else {
72
+ // Reconstruire le parent depuis les données sérialisées en transmettant le parent original
73
+ const reconstructedParent = fromEntityJSON(entityJson.parent, parent);
74
+ // Vérifier que c'est bien une BaseEntity avant de l'utiliser
75
+ if (reconstructedParent && typeof reconstructedParent === "object" && "id" in reconstructedParent) {
76
+ actualParent = reconstructedParent as BaseEntity;
77
+ }
78
+ }
79
+ }
80
+
60
81
  // Récupérer la collection depuis serverData, ou la dériver depuis __entityTag
61
82
  let collection: CollectionKey | null | undefined = json.serverData?.collection;
62
83
 
@@ -74,7 +95,7 @@ export function fromEntityJSON(json: unknown, parent: ApiClient | BaseEntity | n
74
95
  if (!meta.entityClass?.fromJSON) {
75
96
  throw new Error(`Classe inconnue ou fromJSON manquant pour ${json.__entityTag}`);
76
97
  }
77
- return meta.entityClass.fromJSON(json, parent, meta.deps);
98
+ return meta.entityClass.fromJSON(json, actualParent, meta.deps);
78
99
  }
79
100
 
80
101
  /**
@@ -316,12 +316,7 @@ export declare class BaseEntity<TServerData = any> {
316
316
  __entityTag: string;
317
317
  __isSerializedEntity: boolean;
318
318
  serverData: any;
319
- parent: {
320
- id?: string;
321
- type?: string;
322
- __entityTag?: string;
323
- slug?: string;
324
- } | null;
319
+ parent: any;
325
320
  };
326
321
  _serialize(obj: any): any;
327
322
  /**
@@ -776,6 +771,40 @@ export declare class BaseEntity<TServerData = any> {
776
771
  * @throws {ApiResponseError} Si le slug n'existe pas ou est invalide.
777
772
  */
778
773
  entityBySlug(slug: string): Promise<AnyEntity>;
774
+ /**
775
+ * Corriger le userContext d'une entité si elle a un vrai parent différent
776
+ * @param entity - L'entité à corriger
777
+ * @private
778
+ */
779
+ private _fixEntityUserContext;
780
+ /**
781
+ * Extraire l'info du vrai parent depuis les serverData
782
+ * @param entity - L'entité à analyser
783
+ * @private
784
+ */
785
+ private _extractRealParent;
786
+ /**
787
+ * Trouver ou créer le parent réel avec ses données complètes
788
+ * @param parentInfo - Info du parent à créer
789
+ * @private
790
+ */
791
+ private _findOrCreateParent;
792
+ /**
793
+ * Extraire le bon userContext du parent
794
+ * @param parent - Le parent dont on veut extraire le userContext
795
+ * @private
796
+ */
797
+ private _extractUserContext;
798
+ /**
799
+ * Vérifie si l'utilisateur est admin via la hiérarchie parent (logique généraliste)
800
+ * @private
801
+ */
802
+ private _isAdminViaHierarchy;
803
+ /**
804
+ * Vérifie si l'utilisateur est admin d'un parent spécifique
805
+ * @private
806
+ */
807
+ private _isAdminOfParent;
779
808
  /**
780
809
  * Construit dynamiquement des filtres sur un lien entre entités
781
810
  *