@communecter/cocolight-api-client 1.0.22 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communecter/cocolight-api-client",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,8 +4,13 @@ import ObjectID from "bson-objectid";
4
4
  import pkg from "file-type";
5
5
 
6
6
  import { ApiAuthenticationError, ApiError, ApiResponseError, ApiValidationError } from "../error.js";
7
+ import { autoSyncDraftFromSchema, reactive } from "../utils/reactive.js";
7
8
  const { fromBuffer } = pkg;
8
9
 
10
+ /**
11
+ * @typedef {import("../ApiClient.js").default} ApiClient
12
+ * @typedef {import("./EndpointApi.js").default} EndpointApi
13
+ */
9
14
 
10
15
  /**
11
16
  * Classe de base pour toutes les entités métiers : utilisateurs, projets, organisations, etc.
@@ -26,6 +31,9 @@ export class BaseEntity {
26
31
  /** @type {boolean} Indique si `save()` est en cours */
27
32
  _calledFromSave = false;
28
33
 
34
+ /** @type {boolean} Indique si le draft est synchronisé avec le serveur */
35
+ _syncReactiveDraft = false;
36
+
29
37
  /**
30
38
  * Constructeur de l'entité.
31
39
  * @param {Object} parent - L'ApiClient ou une entité parente.
@@ -50,11 +58,11 @@ export class BaseEntity {
50
58
  this.deps = deps;
51
59
 
52
60
  if (parent?.__entityTag === "ApiClient") {
53
- /** @type {import("../ApiClient.js").default} */
61
+ /** @type {ApiClient} */
54
62
  this.apiClient = parent;
55
63
  this.parent = null;
56
64
  } else if (parent?.apiClient) {
57
- /** @type {import("../ApiClient.js").default} */
65
+ /** @type {ApiClient} */
58
66
  this.apiClient = parent.apiClient;
59
67
  this.parent = parent;
60
68
  } else {
@@ -63,10 +71,10 @@ export class BaseEntity {
63
71
 
64
72
  // Gérer les deux cas : fonction constructeur ou instance
65
73
  if (typeof deps.EndpointApi === "function") {
66
- /** @type {import("./EndpointApi.js").default} */
74
+ /** @type {EndpointApi} */
67
75
  this.endpointApi = new deps.EndpointApi(this.apiClient);
68
76
  } else if (typeof deps.EndpointApi === "object") {
69
- /** @type {import("./EndpointApi.js").default} */
77
+ /** @type {EndpointApi} */
70
78
  this.endpointApi = deps.EndpointApi;
71
79
  } else {
72
80
  throw new ApiError("deps.EndpointApi doit être une classe ou une instance valide.");
@@ -184,19 +192,7 @@ export class BaseEntity {
184
192
  */
185
193
  static fromServerData(data, parent, deps) {
186
194
  const instance = new this(parent, {}, deps);
187
- instance._serverData = { ...data };
188
-
189
- const { draft, proxy } = instance._buildDraftAndProxy({
190
- data: { ...data, ...instance.defaultFields },
191
- serverData: instance._serverData,
192
- constant: this.SCHEMA_CONSTANTS,
193
- apiClient: instance.apiClient,
194
- transforms: instance.transforms,
195
- removeFields: instance.removeFields
196
- });
197
-
198
- instance._draftData = draft;
199
- instance.data = proxy;
195
+ instance._setData(data);
200
196
  return instance;
201
197
  }
202
198
 
@@ -208,7 +204,7 @@ export class BaseEntity {
208
204
  * @private
209
205
  */
210
206
  _setData(newData) {
211
- this._serverData = { ...newData };
207
+ this._serverData = reactive({ ...newData });
212
208
 
213
209
  const { draft, proxy } = this._buildDraftAndProxy({
214
210
  data: { ...newData, ...this.defaultFields },
@@ -221,6 +217,10 @@ export class BaseEntity {
221
217
  this._initialDraftData = JSON.parse(JSON.stringify(draft));
222
218
  this._draftData = draft;
223
219
  this.data = proxy;
220
+
221
+ if (this._syncReactiveDraft) {
222
+ this.setupReactiveSync();
223
+ }
224
224
  }
225
225
 
226
226
 
@@ -831,6 +831,25 @@ export class BaseEntity {
831
831
  return Object.keys(changed).length > 0 ? changed : null;
832
832
  }
833
833
 
834
+ /**
835
+ * ───────────────────────────────
836
+ * ReactiveMixin
837
+ * ───────────────────────────────
838
+ */
839
+
840
+ /**
841
+ * Active la synchronisation réactive entre le brouillon et le schéma.
842
+ *
843
+ * @returns {void}
844
+ * @public
845
+ */
846
+ setupReactiveSync() {
847
+ if (!this._syncReactiveDraft) {
848
+ this._syncReactiveDraft = true;
849
+ }
850
+ autoSyncDraftFromSchema(this);
851
+ }
852
+
834
853
  /**
835
854
  * ───────────────────────────────
836
855
  * MutualEntityMixin
@@ -1122,6 +1141,307 @@ export class BaseEntity {
1122
1141
  }
1123
1142
  }
1124
1143
 
1144
+ /**
1145
+ * Construit dynamiquement des filtres sur un lien entre entités
1146
+ *
1147
+ * @param {string} id - L'ID de l'entité cible.
1148
+ * @param {Object} options - Options de filtrage.
1149
+ * @param {"memberOf" | "projects"} [options.linkType] - Le type de lien (ex: "memberOf", "projects", etc.).
1150
+ * @param {boolean} [options.isAdmin] - Si défini, filtre selon l'existence de isAdmin.
1151
+ * @param {boolean} [options.isAdminPending] - Si défini, filtre selon l'existence de isAdminPending.
1152
+ * @param {boolean} [options.isInviting] - Si défini, filtre selon l'existence de isInviting.
1153
+ * @param {boolean} [options.toBeValidated] - Si défini, filtre selon l'existence de toBeValidated.
1154
+ * @param {Array<string>} [options.roles] - Rôles à filtrer.
1155
+ * @returns {Object} - Un objet de filtres à passer à une requête.
1156
+ * @throws {TypeError} - Si les types des paramètres ne sont pas valides.
1157
+ * @private
1158
+ */
1159
+ _buildLinkFilters(id, {
1160
+ linkType,
1161
+ isAdmin,
1162
+ isAdminPending,
1163
+ isInviting,
1164
+ toBeValidated = false,
1165
+ roles = []
1166
+ } = {}) {
1167
+ if (typeof id !== "string") throw new TypeError("id doit être une chaîne.");
1168
+ if (typeof linkType !== "string") throw new TypeError("linkType doit être une chaîne.");
1169
+ if (typeof isAdmin !== "undefined" && typeof isAdmin !== "boolean") {
1170
+ throw new TypeError("isAdmin doit être un booléen.");
1171
+ }
1172
+ if (typeof isAdminPending !== "undefined" && typeof isAdminPending !== "boolean") {
1173
+ throw new TypeError("isAdminPending doit être un booléen.");
1174
+ }
1175
+ if (typeof isInviting !== "undefined" && typeof isInviting !== "boolean") {
1176
+ throw new TypeError("isInviting doit être un booléen.");
1177
+ }
1178
+ if (typeof toBeValidated !== "undefined" && typeof toBeValidated !== "boolean") {
1179
+ throw new TypeError("toBeValidated doit être un booléen.");
1180
+ }
1181
+ if (!Array.isArray(roles)) {
1182
+ throw new TypeError("roles doit être un tableau de chaînes.");
1183
+ }
1184
+
1185
+ const path = `links.${linkType}.${id}`;
1186
+ const filters = {
1187
+ [`${path}`]: { $exists: true }
1188
+ };
1189
+
1190
+ if (typeof toBeValidated === "boolean") {
1191
+ filters[`${path}.toBeValidated`] = { $exists: toBeValidated };
1192
+ }
1193
+
1194
+ if (typeof isInviting === "boolean") {
1195
+ filters[`${path}.isInviting`] = { $exists: isInviting };
1196
+ }
1197
+
1198
+ if (isAdmin === true) {
1199
+ filters[`${path}.isAdmin`] = { $exists: true };
1200
+ }
1201
+
1202
+ if(isAdminPending === true) {
1203
+ filters[`${path}.isAdminPending`] = { $exists: true };
1204
+ }
1205
+
1206
+ if (roles.length > 0) {
1207
+ filters[`${path}.roles`] = { $in: roles };
1208
+ }
1209
+
1210
+ return filters;
1211
+ }
1212
+
1213
+ /**
1214
+ * Retourne les métadonnées de lien pour une entité :
1215
+ * - `linkType` : clé dans `serverData.links`
1216
+ * - `connectTypeConnect` : valeur envoyée pour `connect()`
1217
+ * - `connectTypeDisconnect` : valeur envoyée pour `disconnect()`
1218
+ * @typedef {Object} LinkMeta
1219
+ * @property {string} linkType
1220
+ * @property {string} connectTypeConnect
1221
+ * @property {string} connectTypeDisconnect
1222
+ * @returns {LinkMeta}
1223
+ * @throws {ApiError} - Si le type d'entité est inconnu.
1224
+ * @private
1225
+ */
1226
+ _getLinkMeta() {
1227
+ const map = {
1228
+ organizations: {
1229
+ linkType: "memberOf",
1230
+ connectTypeConnect: "member",
1231
+ connectTypeDisconnect: "members"
1232
+ },
1233
+ projects: {
1234
+ linkType: "projects",
1235
+ connectTypeConnect: "contributor",
1236
+ connectTypeDisconnect: "contributors"
1237
+ },
1238
+ events: {
1239
+ linkType: "events",
1240
+ connectTypeConnect: "attendee",
1241
+ connectTypeDisconnect: "attendees"
1242
+ },
1243
+ citoyens: {
1244
+ linkType: "friends",
1245
+ connectTypeConnect: "friend",
1246
+ connectTypeDisconnect: "friends"
1247
+ }
1248
+ };
1249
+
1250
+ const entityType = this.getEntityType();
1251
+ const meta = map[entityType];
1252
+
1253
+ if (!meta) {
1254
+ throw new ApiError(`Aucune correspondance de lien définie pour le type d'entité "${entityType}"`);
1255
+ }
1256
+
1257
+ return meta;
1258
+ }
1259
+
1260
+ /**
1261
+ * Vérifie si l'entité prend en charge les opérations de lien (`connect`, `disconnect`, etc.).
1262
+ * Utilise `_getLinkMeta()` comme source unique de vérité.
1263
+ *
1264
+ * @throws {ApiError} - Si l'entité ne supporte pas les liens.
1265
+ * @private
1266
+ */
1267
+ _checkLinkableEntity() {
1268
+ try {
1269
+ this._getLinkMeta(); // si l'entité n'est pas supportée, ça throw déjà
1270
+ } catch (error) {
1271
+ if (error instanceof ApiError) {
1272
+ throw new ApiError(`L'entité "${this.getEntityType()}" ne prend pas en charge les actions de lien.`);
1273
+ }
1274
+ throw error;
1275
+ }
1276
+ }
1277
+
1278
+ /**
1279
+ * Retourne le lien de l'utilisateur connecté avec l'entité cible (dans `parent.serverData.links`)
1280
+ * @private
1281
+ */
1282
+ _getLinkFromConnectedUser() {
1283
+ const { linkType } = this._getLinkMeta();
1284
+ return this.parent?.serverData?.links?.[linkType]?.[this.id] || null;
1285
+ }
1286
+
1287
+ /**
1288
+ * Soumet une demande de connexion à une entité (ex : membre, contributeur),
1289
+ * ou valide l'invitation si elle existe déjà.
1290
+ *
1291
+ * @returns {Promise<Object>} - Résultat de l'API
1292
+ * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1293
+ * @private
1294
+ */
1295
+ async _submitLinkRequest() {
1296
+
1297
+ if (!this.id) {
1298
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1299
+ }
1300
+
1301
+ const { connectTypeConnect } = this._getLinkMeta();
1302
+
1303
+ const userLink = this._getLinkFromConnectedUser();
1304
+
1305
+ // Cas : aucun lien → on demande à se connecter
1306
+ if (!userLink) {
1307
+ const data = {
1308
+ parentType: this.getEntityType(),
1309
+ parentId: this.id,
1310
+ connectType: connectTypeConnect
1311
+ };
1312
+
1313
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1314
+ const retour = await this.callIsMe(() => this.endpointApi.connect(data));
1315
+ await this.parent.refresh();
1316
+ return retour;
1317
+ }
1318
+
1319
+ // Cas : invitation reçue → on valide l'invitation
1320
+ if (userLink.isInviting) {
1321
+ return this._acceptLinkRequest();
1322
+ }
1323
+
1324
+ // Cas : déjà en attente
1325
+ if (userLink.toBeValidated) {
1326
+ throw new ApiError("Vous êtes déjà en attente de validation.");
1327
+ }
1328
+
1329
+ // Cas par défaut : rien à faire
1330
+ throw new ApiError("Vous êtes déjà connecté à cette entité.");
1331
+ }
1332
+
1333
+ /**
1334
+ * Soumet une demande pour devenir administrateur d'une entité.
1335
+ *
1336
+ * @returns {Promise<Object>}
1337
+ * @throws {ApiError}
1338
+ * @private
1339
+ */
1340
+ async _submitLinkRequestAdmin() {
1341
+
1342
+ if (!this.id) {
1343
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1344
+ }
1345
+
1346
+ const userLink = this._getLinkFromConnectedUser();
1347
+
1348
+ // Aucun lien existant → envoie une demande avec rôle "admin"
1349
+ if (!userLink) {
1350
+ const data = {
1351
+ parentType: this.getEntityType(),
1352
+ parentId: this.id,
1353
+ connectType: "admin"
1354
+ };
1355
+
1356
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1357
+ const retour = await this.callIsMe(() => this.endpointApi.connect(data));
1358
+ await this.parent.refresh();
1359
+ return retour;
1360
+ }
1361
+
1362
+ // Invitation reçue → accepte automatiquement
1363
+ if (userLink.isInviting) {
1364
+ return this._acceptLinkRequest();
1365
+ }
1366
+
1367
+ // Déjà en attente pour admin
1368
+ if (userLink.toBeValidated && userLink.isAdminPending) {
1369
+ throw new ApiError("Vous êtes déjà en attente de validation pour devenir admin.");
1370
+ }
1371
+
1372
+ // Déjà en attente pour un autre rôle
1373
+ if (userLink.toBeValidated) {
1374
+ throw new ApiError("Vous êtes déjà en attente de validation pour rejoindre cette entité.");
1375
+ }
1376
+
1377
+ // Déjà connecté
1378
+ throw new ApiError("Vous êtes déjà connecté à cette entité.");
1379
+ }
1380
+
1381
+ /**
1382
+ * Accepte une demande de lien (ex : invitation à rejoindre un groupe).
1383
+ *
1384
+ * @returns {Promise<Object>} - Résultat de l'API
1385
+ * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1386
+ * @private
1387
+ */
1388
+ async _acceptLinkRequest() {
1389
+
1390
+ if (!this.id) {
1391
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1392
+ }
1393
+
1394
+ const userLink = this._getLinkFromConnectedUser();
1395
+
1396
+ if (userLink.isInviting) {
1397
+ const data = {
1398
+ parentType: this.getEntityType(),
1399
+ parentId: this.id,
1400
+ linkOption: "isInviting"
1401
+ };
1402
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1403
+ const retour = await this.callIsMe(() => this.endpointApi.linkValidate(data));
1404
+ await this.parent.refresh();
1405
+ return retour;
1406
+ }
1407
+
1408
+ throw new ApiError("Vous n'avez pas d'invitation à valider.");
1409
+ }
1410
+
1411
+ /**
1412
+ * Annule une demande de lien ou se déconnecte d'une entité.
1413
+ *
1414
+ * @returns {Promise<Object>} - Résultat de l'API
1415
+ * @throws {ApiError} - En cas d'erreur de connexion ou d'invitation.
1416
+ * @private
1417
+ */
1418
+ async _leaveLinkRequest() {
1419
+
1420
+ if (!this.id) {
1421
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1422
+ }
1423
+
1424
+ const { connectTypeDisconnect } = this._getLinkMeta();
1425
+
1426
+ const userLink = this._getLinkFromConnectedUser();
1427
+
1428
+ if (!userLink) {
1429
+ throw new ApiError("Vous n'êtes pas connecté à cette entité.");
1430
+ }
1431
+
1432
+ const data = {
1433
+ parentType: this.getEntityType(),
1434
+ parentId: this.id,
1435
+ // Normalement en auto dans le schema mais je le met quand même
1436
+ connectType: connectTypeDisconnect
1437
+ };
1438
+
1439
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1440
+ const retour = await this.callIsMe(() => this.endpointApi.disconnect(data));
1441
+ await this.parent.refresh();
1442
+ return retour;
1443
+ }
1444
+
1125
1445
  /**
1126
1446
  * ───────────────────────────────
1127
1447
  * EntityMixin
@@ -1543,6 +1863,128 @@ export class BaseEntity {
1543
1863
  return this._createFilteredProxy(rawList);
1544
1864
  }
1545
1865
 
1866
+ /**
1867
+ * Soumet une demande pour rejoindre l'entité courante (ex. organisation, projet, événement...).
1868
+ * Si une invitation est en attente, elle est automatiquement acceptée.
1869
+ *
1870
+ * @returns {Promise<Object>} - Résultat de la demande ou de la validation.
1871
+ * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si une demande est déjà en cours.
1872
+ */
1873
+ async requestToJoin(){
1874
+ if (!this.isMe) {
1875
+ throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
1876
+ }
1877
+ this._checkLinkableEntity();
1878
+ return this._submitLinkRequest();
1879
+ }
1880
+
1881
+ /**
1882
+ * Soumet une demande pour devenir administrateur de l'entité courante.
1883
+ * Si une invitation est en attente, elle est automatiquement validée.
1884
+ *
1885
+ * @returns {Promise<Object>} - Résultat de la demande ou de la validation.
1886
+ * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si une demande est déjà en cours.
1887
+ */
1888
+ async requestToJoinAdmin(){
1889
+ if (!this.isMe) {
1890
+ throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
1891
+ }
1892
+ this._checkLinkableEntity();
1893
+ return this._submitLinkRequestAdmin();
1894
+ }
1895
+
1896
+ /**
1897
+ * Accepte une invitation à rejoindre l'entité courante.
1898
+ * Ne fonctionne que si un lien avec `isInviting` est détecté.
1899
+ *
1900
+ * @returns {Promise<Object>} - Résultat de l'acceptation de l'invitation.
1901
+ * @throws {ApiError} - Si aucune invitation n'est en attente ou si l'entité ne supporte pas cette action.
1902
+ */
1903
+ async acceptInvitation(){
1904
+ if (!this.isMe) {
1905
+ throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
1906
+ }
1907
+ this._checkLinkableEntity();
1908
+ return this._acceptLinkRequest();
1909
+ }
1910
+
1911
+ /**
1912
+ * Se désengage de l'entité courante : quitte un rôle (membre, contributeur, etc.).
1913
+ *
1914
+ * @returns {Promise<Object>} - Résultat de la déconnexion.
1915
+ * @throws {ApiError} - Si l'entité ne supporte pas l'action ou si l'utilisateur n'est pas lié à cette entité.
1916
+ */
1917
+ async leave() {
1918
+ if (!this.isMe) {
1919
+ throw new ApiError("Vous devez être connecté pour envoyer une demande de participation.");
1920
+ }
1921
+ this._checkLinkableEntity();
1922
+ return this._leaveLinkRequest();
1923
+ }
1924
+
1925
+ /**
1926
+ * S'abonne à l'entité courante.
1927
+ *
1928
+ * @returns {Promise<Object>} - Résultat de l'abonnement.
1929
+ * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
1930
+ */
1931
+ async follow() {
1932
+ if (!this.isMe) {
1933
+ throw new ApiError("Vous devez être connecté pour suivre cette entité.");
1934
+ }
1935
+
1936
+ if (!this.id) {
1937
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1938
+ }
1939
+
1940
+ const userLink = this.parent?.serverData?.links?.["follows"]?.[this.id] || null;
1941
+
1942
+ if (!userLink) {
1943
+ const data = {
1944
+ parentType: this.getEntityType(),
1945
+ parentId: this.id
1946
+ };
1947
+ const retour = await this.callIsMe(() => this.endpointApi.follow(data));
1948
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1949
+ await this.parent.refresh();
1950
+ return retour;
1951
+ }
1952
+
1953
+ throw new ApiError("Vous êtes déjà abonné à cette entité.");
1954
+ }
1955
+
1956
+ /**
1957
+ * Se désabonne de l'entité courante.
1958
+ *
1959
+ * @returns {Promise<Object>} - Résultat de la désinscription.
1960
+ * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
1961
+ */
1962
+ async unfollow() {
1963
+ if (!this.isMe) {
1964
+ throw new ApiError("Vous devez être connecté pour vous désabonner.");
1965
+ }
1966
+
1967
+ if (!this.id) {
1968
+ throw new ApiError(`${this.constructor.name} non enregistrée.`);
1969
+ }
1970
+
1971
+ const userLink = this.parent?.serverData?.links?.["follows"]?.[this.id] || null;
1972
+
1973
+ if (userLink) {
1974
+ const data = {
1975
+ parentType: this.getEntityType(),
1976
+ parentId: this.id,
1977
+ connectType: "followers"
1978
+ };
1979
+ const retour = await this.callIsMe(() => this.endpointApi.disconnect(data));
1980
+ // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
1981
+ await this.parent.refresh();
1982
+ return retour;
1983
+ }
1984
+
1985
+ throw new ApiError("Vous n'êtes pas abonné à cette entité.");
1986
+ }
1987
+
1546
1988
  }
1547
1989
 
1548
1990
  export default BaseEntity;
@@ -792,17 +792,17 @@ class EndpointApi {
792
792
 
793
793
  /**
794
794
  * Récupérer les contributeurs non administrables : Récupère les contributeurs sans droits d’admin.
795
- * Constant : GET_CONTRIBUTORS
796
- * @param {import("./EndpointApi.types").GetContributorsData} data - Données envoyées à l'API
795
+ * Constant : GET_CONTRIBUTORS_NO_ADMIN
796
+ * @param {import("./EndpointApi.types").GetContributorsNoAdminData} data - Données envoyées à l'API
797
797
  * @returns {Promise<Object>} - Les données de réponse.
798
798
  * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
799
799
  * @throws {Error} - En cas d'erreur inattendue.
800
800
  */
801
- async getContributors(data) {
801
+ async getContributorsNoAdmin(data) {
802
802
  if (!data || typeof data !== "object") {
803
803
  throw new TypeError("Le paramètre data doit être un objet.");
804
804
  }
805
- return this.call("GET_CONTRIBUTORS", data);
805
+ return this.call("GET_CONTRIBUTORS_NO_ADMIN", data);
806
806
  }
807
807
 
808
808
  /**
@@ -1332,6 +1332,22 @@ class EndpointApi {
1332
1332
  return this.callIsConnected("INVITE_EVENT", data);
1333
1333
  }
1334
1334
 
1335
+ /**
1336
+ * Suivre un utilisateur ou un élément : Suivre un utilisateur ou un élément.
1337
+ * Constant : FOLLOW
1338
+ * @param {import("./EndpointApi.types").FollowData} data - Données envoyées à l'API
1339
+ * @returns {Promise<Object>} - Les données de réponse.
1340
+ * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1341
+ * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1342
+ * @throws {Error} - En cas d'erreur inattendue.
1343
+ */
1344
+ async follow(data) {
1345
+ if (!data || typeof data !== "object") {
1346
+ throw new TypeError("Le paramètre data doit être un objet.");
1347
+ }
1348
+ return this.callIsConnected("FOLLOW", data);
1349
+ }
1350
+
1335
1351
  }
1336
1352
 
1337
1353
  export default EndpointApi;
@@ -1809,7 +1809,7 @@ export interface GetSubscribersAdminData {
1809
1809
  }
1810
1810
 
1811
1811
 
1812
- export interface GetContributorsData {
1812
+ export interface GetContributorsNoAdminData {
1813
1813
  /**
1814
1814
  * Nom ou terme recherché
1815
1815
  */
@@ -2116,7 +2116,7 @@ export interface DisconnectData {
2116
2116
  /**
2117
2117
  * Type de connexion
2118
2118
  */
2119
- connectType: "admin" | "member" | "contributor" | "attendee" | "friend";
2119
+ connectType: "members" | "contributors" | "attendees" | "friends" | "followers";
2120
2120
  [k: string]: unknown;
2121
2121
  }
2122
2122
 
@@ -3461,3 +3461,24 @@ export interface InviteEventData {
3461
3461
  };
3462
3462
  [k: string]: unknown;
3463
3463
  }
3464
+
3465
+
3466
+ export interface FollowData {
3467
+ /**
3468
+ * ID de l'élément à suivre (projet, organisation...)
3469
+ */
3470
+ childId: string;
3471
+ /**
3472
+ * Type de l'élément à suivre
3473
+ */
3474
+ childType: "citoyens";
3475
+ /**
3476
+ * Type de l'élément parent (projet, organisation...)
3477
+ */
3478
+ parentType: "citoyens" | "organizations" | "projects" | "events";
3479
+ /**
3480
+ * ID de l'élément parent
3481
+ */
3482
+ parentId: string;
3483
+ [k: string]: unknown;
3484
+ }
package/src/api/Event.js CHANGED
@@ -141,4 +141,26 @@ export class Event extends BaseEntity {
141
141
  return super.news(newsData);
142
142
  }
143
143
 
144
+ /**
145
+ * Suivre un événement.
146
+ * Cette action permet à l'utilisateur de suivre un événement.
147
+ *
148
+ * @returns {Promise<Object>} - Résultat de l'action de suivi.
149
+ * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité ne supporte pas l'action.
150
+ */
151
+ async follow() {
152
+ return super.follow();
153
+ }
154
+
155
+ /**
156
+ * Se désabonne d'un événement.
157
+ *
158
+ * @returns {Promise<Object>} - Résultat de la désinscription.
159
+ * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'entité n'est pas enregistrée.
160
+ */
161
+ async unfollow() {
162
+ return super.unfollow();
163
+ }
164
+
165
+
144
166
  }