@communecter/cocolight-api-client 1.0.128 → 1.0.130

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.128",
3
+ "version": "1.0.130",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
package/src/Api.ts CHANGED
@@ -167,7 +167,7 @@ export default class Api {
167
167
  */
168
168
  async project(projectData: EntityData): Promise<Project> {
169
169
  try {
170
- const project = new Project(this._client, projectData, { EndpointApi, User, Organization, Event, Poi, Badge, News, Comment, Classified, Action });
170
+ const project = new Project(this._client, projectData, { EndpointApi, User, Organization, Event, Poi, Badge, News, Comment, Answer, Classified, Action });
171
171
  if (!projectData.id && !projectData.slug) {
172
172
  throw new Error("Vous devez fournir un id ou un slug pour créer une instance Project.");
173
173
  }
@@ -4835,11 +4835,22 @@ export class BaseEntity<TServerData = any> {
4835
4835
  ...(result.projects && {
4836
4836
  projects: (result.projects as FundingEnvelopeProjectItem[]).map((envelope) => {
4837
4837
  const inner = envelope?.project as { collection?: string } | undefined;
4838
+ let linked: AnyEntity | object | undefined = envelope.project;
4838
4839
  if (inner && typeof inner === "object") {
4839
- const linked = this._linkEntity(inner.collection ?? "projects", inner);
4840
- return { ...envelope, project: linked ?? envelope.project };
4840
+ linked = this._linkEntity(inner.collection ?? "projects", inner) ?? envelope.project;
4841
4841
  }
4842
- return envelope;
4842
+
4843
+ // Linker les actions imbriquées avec le Project linké comme parent sémantique.
4844
+ // (action.parent === Project → save/refresh/isContributor cohérents)
4845
+ const sd = (envelope as { serverData?: { actions?: unknown[] } }).serverData;
4846
+ if (sd?.actions && Array.isArray(sd.actions)
4847
+ && linked && typeof (linked as BaseEntity)._linkEntity === "function") {
4848
+ sd.actions = sd.actions.map((action) =>
4849
+ (linked as BaseEntity)._linkEntity("actions", action as object) ?? action
4850
+ );
4851
+ }
4852
+
4853
+ return { ...envelope, project: linked };
4843
4854
  })
4844
4855
  }),
4845
4856
  ...(result.userOrga && { userOrga: this._linkEntities(Object.values(result.userOrga)) ?? result.userOrga })
@@ -1651,8 +1651,13 @@ export interface GetMembersNoAdminData {
1651
1651
  name?: string;
1652
1652
  /**
1653
1653
  * Types d'entités à inclure dans la recherche
1654
+ *
1655
+ * @minItems 1
1654
1656
  */
1655
- searchType: ("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[];
1657
+ searchType: [
1658
+ "citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative",
1659
+ ...("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[]
1660
+ ];
1656
1661
  /**
1657
1662
  * Critère de recherche (actuellement vide)
1658
1663
  */
@@ -1726,8 +1731,13 @@ export interface GetMembersAdminData {
1726
1731
  name?: string;
1727
1732
  /**
1728
1733
  * Types d'entités à inclure dans la recherche
1734
+ *
1735
+ * @minItems 1
1729
1736
  */
1730
- searchType: ("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[];
1737
+ searchType: [
1738
+ "citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative",
1739
+ ...("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[]
1740
+ ];
1731
1741
  /**
1732
1742
  * Critère de recherche (actuellement vide)
1733
1743
  */
@@ -2193,8 +2203,13 @@ export interface GetContributorsNoAdminData {
2193
2203
  name?: string;
2194
2204
  /**
2195
2205
  * Types d'entités à inclure dans la recherche
2206
+ *
2207
+ * @minItems 1
2196
2208
  */
2197
- searchType: ("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[];
2209
+ searchType: [
2210
+ "citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative",
2211
+ ...("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[]
2212
+ ];
2198
2213
  /**
2199
2214
  * Critère de recherche (actuellement vide)
2200
2215
  */
@@ -2268,8 +2283,13 @@ export interface GetContributorsAdminData {
2268
2283
  name?: string;
2269
2284
  /**
2270
2285
  * Types d'entités à inclure dans la recherche
2286
+ *
2287
+ * @minItems 1
2271
2288
  */
2272
- searchType: ("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[];
2289
+ searchType: [
2290
+ "citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative",
2291
+ ...("citoyens" | "NGO" | "LocalBusiness" | "Group" | "GovernmentOrganization" | "Cooperative")[]
2292
+ ];
2273
2293
  /**
2274
2294
  * Critère de recherche (actuellement vide)
2275
2295
  */
@@ -98,6 +98,49 @@ export function fromEntityJSON(json: unknown, parent: ApiClient | BaseEntity | n
98
98
  return meta.entityClass.fromJSON(json, actualParent, meta.deps);
99
99
  }
100
100
 
101
+ /**
102
+ * Walker récursif : parcourt un objet/tableau et ressuscite toutes les entités
103
+ * sérialisées (marqueurs `__isSerializedEntity` / `__entityTag`) rencontrées.
104
+ *
105
+ * Pensé pour les wrappers non-entité retournés par certains endpoints (ex: `fundingEnvelope`)
106
+ * ou pour réhydrater un état côté client après un transfert JSON (SSR, cache restore).
107
+ *
108
+ * Dates, RegExp et primitives sont préservées. Pour les entités, la chaîne de parents
109
+ * est reconstruite automatiquement depuis les méta embarquées par `toJSON()`.
110
+ *
111
+ * @param obj - L'objet à parcourir (wrapper, array, primitive…)
112
+ * @param parent - Parent racine (typiquement l'ApiClient côté client)
113
+ * @returns L'objet avec toutes les entités revived
114
+ *
115
+ * @example
116
+ * // Côté client SSR : revive l'état dehydraté
117
+ * const apiClient = new ApiClient({ baseURL });
118
+ * const envelope = reviveEntities(window.__INIT__.envelope, apiClient);
119
+ * envelope.projects[0].serverData.actions[0]; // Action instance
120
+ */
121
+ export function reviveEntities<T = unknown>(
122
+ obj: unknown,
123
+ parent: ApiClient | BaseEntity | null = null
124
+ ): T {
125
+ if (obj === null || obj === undefined) return obj as T;
126
+ if (typeof obj !== "object") return obj as T;
127
+ if (obj instanceof Date || obj instanceof RegExp) return obj as T;
128
+
129
+ if (isEntityJSON(obj)) {
130
+ return fromEntityJSON(obj, parent) as T;
131
+ }
132
+
133
+ if (Array.isArray(obj)) {
134
+ return obj.map(item => reviveEntities(item, parent)) as T;
135
+ }
136
+
137
+ const result: Record<string, unknown> = {};
138
+ for (const [key, value] of Object.entries(obj as object)) {
139
+ result[key] = reviveEntities(value, parent);
140
+ }
141
+ return result as T;
142
+ }
143
+
101
144
  /**
102
145
  * Mappe un EntityTag vers son CollectionKey correspondant.
103
146
  */
@@ -322,8 +322,12 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
322
322
  *
323
323
  * // Récupérer les membres en attente de validation pour être admin
324
324
  * const adminPendingMembers = await organization.getMembers({}, { isAdminPending: true });
325
- *
326
- *
325
+ *
326
+ * // Restreindre la recherche : uniquement les citoyens
327
+ * const onlyCitoyens = await organization.getMembers({}, { searchType: "citoyens" });
328
+ *
329
+ * // Restreindre la recherche : uniquement les organisations
330
+ * const onlyOrgas = await organization.getMembers({}, { searchType: "organizations" });
327
331
  */
328
332
  async getMembers(
329
333
  data: Partial<GetMembersAdminData | GetMembersNoAdminData> = {},
@@ -333,6 +337,7 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
333
337
  isAdminPending?: boolean;
334
338
  isInviting?: boolean;
335
339
  roles?: any[];
340
+ searchType?: "all" | "citoyens" | "organizations";
336
341
  restoredState?: PaginatorState;
337
342
  } = {}
338
343
  ): Promise<PaginatorPage<User | Organization>> {
@@ -344,8 +349,14 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
344
349
  /** « Snapshot » local, typé string */
345
350
  const orgId = this.id as string;
346
351
 
347
- data.searchType = this._getDefaultFromEndpoint("GET_MEMBERS_ADMIN", "searchType") as GetMembersAdminData["searchType"];
348
- // data.searchBy = "ALL";
352
+ const allTypes = this._getDefaultFromEndpoint("GET_MEMBERS_ADMIN", "searchType") as GetMembersAdminData["searchType"];
353
+ if (options.searchType === "citoyens") {
354
+ data.searchType = ["citoyens"];
355
+ } else if (options.searchType === "organizations") {
356
+ data.searchType = allTypes.filter(t => t !== "citoyens") as GetMembersAdminData["searchType"];
357
+ } else {
358
+ data.searchType = allTypes;
359
+ }
349
360
 
350
361
  const paginator = this._createPaginatorEngine({
351
362
  initialData: data,
@@ -232,6 +232,11 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
232
232
  * // Récupérer les contributeurs en attente d'invitation
233
233
  * const invitingContributors = await project.getContributors({}, { isInviting: true });
234
234
  *
235
+ * // Restreindre la recherche : uniquement les citoyens
236
+ * const onlyCitoyens = await project.getContributors({}, { searchType: "citoyens" });
237
+ *
238
+ * // Restreindre la recherche : uniquement les organisations
239
+ * const onlyOrgas = await project.getContributors({}, { searchType: "organizations" });
235
240
  */
236
241
  async getContributors(
237
242
  data: Partial<GetContributorsAdminData | GetContributorsNoAdminData> = {},
@@ -241,11 +246,19 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
241
246
  isAdminPending?: boolean;
242
247
  isInviting?: boolean;
243
248
  roles?: any[];
249
+ searchType?: "all" | "citoyens" | "organizations";
244
250
  restoredState?: PaginatorState;
245
251
  } = {}
246
252
  ): Promise<PaginatorPage<User | Organization>> {
247
- data.searchType = this._getDefaultFromEndpoint("GET_CONTRIBUTORS_ADMIN", "searchType") as GetContributorsAdminData["searchType"];
248
- // data.searchBy = "ALL";
253
+ const allTypes = this._getDefaultFromEndpoint("GET_CONTRIBUTORS_ADMIN", "searchType") as GetContributorsAdminData["searchType"];
254
+ if (options.searchType === "citoyens") {
255
+ data.searchType = ["citoyens"];
256
+ } else if (options.searchType === "organizations") {
257
+ data.searchType = allTypes.filter(t => t !== "citoyens") as GetContributorsAdminData["searchType"];
258
+ } else {
259
+ // "all" ou omis : on initialise explicitement (le paginator exige searchType avant la validation AJV)
260
+ data.searchType = allTypes;
261
+ }
249
262
 
250
263
  const paginator = this._createPaginatorEngine({
251
264
  initialData: data,