@communecter/cocolight-api-client 1.0.54 → 1.0.56

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.
Files changed (179) hide show
  1. package/dist/401.cocolight-api-client.browser.js +1 -0
  2. package/dist/401.cocolight-api-client.cjs +1 -0
  3. package/dist/401.cocolight-api-client.mjs.js +1 -0
  4. package/dist/588.cocolight-api-client.browser.js +1 -0
  5. package/dist/588.cocolight-api-client.cjs +1 -0
  6. package/dist/588.cocolight-api-client.mjs.js +1 -0
  7. package/dist/593.cocolight-api-client.browser.js +1 -0
  8. package/dist/593.cocolight-api-client.cjs +1 -0
  9. package/dist/593.cocolight-api-client.mjs.js +1 -0
  10. package/dist/839.cocolight-api-client.browser.js +1 -0
  11. package/dist/839.cocolight-api-client.cjs +1 -0
  12. package/dist/839.cocolight-api-client.mjs.js +1 -0
  13. package/dist/cocolight-api-client.browser.js +3 -3
  14. package/dist/cocolight-api-client.cjs +1 -1
  15. package/dist/cocolight-api-client.mjs.js +1 -1
  16. package/dist/cocolight-api-client.vite.mjs.js +1 -1
  17. package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
  18. package/package.json +29 -17
  19. package/src/{Api.js → Api.ts} +85 -95
  20. package/src/{ApiClient.js → ApiClient.ts} +436 -247
  21. package/src/EJSONType.ts +103 -0
  22. package/src/api/{Badge.js → Badge.ts} +56 -45
  23. package/src/api/BaseEntity.ts +3890 -0
  24. package/src/api/Comment.ts +200 -0
  25. package/src/api/{EndpointApi.js → EndpointApi.ts} +363 -297
  26. package/src/api/EndpointApi.types.ts +4609 -0
  27. package/src/api/EntityRegistry.ts +203 -0
  28. package/src/api/Event.ts +332 -0
  29. package/src/api/News.ts +331 -0
  30. package/src/api/{Organization.js → Organization.ts} +155 -119
  31. package/src/api/{Poi.js → Poi.ts} +68 -60
  32. package/src/api/{Project.js → Project.ts} +150 -127
  33. package/src/api/{User.js → User.ts} +321 -256
  34. package/src/api/UserApi.ts +148 -0
  35. package/src/api/serverDataType/Comment.ts +88 -0
  36. package/src/api/serverDataType/Event.ts +80 -0
  37. package/src/api/serverDataType/News.ts +138 -0
  38. package/src/api/serverDataType/Organization.ts +80 -0
  39. package/src/api/serverDataType/Project.ts +71 -0
  40. package/src/api/serverDataType/User.ts +103 -0
  41. package/src/api/serverDataType/common.ts +80 -0
  42. package/src/endpoints.module.ts +2621 -0
  43. package/src/error.ts +86 -0
  44. package/src/index.ts +86 -0
  45. package/src/mixin/UserMixin.ts +4 -0
  46. package/src/types/api-responses.ts +217 -0
  47. package/src/types/entities.ts +22 -0
  48. package/src/types/error-guards.ts +230 -0
  49. package/src/types/index.ts +39 -0
  50. package/src/types/payloads.ts +21 -0
  51. package/src/types/transforms.ts +110 -0
  52. package/src/utils/{FileOfflineStorageStrategy.node.js → FileOfflineStorageStrategy.node.ts} +15 -12
  53. package/src/utils/{FileStorageStrategy.node.js → FileStorageStrategy.node.ts} +16 -39
  54. package/src/utils/MultiServerFileStorageStrategy.node.ts +67 -0
  55. package/src/utils/MultiServerTokenStorageStrategy.ts +139 -0
  56. package/src/utils/{OfflineClientManager.js → OfflineClientManager.ts} +82 -86
  57. package/src/utils/OfflineQueueStorageStrategy.ts +47 -0
  58. package/src/utils/TokenStorage.ts +77 -0
  59. package/src/utils/compat.ts +12 -0
  60. package/src/utils/createDefaultMultiServerTokenStorageStrategy.ts +35 -0
  61. package/src/utils/{createDefaultOfflineStrategy.js → createDefaultOfflineStrategy.ts} +8 -3
  62. package/src/utils/createDefaultTokenStorageStrategy.ts +33 -0
  63. package/src/utils/{reactive.js → reactive.ts} +49 -40
  64. package/src/utils/stream-utils.node.ts +12 -0
  65. package/types/Api.d.ts +38 -82
  66. package/types/Api.d.ts.map +1 -0
  67. package/types/ApiClient.d.ts +244 -184
  68. package/types/ApiClient.d.ts.map +1 -0
  69. package/types/EJSONType.d.ts +48 -22
  70. package/types/EJSONType.d.ts.map +1 -0
  71. package/types/api/Badge.d.ts +20 -20
  72. package/types/api/Badge.d.ts.map +1 -0
  73. package/types/api/BaseEntity.d.ts +751 -446
  74. package/types/api/BaseEntity.d.ts.map +1 -0
  75. package/types/api/Comment.d.ts +36 -0
  76. package/types/api/EndpointApi.d.ts +347 -295
  77. package/types/api/EndpointApi.d.ts.map +1 -0
  78. package/types/api/EndpointApi.types.d.ts +3914 -4133
  79. package/types/api/EntityRegistry.d.ts +18 -16
  80. package/types/api/EntityRegistry.d.ts.map +1 -0
  81. package/types/api/Event.d.ts +119 -35
  82. package/types/api/Event.d.ts.map +1 -0
  83. package/types/api/News.d.ts +52 -20
  84. package/types/api/News.d.ts.map +1 -0
  85. package/types/api/Organization.d.ts +165 -49
  86. package/types/api/Organization.d.ts.map +1 -0
  87. package/types/api/Poi.d.ts +51 -22
  88. package/types/api/Poi.d.ts.map +1 -0
  89. package/types/api/Project.d.ts +151 -52
  90. package/types/api/Project.d.ts.map +1 -0
  91. package/types/api/User.d.ts +222 -93
  92. package/types/api/User.d.ts.map +1 -0
  93. package/types/api/UserApi.d.ts +60 -9
  94. package/types/api/UserApi.d.ts.map +1 -0
  95. package/types/api/serverDataType/Comment.d.ts +83 -0
  96. package/types/api/serverDataType/Event.d.ts +67 -0
  97. package/types/api/serverDataType/News.d.ts +130 -0
  98. package/types/api/serverDataType/Organization.d.ts +65 -0
  99. package/types/api/serverDataType/Organization.d.ts.map +1 -0
  100. package/types/api/serverDataType/Project.d.ts +58 -0
  101. package/types/api/serverDataType/Project.d.ts.map +1 -0
  102. package/types/api/serverDataType/User.d.ts +86 -0
  103. package/types/api/serverDataType/User.d.ts.map +1 -0
  104. package/types/api/serverDataType/common.d.ts +71 -0
  105. package/types/api/serverDataType/common.d.ts.map +1 -0
  106. package/types/endpoints.module.d.ts +6922 -1215
  107. package/types/endpoints.module.d.ts.map +1 -0
  108. package/types/error.d.ts +25 -51
  109. package/types/error.d.ts.map +1 -0
  110. package/types/index.d.ts +55 -48
  111. package/types/index.d.ts.map +1 -0
  112. package/types/mixin/UserMixin.d.ts +1 -1
  113. package/types/mixin/UserMixin.d.ts.map +1 -0
  114. package/types/types/api-responses.d.ts +190 -0
  115. package/types/types/api-responses.d.ts.map +1 -0
  116. package/types/types/entities.d.ts +17 -0
  117. package/types/types/entities.d.ts.map +1 -0
  118. package/types/types/error-guards.d.ts +99 -0
  119. package/types/types/error-guards.d.ts.map +1 -0
  120. package/types/types/index.d.ts +7 -0
  121. package/types/types/payloads.d.ts +17 -0
  122. package/types/types/payloads.d.ts.map +1 -0
  123. package/types/types/transforms.d.ts +79 -0
  124. package/types/types/transforms.d.ts.map +1 -0
  125. package/types/utils/FileOfflineStorageStrategy.node.d.ts +10 -9
  126. package/types/utils/FileOfflineStorageStrategy.node.d.ts.map +1 -0
  127. package/types/utils/FileStorageStrategy.node.d.ts +9 -20
  128. package/types/utils/FileStorageStrategy.node.d.ts.map +1 -0
  129. package/types/utils/MultiServerFileStorageStrategy.node.d.ts +13 -18
  130. package/types/utils/MultiServerFileStorageStrategy.node.d.ts.map +1 -0
  131. package/types/utils/MultiServerTokenStorageStrategy.d.ts +30 -51
  132. package/types/utils/MultiServerTokenStorageStrategy.d.ts.map +1 -0
  133. package/types/utils/OfflineClientManager.d.ts +52 -88
  134. package/types/utils/OfflineClientManager.d.ts.map +1 -0
  135. package/types/utils/OfflineQueueStorageStrategy.d.ts +12 -9
  136. package/types/utils/OfflineQueueStorageStrategy.d.ts.map +1 -0
  137. package/types/utils/TokenStorage.d.ts +20 -70
  138. package/types/utils/TokenStorage.d.ts.map +1 -0
  139. package/types/utils/compat.d.ts +4 -0
  140. package/types/utils/compat.d.ts.map +1 -0
  141. package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts +2 -11
  142. package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts.map +1 -0
  143. package/types/utils/createDefaultOfflineStrategy.d.ts +2 -3
  144. package/types/utils/createDefaultOfflineStrategy.d.ts.map +1 -0
  145. package/types/utils/createDefaultTokenStorageStrategy.d.ts +2 -12
  146. package/types/utils/createDefaultTokenStorageStrategy.d.ts.map +1 -0
  147. package/types/utils/reactive.d.ts +10 -16
  148. package/types/utils/reactive.d.ts.map +1 -0
  149. package/types/utils/stream-utils.node.d.ts +3 -2
  150. package/types/utils/stream-utils.node.d.ts.map +1 -0
  151. package/dist/123.cocolight-api-client.browser.js +0 -1
  152. package/dist/123.cocolight-api-client.cjs +0 -1
  153. package/dist/22.cocolight-api-client.mjs.js +0 -1
  154. package/dist/339.cocolight-api-client.mjs.js +0 -1
  155. package/dist/394.cocolight-api-client.browser.js +0 -1
  156. package/dist/394.cocolight-api-client.cjs +0 -1
  157. package/dist/405.cocolight-api-client.browser.js +0 -1
  158. package/dist/405.cocolight-api-client.cjs +0 -1
  159. package/dist/774.cocolight-api-client.mjs.js +0 -1
  160. package/dist/790.cocolight-api-client.mjs.js +0 -1
  161. package/dist/931.cocolight-api-client.browser.js +0 -1
  162. package/dist/931.cocolight-api-client.cjs +0 -1
  163. package/src/EJSONType.js +0 -53
  164. package/src/api/BaseEntity.js +0 -2828
  165. package/src/api/EntityRegistry.js +0 -152
  166. package/src/api/Event.js +0 -226
  167. package/src/api/News.js +0 -244
  168. package/src/api/UserApi.js +0 -81
  169. package/src/endpoints.module.js +0 -5
  170. package/src/error.js +0 -121
  171. package/src/index.js +0 -97
  172. package/src/mixin/UserMixin.js +0 -8
  173. package/src/utils/MultiServerFileStorageStrategy.node.js +0 -87
  174. package/src/utils/MultiServerTokenStorageStrategy.js +0 -188
  175. package/src/utils/OfflineQueueStorageStrategy.js +0 -51
  176. package/src/utils/TokenStorage.js +0 -153
  177. package/src/utils/createDefaultMultiServerTokenStorageStrategy.js +0 -51
  178. package/src/utils/createDefaultTokenStorageStrategy.js +0 -49
  179. package/src/utils/stream-utils.node.js +0 -10
@@ -1,2828 +0,0 @@
1
- // BaseEntity.js
2
- import ObjectID from "bson-objectid";
3
- // import { fileTypeFromBuffer } from "file-type";
4
- import EJSON from "ejson";
5
- import pkg from "file-type";
6
-
7
- import { ApiAuthenticationError, ApiError, ApiResponseError, ApiValidationError } from "../error.js";
8
- import { isReactive, isSignal, reactive } from "../utils/reactive.js";
9
- const { fromBuffer } = pkg;
10
-
11
- /**
12
- * @typedef {import("../ApiClient.js").default} ApiClient
13
- * @typedef {import("./EndpointApi.js").default} EndpointApi
14
- */
15
-
16
- /**
17
- * Classe de base pour toutes les entités métiers : utilisateurs, projets, organisations, etc.
18
- * Fournit un système de brouillon (draft), transformation, appel API sécurisé,
19
- * et gestion de données côté client avec support du mode offline.
20
- * @abstract
21
- */
22
- export class BaseEntity {
23
- /** @type {Object} Données de brouillon modifiables */
24
- _draftData = {};
25
-
26
- /** @type {Object} Snapshot initial des données de brouillon */
27
- _initialDraftData = {};
28
-
29
- /** @type {Object|null} Données reçues du serveur */
30
- _serverData = null;
31
-
32
- /** @type {boolean} Indique si `save()` est en cours */
33
- _calledFromSave = false;
34
-
35
- /** @type {boolean} Indique si le draft est synchronisé avec le serveur */
36
- _syncReactiveDraft = false;
37
-
38
- static entityTag = "BaseEntity";
39
-
40
- /**
41
- * Constructeur de l'entité.
42
- * @param {Object} parent - L'ApiClient ou une entité parente.
43
- * @param {Object} parent.apiClient - Instance de l'ApiClient.
44
- * @param {Object} [parent.parent] - Instance parente.
45
- * @param {Object} [data={}] - Données initiales.
46
- * @param {Object} [deps={}] - Dépendances injectées (EndpointApi, autres entités).
47
- * @param {Object|function} deps.EndpointApi - Instance de l'API.
48
- * @param {function} deps.User - Classe d'entité utilisateur.
49
- * @param {function} deps.Organization - Classe d'entité organisation.
50
- * @param {function} deps.Project - Classe d'entité projet.
51
- * @param {function} deps.Poi - Classe d'entité point d'intérêt.
52
- * @param {function} deps.Event - Classe d'entité événement.
53
- * @param {function} deps.Badge - Classe d'entité badge.
54
- * @param {function} deps.News - Classe d'entité actualité.
55
- * @param {string} [config.entityTag] - Tag d'entité (ex: "User", "Organization").
56
- * @param {Object} [config={}] - Configuration optionnelle.
57
- * @throws {ApiError} Si les dépendances ou parent sont invalides.
58
- */
59
- constructor(parent, data = {}, deps = {}, config = {}) {
60
- this.__entityTag = config.entityTag || this.getEntityTag(this.constructor.entityTag) || "BaseEntity";
61
- this.deps = deps;
62
-
63
- if (this.getEntityTag(parent?.__entityTag) === "ApiClient") {
64
- /** @type {ApiClient} */
65
- this.apiClient = parent;
66
- this.parent = null;
67
- this.userContext = null;
68
- } else if (parent?.apiClient) {
69
- /** @type {ApiClient} */
70
- this.apiClient = parent.apiClient;
71
- this.parent = parent;
72
- if (this.getEntityTag(parent?.__entityTag) === "User") {
73
- this.userContext = parent;
74
- } else if (parent?.userContext) {
75
- this.userContext = parent.userContext;
76
- }
77
- } else {
78
- throw new ApiError("Parent invalide ou ApiClient manquant.");
79
- }
80
-
81
- // Gérer les deux cas : fonction constructeur ou instance
82
- if (typeof deps.EndpointApi === "function") {
83
- /** @type {EndpointApi} */
84
- this.endpointApi = new deps.EndpointApi(this.apiClient);
85
- } else if (typeof deps.EndpointApi === "object") {
86
- /** @type {EndpointApi} */
87
- this.endpointApi = deps.EndpointApi;
88
- } else {
89
- throw new ApiError("deps.EndpointApi doit être une classe ou une instance valide.");
90
- }
91
-
92
- this._serverData = reactive({});
93
-
94
- const { draft, proxy, initial } = this._buildDraftAndProxy({
95
- data: { ...data, ...this.defaultFields },
96
- serverData: this._serverData,
97
- constant: this.constructor.SCHEMA_CONSTANTS,
98
- apiClient: this.apiClient,
99
- transforms: this.transforms,
100
- removeFields: this.removeFields
101
- });
102
-
103
- this._initialDraftData = initial;
104
- this._draftData = draft;
105
- this.data = proxy;
106
- }
107
-
108
- getEntityTag = (__entityTag) => __entityTag?.replace(/^_/, "");
109
-
110
- /** @returns {string|null} Identifiant de l'entité */
111
- get id() {
112
- return this._draftData.id || null;
113
- }
114
-
115
- /** @returns {string|null} Slug de l'entité */
116
- get slug() {
117
- return this._draftData.slug || null;
118
- }
119
-
120
- /** Définit un ID (utilisé en interne) */
121
- _id(newId) {
122
- this._draftData.id = newId;
123
- }
124
-
125
- /** @returns {boolean} Indique si l'utilisateur est connecté */
126
- get isConnected() {
127
- return this.apiClient.isConnected;
128
- }
129
-
130
- /** @returns {string|null} Identifiant utilisateur associé */
131
- get userId() {
132
- return this.apiClient.userId;
133
- }
134
-
135
- /** @returns {Object} Données de brouillon courantes */
136
- get draftData() {
137
- return this._draftData;
138
- }
139
-
140
- /** @returns {Object} Données de brouillon initiales */
141
- get initialDraftData() {
142
- return this._initialDraftData;
143
- }
144
-
145
- /** @returns {Object|null} Données brutes du serveur */
146
- get serverData() {
147
- return this._serverData;
148
- }
149
-
150
- /** @returns {boolean} Indique si cette entité représente l'utilisateur connecté */
151
- get isMe() {
152
- return this.isConnected && this.userId === this.userContext?.id;
153
- }
154
-
155
- /** @returns {string} Type de l'entité (ex: 'citoyens') */
156
- getEntityType() {
157
- return this.constructor.entityType;
158
- }
159
-
160
- /**
161
- * Indique si le draft contient des modifications par rapport aux données initiales.
162
- * @returns {boolean}
163
- */
164
- hasChanges() {
165
- return this._serialize(this._toRawDeep(this._draftData)) !== this._serialize(this._initialDraftData);
166
- }
167
-
168
- /**
169
- * Rafraîchit l'entité en rechargeant ses données depuis le serveur.
170
- * @returns {Promise<Object>} Données mises à jour
171
- */
172
- async refresh() {
173
- if (!this.id) throw new ApiError("Impossible de rafraîchir sans ID.");
174
- return this.get();
175
- }
176
-
177
- /**
178
- * Sauvegarde les modifications locales vers le serveur (add ou update).
179
- * @returns {Promise<Object>} Données serveur mises à jour
180
- */
181
- async save() {
182
- if (!this.isConnected) throw new ApiError("Non connecté.");
183
- this._calledFromSave = true;
184
- try {
185
- const payload = { ...this._draftData };
186
-
187
- if (!this.id && typeof this._add === "function") {
188
- await this._add(payload);
189
- this._resetInitialDraftData();
190
- // on refresh le contexte utilisateur si besoin
191
- if(this.userContext) {
192
- await this.userContext.refresh();
193
- }
194
- return await this.refresh();
195
- } else if (typeof this._update === "function") {
196
- const hasChanged = await this._update(payload);
197
- this._resetInitialDraftData();
198
- if (hasChanged) return await this.refresh();
199
- }
200
-
201
- return this._serverData;
202
- } finally {
203
- this._calledFromSave = false;
204
- }
205
- }
206
-
207
- /**
208
- * Crée une nouvelle instance d'entité à partir des données du serveur.
209
- *
210
- * @param {Object} data - Données du serveur.
211
- * @param {BaseEntity} parent - Instance parente.
212
- * @param {Object} deps - Dépendances injectées.
213
- * @returns {BaseEntity} Nouvelle instance d'entité.
214
- */
215
- static fromServerData(data, parent, deps) {
216
- const instance = new this(parent, {}, deps);
217
- instance._setData(data);
218
- return instance;
219
- }
220
-
221
- /**
222
- * Met à jour les données de l'entité avec de nouvelles données.
223
- *
224
- * @param {Object} newData - Les nouvelles données à appliquer.
225
- * @returns {void}
226
- * @private
227
- */
228
- _setData(newData, { forceInitialDraftReset = false } = {}) {
229
- if (this.userContext && this.userContext !== this) {
230
- this.apiClient._logger?.info?.(`[${this.__entityTag}] Mise à jour liée à userContext : ${this.userContext.id}`);
231
- }
232
-
233
- if (isReactive(this._serverData)) {
234
- Object.assign(this._serverData, newData);
235
- } else {
236
- this._serverData = reactive({ ...newData });
237
- }
238
-
239
- const clientDraft = this._draftData ? this._toRawDeep(this._draftData) : {};
240
-
241
- const mergedData = {
242
- ...newData,
243
- ...this.defaultFields,
244
- ...clientDraft
245
- };
246
-
247
- const { draft, proxy, initial } = this._buildDraftAndProxy({
248
- data: mergedData,
249
- serverData: this._serverData,
250
- previousDraft: this._draftData,
251
- constant: this.constructor.SCHEMA_CONSTANTS,
252
- apiClient: this.apiClient,
253
- transforms: this.transforms,
254
- removeFields: this.removeFields
255
- });
256
-
257
- if (forceInitialDraftReset) {
258
- this._initialDraftData = structuredClone(this._toRawDeep(draft));
259
- } else if (!this._initialDraftData) {
260
- this._initialDraftData = initial;
261
- }
262
-
263
- if (isReactive(this._draftData)) {
264
- this._updateDraftPreservingUserChanges(draft);
265
- } else {
266
- this._draftData = reactive(draft);
267
- }
268
-
269
- if (!this.data) {
270
- this.data = proxy;
271
- }
272
-
273
- }
274
-
275
- _updateDraftPreservingUserChanges(draft) {
276
- for (const key of Object.keys(draft)) {
277
- const current = this._draftData?.[key];
278
- const initialValue = this._initialDraftData?.[key];
279
-
280
- const isModified =
281
- current !== undefined &&
282
- initialValue !== undefined &&
283
- this._serialize(this._toRawDeep(current)) !== this._serialize(initialValue);
284
-
285
- if (!isModified) {
286
- this._draftData[key] = draft[key];
287
- }
288
- }
289
- }
290
-
291
- _resetInitialDraftData() {
292
- const raw = this._toRawDeep(this._draftData);
293
- this._initialDraftData = structuredClone(raw);
294
- }
295
-
296
-
297
- _toRawDeep(obj) {
298
-
299
- if (!this || typeof this._toRawDeep !== "function") {
300
- throw new Error("`this._toRawDeep` is not bound correctly. Use a lambda to preserve context.");
301
- }
302
-
303
- if (isSignal(obj)) {
304
- return this._toRawDeep(obj.value);
305
- }
306
-
307
- if (Array.isArray(obj)) {
308
- return obj.map((item) => this._toRawDeep(item));
309
- }
310
-
311
- if (typeof obj === "object" && obj !== null) {
312
- const result = {};
313
- for (const key of Object.keys(obj)) {
314
- result[key] = this._toRawDeep(obj[key]);
315
- }
316
- return result;
317
- }
318
-
319
- return obj; // valeur primitive
320
- }
321
-
322
- /**
323
- * Champs à ajouter automatiquement à chaque draft (ex: `typeElement`).
324
- * Souvent utilisés dans les conditions `if/then` du JSON Schema.
325
- * @type {Object<string, any>}
326
- */
327
- defaultFields = {};
328
-
329
- /**
330
- * Champs à exclure explicitement du draft et des payloads.
331
- * @type {string[]}
332
- */
333
- removeFields = [];
334
-
335
- /**
336
- * Transformations à appliquer à certains champs lors de la lecture depuis le draft.
337
- * Clé = champ, valeur = fonction (val, full) => valeur transformée.
338
- * @type {Object<string, function(any, object): any>}
339
- */
340
- transforms = {};
341
-
342
- /**
343
- * ───────────────────────────────
344
- * JSON
345
- * ───────────────────────────────
346
- */
347
-
348
- /**
349
- * Convertit l'instance en JSON pour l'envoi au serveur.
350
- *
351
- * @returns {Object} Représentation JSON de l'instance.
352
- */
353
- toJSON() {
354
- const parentMeta = {};
355
- if (this.parent?.id) parentMeta.id = this.parent.id;
356
- if (typeof this.parent?.getEntityType === "function") parentMeta.type = this.parent.getEntityType();
357
- if (this.parent?.__entityTag) parentMeta.__entityTag = this.parent.__entityTag;
358
- if (this.parent?.slug) parentMeta.slug = this.parent.slug;
359
-
360
- return {
361
- __entityTag: this.__entityTag,
362
- __isSerializedEntity: true,
363
- serverData: this._serialize(this._serverData),
364
- parent: Object.keys(parentMeta).length > 0 ? parentMeta : null
365
- };
366
- }
367
-
368
-
369
- _serialize(obj) {
370
- try {
371
- return JSON.parse(EJSON.stringify(this._removeUnserializables(obj)));
372
- } catch (e) {
373
- this.apiClient?._logger?.error?.("Erreur de sérialisation EJSON", e);
374
- return null;
375
- }
376
- }
377
-
378
- /**
379
- * Supprime les propriétés non sérialisables d'un objet.
380
- *
381
- * @param {Object} obj - L'objet à nettoyer.
382
- * @param {WeakSet} [seen] - Ensemble pour éviter les références circulaires.
383
- * @returns {Object} L'objet nettoyé.
384
- * @private
385
- */
386
- _removeUnserializables(obj, seen = new WeakSet()) {
387
- if (obj === null || typeof obj !== "object") return obj;
388
-
389
- if (seen.has(obj)) return null;
390
- seen.add(obj);
391
-
392
- // Ignore les proxys réactifs
393
- if (obj.__isReactive && typeof obj.__raw === "object") {
394
- return this._removeUnserializables(obj.__raw, seen);
395
- }
396
-
397
- if (Array.isArray(obj)) {
398
- return obj.map((el) => this._removeUnserializables(el, seen));
399
- }
400
-
401
- const clean = {};
402
- for (const key of Object.keys(obj)) {
403
- const val = obj[key];
404
-
405
- if (
406
- typeof val === "function" ||
407
- typeof val === "symbol" ||
408
- typeof val === "undefined"
409
- ) {
410
- continue;
411
- }
412
-
413
- try {
414
- clean[key] = this._removeUnserializables(val, seen);
415
- // eslint-disable-next-line no-unused-vars
416
- } catch (e) {
417
- clean[key] = null;
418
- }
419
- }
420
-
421
- return clean;
422
- }
423
-
424
- /**
425
- * Restaure les données sérialisées en un objet d'origine.
426
- *
427
- * @param {Object} obj - L'objet à restaurer.
428
- * @returns {Object} L'objet restauré.
429
- */
430
- static _revive(obj) {
431
- return EJSON.fromJSONValue(obj);
432
- }
433
-
434
- /**
435
- * Crée une instance d'entité à partir de données JSON.
436
- *
437
- * @param {Object} json - Données JSON à utiliser.
438
- * @param {Object} parent - Instance parente.
439
- * @param {Object} deps - Dépendances injectées.
440
- * @returns {object} Nouvelle instance d'entité.
441
- */
442
- static fromJSON(json, parent, deps) {
443
-
444
- const { serverData } = json;
445
-
446
- const instance = this.fromServerData(this._revive(serverData), parent, deps);
447
-
448
- return instance;
449
- }
450
-
451
- /**
452
- * ───────────────────────────────
453
- * UtilMixin
454
- * ───────────────────────────────
455
- */
456
-
457
- /**
458
- * Appelle une méthode de l'API.
459
- *
460
- * @param {string} constant - Le nom de la méthode à appeler.
461
- * @param {object} data - Les données à passer à la méthode.
462
- * @returns {Promise<any>} - La promesse de la méthode appelée.
463
- * @throws {ApiValidationError} - Si une erreur se produit lors de l'appel de l'API.
464
- * @throws {ApiResponseError} - Si une erreur se produit lors de l'appel de l'API.
465
- * @throws {ApiClientError} - Si l'utilisateur n'est pas authentifié.
466
- */
467
- async call(constant, data = {}) {
468
- return this.apiClient.safeCall(async () => {
469
- const response = await this.apiClient.callEndpoint(constant, data);
470
- this.apiClient.checkAndThrowApiResponseError(response);
471
- return response.data;
472
- });
473
- }
474
-
475
- /**
476
- * Appelle une méthode de l'API si l'utilisateur n'est pas connecté.
477
- *
478
- * @param {string} constant - Le nom de la méthode à appeler.
479
- * @param {object} data - Les données à passer à la méthode.
480
- * @returns {Promise<any>} - La promesse de la méthode appelée.
481
- * @throws {ApiAuthenticationError} - Si l'utilisateur est connecté.
482
- * @throws {ApiValidationError} - Si une erreur se produit lors de l'appel de l'API.
483
- * @throws {ApiResponseError} - Si une erreur se produit lors de l'appel de l'API.
484
- * @throws {ApiClientError} - Si une erreur se produit lors de l'appel de l'API.
485
- */
486
- async callNoConnected(constant, data = {}) {
487
- if(this.isConnected) {
488
- throw new ApiAuthenticationError("Vous devez ne devez pas être connecté pour faire cette action.");
489
- }
490
- return this.call(constant, data);
491
- }
492
-
493
- /**
494
- * Appelle une méthode de l'API si l'utilisateur est connecté.
495
- *
496
- * @param {string|function} param - Le nom de la méthode à appeler ou une fonction de rappel.
497
- * @param {object} data - Les données à passer à la méthode.
498
- * @returns {Promise<any>} - La promesse de la méthode appelée.
499
- * @throws {ApiAuthenticationError} - Si l'utilisateur n'est pas connecté.
500
- * @throws {ApiValidationError} - Si une erreur se produit lors de l'appel de l'API.
501
- * @throws {ApiResponseError} - Si une erreur se produit lors de l'appel de l'API.
502
- * @throws {ApiClientError} - Si une erreur se produit lors de l'appel de l'API.
503
- */
504
- async callIsConnected(param, data = {}) {
505
- if(!this.isConnected) {
506
- throw new ApiAuthenticationError("Vous devez être connecté pour faire cette action.");
507
- }
508
- // Si le premier paramètre est une fonction, on l'exécute en tant que callback
509
- if (typeof param === "function") {
510
- return await param();
511
- }
512
- return this.call(param, data);
513
- }
514
-
515
- /**
516
- * Appelle une méthode de l'API si l'utilisateur est lui-même.
517
- *
518
- * @param {string|function} param - Le nom de la méthode à appeler ou une fonction de rappel.
519
- * @param {object} data - Les données à passer à la méthode.
520
- * @returns {Promise<any>} - La promesse de la méthode appelée.
521
- * @throws {ApiAuthenticationError} - Si l'utilisateur n'est pas lui-même.
522
- * @throws {ApiValidationError} - Si une erreur se produit lors de l'appel de l'API.
523
- * @throws {ApiResponseError} - Si une erreur se produit lors de l'appel de l'API.
524
- * @throws {ApiClientError} - Si une erreur se produit lors de l'appel de l'API.
525
- */
526
- async callIsMe(param, data = {}) {
527
- if (!this.isMe) {
528
- throw new ApiAuthenticationError("Vous devez être vous-même pour faire cette action.");
529
- }
530
- // Si le premier paramètre est une fonction, on l'exécute en tant que callback
531
- if (typeof param === "function") {
532
- return await param();
533
- }
534
- // Sinon, on considère qu'il s'agit d'un constant et on appelle la méthode par défaut
535
- return await this.callIsConnected(param, data);
536
- }
537
-
538
- /**
539
- * Valide une image d'entrée.
540
- *
541
- * @param {File|Blob|Buffer|ReadableStream} imageInput - L'image à valider.
542
- * @returns {Promise<File|Buffer|ReadableStream>} - L'image validée.
543
- * @private
544
- */
545
- async _validateImage(imageInput){
546
- const image = await this._validateUploadInput(imageInput, {
547
- allowedMimeTypes: ["image/jpeg", "image/png", "image/jpg"],
548
- expectedType: "image"
549
- });
550
- return image;
551
- }
552
-
553
- /**
554
- * Valide un fichier d'entrée.
555
- *
556
- * @param {File|Blob|Buffer|ReadableStream} fileInput - Le fichier à valider.
557
- * @returns {Promise<File|Buffer|ReadableStream>} - Le fichier validé.
558
- * @private
559
- */
560
- async _validateFile(fileInput){
561
- const file = await this._validateUploadInput(fileInput, {
562
- allowedMimeTypes: ["application/pdf", "text/plain", "text/csv"],
563
- expectedType: "file"
564
- });
565
- return file;
566
- }
567
-
568
- /**
569
- * Valide les entrées d'upload de fichiers.
570
- *
571
- * @param {File|Blob|Buffer|ReadableStream} input - Le fichier à valider.
572
- * @param {Object} options - Options de validation.
573
- * @param {Array} options.allowedMimeTypes - Types MIME autorisés.
574
- * @param {string} options.expectedType - Type de fichier attendu (ex: "image", "file").
575
- * @returns {Promise<File|Buffer|ReadableStream>} - Le fichier validé.
576
- * @throws {ApiValidationError} - Si le type de fichier est invalide.
577
- * @throws {Error} - Si le type de fichier est inconnu.
578
- * @private
579
- */
580
- async _validateUploadInput(input, { allowedMimeTypes = [], expectedType = "any" }) {
581
- if (!input) {
582
- throw new ApiValidationError("Le fichier est requis.");
583
- }
584
-
585
- const isNode = typeof window === "undefined" && typeof process !== "undefined";
586
- let mimeType = "";
587
- let output = input;
588
-
589
- // Navigateur : File
590
- if (typeof File !== "undefined" && input instanceof File) {
591
- mimeType = input.type;
592
- if (!allowedMimeTypes.includes(mimeType)) {
593
- throw new ApiValidationError("Le type du fichier est invalide.");
594
- }
595
- }
596
-
597
- // Navigateur : Blob
598
- else if (typeof Blob !== "undefined" && input instanceof Blob) {
599
- mimeType = input.type;
600
- if (!allowedMimeTypes.includes(mimeType)) {
601
- throw new ApiValidationError("Le type du fichier est invalide.");
602
- }
603
-
604
- const ext = mimeType.split("/")[1] || "bin";
605
- const fileName = `${Date.now()}.${ext}`;
606
- output = new File([input], fileName, { type: mimeType });
607
- }
608
-
609
- // Node.js : Buffer
610
- else if (isNode && Buffer.isBuffer(input)) {
611
- const fileTypeResult = await fromBuffer(input);
612
- mimeType = fileTypeResult?.mime;
613
-
614
- if (!fileTypeResult || !allowedMimeTypes.includes(mimeType)) {
615
- throw new ApiValidationError("Le type du fichier est invalide.");
616
- }
617
-
618
- // Pour un fichier image, on transforme en stream
619
- if (expectedType === "image") {
620
- const ext = fileTypeResult.ext;
621
- const filename = `${Date.now()}.${ext}`;
622
- output = await this._createReadStreamFromBuffer(input, filename, mimeType);
623
- }
624
- }
625
-
626
- // Node.js : ReadableStream
627
- else if (isNode && input?.readable && typeof input._read === "function") {
628
- const previewChunks = [];
629
- const tee = await this._passThrough();
630
- const resultStream = await this._passThrough();
631
-
632
- const MAX_BYTES = 4100;
633
- let bytesRead = 0;
634
-
635
- input.on("data", (chunk) => {
636
- if (bytesRead < MAX_BYTES) {
637
- previewChunks.push(chunk);
638
- bytesRead += chunk.length;
639
- }
640
- });
641
-
642
- input.pipe(tee).pipe(resultStream);
643
- await new Promise((resolve) => setTimeout(resolve, 10));
644
-
645
- const previewBuffer = Buffer.concat(previewChunks);
646
- const fileTypeResult = await fromBuffer(previewBuffer);
647
- mimeType = fileTypeResult?.mime;
648
-
649
- if (!fileTypeResult || !allowedMimeTypes.includes(mimeType)) {
650
- throw new ApiValidationError("Le type du fichier est invalide.");
651
- }
652
-
653
- resultStream.path = `${Date.now()}.${fileTypeResult.ext}`;
654
- resultStream.mimeType = mimeType;
655
- output = resultStream;
656
- }
657
-
658
- else {
659
- throw new ApiValidationError("Type de fichier non reconnu.");
660
- }
661
-
662
- return output;
663
- }
664
-
665
- /**
666
- * Transforme un Buffer en ReadableStream équivalent à fs.createReadStream
667
- * @param {Buffer} buffer - Le buffer contenant les données binaires
668
- * @param {string} filename - Nom de fichier (utilisé dans FormData)
669
- * @param {string} mimeType - Type MIME (utilisé dans FormData)
670
- * @returns {Object} - { stream, filename, mimeType }
671
- * @private
672
- */
673
- async _createReadStreamFromBuffer(buffer, filename = "file.bin", mimeType = "application/octet-stream") {
674
- const stream = await this._bufferToReadable(buffer);
675
- stream.path = filename; // 👈 hack pour simuler un vrai fichier ReadStream
676
- stream.mimeType = mimeType;
677
- return stream;
678
- }
679
-
680
- /**
681
- * Transforme un Buffer en ReadableStream.
682
- *
683
- * @param {Buffer} buffer - Le buffer à transformer.
684
- * @returns {Promise<stream.Readable>} - Un ReadableStream.
685
- * @throws {Error} - Si appelé dans le navigateur.
686
- * @private
687
- */
688
- async _bufferToReadable(buffer) {
689
- if (typeof window === "undefined") {
690
- const { bufferToReadable } = await import("../utils/stream-utils.node.js");
691
- return bufferToReadable(buffer);
692
- } else {
693
- throw new Error("bufferToReadable ne doit pas être appelé dans le navigateur");
694
- }
695
- }
696
-
697
- /**
698
- * Crée un PassThrough stream pour le traitement des fichiers.
699
- *
700
- * @returns {Promise<stream.PassThrough>} - Un PassThrough stream.
701
- * @throws {Error} - Si appelé dans le navigateur.
702
- * @private
703
- */
704
- async _passThrough() {
705
- if (typeof window === "undefined") {
706
- const { createPassThrough } = await import("../utils/stream-utils.node.js");
707
- return createPassThrough();
708
- } else {
709
- throw new Error("passThrough ne doit pas être appelé dans le navigateur");
710
- }
711
- }
712
-
713
- // async _bufferToReadable(buffer) {
714
- // if (typeof window === "undefined") {
715
- // const { Readable } = await import("stream");
716
- // return Readable.from(buffer);
717
- // } else {
718
- // throw new Error("bufferToReadable ne doit pas être appelé dans le navigateur");
719
- // }
720
- // },
721
-
722
- // async _passThrough() {
723
- // if (typeof window === "undefined") {
724
- // const { PassThrough } = await import("stream");
725
- // return new PassThrough();
726
- // } else {
727
- // throw new Error("passThrough ne doit pas être appelé dans le navigateur");
728
- // }
729
- // },
730
-
731
- /**
732
- * Supprime les propriétés d'un objet en fonction d'une liste de clés.
733
- *
734
- * @param {Object} obj - L'objet source.
735
- * @param {Array} propsToRemove - Liste des clés à supprimer
736
- * @param {boolean} [deep=false] - Si vrai, supprime les propriétés de manière récursive.
737
- * @returns {Object} - Un nouvel objet sans les propriétés supprimées.
738
- * @private
739
- */
740
- _omitProps(obj, propsToRemove) {
741
- if (!obj || typeof obj !== "object") return {};
742
-
743
- const result = { ...obj };
744
- for (const prop of propsToRemove) {
745
- delete result[prop];
746
- }
747
- return result;
748
- }
749
-
750
- /**
751
- * Extrait les propriétés d'un objet en fonction d'une liste de clés.
752
- *
753
- * @param {Object} obj - L'objet source.
754
- * @param {Array} keys - Liste des clés à extraire.
755
- * @param {Object} [transforms={}] - Transformations à appliquer aux valeurs.
756
- * @returns {Object} - Un nouvel objet contenant les propriétés extraites.
757
- * @private
758
- */
759
- _pickProps(obj, keys, transforms = {}) {
760
- if (!obj || typeof obj !== "object") return {};
761
-
762
- const result = {};
763
-
764
- for (const key of keys) {
765
- if (key in obj) {
766
- const value = obj[key];
767
- if (typeof transforms[key] === "function") {
768
- result[key] = transforms[key](value, obj); // (valeur, objet source)
769
- } else {
770
- result[key] = value;
771
- }
772
- }
773
- }
774
-
775
- return result;
776
- }
777
-
778
- /**
779
- * Vérifie si au moins une clé est présente dans l'objet et n'est pas nulle.
780
- *
781
- * @param {Object} obj - L'objet à vérifier.
782
- * @param {Array} keys - Liste des clés à vérifier.
783
- * @returns {boolean} - true si au moins une clé est présente et non nulle, sinon false.
784
- * @private
785
- */
786
- _hasAtLeastOne(obj, keys = []) {
787
- return keys.some((key) => key in obj && obj[key] != null);
788
- }
789
-
790
- /**
791
- * Crée un proxy filtré pour une liste d'entités.
792
- * @param {Array} list - Liste d'entités.
793
- * @returns {Proxy} Proxy filtré.
794
- * @private
795
- */
796
- _createFilteredProxy(list) {
797
- return new Proxy(list, {
798
- get(target, prop, receiver) {
799
- if (typeof prop === "string" && !isNaN(prop)) {
800
- const active = target.filter(n => !n._isDeleted);
801
- return active[prop];
802
- }
803
- if (prop === "length") {
804
- return target.filter(n => !n._isDeleted).length;
805
- }
806
- if (typeof target[prop] === "function") {
807
- return (...args) => target.filter(n => !n._isDeleted)[prop](...args);
808
- }
809
- return Reflect.get(target, prop, receiver);
810
- }
811
- });
812
- }
813
-
814
- /**
815
- * Génère un nouvel identifiant unique.
816
- * @returns {string} Un identifiant unique.
817
- * @private
818
- */
819
- _newId() {
820
- const newId = new ObjectID();
821
- return newId.toString();
822
- }
823
-
824
- /**
825
- * ───────────────────────────────
826
- * DraftStateMixin
827
- * ───────────────────────────────
828
- */
829
-
830
- /**
831
- * Crée un proxy combinant draft + serveur, avec transformations facultatives.
832
- * @param {Object} server
833
- * @param {Object} draft
834
- * @param {Array} allowedFields - champs autorisés dans le draft
835
- * @param {Object} [transforms={}] - transformateurs de lecture
836
- * @param {Object} [options={}] - options
837
- * @returns {Proxy}
838
- * @private
839
- */
840
- _createDraftProxy(apiClient, server = {}, draft = {}, allowedFields = [], transforms = {}, options = {}) {
841
- return new Proxy({}, {
842
- get: (_, prop) => {
843
- // Ne pas tenter d’accéder à des propriétés système
844
- if (typeof prop !== "string" && typeof prop !== "symbol") return undefined;
845
-
846
- // Lecture explicite — déclenche signal si draft est réactif
847
- if (prop in draft) {
848
- const value = draft[prop];
849
- const transformer = transforms[prop];
850
- return typeof transformer === "function" ? transformer(value) : value;
851
- }
852
-
853
- // Fallback vers serverData — aussi potentiellement réactif
854
- if (server && typeof server === "object" && prop in server) {
855
- const value = server[prop];
856
- const transformer = transforms[prop];
857
- return typeof transformer === "function" ? transformer(value) : value;
858
- }
859
-
860
- return undefined;
861
- },
862
-
863
- set: (_, prop, value) => {
864
- if (!allowedFields.includes(prop)) {
865
- const message = `[DraftProxy] Le champ "${prop}" n'est pas autorisé.`;
866
- if (options.throwOnError) {
867
- throw new ApiValidationError(message, 400, null, {
868
- field: prop,
869
- value,
870
- allowedFields
871
- });
872
- }
873
- apiClient._logger.warn(message);
874
- return false;
875
- }
876
- const current = draft[prop];
877
-
878
- if (current && typeof current === "object" && current.__isSignal === true) {
879
- current.value = value; // ✅ met à jour le signal
880
- } else {
881
- draft[prop] = value; // fallback classique
882
- }
883
- return true;
884
- },
885
-
886
- deleteProperty: (_, prop) => {
887
- if (!allowedFields.includes(prop)) return false;
888
- delete draft[prop];
889
- return true;
890
- },
891
-
892
- has: (_, prop) => prop in draft || prop in server,
893
-
894
- ownKeys: () => [...new Set([
895
- ...Object.keys(server || {}),
896
- ...Object.keys(draft || {})
897
- ])],
898
-
899
- getOwnPropertyDescriptor: () => ({
900
- enumerable: true,
901
- configurable: true
902
- })
903
- });
904
- }
905
-
906
- /**
907
- * Extrait les champs modifiables d'un schéma JSON.
908
- *
909
- * @param {Object} schema - Le schéma JSON à analyser.
910
- * @param {Object} data - Les données à comparer.
911
- * @param {Object} ctx - Contexte d'extraction (pour la récursion).
912
- * @param {Object} ctx.defs - Définitions de schéma.
913
- * @param {Set} ctx.visited - Ensemble des schémas déjà visités.
914
- * @returns {Array} - Liste des champs modifiables.
915
- * @private
916
- */
917
- _extractWritableFields(schema = {}, data = {}, ctx = { defs: {}, visited: new Set() }) {
918
- if (!schema || typeof schema !== "object") return [];
919
- if (schema.$id && ctx.visited.has(schema.$id)) return [];
920
- if (schema.$id) ctx.visited.add(schema.$id);
921
- ctx.defs = schema.$defs || schema.definitions || ctx.defs;
922
-
923
- const fields = [];
924
-
925
- if (schema.$ref) {
926
- const refKey = schema.$ref.replace(/^#\/?(\$defs|definitions)\//, "");
927
- const resolved = ctx.defs?.[refKey];
928
- if (resolved) fields.push(...this._extractWritableFields(resolved, data, ctx));
929
- }
930
-
931
- if (schema.allOf) {
932
- schema.allOf.forEach(s => fields.push(...this._extractWritableFields(s, data, ctx)));
933
- }
934
-
935
- if (schema.if && schema.then) {
936
- const condition = schema.if?.properties;
937
- let matches = true;
938
- if (condition) {
939
- for (const key in condition) {
940
- const expected = condition[key]?.const;
941
- if (data[key] !== expected) {
942
- matches = false;
943
- break;
944
- }
945
- }
946
- }
947
- if (matches && schema.then) {
948
- fields.push(...this._extractWritableFields(schema.then, data, ctx));
949
- } else if (!matches && schema.else) {
950
- fields.push(...this._extractWritableFields(schema.else, data, ctx));
951
- }
952
- }
953
-
954
- if (schema.properties) {
955
- fields.push(...Object.entries(schema.properties)
956
- // eslint-disable-next-line no-unused-vars
957
- .filter(([_, def]) => def.readOnly !== true && def.const === undefined)
958
- .map(([key]) => key));
959
- }
960
-
961
- return [...new Set(fields)];
962
- }
963
-
964
- /**
965
- * Construit un brouillon et un proxy à partir des données et du schéma.
966
- *
967
- * @param {Object} options - Options de construction.
968
- * @param {Object} options.data - Données à utiliser pour le brouillon.
969
- * @param {Object} [options.serverData=null] - Données du serveur.
970
- * @param {string|Array} options.constant - Nom de la constante ou tableau de constantes.
971
- * @param {ApiClient} options.apiClient - Instance de l'API.
972
- * @param {Object} [options.transforms={}] - Transformations à appliquer.
973
- * @param {boolean} [options.throwOnError=true] - Si vrai, lève une erreur en cas de problème.
974
- * @param {Array} [options.removeFields=[]] - Liste des champs à ignorer.
975
- * @returns {Object} - Objet contenant le brouillon et le proxy.
976
- * @private
977
- */
978
- _buildDraftAndProxy({ data = {}, serverData = null, previousDraft = null, constant, apiClient, transforms = {}, throwOnError = true, removeFields = [] }) {
979
- const constants = Array.isArray(constant) ? constant : [constant];
980
- const combinedSchema = {
981
- allOf: [],
982
- $defs: {}
983
- };
984
-
985
- for (const key of constants) {
986
- const sch = apiClient.getRequestSchema(key);
987
- if (!sch) throw new ApiError(`Unable to find schema for ${key}.`);
988
-
989
- // Extraire et fusionner les $defs
990
- if (sch.$defs) {
991
- for (const [defKey, defVal] of Object.entries(sch.$defs)) {
992
- if (combinedSchema.$defs[defKey]) {
993
- apiClient._logger.warn(`Duplicate $defs key '${defKey}' from schema '${key}'`);
994
- } else {
995
- combinedSchema.$defs[defKey] = defVal;
996
- }
997
- }
998
- }
999
-
1000
- combinedSchema.allOf.push(sch);
1001
- }
1002
-
1003
- let allowed = this._extractWritableFields(combinedSchema, data);
1004
-
1005
- if (data.id && allowed.indexOf("id") === -1) {
1006
- allowed.push("id");
1007
- }
1008
- if (data.slug && allowed.indexOf("slug") === -1) {
1009
- allowed.push("slug");
1010
- }
1011
-
1012
- allowed = allowed.filter(k => !removeFields.includes(k));
1013
-
1014
- // Transformation des champs autorisés
1015
- const rawDraft = Object.fromEntries(
1016
- allowed.map((key) => {
1017
- const raw = data[key];
1018
- const transformed = typeof transforms[key] === "function"
1019
- ? transforms[key](raw, data)
1020
- : raw;
1021
- return [key, transformed];
1022
- // eslint-disable-next-line no-unused-vars
1023
- }).filter(([_, v]) => v !== undefined)
1024
- );
1025
-
1026
- const initial = structuredClone ? structuredClone(rawDraft) : JSON.parse(JSON.stringify(rawDraft));
1027
-
1028
- const draft = isReactive(previousDraft)
1029
- ? Object.assign(previousDraft, rawDraft)
1030
- : reactive(rawDraft);
1031
-
1032
- // Assure que serverData est réactif si c'est un objet
1033
- const reactiveServer = isReactive(serverData)
1034
- ? serverData
1035
- : (serverData && typeof serverData === "object" ? reactive(serverData) : serverData);
1036
-
1037
- const proxy = this._createDraftProxy(apiClient, reactiveServer, draft, allowed, transforms, { throwOnError });
1038
-
1039
- return { draft, proxy, initial};
1040
- }
1041
-
1042
- /**
1043
- * Extrait les champs modifiés du schéma.
1044
- *
1045
- * @param {ApiClient} apiClient - Instance de l'API.
1046
- * @param {string} constant - Nom de la constante.
1047
- * @param {Object} data - Données à comparer.
1048
- * @param {Function} getInitialDraft - Fonction pour obtenir le brouillon initial.
1049
- * @param {Array} removeFields - Liste des champs à ignorer.
1050
- * @returns {Object|null} - Champs modifiés ou null.
1051
- * @private
1052
- */
1053
- _extractChangedFieldsFromSchema(apiClient, constant, data = {}, getInitialDraft, removeFields = []) {
1054
- const schema = apiClient.getRequestSchema(constant);
1055
- let allowed = this._extractWritableFields(schema, data);
1056
- const changed = {};
1057
- const initialDraft = getInitialDraft?.() || {};
1058
-
1059
- // on enlève les champs qui ne sont pas dans le draft
1060
- // ou qui sont dans removeFields
1061
- allowed = allowed.filter(k => !removeFields.includes(k));
1062
-
1063
- for (const key of allowed) {
1064
- // on verifie que le champ existe dans le draft
1065
- // sinon on ne le prend pas en compte
1066
-
1067
- if (data[key] === undefined) continue;
1068
-
1069
- const current = data[key];
1070
- const initial = initialDraft[key];
1071
-
1072
- const changedValue =
1073
- JSON.stringify(current) !== JSON.stringify(initial);
1074
-
1075
- if (changedValue) {
1076
- changed[key] = current;
1077
- }
1078
- }
1079
-
1080
- return Object.keys(changed).length > 0 ? changed : null;
1081
- }
1082
-
1083
- /**
1084
- * Extrait tous les champs valides selon le schéma, et retourne uniquement ceux qui ont changé par rapport au draft initial.
1085
- * Contrairement à `_extractChangedFieldsFromSchema`, cette méthode retourne l'ensemble des champs valides (`updated`)
1086
- * uniquement s'il y a au moins un champ modifié (`changed`).
1087
- *
1088
- * ⚠️ Les champs sont filtrés en fonction :
1089
- * - des champs définis comme modifiables dans le schéma (`writeable`)
1090
- * - des champs non exclus dans `removeFields`
1091
- * - des champs définis dans `data` (les `undefined` sont ignorés)
1092
- *
1093
- * @param {ApiClient} apiClient - L’instance de client API contenant les schémas.
1094
- * @param {string} constant - Le nom de la constante de schéma (ex: "ADD_EVENT").
1095
- * @param {Object} data - Les nouvelles données à comparer avec le draft initial.
1096
- * @param {() => Object} getInitialDraft - Fonction qui retourne le draft initial (souvent `this.initialDraftData`).
1097
- * @param {string[]} [removeFields=[]] - Champs à ignorer même s’ils sont valides.
1098
- * @returns {Object|null} - Un objet `updated` avec les champs valides si au moins un champ a changé, sinon `null`.
1099
- * @private
1100
- */
1101
- _extractAllValidFieldsFromSchema(apiClient, constant, data = {}, getInitialDraft, removeFields = []) {
1102
- const schema = apiClient.getRequestSchema(constant);
1103
- let allowed = this._extractWritableFields(schema, data);
1104
- const changed = {};
1105
- const updated = {};
1106
- const initialDraft = getInitialDraft?.() || {};
1107
-
1108
- // on enlève les champs qui ne sont pas dans le draft
1109
- // ou qui sont dans removeFields
1110
- allowed = allowed.filter(k => !removeFields.includes(k));
1111
-
1112
- for (const key of allowed) {
1113
- // on verifie que le champ existe dans le draft
1114
- // sinon on ne le prend pas en compte
1115
-
1116
- if (data[key] === undefined) continue;
1117
-
1118
- const current = data[key];
1119
- const initial = initialDraft[key];
1120
-
1121
- const changedValue =
1122
- JSON.stringify(current) !== JSON.stringify(initial);
1123
-
1124
- if (changedValue) {
1125
- changed[key] = current;
1126
- }
1127
-
1128
- updated[key] = current;
1129
- }
1130
-
1131
- return Object.keys(changed).length > 0 ? updated : null;
1132
- }
1133
-
1134
- /**
1135
- * ───────────────────────────────
1136
- * MutualEntityMixin
1137
- * ───────────────────────────────
1138
- */
1139
-
1140
- /**
1141
- * Résout l'identifiant de l'entité si seul le slug est fourni.
1142
- * @param {string} type - Le type d'entité (ex : "citoyens", "organizations", "projects").
1143
- * @returns {Promise<string>} L'identifiant résolu.
1144
- * @private
1145
- */
1146
- async _resolveId(type) {
1147
- if (!this.id && this.slug) {
1148
- try {
1149
- const data = await this.endpointApi.getElementsKey({
1150
- pathParams:{
1151
- slug: this.slug
1152
- }
1153
- });
1154
- if(data?.contextId && data?.contextType === type) {
1155
- this._id(data.contextId);
1156
- } else {
1157
- throw new ApiResponseError(`Le slug ${this.slug} ne correspond pas à un ${type}`, 200, data);
1158
- }
1159
- } catch (error) {
1160
- if(error instanceof ApiResponseError) {
1161
- if(error?.responseData?.contextType !== type) {
1162
- throw error;
1163
- } else {
1164
- throw new ApiResponseError(`Impossible de récupérer l'identifiant pour le slug ${this.slug}`, error.status, error.responseData);
1165
- }
1166
- } else {
1167
- throw error;
1168
- }
1169
- }
1170
- }
1171
- return this.id;
1172
- }
1173
-
1174
- /**
1175
- * Récupère le profil public de l'entité.
1176
- *
1177
- * @returns {Promise<Object>} - Les données du profil public.
1178
- * @private
1179
- */
1180
- async _getPublicProfile() {
1181
- await this._resolveId(this.getEntityType());
1182
- return this.endpointApi.getElementsAbout({ pathParams: { id: this.id, type: this.getEntityType() } });
1183
- }
1184
-
1185
- /**
1186
- * Récupère la classe d'entité et ses dépendances à partir du type d'entité.
1187
- *
1188
- * @param {string} entityType - Le type d'entité.
1189
- * @return {{ entityClass: Function, deps: Object } | null}
1190
- * @private
1191
- */
1192
- _getEntityMeta(entityType) {
1193
- const selfClass = this.constructor;
1194
- const selfTag = this.__entityTag;
1195
-
1196
- const commonDeps = {
1197
- EndpointApi: this.deps.EndpointApi,
1198
- User: selfTag === "User" ? selfClass : this.deps.User,
1199
- Organization: selfTag === "Organization" ? selfClass : this.deps.Organization,
1200
- Project: selfTag === "Project" ? selfClass : this.deps.Project,
1201
- Event: selfTag === "Event" ? selfClass : this.deps.Event,
1202
- Poi: selfTag === "Poi" ? selfClass : this.deps.Poi,
1203
- Badge: selfTag === "Badge" ? selfClass : this.deps.Badge,
1204
- News: selfTag === "News" ? selfClass : this.deps.News
1205
- };
1206
-
1207
- const map = {
1208
- citoyens: { entityClass: commonDeps.User, deps: commonDeps },
1209
- organizations:{ entityClass: commonDeps.Organization, deps: commonDeps },
1210
- projects: { entityClass: commonDeps.Project, deps: commonDeps },
1211
- events: { entityClass: commonDeps.Event, deps: { ...commonDeps, Badge: undefined } },
1212
- poi: { entityClass: commonDeps.Poi, deps: { ...commonDeps, Badge: undefined, News: undefined } },
1213
- news: { entityClass: commonDeps.News, deps: { ...commonDeps } },
1214
- badges: { entityClass: commonDeps.Badge, deps: {
1215
- EndpointApi: commonDeps.EndpointApi,
1216
- User: commonDeps.User,
1217
- Organization: commonDeps.Organization,
1218
- Project: commonDeps.Project
1219
- } }
1220
- };
1221
-
1222
- return map[entityType] || null;
1223
- }
1224
-
1225
- /**
1226
- * Lier des données d'entité à une instance d'entité.
1227
- *
1228
- * @param {string} entityType - Le type d'entité (ex : "citoyens", "organisations", "projets").
1229
- * @param {Object} entityData - Les données de l'entité à lier.
1230
- * @return {Object} L'entité liée.
1231
- * @private
1232
- */
1233
- _linkEntity(entityType, entityData) {
1234
- const meta = this._getEntityMeta(entityType);
1235
- if (!meta) return entityData;
1236
- return meta.entityClass.fromServerData(entityData, this, meta.deps);
1237
- }
1238
-
1239
- /**
1240
- * Récupère et lie une entité à partir de son ID.
1241
- *
1242
- * @param {string} entityType - Le type d'entité.
1243
- * @param {string} entityId - L'identifiant de l'entité.
1244
- * @param {Object} [options] - Options supplémentaires :
1245
- * @param {boolean} [options.skipGet] - Si true, ne pas appeler `get()` sur l'entité.
1246
- * @return {Promise<Object|null>} L'entité liée ou null.
1247
- * @private
1248
- */
1249
- async _linkEntityById(entityType, entityId, options = {}) {
1250
- const meta = this._getEntityMeta(entityType);
1251
- if (!meta) return null;
1252
- const entity = new meta.entityClass(this, { id: entityId }, meta.deps);
1253
- if(options?.skipGet) return entity;
1254
- await entity.get();
1255
- return entity;
1256
- }
1257
-
1258
- /**
1259
- * Lie une liste d'entités à partir de leurs données.
1260
- *
1261
- * @param {Array<Object>} results - Liste de données d'entités.
1262
- * @return {Array<Object>} Liste d'entités liées.
1263
- * @private
1264
- */
1265
- _linkEntities(results) {
1266
- return results.flatMap(d => {
1267
- if (!d?.collection) {
1268
- this.apiClient._logger?.warn?.(`Objet ignoré car sans 'collection' : ${d?.id}`);
1269
- return []; // exclu de la liste
1270
- }
1271
-
1272
- return [this._linkEntity?.(d.collection, d) ?? d];
1273
- });
1274
- }
1275
-
1276
- /**
1277
- * Lie des entités présentes dans `this.serverData` à partir de leurs IDs,
1278
- * en les filtrant dynamiquement et en optionnellement les transformant.
1279
- *
1280
- * @param {string} entityType - Le type d'entité (ex : "badges", "citoyens", etc.).
1281
- * @param {Object} filters - Clés/valeurs de filtres dynamiques. Les valeurs peuvent être :
1282
- * - un littéral (comparaison stricte ou intelligente selon le type),
1283
- * - une chaîne (utilise `includes` insensible à la casse),
1284
- * - une RegExp (appliquée si la valeur est une chaîne),
1285
- * - une fonction `(value) => boolean`.
1286
- * @param {Object} [options] - Options supplémentaires :
1287
- * @param {string} [options.key] - Le champ de `this.serverData` à utiliser (par défaut = entityType).
1288
- * @param {Function} [options.mapFn] - Fonction de transformation `(entity) => any` appliquée au résultat.
1289
- *
1290
- * @return {Promise<Array<Object>>} Liste des entités liées, filtrées et éventuellement transformées.
1291
- *
1292
- * @example
1293
- * // Tous les badges avec `name` contenant "codev"
1294
- * const badges = await this.linkEntitiesFromServerData("badges", { name: "codev" });
1295
- *
1296
- * @example
1297
- * // Badges non expirés et visibles
1298
- * const badges = await this.linkEntitiesFromServerData("badges", {
1299
- * expiredOn: false,
1300
- * show: "true"
1301
- * });
1302
- *
1303
- * @example
1304
- * // Badges émis après 2023
1305
- * const badges = await this.linkEntitiesFromServerData("badges", {
1306
- * issuedOn: (v) => new Date(v) >= new Date("2023-01-01")
1307
- * });
1308
- *
1309
- * @example
1310
- * // Extraire uniquement les noms des badges
1311
- * const namesOnly = await this.linkEntitiesFromServerData("badges", {}, {
1312
- * mapFn: (badge) => badge.meta.name
1313
- * });
1314
- */
1315
- async linkEntitiesFromServerData(entityType, filters = {}, options = {}) {
1316
- const key = options.key || entityType;
1317
- const mapFn = typeof options.mapFn === "function" ? options.mapFn : null;
1318
-
1319
- const isTruthy = (v) => v === true || v === "true";
1320
- const isFalsy = (v) => v === false || v === "false";
1321
-
1322
- const data = this.serverData?.[key];
1323
- const result = [];
1324
-
1325
- if (data && typeof data === "object" && Object.keys(data).length > 0) {
1326
- for (const id of Object.keys(data)) {
1327
- const meta = data[id];
1328
-
1329
- const matches = Object.entries(filters).every(([key, expected]) => {
1330
- const actual = meta[key];
1331
-
1332
- if (typeof expected === "function") {
1333
- try {
1334
- return expected(actual);
1335
- } catch (err) {
1336
- console.warn(`Erreur dans le filtre personnalisé pour ${key}`, err);
1337
- return false;
1338
- }
1339
- }
1340
-
1341
- if (expected instanceof RegExp) {
1342
- return typeof actual === "string" && expected.test(actual);
1343
- }
1344
-
1345
- if (typeof expected === "string" && typeof actual === "string") {
1346
- return actual.toLowerCase().includes(expected.toLowerCase());
1347
- }
1348
-
1349
- if (isTruthy(expected)) return isTruthy(actual);
1350
- if (isFalsy(expected)) return isFalsy(actual);
1351
-
1352
- if (
1353
- typeof actual === "string" &&
1354
- typeof expected === "string" &&
1355
- !isNaN(Date.parse(actual)) &&
1356
- !isNaN(Date.parse(expected))
1357
- ) {
1358
- return new Date(actual).getTime() === new Date(expected).getTime();
1359
- }
1360
-
1361
- return actual === expected;
1362
- });
1363
-
1364
- if (!matches) continue;
1365
-
1366
- try {
1367
- const entity = await this._linkEntityById(entityType, id);
1368
- if (entity) {
1369
- entity.meta = meta;
1370
- result.push(mapFn ? mapFn(entity) : entity);
1371
- }
1372
- } catch (error) {
1373
- this._logger?.error?.(`Erreur lors de la récupération de l'entité ${entityType} (${id}) :`, error);
1374
- }
1375
- }
1376
- }
1377
- return result;
1378
- }
1379
-
1380
- /**
1381
- * Crée une instance d'entité à partir des données fournies.
1382
- * @param {string} entityType - Le type d'entité (ex : "citoyens", "organisations", "projets").
1383
- * @param {Object} entityData - Les données de l'entité.
1384
- * @return {Object} L'entité créée.
1385
- * @private
1386
- */
1387
- _entityInstanceData(entityType, entityData) {
1388
- const meta = this._getEntityMeta(entityType);
1389
- const selfKey = this.__entityTag; // ex: "User", "Organization", etc.
1390
- const selfClass = this.constructor;
1391
-
1392
- // pour citoyens la signature est différentes
1393
- return new meta.entityClass(this, entityData, {
1394
- [selfKey]: selfClass,
1395
- ...meta.deps
1396
- });
1397
- }
1398
-
1399
- /**
1400
- * Crée une instance d'entité, et déclenche get() si certaines propriétés sont présentes.
1401
- * @param {string} entityType
1402
- * @param {Object} entityData
1403
- * @return {Promise<Object>}
1404
- */
1405
- async entity(entityType, entityData = {}) {
1406
- try {
1407
- const entity = this._entityInstanceData(entityType, entityData);
1408
-
1409
- const fetchKeysByEntity = {
1410
- citoyens: ["id", "slug"],
1411
- organizations: ["id", "slug"],
1412
- projects: ["id", "slug"],
1413
- events: ["id", "slug"],
1414
- poi: ["id", "slug"],
1415
- news: ["id"],
1416
- badges: ["id"],
1417
- };
1418
-
1419
- const fetchKeys = fetchKeysByEntity[entityType];
1420
-
1421
- if (fetchKeys && this._hasAtLeastOne(entityData, fetchKeys)) {
1422
- await entity.get();
1423
- }
1424
-
1425
- return entity;
1426
- } catch (error) {
1427
- this.apiClient._logger.error(`[Api.${this.__entityTag}.${entityType}] Erreur lors de la création d'une instance ${entityType} :`, error.message);
1428
- throw error;
1429
- }
1430
- }
1431
-
1432
- /**
1433
- * Récupère et lie une entité à partir de son slug.
1434
- * @param {string} slug - Le slug de l'entité.
1435
- * @return {Promise<Object|null>} L'entité liée ou null.
1436
- */
1437
- async entityBySlug(slug) {
1438
- if (!slug) {
1439
- throw new ApiError("Le slug est requis.");
1440
- }
1441
- try {
1442
- const data = await this.endpointApi.getElementsKey({
1443
- pathParams:{
1444
- slug: slug
1445
- }
1446
- });
1447
- if(data?.contextId && data?.contextType) {
1448
- const entity = await this.entity(data.contextType, { id: data.contextId });
1449
- return entity;
1450
- } else {
1451
- throw new ApiResponseError(`Le slug ${slug} n'est pas valide`, 200, data);
1452
- }
1453
- } catch (error) {
1454
- if(error instanceof ApiResponseError) {
1455
- throw new ApiResponseError(`Impossible de récupérer l'identifiant pour le slug ${slug}`, error.status, error.responseData);
1456
- } else {
1457
- throw error;
1458
- }
1459
- }
1460
- }
1461
-
1462
-
1463
- /**
1464
- * Construit dynamiquement des filtres sur un lien entre entités
1465
- *
1466
- * @param {string} id - L'ID de l'entité cible.
1467
- * @param {Object} options - Options de filtrage.
1468
- * @param {"memberOf" | "projects"} [options.linkType] - Le type de lien (ex: "memberOf", "projects", etc.).
1469
- * @param {boolean} [options.isAdmin] - Si défini, filtre selon l'existence de isAdmin.
1470
- * @param {boolean} [options.isAdminPending] - Si défini, filtre selon l'existence de isAdminPending.
1471
- * @param {boolean} [options.isInviting] - Si défini, filtre selon l'existence de isInviting.
1472
- * @param {boolean} [options.toBeValidated] - Si défini, filtre selon l'existence de toBeValidated.
1473
- * @param {Array<string>} [options.roles] - Rôles à filtrer.
1474
- * @returns {Object} - Un objet de filtres à passer à une requête.
1475
- * @throws {TypeError} - Si les types des paramètres ne sont pas valides.
1476
- * @private
1477
- */
1478
- _buildLinkFilters(id, {
1479
- linkType,
1480
- isAdmin,
1481
- isAdminPending,
1482
- isInviting,
1483
- toBeValidated = false,
1484
- roles = []
1485
- } = {}) {
1486
- if (typeof id !== "string") throw new TypeError("id doit être une chaîne.");
1487
- if (typeof linkType !== "string") throw new TypeError("linkType doit être une chaîne.");
1488
- if (typeof isAdmin !== "undefined" && typeof isAdmin !== "boolean") {
1489
- throw new TypeError("isAdmin doit être un booléen.");
1490
- }
1491
- if (typeof isAdminPending !== "undefined" && typeof isAdminPending !== "boolean") {
1492
- throw new TypeError("isAdminPending doit être un booléen.");
1493
- }
1494
- if (typeof isInviting !== "undefined" && typeof isInviting !== "boolean") {
1495
- throw new TypeError("isInviting doit être un booléen.");
1496
- }
1497
- if (typeof toBeValidated !== "undefined" && typeof toBeValidated !== "boolean") {
1498
- throw new TypeError("toBeValidated doit être un booléen.");
1499
- }
1500
- if (!Array.isArray(roles)) {
1501
- throw new TypeError("roles doit être un tableau de chaînes.");
1502
- }
1503
-
1504
- const path = `links.${linkType}.${id}`;
1505
- const filters = {
1506
- [`${path}`]: { $exists: true }
1507
- };
1508
-
1509
- if (typeof toBeValidated === "boolean") {
1510
- filters[`${path}.toBeValidated`] = { $exists: toBeValidated };
1511
- }
1512
-
1513
- if (typeof isInviting === "boolean") {
1514
- filters[`${path}.isInviting`] = { $exists: isInviting };
1515
- }
1516
-
1517
- if (isAdmin === true) {
1518
- filters[`${path}.isAdmin`] = { $exists: true };
1519
- }
1520
-
1521
- if(isAdminPending === true) {
1522
- filters[`${path}.isAdminPending`] = { $exists: true };
1523
- }
1524
-
1525
- if (roles.length > 0) {
1526
- filters[`${path}.roles`] = { $in: roles };
1527
- }
1528
-
1529
- return filters;
1530
- }
1531
-
1532
- /**
1533
- * Retourne les métadonnées de lien pour une entité :
1534
- * - `linkType` : clé dans `serverData.links`
1535
- * - `connectTypeConnect` : valeur envoyée pour `connect()`
1536
- * - `connectTypeDisconnect` : valeur envoyée pour `disconnect()`
1537
- * @typedef {Object} LinkMeta
1538
- * @property {string} linkType
1539
- * @property {string} connectTypeConnect
1540
- * @property {string} connectTypeDisconnect
1541
- * @returns {LinkMeta}
1542
- * @throws {ApiError} - Si le type d'entité est inconnu.
1543
- * @private
1544
- */
1545
- _getLinkMeta() {
1546
- const map = {
1547
- organizations: {
1548
- linkType: "memberOf",
1549
- connectTypeConnect: "member",
1550
- connectTypeDisconnect: "members"
1551
- },
1552
- projects: {
1553
- linkType: "projects",
1554
- connectTypeConnect: "contributor",
1555
- connectTypeDisconnect: "contributors"
1556
- },
1557
- events: {
1558
- linkType: "events",
1559
- connectTypeConnect: "attendee",
1560
- connectTypeDisconnect: "attendees"
1561
- },
1562
- citoyens: {
1563
- linkType: "friends",
1564
- connectTypeConnect: "friend",
1565
- connectTypeDisconnect: "friends"
1566
- }
1567
- };
1568
-
1569
- const entityType = this.getEntityType();
1570
- const meta = map[entityType];
1571
-
1572
- if (!meta) {
1573
- throw new ApiError(`Aucune correspondance de lien définie pour le type d'entité "${entityType}"`);
1574
- }
1575
-
1576
- return meta;
1577
- }
1578
-
1579
- /**
1580
- * Vérifie si l'entité prend en charge les opérations de lien (`connect`, `disconnect`, etc.).
1581
- * Utilise `_getLinkMeta()` comme source unique de vérité.
1582
- *
1583
- * @throws {ApiError} - Si l'entité ne supporte pas les liens.
1584
- * @private
1585
- */
1586
- _checkLinkableEntity() {
1587
- try {
1588
- this._getLinkMeta(); // si l'entité n'est pas supportée, ça throw déjà
1589
- } catch (error) {
1590
- if (error instanceof ApiError) {
1591
- throw new ApiError(`L'entité "${this.getEntityType()}" ne prend pas en charge les actions de lien.`);
1592
- }
1593
- throw error;
1594
- }
1595
- }
1596
-
1597
- /**
1598
- * Retourne le lien de l'utilisateur connecté avec l'entité cible (dans `parent.serverData.links`)
1599
- * @private
1600
- */
1601
- _getLinkFromConnectedUser() {
1602
- const { linkType } = this._getLinkMeta();
1603
- return this?.userContext?.serverData?.links?.[linkType]?.[this.id] || null;
1604
- }
1605
-
1606
- /**
1607
- * Soumet une demande de connexion à une entité (ex : membre, contributeur),
1608
- * ou valide l'invitation si elle existe déjà.
1609
- *
1610
- * @returns {Promise<Object>} - Résultat de l'API
1611
- * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1612
- * @private
1613
- */
1614
- async _submitLinkRequest() {
1615
-
1616
- if (!this.id) {
1617
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
1618
- }
1619
-
1620
- const { connectTypeConnect } = this._getLinkMeta();
1621
-
1622
- const userLink = this._getLinkFromConnectedUser();
1623
-
1624
- // Cas : aucun lien → on demande à se connecter
1625
- if (!userLink) {
1626
- const data = {
1627
- parentType: this.getEntityType(),
1628
- parentId: this.id,
1629
- connectType: connectTypeConnect
1630
- };
1631
-
1632
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1633
- const retour = await this.callIsMe(() => this.endpointApi.connect(data));
1634
- await this.userContext.refresh();
1635
- return retour;
1636
- }
1637
-
1638
- // Cas : invitation reçue → on valide l'invitation
1639
- if (userLink.isInviting) {
1640
- return this._acceptLinkRequest();
1641
- }
1642
-
1643
- // Cas : déjà en attente
1644
- if (userLink.toBeValidated) {
1645
- throw new ApiError("Vous êtes déjà en attente de validation.");
1646
- }
1647
-
1648
- // Cas par défaut : rien à faire
1649
- throw new ApiError("Vous êtes déjà connecté à cette entité.");
1650
- }
1651
-
1652
- /**
1653
- * Soumet une demande pour devenir administrateur d'une entité.
1654
- *
1655
- * @returns {Promise<Object>}
1656
- * @throws {ApiError}
1657
- * @private
1658
- */
1659
- async _submitLinkRequestAdmin() {
1660
-
1661
- if (!this.id) {
1662
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
1663
- }
1664
-
1665
- const userLink = this._getLinkFromConnectedUser();
1666
-
1667
- // Aucun lien existant → envoie une demande avec rôle "admin"
1668
- if (!userLink) {
1669
- const data = {
1670
- parentType: this.getEntityType(),
1671
- parentId: this.id,
1672
- connectType: "admin"
1673
- };
1674
-
1675
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1676
- const retour = await this.callIsMe(() => this.endpointApi.connect(data));
1677
- await this.userContext.refresh();
1678
- return retour;
1679
- }
1680
-
1681
- // Invitation reçue → accepte automatiquement
1682
- if (userLink.isInviting) {
1683
- return this._acceptLinkRequest();
1684
- }
1685
-
1686
- // Déjà en attente pour admin
1687
- if (userLink.toBeValidated && userLink.isAdminPending) {
1688
- throw new ApiError("Vous êtes déjà en attente de validation pour devenir admin.");
1689
- }
1690
-
1691
- // Déjà en attente pour un autre rôle
1692
- if (userLink.toBeValidated) {
1693
- throw new ApiError("Vous êtes déjà en attente de validation pour rejoindre cette entité.");
1694
- }
1695
-
1696
- // Déjà connecté
1697
- throw new ApiError("Vous êtes déjà connecté à cette entité.");
1698
- }
1699
-
1700
- /**
1701
- * Accepte une demande de lien (ex : invitation à rejoindre un groupe).
1702
- *
1703
- * @returns {Promise<Object>} - Résultat de l'API
1704
- * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1705
- * @private
1706
- */
1707
- async _acceptLinkRequest() {
1708
-
1709
- if (!this.id) {
1710
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
1711
- }
1712
-
1713
- const userLink = this._getLinkFromConnectedUser();
1714
-
1715
- if (userLink.isInviting) {
1716
- const data = {
1717
- parentType: this.getEntityType(),
1718
- parentId: this.id,
1719
- linkOption: "isInviting"
1720
- };
1721
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1722
- const retour = await this.callIsMe(() => this.endpointApi.linkValidate(data));
1723
- await this.userContext.refresh();
1724
- return retour;
1725
- }
1726
-
1727
- throw new ApiError("Vous n'avez pas d'invitation à valider.");
1728
- }
1729
-
1730
- /**
1731
- * Annule une demande de lien ou se déconnecte d'une entité.
1732
- *
1733
- * @returns {Promise<Object>} - Résultat de l'API
1734
- * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1735
- * @private
1736
- */
1737
- async _leaveLinkRequest() {
1738
-
1739
- if (!this.id) {
1740
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
1741
- }
1742
-
1743
- const { connectTypeDisconnect } = this._getLinkMeta();
1744
-
1745
- const userLink = this._getLinkFromConnectedUser();
1746
-
1747
- if (!userLink) {
1748
- throw new ApiError("Vous n'êtes pas connecté à cette entité.");
1749
- }
1750
-
1751
- const data = {
1752
- parentType: this.getEntityType(),
1753
- parentId: this.id,
1754
- // Normalement en auto dans le schema mais je le met quand même
1755
- connectType: connectTypeDisconnect
1756
- };
1757
-
1758
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1759
- const retour = await this.callIsMe(() => this.endpointApi.disconnect(data));
1760
- await this.userContext.refresh();
1761
- return retour;
1762
- }
1763
-
1764
- /**
1765
- * ───────────────────────────────
1766
- * EntityMixin
1767
- * ───────────────────────────────
1768
- */
1769
-
1770
- /**
1771
- * Récupérer le profil public de l'entité
1772
- *
1773
- * @returns {Promise<Object>} - Les données de réponse.
1774
- */
1775
- async get() {
1776
- return this.apiClient.safeCall(async () => {
1777
- const data = await this._getPublicProfile();
1778
- this._setData(data, { forceInitialDraftReset: true });
1779
- return data;
1780
- });
1781
- }
1782
-
1783
- /**
1784
- * Mettre à jour les paramètres d'un élément : Mise à jour des paramètres spécifiques d'un élément.
1785
- * Constant : UPDATE_SETTINGS
1786
- * @param {Object} data - Les données à envoyer.
1787
- * @param {string} data.type - data.type
1788
- * @param {undefined} data.value - data.value
1789
- * @returns {Promise<Object>} - Les données de réponse.
1790
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1791
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1792
- * @throws {Error} - En cas d'erreur inattendue.
1793
- */
1794
- async updateSettings(data = {}) {
1795
- data.idEntity = this.id;
1796
- data.typeEntity = this.getEntityType();
1797
- return this.callIsConnected(() => this.endpointApi.updateSettings(data));
1798
- }
1799
-
1800
- /**
1801
- * Mettre à jour la description d'un élément : Permet de mettre à jour la description courte et complète d'un élément.
1802
- * Constant : UPDATE_BLOCK_DESCRIPTION
1803
- * @param {Object} data - Les données à envoyer.
1804
- * @param {string} data.descMentions - Mentions dans la description (default: "")
1805
- * @param {string} data.shortDescription - Courte description
1806
- * @param {string} data.description - Description complète
1807
- * @returns {Promise<Object>} - Les données de réponse.
1808
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1809
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1810
- * @throws {Error} - En cas d'erreur inattendue.
1811
- */
1812
- async updateDescription(data = {}) {
1813
- data.typeElement = this.getEntityType();
1814
- data.id = this.id;
1815
- return this.callIsConnected(() => this.endpointApi.updateBlockDescription(data));
1816
- }
1817
-
1818
- /**
1819
- * Mettre à jour les informations d'un élément : Permet de mettre à jour les informations générales d'un élément (nom, contacts, etc.).
1820
- * Constant : UPDATE_BLOCK_INFO
1821
- */
1822
- async updateInfo(data = {}) {
1823
- data.typeElement = this.getEntityType();
1824
- data.id = this.id;
1825
- return this.callIsConnected(() => this.endpointApi.updateBlockInfo(data));
1826
- }
1827
-
1828
- /**
1829
- * Mettre à jour les réseaux sociaux d'un élément : Permet de mettre à jour les liens vers les réseaux sociaux d'un élément.
1830
- * Constant : UPDATE_BLOCK_SOCIAL
1831
- */
1832
- async updateSocial(data = {}) {
1833
- data.typeElement = this.getEntityType();
1834
- data.id = this.id;
1835
- return this.callIsConnected(() => this.endpointApi.updateBlockSocial(data));
1836
- }
1837
-
1838
- /**
1839
- * Mettre à jour les localités d'un élément : Permet de mettre à jour l'adresse et les informations géographiques d'un élément.
1840
- * Constant : UPDATE_BLOCK_LOCALITY
1841
- */
1842
- async updateLocality(data = {}) {
1843
- data.typeElement = this.getEntityType();
1844
- data.id = this.id;
1845
- return this.callIsConnected(() => this.endpointApi.updateBlockLocality(data));
1846
- }
1847
-
1848
- /**
1849
- * Mettre à jour le slug d'un élément : Permet de mettre à jour le slug pour une URL simplifiée.
1850
- * Constant : UPDATE_BLOCK_SLUG
1851
- * @param {Object} data - Les données à envoyer.
1852
- * @param {string} data.slug - Le nouveau slug à appliquer.
1853
- * @returns {Promise<Object>} - Les données de réponse.
1854
- */
1855
- async updateSlug({ slug }) {
1856
- try {
1857
- await this.endpointApi.check({ type: this.getEntityType(), id: this.id, slug });
1858
- } catch (error) {
1859
- if(error instanceof ApiResponseError) {
1860
- throw new ApiResponseError("Erreur lors de la vérification du slug.", error.status, error.data);
1861
- }
1862
- throw error;
1863
- }
1864
- return this.callIsConnected(() => this.endpointApi.updateBlockSlug({ typeElement: this.getEntityType(), id: this.id, slug }));
1865
- }
1866
-
1867
- /**
1868
- * Mettre à jour l'image de profil : Permet de mettre à jour l'image de profil d'un utilisateur ou d'une entité.
1869
- * Constant : PROFIL_IMAGE
1870
- * @param {Object} data - Les données à envoyer.
1871
- * @param {string} data.profil_avatar - L'image de profil à mettre à jour.
1872
- * @returns {Promise<Object>} - Les données de réponse.
1873
- */
1874
- async updateImageProfil({ profil_avatar: image }) {
1875
- image = await this._validateImage(image);
1876
- const data = { pathParams: { folder: this.getEntityType(), ownerId: this.id }, profil_avatar: image };
1877
- return this.callIsConnected(() => this.endpointApi.profilImage(data));
1878
- }
1879
-
1880
- /**
1881
- * Crée une instance d'organisation et récupère son profil si nécessaire.
1882
- *
1883
- * @param {Object} organizationData - Les données nécessaires pour initialiser l'organisation.
1884
- * @returns {Promise<Organization>} Une promesse qui résout l'objet Organisation créé.
1885
- * @throws {Error} Si une erreur se produit lors de la création de l'organisation.
1886
- */
1887
- async organization(organizationData = {}) {
1888
- if(!this.isMe){
1889
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer une organisation.");
1890
- }
1891
- return this.entity("organizations", organizationData);
1892
- }
1893
-
1894
- /**
1895
- * Crée une instance de projet et récupère son profil si nécessaire.
1896
- *
1897
- * @param {Object} projectData - Les données nécessaires pour initialiser le projet.
1898
- * @returns {Promise<Project>} Une promesse qui résout l'objet Projet créé.
1899
- * @throws {Error} Si une erreur se produit lors de la création du projet.
1900
- */
1901
- async project(projectData = {}) {
1902
- // TODO: Vérifier si l'utilisateur est admin de l'organisation
1903
- if(!this.isConnected){
1904
- throw new ApiError("Vous devez être connecté pour créer un projet.");
1905
- }
1906
- return this.entity("projects", projectData);
1907
- }
1908
-
1909
- /**
1910
- * Crée une instance de POI et la récupère si nécessaire.
1911
- *
1912
- * @param {Object} poiData - Les données nécessaires pour initialiser le POI.
1913
- * @returns {Promise<Poi>} Une promesse qui résout l'objet POI créé.
1914
- * @throws {Error} Si une erreur se produit lors de la création du POI.
1915
- */
1916
- async poi(poiData = {}) {
1917
- // TODO: Vérifier si l'utilisateur est admin de l'organisation
1918
- if(!this.isConnected){
1919
- throw new ApiError("Vous devez être connecté pour créer un POI.");
1920
- }
1921
- return this.entity("poi", poiData);
1922
- }
1923
-
1924
- /**
1925
- * Crée une instance d'événement et la récupère si nécessaire.
1926
- *
1927
- * @param {Object} eventData - Les données nécessaires pour initialiser l'événement.
1928
- * @returns {Promise<Event>} Une promesse qui résout l'objet Événement créé.
1929
- * @throws {Error} Si une erreur se produit lors de la création de l'événement.
1930
- */
1931
- async event(eventData = {}) {
1932
- // TODO: Vérifier si l'utilisateur est admin de l'organisation
1933
- if(!this.isConnected){
1934
- throw new ApiError("Vous devez être connecté pour créer un événement.");
1935
- }
1936
- return this.entity("events", eventData);
1937
- }
1938
-
1939
- /**
1940
- * Crée une instance de badge et la récupère si nécessaire.
1941
- *
1942
- * @param {Object} badgeData - Les données nécessaires pour initialiser le badge.
1943
- * @returns {Promise<Badge>} Une promesse qui résout l'objet Badge créé.
1944
- * @throws {Error} Si une erreur se produit lors de la création du badge.
1945
- */
1946
- async badge(badgeData = {}) {
1947
- // TODO: Vérifier si l'utilisateur est admin de l'organisation
1948
- if(!this.isConnected){
1949
- throw new ApiError("Vous devez être connecté pour créer un badge.");
1950
- }
1951
- return this.entity("badges", badgeData);
1952
- }
1953
-
1954
- /**
1955
- * Crée une instance de news et la récupère si nécessaire.
1956
- *
1957
- * @param {Object} newsData - Les données nécessaires pour initialiser la news.
1958
- * @returns {Promise<News>} Une promesse qui résout l'objet News créé.
1959
- * @throws {Error} Si une erreur se produit lors de la création de la news.
1960
- */
1961
- async news(newsData = {}) {
1962
- if(!this.isConnected){
1963
- throw new ApiError("Vous devez être connecté.");
1964
- }
1965
- return this.entity("news", newsData);
1966
- }
1967
-
1968
- /**
1969
- * Récupérer les organisations d'une entitée : la liste des organisations dont l'entité est membre ou admin valide.
1970
- * Constant : GET_ORGANIZATIONS_ADMIN | GET_ORGANIZATIONS_NO_ADMIN
1971
- * @param {Object} data - Les données à envoyer.
1972
- * @param {boolean} isNext - Indique si c'est une recherche suivante (pagination).
1973
- * @returns {Promise<Object>} - Les données de réponse.
1974
- */
1975
- async getOrganizations(data = {}) {
1976
- data.searchType = this._getDefaultFromEndpoint("GET_ORGANIZATIONS_NO_ADMIN", "searchType");
1977
- // data.searchBy = "ALL";
1978
-
1979
- const paginator = this._createPaginatorEngine({
1980
- initialData: data,
1981
- finalizer: async (finalData) => {
1982
- if(this.isMe){
1983
- finalData.pathParams = { type: this.getEntityType(), id: this.id };
1984
- // NOTE : dans le schema je crois que si pas de finalData.filters alors le default ce fait avec finalData.pathParams
1985
- // finalData.filters = {
1986
- // [`links.members.${this.id}`]: { "$exists": true },
1987
- // [`links.members.${this.id}.toBeValidated`]: { "$exists": false },
1988
- // [`links.members.${this.id}.isInviting`]: { "$exists": false }
1989
- // };
1990
- } else {
1991
- delete finalData?.pathParams;
1992
- finalData.filters = {
1993
- [`links.members.${this.id}`]: { "$exists": true },
1994
- [`links.members.${this.id}.toBeValidated`]: { "$exists": false },
1995
- [`links.members.${this.id}.isInviting`]: { "$exists": false }
1996
- };
1997
- }
1998
-
1999
- const fetchFn = this.isMe
2000
- ? () => this.callIsMe(() => this.endpointApi.getOrganizationsAdmin(finalData))
2001
- : () => this.endpointApi.getOrganizationsNoAdmin(finalData);
2002
-
2003
- return fetchFn();
2004
- }
2005
- });
2006
-
2007
- return paginator.next();
2008
- }
2009
-
2010
- /**
2011
- * Récupérer les projets d'une entitée : liste des projets de l'entité ou elle est "parent" ou "contributeur".
2012
- * Constant : GET_PROJECTS_ADMIN | GET_PROJECTS_NO_ADMIN
2013
- * @param {Object} data - Les données à envoyer.
2014
- * @param {boolean} isNext - Indique si c'est une recherche suivante (pagination).
2015
- * @returns {Promise<Object>} - Les données de réponse.
2016
- */
2017
- async getProjects(data = {}) {
2018
- data.searchType = this._getDefaultFromEndpoint("GET_PROJECTS_ADMIN", "searchType");
2019
- // data.searchBy = "ALL";
2020
-
2021
- const paginator = this._createPaginatorEngine({
2022
- initialData: data,
2023
- finalizer: async (finalData) => {
2024
- if(this.isMe){
2025
- finalData.pathParams = { type: this.getEntityType(), id: this.id };
2026
- // NOTE : dans le schema je crois que si pas de finalData.filters alors le default ce fait avec finalData.pathParams
2027
- // finalData.filters = {
2028
- // "$or": {
2029
- // [`links.contributors.${this.id}`]: { "$exists": true },
2030
- // [`parent.${this.id}`]: { "$exists": true }
2031
- // },
2032
- // [`links.contributors.${this.id}`]: { "$exists": true }
2033
- // };
2034
- } else {
2035
- delete finalData?.pathParams;
2036
- finalData.filters = {
2037
- "$or": {
2038
- [`links.contributors.${this.id}`]: { "$exists": true },
2039
- [`parent.${this.id}`]: { "$exists": true }
2040
- },
2041
- [`links.contributors.${this.id}`]: { "$exists": true }
2042
- };
2043
- }
2044
-
2045
- const fetchFn = this.isMe
2046
- ? () => this.callIsMe(() => this.endpointApi.getProjectsAdmin(finalData))
2047
- : () => this.endpointApi.getProjectsNoAdmin(finalData);
2048
-
2049
- return fetchFn();
2050
- }
2051
- });
2052
-
2053
- return paginator.next();
2054
- }
2055
-
2056
- /**
2057
- * Récupérer les POIs d'une entité : liste des POIs de l'entité ou elle est "parent".
2058
- * Constant : GET_POIS_NO_ADMIN / GET_POIS_ADMIN
2059
- * @param {Object} data - Les données à envoyer.
2060
- * @param {boolean} isNext - Indique si c'est une recherche suivante (pagination).
2061
- * @returns {Promise<Object>} - Les données de réponse.
2062
- */
2063
- async getPois(data = {}) {
2064
- data.searchType = this._getDefaultFromEndpoint("GET_POIS_ADMIN", "searchType");
2065
- // data.searchBy = "ALL";
2066
-
2067
- const paginator = this._createPaginatorEngine({
2068
- initialData: data,
2069
- finalizer: async (finalData) => {
2070
- if(this.isMe){
2071
- finalData.pathParams = { type: this.getEntityType(), id: this.id };
2072
- // NOTE : dans le schema je crois que si pas de finalData.filters alors le default ce fait avec finalData.pathParams
2073
- // finalData.filters = {
2074
- // [`parent.${this.id}`]: { "$exists": true },
2075
- // };
2076
- } else {
2077
- delete finalData?.pathParams;
2078
- finalData.filters = {
2079
- [`parent.${this.id}`]: { "$exists": true },
2080
- };
2081
- }
2082
-
2083
- const fetchFn = this.isMe
2084
- ? () => this.callIsMe(() => this.endpointApi.getPoisAdmin(finalData))
2085
- : () => this.endpointApi.getPoisNoAdmin(finalData);
2086
-
2087
- return fetchFn();
2088
- }
2089
- });
2090
-
2091
- return paginator.next();
2092
- }
2093
-
2094
- /**
2095
- * Récupérer les abonnés d'une entité
2096
- * Constant : GET_SUBSCRIBERS
2097
- * @param {Object} data - Les données à envoyer.
2098
- * @param {boolean} isNext - Indique si c'est une recherche suivante (pagination).
2099
- * @returns {Promise<Object>} - Les données de réponse.
2100
- */
2101
- async getSubscribers(data = {}) {
2102
- data.searchType = this._getDefaultFromEndpoint("GET_SUBSCRIBERS", "searchType");
2103
- // data.searchBy = "ALL";
2104
-
2105
- const paginator = this._createPaginatorEngine({
2106
- initialData: data,
2107
- finalizer: async (finalData) => {
2108
- delete finalData?.pathParams;
2109
-
2110
- finalData.filters = {
2111
- [`links.follows.${this.id}`]: { "$exists": true },
2112
- [`links.follows.${this.id}.toBeValidated`]: { "$exists": false },
2113
- [`links.follows.${this.id}.isInviting`]: { "$exists": false }
2114
- };
2115
-
2116
- return this.endpointApi.getSubscribers(finalData);
2117
- }
2118
- });
2119
-
2120
- return paginator.next();
2121
- }
2122
-
2123
- /**
2124
- * Liste des badges créés par l'entité
2125
- * Constant : GET_BADGES
2126
- * @param {Object} data - Les données à envoyer.
2127
- * @param {boolean} isNext - Indique si c'est une recherche suivante (pagination).
2128
- * @returns {Promise<Object>} - Les données de réponse.
2129
- */
2130
- async getBadgesIssuer(data = {}) {
2131
- data.searchType = this._getDefaultFromEndpoint("GET_BADGES", "searchType");
2132
- // data.searchBy = "ALL";
2133
-
2134
- const paginator = this._createPaginatorEngine({
2135
- initialData: data,
2136
- finalizer: async (finalData) => {
2137
- delete finalData?.pathParams;
2138
-
2139
- finalData.filters = finalData.filters || {};
2140
- finalData.filters["$or"] = {};
2141
- finalData.filters["$or"][`issuer.${this.id}`] = { "$exists": true };
2142
-
2143
- return this.endpointApi.getBadges(finalData);
2144
- }
2145
- });
2146
-
2147
- return paginator.next();
2148
- }
2149
-
2150
- /**
2151
- * Récupérer les actualités : Récupère la liste d’actualités selon plusieurs critères.
2152
- * Constant : GET_NEWS
2153
- * @param {Object} data - Les données à envoyer.
2154
- * @param {number} data.dateLimit - Limite de date timestamp ou 0 (default: 0)
2155
- * @param {object} data.search - data.search
2156
- * @param {string} data.search.name - Nom ou terme recherché (default: "")
2157
- * @param {number} data.indexStep - Nombre de résultats par page (default: 12)
2158
- * @returns {Promise<Object>} - Les données de réponse.
2159
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
2160
- * @throws {Error} - En cas d'erreur inattendue.
2161
- */
2162
- async getNews(data = {}) {
2163
- data.pathParams = { type: this.getEntityType(), id: this.id };
2164
- const arrayObjet = await this.endpointApi.getNews(data);
2165
- if(!Array.isArray(arrayObjet)){
2166
- throw new ApiResponseError("Erreur lors de la récupération des actualités.", 500, arrayObjet);
2167
- }
2168
-
2169
- const rawList = this._linkEntities(arrayObjet);
2170
-
2171
- return this._createFilteredProxy(rawList);
2172
- }
2173
-
2174
- /**
2175
- * Récupérer la galerie d'une entité.
2176
- * Constant : GET_GALLERY
2177
- * @param {Object} data - Les données à envoyer.
2178
- * @param {string} data.pathParams.docType - Type de document (default: "image")
2179
- * @param {string} data.contentKey - Clé de contenu pour la galerie (default: "null")
2180
- * @param {string} data.folderId - ID du dossier pour la galerie (default: "null")
2181
- * @returns {Promise<Object>} - Les données de réponse.
2182
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
2183
- * @throws {Error} - En cas d'erreur inattendue.
2184
- */
2185
- async getGallery(data = {}) {
2186
- data.pathParams = { type: this.getEntityType(), id: this.id, docType: data.pathParams?.docType || "image" };
2187
- const arrayObjet = await this.endpointApi.getGallery(data);
2188
-
2189
- return arrayObjet;
2190
- }
2191
-
2192
- /**
2193
- * Soumet une demande pour rejoindre l'entité courante (ex. organisation, projet, événement...).
2194
- * Si une invitation est en attente, elle est automatiquement acceptée.
2195
- *
2196
- * @returns {Promise<Object>} - Résultat de la demande ou de la validation.
2197
- * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si une demande est déjà en cours.
2198
- */
2199
- async requestToJoin(){
2200
- if (!this.isMe) {
2201
- throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
2202
- }
2203
- this._checkLinkableEntity();
2204
- return this._submitLinkRequest();
2205
- }
2206
-
2207
- /**
2208
- * Soumet une demande pour devenir administrateur de l'entité courante.
2209
- * Si une invitation est en attente, elle est automatiquement validée.
2210
- *
2211
- * @returns {Promise<Object>} - Résultat de la demande ou de la validation.
2212
- * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si une demande est déjà en cours.
2213
- */
2214
- async requestToJoinAdmin(){
2215
- if (!this.isMe) {
2216
- throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
2217
- }
2218
- this._checkLinkableEntity();
2219
- return this._submitLinkRequestAdmin();
2220
- }
2221
-
2222
- /**
2223
- * Accepte une invitation à rejoindre l'entité courante.
2224
- * Ne fonctionne que si un lien avec `isInviting` est détecté.
2225
- *
2226
- * @returns {Promise<Object>} - Résultat de l'acceptation de l'invitation.
2227
- * @throws {ApiError} - Si aucune invitation n'est en attente ou si l'entité ne supporte pas cette action.
2228
- */
2229
- async acceptInvitation(){
2230
- if (!this.isMe) {
2231
- throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
2232
- }
2233
- this._checkLinkableEntity();
2234
- return this._acceptLinkRequest();
2235
- }
2236
-
2237
- /**
2238
- * Se désengage de l'entité courante : quitte un rôle (membre, contributeur, etc.).
2239
- *
2240
- * @returns {Promise<Object>} - Résultat de la déconnexion.
2241
- * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si l'utilisateur n'est pas lié à cette entité.
2242
- */
2243
- async leave() {
2244
- if (!this.isMe) {
2245
- throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
2246
- }
2247
- this._checkLinkableEntity();
2248
- return this._leaveLinkRequest();
2249
- }
2250
-
2251
- /**
2252
- * S'abonne à l'entité courante.
2253
- *
2254
- * @returns {Promise<Object>} - Résultat de l'abonnement.
2255
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
2256
- */
2257
- async follow() {
2258
- if (!this.isMe) {
2259
- throw new ApiError("Vous devez être connecté pour suivre cette entité.");
2260
- }
2261
-
2262
- if (!this.id) {
2263
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
2264
- }
2265
-
2266
- const userLink = this.userContext?.serverData?.links?.["follows"]?.[this.id] || null;
2267
-
2268
- if (!userLink) {
2269
- const data = {
2270
- parentType: this.getEntityType(),
2271
- parentId: this.id
2272
- };
2273
- const retour = await this.callIsMe(() => this.endpointApi.follow(data));
2274
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
2275
- await this.userContext.refresh();
2276
- return retour;
2277
- }
2278
-
2279
- throw new ApiError("Vous êtes déjà abonné à cette entité.");
2280
- }
2281
-
2282
- /**
2283
- * Se désabonne de l'entité courante.
2284
- *
2285
- * @returns {Promise<Object>} - Résultat de la désinscription.
2286
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
2287
- */
2288
- async unfollow() {
2289
- if (!this.isMe) {
2290
- throw new ApiError("Vous devez être connecté pour vous désabonner.");
2291
- }
2292
-
2293
- if (!this.id) {
2294
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
2295
- }
2296
-
2297
- const userLink = this.userContext?.serverData?.links?.["follows"]?.[this.id] || null;
2298
-
2299
- if (userLink) {
2300
- const data = {
2301
- parentType: this.getEntityType(),
2302
- parentId: this.id,
2303
- connectType: "followers"
2304
- };
2305
- const retour = await this.callIsMe(() => this.endpointApi.disconnect(data));
2306
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
2307
- await this.userContext.refresh();
2308
- return retour;
2309
- }
2310
-
2311
- throw new ApiError("Vous n'êtes pas abonné à cette entité.");
2312
- }
2313
-
2314
-
2315
- /**
2316
- * Vérifie si l'utilisateur est connecté et a accès à l'entité.
2317
- *
2318
- * @param {string} action - Action à effectuer.
2319
- * @returns {void}
2320
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
2321
- * @private
2322
- */
2323
- _checkAccess(action = "effectuer cette action") {
2324
- if (!this.isMe) {
2325
- throw new ApiError(`Vous devez être connecté pour ${action}`);
2326
- }
2327
- if (!this.id) {
2328
- throw new ApiError(`${this.constructor.name} non enregistrée.`);
2329
- }
2330
- }
2331
-
2332
- /**
2333
- * Vérifie si l'utilisateur a un lien valide avec l'entité.
2334
- *
2335
- * @param {Object} userLink - Lien de l'utilisateur avec l'entité.
2336
- * @param {boolean} userLink.toBeValidated - Indique si le lien est en attente de validation.
2337
- * @param {boolean} userLink.isInviting - Indique si l'utilisateur a été invité.
2338
- * @returns {boolean} - `true` si le lien est valide, `false` sinon.
2339
- * @private
2340
- */
2341
- _validateUserLink(userLink) {
2342
- if (!userLink) return false;
2343
- const { toBeValidated, isInviting } = userLink;
2344
- return !toBeValidated && !isInviting;
2345
- }
2346
-
2347
- /**
2348
- * Vérifie si l'entité est d'un type spécifique.
2349
- *
2350
- * @param {...string} types - Types d'entité attendus.
2351
- * @throws {ApiError} - Si l'entité n'est pas du type attendu.
2352
- * @private
2353
- */
2354
- _assertEntityType(...types) {
2355
- const expectedTypes = Array.isArray(types[0]) ? types[0] : types;
2356
- if (!expectedTypes.includes(this.getEntityType())) {
2357
- throw new ApiError(`L'entité doit être de type : ${expectedTypes.join(", ")}, reçu : ${this.getEntityType()}`);
2358
- }
2359
- }
2360
-
2361
- /**
2362
- * Vérifie si l'entité est liée à un type de lien spécifique.
2363
- *
2364
- * @param {string} linkType - Type de lien à vérifier.
2365
- * @returns {boolean} - `true` si l'entité est liée, `false` sinon.
2366
- * @private
2367
- */
2368
- _isLinked(linkType) {
2369
- return !!this.userContext?.serverData?.links?.[linkType]?.[this.id];
2370
- }
2371
-
2372
- /**
2373
- * Vérifie si l'utilisateur est l'auteur de l'entité.
2374
- *
2375
- * @returns {boolean} - `true` si l'utilisateur est l'auteur, `false` sinon.
2376
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si les données du serveur ne sont pas disponibles.
2377
- */
2378
- isAuthor() {
2379
- this._checkAccess("vérifier l'auteur");
2380
- if (!this.serverData) {
2381
- throw new ApiError("Aucune donnée serveur disponible.");
2382
- }
2383
- return this.userId && this.serverData?.creator === this.userId;
2384
- }
2385
-
2386
- /**
2387
- * Vérifie si l'utilisateur est administrateur de l'entité.
2388
- *
2389
- * @returns {boolean} - `true` si l'utilisateur est administrateur, `false` sinon.
2390
- * @throws {ApiError}
2391
- */
2392
- isAdmin() {
2393
- this._checkAccess("vérifier l'administrateur.");
2394
- this._assertEntityType("organizations", "projects", "events");
2395
- const userLink = this._getLinkFromConnectedUser();
2396
- return this._validateUserLink(userLink) && userLink?.isAdmin === true && !userLink?.isAdminPending;
2397
- }
2398
-
2399
- /**
2400
- * Vérifie si l'utilisateur est soit l'auteur, soit administrateur de l'entité.
2401
- *
2402
- * @returns {boolean} - `true` si l'utilisateur est l'auteur ou administrateur, `false` sinon.
2403
- * @throws {ApiError}
2404
- */
2405
- isAuthorOrAdmin() {
2406
- this._checkAccess("vérifier l'auteur ou l'administrateur.");
2407
- return this.isAuthor() || this.isAdmin();
2408
- }
2409
-
2410
- /**
2411
- * Vérifie si l'utilisateur est membre de l'entité.
2412
- *
2413
- * @returns {boolean} - `true` si l'utilisateur est membre, `false` sinon.
2414
- * @throws {ApiError}
2415
- */
2416
- isMember() {
2417
- this._checkAccess("vérifier le membre.");
2418
- this._assertEntityType("organizations");
2419
- const userLink = this._getLinkFromConnectedUser();
2420
- return this._validateUserLink(userLink);
2421
- }
2422
-
2423
- /**
2424
- * Vérifie si l'utilisateur est contributeur de l'entité.
2425
- *
2426
- * @returns {boolean} - `true` si l'utilisateur est contributeur, `false` sinon.
2427
- * @throws {ApiError}
2428
- */
2429
- isContributor() {
2430
- this._checkAccess("vérifier le contributeur.");
2431
- this._assertEntityType("projects");
2432
- const userLink = this._getLinkFromConnectedUser();
2433
- return this._validateUserLink(userLink);
2434
- }
2435
-
2436
- /**
2437
- * Vérifie si l'utilisateur est participant de l'entité.
2438
- *
2439
- * @returns {boolean} - `true` si l'utilisateur est participant, `false` sinon.
2440
- * @throws {ApiError}
2441
- */
2442
- isAttendee() {
2443
- this._checkAccess("vérifier si vous êtes un participant.");
2444
- this._assertEntityType("events");
2445
- const userLink = this._getLinkFromConnectedUser();
2446
- return this._validateUserLink(userLink);
2447
- }
2448
-
2449
- /**
2450
- * Vérifie si l'utilisateur suit l'entité.
2451
- *
2452
- * @returns {boolean} - `true` si l'utilisateur suit l'entité, `false` sinon.
2453
- * @throws {ApiError}
2454
- */
2455
- isFollower() {
2456
- this._checkAccess("vérifier si il vous suit.");
2457
- this._assertEntityType("citoyens","organizations", "projects", "events", "poi");
2458
- return this._isLinked("followers");
2459
- }
2460
-
2461
- /**
2462
- * Vérifie si l'utilisateur est abonné à l'entité.
2463
- *
2464
- * @returns {boolean} - `true` si l'utilisateur est abonné, `false` sinon.
2465
- * @throws {ApiError}
2466
- */
2467
- isFollowing() {
2468
- this._checkAccess("vérifier si vous le suivez.");
2469
- this._assertEntityType("citoyens","organizations", "projects", "events", "poi");
2470
- return this._isLinked("follows");
2471
- }
2472
-
2473
- /**
2474
- * Récupère le JSON personnalisé de l'entité
2475
- *
2476
- * @returns {Promise<Object>} - Le JSON personnalisé de l'entité.
2477
- * @throws {ApiError} - Si le slug de l'entité n'est pas défini.
2478
- */
2479
- async getCostumJson() {
2480
- if (!this.serverData.slug) {
2481
- throw new ApiError("slug de l'entité non défini");
2482
- }
2483
- const data = {
2484
- pathParams: {
2485
- slug: this.serverData.slug
2486
- }
2487
- };
2488
- return this.endpointApi.getCostumJson(data);
2489
- }
2490
-
2491
- /**
2492
- * Génère des plages d'index pour la pagination.
2493
- *
2494
- * @param {Array<string>} searchType - Types de recherche.
2495
- * @param {number} indexStep - Pas d'index.
2496
- * @param {Object} previousRanges - Plages précédentes.
2497
- * @returns {Object} - Plages d'index générées.
2498
- * @private
2499
- */
2500
- _generateRanges(searchType, indexStep, previousRanges = {}) {
2501
- const ranges = {};
2502
- for (const type of searchType) {
2503
- const previous = previousRanges[type] || { indexMax: 0 };
2504
- ranges[type] = {
2505
- indexMin: previous.indexMax || 0,
2506
- indexMax: previous.indexMax + indexStep
2507
- };
2508
- }
2509
- return ranges;
2510
- }
2511
-
2512
- /**
2513
- * Normalise le compte des résultats.
2514
- *
2515
- * @param {Object} count - Compte des résultats.
2516
- * @returns {Object} - Compte normalisé.
2517
- * @private
2518
- */
2519
- _normalizeCount(count = {}) {
2520
- // suppression des indésirables
2521
- delete count.spam;
2522
-
2523
- // calcul du total (somme des valeurs numériques)
2524
- count.total = Object.values(count).reduce((acc, val) => acc + (typeof val === "number" ? val : 0), 0);
2525
-
2526
- return count;
2527
- }
2528
-
2529
- /**
2530
- * Récupère une valeur par défaut depuis un endpoint donné.
2531
- *
2532
- * @param {string} constant - Le nom unique de l’endpoint (ex: "GET_ORGANIZATIONS_NO_ADMIN")
2533
- * @param {string} path - Le chemin vers la propriété (ex: "searchType")
2534
- * @returns {*} La valeur par défaut, ou undefined si non trouvée
2535
- */
2536
- _getDefaultFromEndpoint(constant, path) {
2537
- const endpoint = this.apiClient._endpoints.find((ep) => ep.constant === constant);
2538
- if (!endpoint?.request?.properties?.[path]) return undefined;
2539
- return endpoint.request.properties[path].default;
2540
- }
2541
-
2542
- /**
2543
- * Coeur de pagination stateless et réutilisable, sans logique métier.
2544
- *
2545
- * @param {Object} config
2546
- * @param {Object} config.initialData - Paramètres de départ
2547
- * @param {Function} config.finalizer - Fonction async qui retourne { results, count }
2548
- *
2549
- * @returns {Object} paginator avec .next() et .prev()
2550
- */
2551
- _createPaginatorEngine({ initialData, finalizer }) {
2552
- const Entity = this;
2553
-
2554
- const state = {
2555
- cursor: undefined,
2556
- count: 0,
2557
- index: 0,
2558
- history: [],
2559
- sizes: []
2560
- };
2561
-
2562
- const hasStep = (d) => d?.indexStep && d.indexStep > 0;
2563
-
2564
- async function getPage(isNext = false) {
2565
- let data = { ...initialData };
2566
-
2567
- if (!state.cursor || (!isNext && state.history.length === 0)) {
2568
- state.count = 0;
2569
- state.index = 0;
2570
- state.history = [];
2571
- state.sizes = [];
2572
-
2573
- data.countType = data.searchType;
2574
-
2575
- if (!data?.searchBy && hasStep(data)) {
2576
- data.ranges = Entity._generateRanges(data.searchType, data.indexStep);
2577
- data.indexMin = data.indexMin ?? 0;
2578
- data.indexMax = data.indexMax ?? data.indexStep;
2579
- } else if (data?.searchBy === "ALL" && hasStep(data)) {
2580
- data.indexMin = data.indexMin ?? 0;
2581
- data.indexMax = data.indexMax ?? data.indexStep;
2582
- }
2583
-
2584
- state.cursor = { ...data };
2585
- }
2586
-
2587
- const cursor = state.cursor;
2588
-
2589
- if (isNext) {
2590
- state.history.push({ ...cursor });
2591
-
2592
- if (!cursor.searchBy && hasStep(cursor)) {
2593
- cursor.ranges = Entity._generateRanges(cursor.searchType, cursor.indexStep, cursor.ranges);
2594
- cursor.indexMin = cursor.indexMax ?? 0;
2595
- cursor.indexMax = (cursor.indexMax ?? 0) + cursor.indexStep;
2596
- } else if (cursor.searchBy === "ALL" && hasStep(cursor)) {
2597
- cursor.indexMin = cursor.indexMax ?? 0;
2598
- cursor.indexMax = (cursor.indexMax ?? 0) + cursor.indexStep;
2599
- }
2600
-
2601
- state.cursor = { ...cursor };
2602
- }
2603
-
2604
- data = { ...state.cursor };
2605
-
2606
- if (!isNext && (!data?.searchType || !Array.isArray(data.searchType) || data.searchType.length === 0)) {
2607
- throw new Error("searchType non défini");
2608
- }
2609
-
2610
- const result = await finalizer(data);
2611
- if (!Array.isArray(result.results)) {
2612
- throw new Error("Les résultats doivent être un tableau");
2613
- }
2614
-
2615
- state.count += result.results.length;
2616
- state.sizes.push(result.results.length);
2617
- if (isNext) state.index++;
2618
-
2619
- const count = Entity._normalizeCount(result.count);
2620
- const rawList = Entity._linkEntities(result.results);
2621
-
2622
- const hasNext = hasStep(data) && state.count < count.total;
2623
- const hasPrev = state.history.length > 0;
2624
-
2625
- return {
2626
- count,
2627
- results: rawList,
2628
- pageIndex: state.index,
2629
- pageNumber: state.index + 1,
2630
- hasNext,
2631
- hasPrev,
2632
- next: hasNext ? () => getPage(true) : undefined,
2633
- prev: hasPrev
2634
- ? async () => {
2635
- const previous = state.history.pop();
2636
- const lastPageSize = state.sizes.pop() ?? 0;
2637
- state.count -= lastPageSize;
2638
- state.index = Math.max(0, state.index - 1);
2639
- state.cursor = { ...previous };
2640
- return getPage(false);
2641
- }
2642
- : undefined
2643
- };
2644
- }
2645
-
2646
- return {
2647
- next: () => getPage(false)
2648
- };
2649
- }
2650
-
2651
- /**
2652
- * Injection de contexte Communecter dans une requête finalizer.
2653
- *
2654
- * @param {Function} baseFinalizer - fonction async(data) => { results, count }
2655
- * @returns {Function} fonction enrichie
2656
- */
2657
- _withCostumContext(baseFinalizer) {
2658
- return async (data) => {
2659
- const finalData = {
2660
- ...data,
2661
- costumSlug: this.serverData.slug,
2662
- contextId: this.serverData.id,
2663
- contextType: this.getEntityType(),
2664
- sourceKey: [...(data.sourceKey || []), this.serverData.slug]
2665
- };
2666
- return baseFinalizer(finalData);
2667
- };
2668
- }
2669
-
2670
- /**
2671
- * ───────────────────────────────
2672
- * custom
2673
- * ───────────────────────────────
2674
- */
2675
-
2676
- /**
2677
- * Recherche liée à l'entité, version stateless.
2678
- *
2679
- * @param {Object} data - Données initiales de recherche.
2680
- * @returns {Object} - Un paginateur avec .next(), .prev(), etc.
2681
- */
2682
- async searchCostum(data = {}) {
2683
-
2684
- const paginator = this._createPaginatorEngine({
2685
- initialData: data,
2686
- finalizer: this._withCostumContext((finalData) => this.endpointApi.globalAutocompleteCostum(finalData)),
2687
- });
2688
-
2689
- return paginator.next();
2690
- }
2691
-
2692
- async costumEventRequestActors(data = {}){
2693
-
2694
- data = {
2695
- pathParams: {
2696
- id: this.id,
2697
- type: this.getEntityType()
2698
- },
2699
- ...data
2700
- };
2701
-
2702
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestActors(finalData));
2703
-
2704
- const actors = await wrappedFinalizer(data);
2705
-
2706
- // const transformedActors = actors
2707
- // .filter(actor => {
2708
- // const isValid = !!actor?.type;
2709
- // if (!isValid) {
2710
- // this.apiClient._logger?.warn?.(`Objet ignoré car sans 'type' : ${actor?.id}`);
2711
- // }
2712
- // return isValid;
2713
- // })
2714
- // .map(({ id, name, type, ...meta }) => ({
2715
- // id,
2716
- // name,
2717
- // collection: type,
2718
- // meta
2719
- // }));
2720
-
2721
- // const rawList = this._linkEntities(transformedActors);
2722
-
2723
- return actors;
2724
- }
2725
-
2726
- async costumEventRequestSubevents(data = {}){
2727
-
2728
- data = {
2729
- pathParams: {
2730
- id: this.id,
2731
- type: this.getEntityType()
2732
- },
2733
- ...data
2734
- };
2735
-
2736
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestSubevents(finalData));
2737
-
2738
- return wrappedFinalizer(data);
2739
- }
2740
-
2741
- async costumEventRequestDates(data = {}){
2742
- data = {
2743
- pathParams: {
2744
- id: this.id,
2745
- type: this.getEntityType()
2746
- },
2747
- ...data
2748
- };
2749
-
2750
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestDates(finalData));
2751
-
2752
- return wrappedFinalizer(data);
2753
- }
2754
-
2755
- async costumEventRequestElementEvent(data = {}){
2756
- data = {
2757
- pathParams: {
2758
- id: this.id,
2759
- type: this.getEntityType()
2760
- },
2761
- ...data
2762
- };
2763
-
2764
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestElementEvent(finalData));
2765
-
2766
- return wrappedFinalizer(data);
2767
- }
2768
-
2769
- async costumEventRequestCategories(data = {}){
2770
- data = {
2771
- pathParams: {
2772
- id: this.id,
2773
- type: this.getEntityType()
2774
- },
2775
- ...data
2776
- };
2777
-
2778
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestCategories(finalData));
2779
-
2780
- return wrappedFinalizer(data);
2781
- }
2782
-
2783
- async costumEventRequestEvent(data = {}){
2784
- data = {
2785
- pathParams: {
2786
- id: this.id,
2787
- type: this.getEntityType()
2788
- },
2789
- ...data
2790
- };
2791
-
2792
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestEvent(finalData));
2793
-
2794
- return wrappedFinalizer(data);
2795
- }
2796
-
2797
- async costumEventRequestLinkTlToEvent(data = {}){
2798
- data = {
2799
- pathParams: {
2800
- id: this.id,
2801
- type: this.getEntityType()
2802
- },
2803
- ...data
2804
- };
2805
-
2806
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestLinkTlToEvent(finalData));
2807
-
2808
- return wrappedFinalizer(data);
2809
- }
2810
-
2811
- async costumEventRequestLoadContextTag(data = {}){
2812
- data = {
2813
- pathParams: {
2814
- id: this.id,
2815
- type: this.getEntityType()
2816
- },
2817
- ...data
2818
- };
2819
-
2820
- const wrappedFinalizer = this._withCostumContext((finalData) => this.endpointApi.costumEventRequestLoadContextTag(finalData));
2821
-
2822
- return wrappedFinalizer(data);
2823
- }
2824
-
2825
-
2826
- }
2827
-
2828
- export default BaseEntity;