@communecter/cocolight-api-client 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/123.cocolight-api-client.browser.js +1 -0
- package/dist/123.cocolight-api-client.cjs +1 -0
- package/dist/774.cocolight-api-client.mjs.js +1 -0
- package/dist/cocolight-api-client.browser.js +3 -3
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/package.json +2 -2
- package/src/Api.js +7 -4
- package/src/ApiClient.js +8 -3
- package/src/api/Badge.js +117 -0
- package/src/api/BaseEntity.js +162 -0
- package/src/api/EndpointApi.js +6 -6
- package/src/api/Event.js +144 -0
- package/src/api/News.js +134 -219
- package/src/api/Organization.js +117 -238
- package/src/api/Poi.js +158 -0
- package/src/api/Project.js +124 -234
- package/src/api/User.js +333 -91
- package/src/api/UserApi.js +5 -2
- package/src/endpoints.module.js +2 -2
- package/src/index.js +3 -1
- package/src/mixin/EntityMixin.js +320 -1
- package/src/mixin/MutualEntityMixin.js +224 -3
- package/src/mixin/UtilMixin.js +7 -3
- package/src/utils/MultiServerFileStorageStrategy.node.js +65 -0
- package/src/utils/MultiServerTokenStorageStrategy.js +131 -0
- package/src/utils/createDefaultMultiServerTokenStorageStrategy.js +45 -0
- package/src/utils/createDefaultTokenStorageStrategy.js +0 -2
- package/src/mixin/NewsMixin.js +0 -42
package/src/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import Api from "./Api.js";
|
|
2
2
|
import ApiClient from "./ApiClient.js";
|
|
3
3
|
import * as error from "./error.js";
|
|
4
|
+
import { createDefaultMultiServerTokenStorageStrategy } from "./utils/createDefaultMultiServerTokenStorageStrategy.js";
|
|
4
5
|
import { createDefaultTokenStorageStrategy } from "./utils/createDefaultTokenStorageStrategy.js";
|
|
6
|
+
import { MultiServerTokenStorageStrategy } from "./utils/MultiServerTokenStorageStrategy.js";
|
|
5
7
|
import OfflineClientManager from "./utils/OfflineClientManager.js";
|
|
6
8
|
import { TokenStorageStrategy } from "./utils/TokenStorage.js";
|
|
7
9
|
|
|
8
|
-
export default { ApiClient, Api, error, tokenStorageStrategy: { createDefaultTokenStorageStrategy, TokenStorageStrategy }, OfflineClientManager };
|
|
10
|
+
export default { ApiClient, Api, error, tokenStorageStrategy: { createDefaultTokenStorageStrategy, TokenStorageStrategy, createDefaultMultiServerTokenStorageStrategy, MultiServerTokenStorageStrategy }, OfflineClientManager };
|
package/src/mixin/EntityMixin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiResponseError } from "../error.js";
|
|
1
|
+
import { ApiError, ApiResponseError } from "../error.js";
|
|
2
2
|
|
|
3
3
|
// EntityMixin.js
|
|
4
4
|
export const EntityMixin = {
|
|
@@ -104,6 +104,325 @@ export const EntityMixin = {
|
|
|
104
104
|
image = await this._validateImage(image);
|
|
105
105
|
const data = { pathParams: { folder: this.getEntityType(), ownerId: this.id }, profil_avatar: image };
|
|
106
106
|
return this.callIsConnected(() => this.endpointApi.profilImage(data));
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Crée une instance d'organisation et récupère son profil si nécessaire.
|
|
111
|
+
*
|
|
112
|
+
* @param {Object} organizationData - Les données nécessaires pour initialiser l'organisation.
|
|
113
|
+
* @returns {Promise<Organization>} Une promesse qui résout l'objet Organisation créé.
|
|
114
|
+
* @throws {Error} Si une erreur se produit lors de la création de l'organisation.
|
|
115
|
+
*/
|
|
116
|
+
async organization(organizationData = {}) {
|
|
117
|
+
if(!this.isMe){
|
|
118
|
+
throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer une organisation.");
|
|
119
|
+
}
|
|
120
|
+
return this.entity("organizations", organizationData);
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Crée une instance de projet et récupère son profil si nécessaire.
|
|
125
|
+
*
|
|
126
|
+
* @param {Object} projectData - Les données nécessaires pour initialiser le projet.
|
|
127
|
+
* @returns {Promise<Project>} Une promesse qui résout l'objet Projet créé.
|
|
128
|
+
* @throws {Error} Si une erreur se produit lors de la création du projet.
|
|
129
|
+
*/
|
|
130
|
+
async project(projectData = {}) {
|
|
131
|
+
// TODO: Vérifier si l'utilisateur est admin de l'organisation
|
|
132
|
+
if(!this.isConnected){
|
|
133
|
+
throw new ApiError("Vous devez être connecté pour créer un projet.");
|
|
134
|
+
}
|
|
135
|
+
return this.entity("projects", projectData);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Crée une instance de POI et la récupère si nécessaire.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} poiData - Les données nécessaires pour initialiser le POI.
|
|
142
|
+
* @returns {Promise<Poi>} Une promesse qui résout l'objet POI créé.
|
|
143
|
+
* @throws {Error} Si une erreur se produit lors de la création du POI.
|
|
144
|
+
*/
|
|
145
|
+
async poi(poiData = {}) {
|
|
146
|
+
// TODO: Vérifier si l'utilisateur est admin de l'organisation
|
|
147
|
+
if(!this.isConnected){
|
|
148
|
+
throw new ApiError("Vous devez être connecté pour créer un POI.");
|
|
149
|
+
}
|
|
150
|
+
return this.entity("poi", poiData);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Crée une instance d'événement et la récupère si nécessaire.
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} eventData - Les données nécessaires pour initialiser l'événement.
|
|
157
|
+
* @returns {Promise<Event>} Une promesse qui résout l'objet Événement créé.
|
|
158
|
+
* @throws {Error} Si une erreur se produit lors de la création de l'événement.
|
|
159
|
+
*/
|
|
160
|
+
async event(eventData = {}) {
|
|
161
|
+
// TODO: Vérifier si l'utilisateur est admin de l'organisation
|
|
162
|
+
if(!this.isConnected){
|
|
163
|
+
throw new ApiError("Vous devez être connecté pour créer un événement.");
|
|
164
|
+
}
|
|
165
|
+
return this.entity("events", eventData);
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Crée une instance de badge et la récupère si nécessaire.
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} badgeData - Les données nécessaires pour initialiser le badge.
|
|
172
|
+
* @returns {Promise<Badge>} Une promesse qui résout l'objet Badge créé.
|
|
173
|
+
* @throws {Error} Si une erreur se produit lors de la création du badge.
|
|
174
|
+
*/
|
|
175
|
+
async badge(badgeData = {}) {
|
|
176
|
+
// TODO: Vérifier si l'utilisateur est admin de l'organisation
|
|
177
|
+
if(!this.isConnected){
|
|
178
|
+
throw new ApiError("Vous devez être connecté pour créer un badge.");
|
|
179
|
+
}
|
|
180
|
+
return this.entity("badges", badgeData);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Crée une instance de news et la récupère si nécessaire.
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} newsData - Les données nécessaires pour initialiser la news.
|
|
187
|
+
* @returns {Promise<News>} Une promesse qui résout l'objet News créé.
|
|
188
|
+
* @throws {Error} Si une erreur se produit lors de la création de la news.
|
|
189
|
+
*/
|
|
190
|
+
async news(newsData = {}) {
|
|
191
|
+
if(!this.isConnected){
|
|
192
|
+
throw new ApiError("Vous devez être connecté.");
|
|
193
|
+
}
|
|
194
|
+
return this.entity("news", newsData);
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Récupérer les organisations d'une entitée : la liste des organisations dont l'entité est membre ou admin valide.
|
|
199
|
+
* Constant : GET_ORGANIZATIONS_ADMIN | GET_ORGANIZATIONS_NO_ADMIN
|
|
200
|
+
* @param {Object} data - Les données à envoyer.
|
|
201
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
202
|
+
*/
|
|
203
|
+
async getOrganizations(data = {}) {
|
|
204
|
+
|
|
205
|
+
if(this.isMe){
|
|
206
|
+
data.pathParams = { type: this.getEntityType(), id: this.id };
|
|
207
|
+
// NOTE : dans le schema je crois que si pas de data.filters alors le default ce fait avec data.pathParams
|
|
208
|
+
// data.filters = {
|
|
209
|
+
// [`links.members.${this.id}`]: { "$exists": true },
|
|
210
|
+
// [`links.members.${this.id}.toBeValidated`]: { "$exists": false },
|
|
211
|
+
// [`links.members.${this.id}.isInviting`]: { "$exists": false }
|
|
212
|
+
// };
|
|
213
|
+
} else {
|
|
214
|
+
delete data?.pathParams;
|
|
215
|
+
data.filters = {
|
|
216
|
+
[`links.members.${this.id}`]: { "$exists": true },
|
|
217
|
+
[`links.members.${this.id}.toBeValidated`]: { "$exists": false },
|
|
218
|
+
[`links.members.${this.id}.isInviting`]: { "$exists": false }
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const fetchFn = this.isMe
|
|
223
|
+
? () => this.callIsMe(() => this.endpointApi.getOrganizationsAdmin(data))
|
|
224
|
+
: () => this.endpointApi.getOrganizationsNoAdmin(data);
|
|
225
|
+
|
|
226
|
+
const arrayObjetOrganizations = await fetchFn();
|
|
227
|
+
|
|
228
|
+
if (!Array.isArray(arrayObjetOrganizations.results)) {
|
|
229
|
+
throw new ApiResponseError("Erreur lors de la récupération des organisations.", 500, arrayObjetOrganizations.results);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// nettoyage du count
|
|
233
|
+
delete arrayObjetOrganizations?.count?.spam;
|
|
234
|
+
|
|
235
|
+
// calcul du total
|
|
236
|
+
const totalCount = Object.values(arrayObjetOrganizations.count || {}).reduce((acc, val) => acc + val, 0);
|
|
237
|
+
arrayObjetOrganizations.count.total = totalCount;
|
|
238
|
+
|
|
239
|
+
const rawOrganizationsList = arrayObjetOrganizations.results.map(
|
|
240
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
count: arrayObjetOrganizations.count,
|
|
245
|
+
results: rawOrganizationsList
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Récupérer les projets d'une entitée : liste des projets de l'entité ou elle est "parent" ou "contributeur".
|
|
251
|
+
* Constant : GET_PROJECTS_ADMIN | GET_PROJECTS_NO_ADMIN
|
|
252
|
+
* @param {Object} data - Les données à envoyer.
|
|
253
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
254
|
+
*/
|
|
255
|
+
async getProjects(data = {}) {
|
|
256
|
+
|
|
257
|
+
if(this.isMe){
|
|
258
|
+
data.pathParams = { type: this.getEntityType(), id: this.id };
|
|
259
|
+
// NOTE : dans le schema je crois que si pas de data.filters alors le default ce fait avec data.pathParams
|
|
260
|
+
// data.filters = {
|
|
261
|
+
// "$or": {
|
|
262
|
+
// [`links.contributors.${this.id}`]: { "$exists": true },
|
|
263
|
+
// [`parent.${this.id}`]: { "$exists": true }
|
|
264
|
+
// },
|
|
265
|
+
// [`links.contributors.${this.id}`]: { "$exists": true }
|
|
266
|
+
// };
|
|
267
|
+
} else {
|
|
268
|
+
delete data?.pathParams;
|
|
269
|
+
data.filters = {
|
|
270
|
+
"$or": {
|
|
271
|
+
[`links.contributors.${this.id}`]: { "$exists": true },
|
|
272
|
+
[`parent.${this.id}`]: { "$exists": true }
|
|
273
|
+
},
|
|
274
|
+
[`links.contributors.${this.id}`]: { "$exists": true }
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const fetchFn = this.isMe
|
|
279
|
+
? () => this.callIsMe(() => this.endpointApi.getProjectsAdmin(data))
|
|
280
|
+
: () => this.endpointApi.getProjectsNoAdmin(data);
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
const arrayObjetProjects = await fetchFn();
|
|
284
|
+
|
|
285
|
+
if (!Array.isArray(arrayObjetProjects.results)) {
|
|
286
|
+
throw new ApiResponseError("Erreur lors de la récupération des projets.", 500, arrayObjetProjects.results);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const rawProjectsList = arrayObjetProjects.results.map(
|
|
290
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
count: arrayObjetProjects?.count?.projects ?? 0,
|
|
295
|
+
results: rawProjectsList
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Récupérer les POIs d'une entité : liste des POIs de l'entité ou elle est "parent".
|
|
301
|
+
* Constant : GET_POIS_NO_ADMIN / GET_POIS_ADMIN
|
|
302
|
+
* @param {Object} data - Les données à envoyer.
|
|
303
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
304
|
+
*/
|
|
305
|
+
async getPois(data = {}) {
|
|
306
|
+
|
|
307
|
+
if(this.isMe){
|
|
308
|
+
data.pathParams = { type: this.getEntityType(), id: this.id };
|
|
309
|
+
// NOTE : dans le schema je crois que si pas de data.filters alors le default ce fait avec data.pathParams
|
|
310
|
+
// data.filters = {
|
|
311
|
+
// [`parent.${this.id}`]: { "$exists": true },
|
|
312
|
+
// };
|
|
313
|
+
} else {
|
|
314
|
+
delete data?.pathParams;
|
|
315
|
+
data.filters = {
|
|
316
|
+
[`parent.${this.id}`]: { "$exists": true },
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const fetchFn = this.isMe
|
|
321
|
+
? () => this.callIsMe(() => this.endpointApi.getPoisAdmin(data))
|
|
322
|
+
: () => this.endpointApi.getPoisNoAdmin(data);
|
|
323
|
+
|
|
324
|
+
const arrayObjetPois = await fetchFn();
|
|
325
|
+
if (!Array.isArray(arrayObjetPois.results)) {
|
|
326
|
+
throw new ApiResponseError("Erreur lors de la récupération des POIs.", 500, arrayObjetPois.results);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// lier les entités au objets
|
|
330
|
+
const rawPoisList = arrayObjetPois.results.map(
|
|
331
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
count: arrayObjetPois.count?.poi ?? 0,
|
|
336
|
+
results: rawPoisList
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Récupérer les abonnés d'une entité
|
|
342
|
+
* Constant : GET_SUBSCRIBERS
|
|
343
|
+
* @param {Object} data - Les données à envoyer.
|
|
344
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
345
|
+
*/
|
|
346
|
+
async getSubscribers(data = {}) {
|
|
347
|
+
delete data?.pathParams;
|
|
348
|
+
|
|
349
|
+
data.filters = {
|
|
350
|
+
[`links.follows.${this.id}`]: { "$exists": true },
|
|
351
|
+
[`links.follows.${this.id}.toBeValidated`]: { "$exists": false },
|
|
352
|
+
[`links.follows.${this.id}.isInviting`]: { "$exists": false }
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const arrayObjetSubscribers = await this.endpointApi.getSubscribers(data);
|
|
356
|
+
if (!Array.isArray(arrayObjetSubscribers.results)) {
|
|
357
|
+
throw new ApiResponseError("Erreur lors de la récupération des abonnés.", 500, arrayObjetSubscribers.results);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// lier les entités au objets
|
|
361
|
+
const rawSubscribersList = arrayObjetSubscribers.results.map(
|
|
362
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
count: arrayObjetSubscribers.count,
|
|
367
|
+
results: rawSubscribersList
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Liste des badges créés par l'entité
|
|
373
|
+
* Constant : GET_BADGES
|
|
374
|
+
* @param {Object} data - Les données à envoyer.
|
|
375
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
376
|
+
*/
|
|
377
|
+
async getBadgesIssuer(data = {}) {
|
|
378
|
+
delete data?.pathParams;
|
|
379
|
+
|
|
380
|
+
data.filters = data.filters || {};
|
|
381
|
+
data.filters["$or"] = {};
|
|
382
|
+
data.filters["$or"][`issuer.${this.id}`] = { "$exists": true };
|
|
383
|
+
|
|
384
|
+
const arrayObjetBadges = await this.endpointApi.getBadges(data);
|
|
385
|
+
if (!Array.isArray(arrayObjetBadges.results)) {
|
|
386
|
+
throw new ApiResponseError("Erreur lors de la récupération des badges.", 500, arrayObjetBadges.results);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// lier les entités au objets
|
|
390
|
+
const rawBadgesList = arrayObjetBadges.results.map(
|
|
391
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
count: arrayObjetBadges.count?.badges ?? 0,
|
|
396
|
+
results: rawBadgesList
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Récupérer les actualités : Récupère la liste d’actualités selon plusieurs critères.
|
|
402
|
+
* Constant : GET_NEWS
|
|
403
|
+
* @param {Object} data - Les données à envoyer.
|
|
404
|
+
* @param {number} data.dateLimit - Limite de date timestamp ou 0 (default: 0)
|
|
405
|
+
* @param {object} data.search - data.search
|
|
406
|
+
* @param {string} data.search.name - Nom ou terme recherché (default: "")
|
|
407
|
+
* @param {number} data.indexStep - Nombre de résultats par page (default: 12)
|
|
408
|
+
* @returns {Promise<Object>} - Les données de réponse.
|
|
409
|
+
* @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
|
|
410
|
+
* @throws {Error} - En cas d'erreur inattendue.
|
|
411
|
+
*/
|
|
412
|
+
async getNews(data = {}) {
|
|
413
|
+
data.pathParams = { type: this.getEntityType(), id: this.id };
|
|
414
|
+
const arrayObjetNews = await this.endpointApi.getNews(data);
|
|
415
|
+
if(!Array.isArray(arrayObjetNews)){
|
|
416
|
+
throw new ApiResponseError("Erreur lors de la récupération des actualités.", 500, arrayObjetNews);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const rawNewsList = arrayObjetNews.map(
|
|
420
|
+
(d) => this.linkEntity?.(d.collection, d) ?? d
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return this._createFilteredProxy(rawNewsList);
|
|
107
424
|
}
|
|
425
|
+
|
|
426
|
+
|
|
108
427
|
};
|
|
109
428
|
|
|
@@ -42,7 +42,228 @@ export const MutualEntityMixin = {
|
|
|
42
42
|
async getPublicProfile() {
|
|
43
43
|
await this.resolveId(this.getEntityType());
|
|
44
44
|
return this.endpointApi.getElementsAbout({ pathParams: { id: this.id, type: this.getEntityType() } });
|
|
45
|
-
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Récupère la classe d'entité et ses dépendances à partir du type d'entité.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} entityType - Le type d'entité.
|
|
51
|
+
* @return {{ entityClass: Function, deps: Object } | null}
|
|
52
|
+
*/
|
|
53
|
+
_getEntityMeta(entityType) {
|
|
54
|
+
const selfClass = this.constructor;
|
|
55
|
+
const selfTag = this.__entityTag;
|
|
56
|
+
|
|
57
|
+
const commonDeps = {
|
|
58
|
+
EndpointApi: this.deps.EndpointApi,
|
|
59
|
+
User: selfTag === "User" ? selfClass : this.deps.User,
|
|
60
|
+
Organization: selfTag === "Organization" ? selfClass : this.deps.Organization,
|
|
61
|
+
Project: selfTag === "Project" ? selfClass : this.deps.Project,
|
|
62
|
+
Event: selfTag === "Event" ? selfClass : this.deps.Event,
|
|
63
|
+
Poi: selfTag === "Poi" ? selfClass : this.deps.Poi,
|
|
64
|
+
Badge: selfTag === "Badge" ? selfClass : this.deps.Badge,
|
|
65
|
+
News: selfTag === "News" ? selfClass : this.deps.News
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const map = {
|
|
69
|
+
citoyens: { entityClass: commonDeps.User, deps: commonDeps },
|
|
70
|
+
organizations:{ entityClass: commonDeps.Organization, deps: commonDeps },
|
|
71
|
+
projects: { entityClass: commonDeps.Project, deps: commonDeps },
|
|
72
|
+
events: { entityClass: commonDeps.Event, deps: { ...commonDeps, Badge: undefined } },
|
|
73
|
+
poi: { entityClass: commonDeps.Poi, deps: { ...commonDeps, Badge: undefined, News: undefined } },
|
|
74
|
+
news: { entityClass: commonDeps.News, deps: { ...commonDeps } },
|
|
75
|
+
badges: { entityClass: commonDeps.Badge, deps: {
|
|
76
|
+
EndpointApi: commonDeps.EndpointApi,
|
|
77
|
+
User: commonDeps.User,
|
|
78
|
+
Organization: commonDeps.Organization,
|
|
79
|
+
Project: commonDeps.Project
|
|
80
|
+
} }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return map[entityType] || null;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lier des données d'entité à une instance d'entité.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} entityType - Le type d'entité (ex : "citoyens", "organisations", "projets").
|
|
90
|
+
* @param {Object} entityData - Les données de l'entité à lier.
|
|
91
|
+
* @return {Object} L'entité liée.
|
|
92
|
+
*/
|
|
93
|
+
linkEntity(entityType, entityData) {
|
|
94
|
+
const meta = this._getEntityMeta(entityType);
|
|
95
|
+
if (!meta) return entityData;
|
|
96
|
+
return meta.entityClass.fromServerData(entityData, this, meta.deps);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Récupère et lie une entité à partir de son ID.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} entityType - Le type d'entité.
|
|
103
|
+
* @param {string} entityId - L'identifiant de l'entité.
|
|
104
|
+
* @param {Object} [options] - Options supplémentaires :
|
|
105
|
+
* @param {boolean} [options.skipGet] - Si true, ne pas appeler `get()` sur l'entité.
|
|
106
|
+
* @return {Promise<Object|null>} L'entité liée ou null.
|
|
107
|
+
*/
|
|
108
|
+
async linkEntityById(entityType, entityId, options = {}) {
|
|
109
|
+
const meta = this._getEntityMeta(entityType);
|
|
110
|
+
if (!meta) return null;
|
|
111
|
+
const entity = new meta.entityClass(this, { id: entityId }, meta.deps);
|
|
112
|
+
if(options?.skipGet) return entity;
|
|
113
|
+
await entity.get();
|
|
114
|
+
return entity;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Lie des entités présentes dans `this.serverData` à partir de leurs IDs,
|
|
120
|
+
* en les filtrant dynamiquement et en optionnellement les transformant.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} entityType - Le type d'entité (ex : "badges", "citoyens", etc.).
|
|
123
|
+
* @param {Object} filters - Clés/valeurs de filtres dynamiques. Les valeurs peuvent être :
|
|
124
|
+
* - un littéral (comparaison stricte ou intelligente selon le type),
|
|
125
|
+
* - une chaîne (utilise `includes` insensible à la casse),
|
|
126
|
+
* - une RegExp (appliquée si la valeur est une chaîne),
|
|
127
|
+
* - une fonction `(value) => boolean`.
|
|
128
|
+
* @param {Object} [options] - Options supplémentaires :
|
|
129
|
+
* @param {string} [options.key] - Le champ de `this.serverData` à utiliser (par défaut = entityType).
|
|
130
|
+
* @param {Function} [options.mapFn] - Fonction de transformation `(entity) => any` appliquée au résultat.
|
|
131
|
+
*
|
|
132
|
+
* @return {Promise<Array<Object>>} Liste des entités liées, filtrées et éventuellement transformées.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Tous les badges avec `name` contenant "codev"
|
|
136
|
+
* const badges = await this.linkEntitiesFromServerData("badges", { name: "codev" });
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Badges non expirés et visibles
|
|
140
|
+
* const badges = await this.linkEntitiesFromServerData("badges", {
|
|
141
|
+
* expiredOn: false,
|
|
142
|
+
* show: "true"
|
|
143
|
+
* });
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Badges émis après 2023
|
|
147
|
+
* const badges = await this.linkEntitiesFromServerData("badges", {
|
|
148
|
+
* issuedOn: (v) => new Date(v) >= new Date("2023-01-01")
|
|
149
|
+
* });
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // Extraire uniquement les noms des badges
|
|
153
|
+
* const namesOnly = await this.linkEntitiesFromServerData("badges", {}, {
|
|
154
|
+
* mapFn: (badge) => badge.meta.name
|
|
155
|
+
* });
|
|
156
|
+
*/
|
|
157
|
+
async linkEntitiesFromServerData(entityType, filters = {}, options = {}) {
|
|
158
|
+
const key = options.key || entityType;
|
|
159
|
+
const mapFn = typeof options.mapFn === "function" ? options.mapFn : null;
|
|
160
|
+
|
|
161
|
+
const isTruthy = (v) => v === true || v === "true";
|
|
162
|
+
const isFalsy = (v) => v === false || v === "false";
|
|
163
|
+
|
|
164
|
+
const data = this.serverData?.[key];
|
|
165
|
+
const result = [];
|
|
166
|
+
|
|
167
|
+
if (data && typeof data === "object" && Object.keys(data).length > 0) {
|
|
168
|
+
for (const id of Object.keys(data)) {
|
|
169
|
+
const meta = data[id];
|
|
170
|
+
|
|
171
|
+
const matches = Object.entries(filters).every(([key, expected]) => {
|
|
172
|
+
const actual = meta[key];
|
|
173
|
+
|
|
174
|
+
if (typeof expected === "function") {
|
|
175
|
+
try {
|
|
176
|
+
return expected(actual);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.warn(`Erreur dans le filtre personnalisé pour ${key}`, err);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (expected instanceof RegExp) {
|
|
184
|
+
return typeof actual === "string" && expected.test(actual);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
188
|
+
return actual.toLowerCase().includes(expected.toLowerCase());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (isTruthy(expected)) return isTruthy(actual);
|
|
192
|
+
if (isFalsy(expected)) return isFalsy(actual);
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
typeof actual === "string" &&
|
|
196
|
+
typeof expected === "string" &&
|
|
197
|
+
!isNaN(Date.parse(actual)) &&
|
|
198
|
+
!isNaN(Date.parse(expected))
|
|
199
|
+
) {
|
|
200
|
+
return new Date(actual).getTime() === new Date(expected).getTime();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return actual === expected;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!matches) continue;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const entity = await this.linkEntityById(entityType, id);
|
|
210
|
+
if (entity) {
|
|
211
|
+
entity.meta = meta;
|
|
212
|
+
result.push(mapFn ? mapFn(entity) : entity);
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this._logger?.error?.(`Erreur lors de la récupération de l'entité ${entityType} (${id}) :`, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
},
|
|
46
221
|
|
|
47
|
-
|
|
48
|
-
|
|
222
|
+
|
|
223
|
+
entityInstanceData(entityType, entityData) {
|
|
224
|
+
const meta = this._getEntityMeta(entityType);
|
|
225
|
+
const selfKey = this.__entityTag; // ex: "User", "Organization", etc.
|
|
226
|
+
const selfClass = this.constructor;
|
|
227
|
+
|
|
228
|
+
// pour citoyens la signature est différentes
|
|
229
|
+
return new meta.entityClass(this, entityData, {
|
|
230
|
+
[selfKey]: selfClass,
|
|
231
|
+
...meta.deps
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Crée une instance d'entité, et déclenche get() si certaines propriétés sont présentes.
|
|
237
|
+
* TODO : pas finis
|
|
238
|
+
* @param {string} entityType
|
|
239
|
+
* @param {Object} entityData
|
|
240
|
+
* @return {Promise<Object>}
|
|
241
|
+
*/
|
|
242
|
+
async entity(entityType, entityData = {}) {
|
|
243
|
+
try {
|
|
244
|
+
const entity = this.entityInstanceData(entityType, entityData);
|
|
245
|
+
|
|
246
|
+
const fetchKeysByEntity = {
|
|
247
|
+
citoyens: ["id", "slug"],
|
|
248
|
+
organisations: ["id", "slug"],
|
|
249
|
+
projets: ["id", "slug"],
|
|
250
|
+
events: ["id", "slug"],
|
|
251
|
+
poi: ["id", "slug"],
|
|
252
|
+
news: ["id"],
|
|
253
|
+
badges: ["id"],
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const fetchKeys = fetchKeysByEntity[entityType];
|
|
257
|
+
|
|
258
|
+
if (fetchKeys && this._hasAtLeastOne(entityData, fetchKeys)) {
|
|
259
|
+
await entity.get();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return entity;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
this.apiClient._logger.error(`[Api.${this.__entityTag}.${entityType}] Erreur lors de la création d'une instance ${entityType} :`, error.message);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
};
|
package/src/mixin/UtilMixin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ObjectID from "bson-objectid";
|
|
2
2
|
// import { fileTypeFromBuffer } from "file-type";
|
|
3
3
|
import pkg from "file-type";
|
|
4
|
-
const {
|
|
4
|
+
const { fromBuffer } = pkg;
|
|
5
5
|
|
|
6
6
|
import { ApiAuthenticationError, ApiValidationError } from "../error.js";
|
|
7
7
|
|
|
@@ -136,7 +136,7 @@ export const UtilMixin = {
|
|
|
136
136
|
|
|
137
137
|
// Node.js : Buffer
|
|
138
138
|
else if (isNode && Buffer.isBuffer(input)) {
|
|
139
|
-
const fileTypeResult = await
|
|
139
|
+
const fileTypeResult = await fromBuffer(input);
|
|
140
140
|
mimeType = fileTypeResult?.mime;
|
|
141
141
|
|
|
142
142
|
if (!fileTypeResult || !allowedMimeTypes.includes(mimeType)) {
|
|
@@ -171,7 +171,7 @@ export const UtilMixin = {
|
|
|
171
171
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
172
172
|
|
|
173
173
|
const previewBuffer = Buffer.concat(previewChunks);
|
|
174
|
-
const fileTypeResult = await
|
|
174
|
+
const fileTypeResult = await fromBuffer(previewBuffer);
|
|
175
175
|
mimeType = fileTypeResult?.mime;
|
|
176
176
|
|
|
177
177
|
if (!fileTypeResult || !allowedMimeTypes.includes(mimeType)) {
|
|
@@ -269,6 +269,10 @@ export const UtilMixin = {
|
|
|
269
269
|
return result;
|
|
270
270
|
},
|
|
271
271
|
|
|
272
|
+
_hasAtLeastOne(obj, keys = []) {
|
|
273
|
+
return keys.some((key) => key in obj && obj[key] != null);
|
|
274
|
+
},
|
|
275
|
+
|
|
272
276
|
_createFilteredProxy(list) {
|
|
273
277
|
return new Proxy(list, {
|
|
274
278
|
get(target, prop, receiver) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
import { MultiServerTokenStorageStrategy } from "./MultiServerTokenStorageStrategy.js";
|
|
6
|
+
|
|
7
|
+
export class MultiServerFileStorageStrategy extends MultiServerTokenStorageStrategy {
|
|
8
|
+
constructor(filename = "tokens.json", dir = path.join(os.homedir(), ".config", "cocolight")) {
|
|
9
|
+
super();
|
|
10
|
+
this.filePath = path.join(dir, filename);
|
|
11
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_readFile() {
|
|
15
|
+
if (!fs.existsSync(this.filePath)) return {};
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(this.filePath, "utf8"));
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_writeFile(data) {
|
|
24
|
+
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf8");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getAccessToken() {
|
|
28
|
+
this._ensureContext();
|
|
29
|
+
return this._readFile()[this._currentBaseURL]?.accessToken || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setAccessToken(token) {
|
|
33
|
+
this._ensureContext();
|
|
34
|
+
const all = this._readFile();
|
|
35
|
+
all[this._currentBaseURL] = { ...(all[this._currentBaseURL] || {}), accessToken: token };
|
|
36
|
+
this._writeFile(all);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getRefreshToken() {
|
|
40
|
+
this._ensureContext();
|
|
41
|
+
return this._readFile()[this._currentBaseURL]?.refreshToken || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setRefreshToken(token) {
|
|
45
|
+
this._ensureContext();
|
|
46
|
+
const all = this._readFile();
|
|
47
|
+
all[this._currentBaseURL] = { ...(all[this._currentBaseURL] || {}), refreshToken: token };
|
|
48
|
+
this._writeFile(all);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clear() {
|
|
52
|
+
this._ensureContext();
|
|
53
|
+
const all = this._readFile();
|
|
54
|
+
delete all[this._currentBaseURL];
|
|
55
|
+
this._writeFile(all);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getServers() {
|
|
59
|
+
return Object.keys(this._readFile());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exportAll() {
|
|
63
|
+
return this._readFile();
|
|
64
|
+
}
|
|
65
|
+
}
|