@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/dist/cocolight-api-client.browser.js +1 -1
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +1 -1
- package/src/api/BaseEntity.ts +231 -7
- package/src/api/EntityRegistry.ts +22 -1
- package/types/api/BaseEntity.d.ts +35 -6
package/package.json
CHANGED
package/src/api/BaseEntity.ts
CHANGED
|
@@ -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?.
|
|
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:
|
|
722
|
-
const parentMeta:
|
|
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")
|
|
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
|
-
|
|
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,
|
|
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
|
*
|