@communecter/cocolight-api-client 1.0.6 → 1.0.7

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/src/ApiClient.js CHANGED
@@ -190,6 +190,16 @@ export default class ApiClient extends EventEmitter {
190
190
  return this._refreshToken;
191
191
  }
192
192
 
193
+ /**
194
+ * Indique si le client est connecté.
195
+ * On considère que le client est connecté si un token d'accès (_accessToken) est défini.
196
+ *
197
+ * @returns {boolean} True si connecté, false sinon.
198
+ */
199
+ get isConnected() {
200
+ return !!this._accessToken;
201
+ }
202
+
193
203
  /**
194
204
  * Extrait l'identifiant depuis un JWT.
195
205
  *
@@ -202,10 +212,10 @@ export default class ApiClient extends EventEmitter {
202
212
  // Décodage du token grâce à jwt-decode
203
213
  const payload = jwtDecode(token);
204
214
  // L'identifiant peut être dans "id" ou "userId"
205
- this._logger.log("[ApiClient] Payload décodé :", payload);
215
+ this._logger.debug("[ApiClient] Payload décodé :", payload);
206
216
  return payload.id || payload.userId || null;
207
217
  } catch (err) {
208
- this._logger.error("[ApiClient] Erreur lors du décodage du token :", err.message);
218
+ this._logger.error("[ApiClient] Erreur lors du décodage du token :", err);
209
219
  return null;
210
220
  }
211
221
  }
@@ -232,6 +242,8 @@ export default class ApiClient extends EventEmitter {
232
242
  }
233
243
  return false;
234
244
  } catch (err) {
245
+ // Si on a une erreur, on reset la session
246
+ this.resetSession();
235
247
  this._logger.error(`[ApiClient] Refresh Error : ${err.message}`);
236
248
  return false;
237
249
  }
@@ -458,6 +470,14 @@ export default class ApiClient extends EventEmitter {
458
470
  return obj;
459
471
  }
460
472
 
473
+ async safeCall(fn, ...args) {
474
+ try {
475
+ return await fn(...args);
476
+ } catch (error) {
477
+ this._logger.error(`[ApiClient.safeCall] Erreur: ${error.message}`);
478
+ throw error;
479
+ }
480
+ }
461
481
 
462
482
  /**
463
483
  * Calls an API endpoint with the specified parameters.
@@ -645,7 +665,12 @@ export default class ApiClient extends EventEmitter {
645
665
  return response;
646
666
  } catch (error) {
647
667
  this._updateCircuitBreakerError();
648
- throw error;
668
+ this._logger.error(`[ApiClient] Erreur lors de l'appel de ${constant}: ${error.message}`);
669
+ throw new ApiClientError(
670
+ `Erreur lors de l'appel de l'API : ${error.message}`,
671
+ error.response ? error.response.status : 500,
672
+ error.response ? error.response.data : null
673
+ );
649
674
  }
650
675
  }
651
676
 
@@ -0,0 +1,43 @@
1
+ import { ApiResponseError } from "../error.js";
2
+
3
+ // EntityMixin.js
4
+ export const EntityMixin = {
5
+ /**
6
+ * Résout l'identifiant de l'entité si seul le slug est fourni.
7
+ * @param {string} type - Le type d'entité (ex : "citoyens", "organizations", "projects").
8
+ * @returns {Promise<string>} L'identifiant résolu.
9
+ */
10
+ async resolveId(type) {
11
+ return this.apiClient.safeCall(async () => {
12
+ if (!this.id && this.slug) {
13
+ const response = await this.apiClient.callEndpoint("GET_ELEMENTS_KEY", {
14
+ pathParams:{
15
+ slug: this.slug
16
+ }
17
+ });
18
+ if(response?.data?.result === true) {
19
+ if(response?.data?.contextId && response?.data?.contextType === type) {
20
+ this.id = response.data.contextId;
21
+ } else {
22
+ throw new ApiResponseError(`Le slug ${this.slug} ne correspond pas à un ${type}`, response.status, response.data);
23
+ }
24
+ } else {
25
+ throw new ApiResponseError(`Impossible de récupérer l'identifiant pour le slug ${this.slug}`, response.status, response.data);
26
+ }
27
+ }
28
+ return this.id;
29
+ });
30
+ },
31
+
32
+ /**
33
+ * Récupère le profil public de l'entité.
34
+ * @returns {Promise<Object>} Les données du profil public.
35
+ */
36
+ async getPublicProfile() {
37
+ return this.apiClient.safeCall(async () => {
38
+ await this.resolveId(this.getEntityType());
39
+ return this.apiClient.callEndpoint("GET_ELEMENTS_ABOUT", { pathParams: { id: this.id } });
40
+ });
41
+ }
42
+ };
43
+
@@ -0,0 +1,79 @@
1
+ import { EntityMixin } from "./EntityMixin.js";
2
+
3
+ // Organization.js
4
+ export class Organization {
5
+ // Champs privés pour protéger l'état
6
+ #id;
7
+ #slug;
8
+ #data = null;
9
+
10
+ /**
11
+ * Crée une instance de Organization.
12
+ * @param {ApiClient} apiClient - L'instance d'ApiClient.
13
+ * @param {Object} identifier - Objet contenant { id } ou { slug }.
14
+ */
15
+
16
+ constructor(apiClient, { id, slug } = {}) {
17
+ if (!id && !slug) {
18
+ throw new Error("Vous devez fournir un id ou un slug pour créer un User.");
19
+ }
20
+ this.apiClient = apiClient;
21
+ this.#id = id || null;
22
+ this.#slug = slug || null;
23
+ }
24
+
25
+ // Getters en lecture seule pour chaque propriété
26
+ get id() {
27
+ return this.#id;
28
+ }
29
+
30
+ get slug() {
31
+ return this.#slug;
32
+ }
33
+
34
+ _slug(newSlug) {
35
+ this.#slug = newSlug;
36
+ }
37
+
38
+ get isConnected() {
39
+ return this.apiClient.isConnected;
40
+ }
41
+
42
+ get data() {
43
+ return this.#data;
44
+ }
45
+
46
+ _setData(newData) {
47
+ this.#data = newData;
48
+ }
49
+
50
+ get userId() {
51
+ return this.apiClient.userId;
52
+ }
53
+
54
+ get isMe() {
55
+ return this.isConnected && this.userId === this.id;
56
+ }
57
+
58
+ getEntityType() {
59
+ return "organizations";
60
+ }
61
+
62
+ /**
63
+ * Récupère le profil complet de l'organisation.
64
+ *
65
+ * @returns {Promise<Object>} Le profil complet.
66
+ */
67
+ async getProfile() {
68
+ return this.apiClient.safeCall(async () => {
69
+ const response = await this.getPublicProfile();
70
+ this._setData(response.data);
71
+ return response.data;
72
+ });
73
+ }
74
+
75
+ }
76
+
77
+ // Incorporation du mixin dans Organization
78
+ Object.assign(Organization.prototype, EntityMixin);
79
+
@@ -0,0 +1,79 @@
1
+ import { EntityMixin } from "./EntityMixin.js";
2
+
3
+ // Project.js
4
+ export class Project {
5
+ // Champs privés pour protéger l'état
6
+ #id;
7
+ #slug;
8
+ #data = null;
9
+
10
+ /**
11
+ * Crée une instance de Project.
12
+ * @param {ApiClient} apiClient - L'instance d'ApiClient.
13
+ * @param {Object} identifier - Objet contenant { id } ou { slug }.
14
+ */
15
+
16
+ constructor(apiClient, { id, slug } = {}) {
17
+ if (!id && !slug) {
18
+ throw new Error("Vous devez fournir un id ou un slug pour créer un User.");
19
+ }
20
+ this.apiClient = apiClient;
21
+ this.#id = id || null;
22
+ this.#slug = slug || null;
23
+ }
24
+
25
+ // Getters en lecture seule pour chaque propriété
26
+ get id() {
27
+ return this.#id;
28
+ }
29
+
30
+ get slug() {
31
+ return this.#slug;
32
+ }
33
+
34
+ _slug(newSlug) {
35
+ this.#slug = newSlug;
36
+ }
37
+
38
+ get isConnected() {
39
+ return this.apiClient.isConnected;
40
+ }
41
+
42
+ get data() {
43
+ return this.#data;
44
+ }
45
+
46
+ _setData(newData) {
47
+ this.#data = newData;
48
+ }
49
+
50
+ get userId() {
51
+ return this.apiClient.userId;
52
+ }
53
+
54
+ get isMe() {
55
+ return this.isConnected && this.userId === this.id;
56
+ }
57
+
58
+ getEntityType() {
59
+ return "projects";
60
+ }
61
+
62
+ /**
63
+ * Récupère le profil complet de l'organisation.
64
+ *
65
+ * @returns {Promise<Object>} Le profil complet.
66
+ */
67
+ async getProfile() {
68
+ return this.apiClient.safeCall(async () => {
69
+ const response = await this.getPublicProfile();
70
+ this._setData(response.data);
71
+ return response.data;
72
+ });
73
+ }
74
+
75
+ }
76
+
77
+ // Incorporation du mixin dans Project
78
+ Object.assign(Project.prototype, EntityMixin);
79
+
@@ -0,0 +1,89 @@
1
+ import { EntityMixin } from "./EntityMixin.js";
2
+
3
+ // User.js
4
+ export class User {
5
+ // Champs privés pour protéger l'état
6
+ #id;
7
+ #slug;
8
+ #data;
9
+
10
+ /**
11
+ * Crée une instance de User.
12
+ * @param {ApiClient} apiClient - L'instance d'ApiClient.
13
+ * @param {Object} identifier - Objet contenant { id } ou { slug }.
14
+ * @param {Object} [data={}] - Données supplémentaires.
15
+ */
16
+
17
+ constructor(apiClient, { id, slug } = {}, data = {}) {
18
+ if (!id && !slug) {
19
+ throw new Error("Vous devez fournir un id ou un slug pour créer un User.");
20
+ }
21
+ this.apiClient = apiClient;
22
+ this.#id = id || null;
23
+ this.#slug = slug || null;
24
+ this.#data = data;
25
+ }
26
+
27
+ // Getters en lecture seule pour chaque propriété
28
+ get id() {
29
+ return this.#id;
30
+ }
31
+
32
+ get slug() {
33
+ return this.#slug;
34
+ }
35
+
36
+ _slug(newSlug) {
37
+ this.#slug = newSlug;
38
+ }
39
+
40
+ get isConnected() {
41
+ return this.apiClient.isConnected;
42
+ }
43
+
44
+ // Getter pour accéder aux données
45
+ get data() {
46
+ return this.#data;
47
+ }
48
+
49
+ // Méthode interne qui permet de mettre à jour les données
50
+ _setData(newData) {
51
+ this.#data = newData;
52
+ }
53
+
54
+ getEntityType() {
55
+ return "citoyens";
56
+ }
57
+
58
+ get userId() {
59
+ return this.apiClient.userId;
60
+ }
61
+
62
+ get isMe() {
63
+ return this.isConnected && this.userId === this.id;
64
+ }
65
+
66
+ /**
67
+ * Récupère le profil complet de l'utilisateur.
68
+ * Si l'utilisateur est connecté, on appelle le endpoint ME_INFO_URL,
69
+ * sinon, on peut imaginer appeler un endpoint public.
70
+ *
71
+ * @returns {Promise<Object>} Le profil complet.
72
+ */
73
+ async getProfile() {
74
+ return this.apiClient.safeCall(async () => {
75
+ if (this.isMe) {
76
+ const response = await this.apiClient.callEndpoint("ME_INFO_URL", {});
77
+ return response.data;
78
+ } else {
79
+ const response = await this.getPublicProfile();
80
+ return response.data;
81
+ }
82
+ });
83
+ }
84
+
85
+ }
86
+
87
+ // Incorporation du mixin dans User
88
+ Object.assign(User.prototype, EntityMixin);
89
+
@@ -0,0 +1,48 @@
1
+ // UserApi.js
2
+ import ApiClient from "../ApiClient.js";
3
+ import { ApiResponseError } from "../error.js";
4
+ import { User } from "./User.js";
5
+
6
+ export class UserApi {
7
+ constructor(options) {
8
+ // Injection de dépendance : ApiClient est créé à partir des options
9
+ this.client = new ApiClient(options);
10
+ this.loggedUser = null;
11
+ }
12
+ // Méthode d'authentification : récupère les données utilisateur depuis un endpoint
13
+ async login(email, password) {
14
+ return this.client.safeCall(async () => {
15
+ // Appel à un endpoint d'authentification
16
+ const response = await this.client.callEndpoint("AUTHENTICATE_URL", { email, password });
17
+ // Création d'une instance de LoggedInUser à partir des données reçues
18
+ this.loggedUser = new User(this.client, { id: response.data.user.id }, response.data.user);
19
+ return this.loggedUser;
20
+ });
21
+ }
22
+
23
+ async register({
24
+ name,
25
+ username,
26
+ email,
27
+ pwd,
28
+ } = {}) {
29
+ return this.client.safeCall(async () => {
30
+ const response = await this.client.callEndpoint("PERSON_REGISTER", { name, username, email, pwd });
31
+ if(response?.data?.result === false) {
32
+ throw new ApiResponseError(response.data.msg, response.status, response.data);
33
+ }
34
+ return response.data;
35
+ });
36
+ }
37
+
38
+
39
+ async recoverPassword(email) {
40
+ return this.client.safeCall(async () => {
41
+ const response = await this.client.callEndpoint("PASSWORD_RECOVERY", { email });
42
+ if(response?.data?.result === false) {
43
+ throw new ApiResponseError(response.data.msg, response.status, response.data);
44
+ }
45
+ return response.data;
46
+ });
47
+ }
48
+ }