@communecter/cocolight-api-client 1.0.77 → 1.0.79

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/api/User.ts CHANGED
@@ -15,7 +15,8 @@ import type {
15
15
  GetSubscriptionsData,
16
16
  GetOrganizationsNoAdminData,
17
17
  GetOrganizationsAdminData,
18
- GetFriendsAdminData
18
+ GetFriendsAdminData,
19
+ DemoteAdminData
19
20
  } from "./EndpointApi.types.js";
20
21
  import type { Organization } from "./Organization.js";
21
22
  import type { EntityTypes } from "@/types/entities.js";
@@ -888,6 +889,668 @@ export class User extends BaseEntity<UserItemNormalized> {
888
889
  }
889
890
  return super.entityBySlug(slug);
890
891
  }
892
+
893
+ /**
894
+ * Valide les préconditions communes pour les méthodes de vérification de rôle.
895
+ * @private
896
+ * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
897
+ * @param expectedTypes - Types d'entité parent autorisés.
898
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
899
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
900
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
901
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
902
+ * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
903
+ */
904
+ private _validateRoleCheckPreconditions(methodName: string, expectedTypes: string[]): void {
905
+ if (!this.userId) {
906
+ throw new ApiError(`Vous devez être connecté pour ${methodName}.`, 401);
907
+ }
908
+
909
+ if(!this.parent?.isAdmin()){
910
+ throw new ApiError("Vous devez être administrateur pour effectuer cette action.", 401);
911
+ }
912
+
913
+ if (!this.id) {
914
+ throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
915
+ }
916
+
917
+ if(!this.parent?.id){
918
+ throw new ApiError("L'entité parente n'est pas enregistrée.", 404);
919
+ }
920
+
921
+ if (!expectedTypes.includes(this.parent.getEntityType())) {
922
+ throw new ApiError(`L'entité doit être de type : ${expectedTypes.join(", ")}, reçu : ${this.parent.getEntityType()}`, 400);
923
+ }
924
+ }
925
+
926
+
927
+ /**
928
+ * Récupère le lien parent pour l'utilisateur actuel.
929
+ *
930
+ * @returns Le lien parent de l'utilisateur ou `null` s'il n'existe pas.
931
+ * @private
932
+ */
933
+ private _getParentLinkForUser(): any {
934
+ const { connectTypeDisconnect } = this.parent!._getLinkMeta();
935
+ const userId = this!.id;
936
+ if (!userId) return null;
937
+ return this.parent?.serverData?.links?.[connectTypeDisconnect]?.[userId] || null;
938
+ }
939
+
940
+ /**
941
+ * Vérifie si l'utilisateur est administrateur de l'entité parente.
942
+ *
943
+ * Cette méthode permet de vérifier si un utilisateur possède les droits d'administration
944
+ * sur l'organisation ou le projet parent. Elle est particulièrement utile après avoir
945
+ * récupéré des membres via `getMembers()` pour déterminer leur niveau de permissions.
946
+ *
947
+ * @returns `true` si l'utilisateur est administrateur de l'entité parente, `false` sinon.
948
+ *
949
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
950
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
951
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
952
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
953
+ * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "organizations" ou "projects".
954
+ *
955
+ * @example
956
+ * // Vérifier les admins d'une organisation
957
+ * const org = await me.organization({ slug: "openAtlas" });
958
+ * const members = await org.getMembers();
959
+ *
960
+ * for (const member of members.results) {
961
+ * if (member.getEntityType() === "citoyens") {
962
+ * if (member.isAdmin()) {
963
+ * console.log(`${member.data.name} est administrateur`);
964
+ * }
965
+ * }
966
+ * }
967
+ *
968
+ * @example
969
+ * // Récupérer uniquement les membres admins
970
+ * const org = await me.organization({ slug: "myOrg" });
971
+ * const adminMembers = await org.getMembers({}, { isAdmin: true });
972
+ *
973
+ * // Vérifier chaque admin
974
+ * for (const admin of adminMembers.results) {
975
+ * if (admin.getEntityType() === "citoyens" && admin.isAdmin()) {
976
+ * console.log(`${admin.data.name} a les droits admin`);
977
+ * }
978
+ * }
979
+ */
980
+ override isAdmin(): boolean {
981
+ this._validateRoleCheckPreconditions("isAdmin", ["organizations", "projects"]);
982
+ const parentLink = this._getParentLinkForUser();
983
+ return this._validateUserLink(parentLink) && parentLink?.isAdmin === true;
984
+ }
985
+
986
+ /**
987
+ * Vérifie si l'utilisateur est membre de l'organisation parente.
988
+ *
989
+ * Cette méthode permet de vérifier si un utilisateur est membre actif d'une organisation.
990
+ * Elle est utile pour déterminer si un utilisateur a accès aux ressources de l'organisation.
991
+ *
992
+ * @returns `true` si l'utilisateur est membre validé de l'organisation parente, `false` sinon.
993
+ *
994
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
995
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
996
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
997
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
998
+ * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "organizations".
999
+ *
1000
+ * @example
1001
+ * // Vérifier les membres d'une organisation
1002
+ * const org = await me.organization({ slug: "myOrg" });
1003
+ * const members = await org.getMembers();
1004
+ *
1005
+ * for (const member of members.results) {
1006
+ * if (member.getEntityType() === "citoyens" && member.isMember()) {
1007
+ * console.log(`${member.data.name} est membre de l'organisation`);
1008
+ * }
1009
+ * }
1010
+ */
1011
+ override isMember(): boolean {
1012
+ this._validateRoleCheckPreconditions("isMember", ["organizations"]);
1013
+ const parentLink = this._getParentLinkForUser();
1014
+ return this._validateUserLink(parentLink);
1015
+ }
1016
+
1017
+ /**
1018
+ * Vérifie si l'utilisateur est contributeur du projet parent.
1019
+ *
1020
+ * Cette méthode permet de vérifier si un utilisateur est contributeur actif d'un projet.
1021
+ * Elle est utile pour déterminer si un utilisateur peut participer aux activités du projet.
1022
+ *
1023
+ * @returns `true` si l'utilisateur est contributeur validé du projet parent, `false` sinon.
1024
+ *
1025
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1026
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1027
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1028
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1029
+ * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "projects".
1030
+ *
1031
+ * @example
1032
+ * // Vérifier les contributeurs d'un projet
1033
+ * const project = await me.project({ slug: "myProject" });
1034
+ * const contributors = await project.getContributors();
1035
+ *
1036
+ * for (const contributor of contributors.results) {
1037
+ * if (contributor.getEntityType() === "citoyens" && contributor.isContributor()) {
1038
+ * console.log(`${contributor.data.name} est contributeur du projet`);
1039
+ * }
1040
+ * }
1041
+ */
1042
+ override isContributor(): boolean {
1043
+ this._validateRoleCheckPreconditions("isContributor", ["projects"]);
1044
+ const parentLink = this._getParentLinkForUser();
1045
+ return this._validateUserLink(parentLink);
1046
+ }
1047
+
1048
+ /**
1049
+ * Vérifie si l'utilisateur est participant de l'événement parent.
1050
+ *
1051
+ * Cette méthode permet de vérifier si un utilisateur est inscrit comme participant à un événement.
1052
+ * Elle est utile pour déterminer si un utilisateur a confirmé sa participation à l'événement.
1053
+ *
1054
+ * @returns `true` si l'utilisateur est participant validé de l'événement parent, `false` sinon.
1055
+ *
1056
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1057
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1058
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1059
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1060
+ * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "events".
1061
+ *
1062
+ * @example
1063
+ * // Vérifier les participants d'un événement
1064
+ * const event = await me.event({ slug: "myEvent" });
1065
+ * const attendees = await event.getAttendees();
1066
+ *
1067
+ * for (const attendee of attendees.results) {
1068
+ * if (attendee.getEntityType() === "citoyens" && attendee.isAttendee()) {
1069
+ * console.log(`${attendee.data.name} participe à l'événement`);
1070
+ * }
1071
+ * }
1072
+ */
1073
+ override isAttendee(): boolean {
1074
+ this._validateRoleCheckPreconditions("isAttendee", ["events"]);
1075
+ const parentLink = this._getParentLinkForUser();
1076
+ return this._validateUserLink(parentLink);
1077
+ }
1078
+
1079
+ /**
1080
+ * Vérifie si l'utilisateur a une invitation en attente d'acceptation pour l'entité parente.
1081
+ *
1082
+ * Cette méthode vérifie si l'utilisateur a été invité à rejoindre l'organisation, le projet
1083
+ * ou l'événement parent, mais n'a pas encore accepté l'invitation (`isInviting: true`).
1084
+ *
1085
+ * @returns {boolean} `true` si l'utilisateur a une invitation en attente, `false` sinon
1086
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1087
+ *
1088
+ * @example
1089
+ * ```typescript
1090
+ * const org = await me.organization({ slug: "myOrg" });
1091
+ * const members = await org.getMembers();
1092
+ * const invitedUser = members.results.find(m => m.isInviting());
1093
+ * console.log("Invitation en attente:", invitedUser?.data.name);
1094
+ * ```
1095
+ */
1096
+ isInviting(): boolean {
1097
+ this._validateRoleCheckPreconditions("isInviting", ["organizations", "projects", "events"]);
1098
+ const parentLink = this._getParentLinkForUser();
1099
+ if (!parentLink) return false;
1100
+ return parentLink?.isInviting === true;
1101
+ }
1102
+
1103
+ /**
1104
+ * Vérifie si l'utilisateur a une invitation en attente avec des droits d'admin.
1105
+ *
1106
+ * Cette méthode vérifie si l'utilisateur a été invité à rejoindre l'organisation ou le projet
1107
+ * parent en tant qu'administrateur, mais n'a pas encore accepté (`isInviting: true` et `isAdmin: true`).
1108
+ *
1109
+ * @returns {boolean} `true` si l'utilisateur a une invitation admin en attente, `false` sinon
1110
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1111
+ *
1112
+ * @example
1113
+ * ```typescript
1114
+ * const org = await me.organization({ slug: "myOrg" });
1115
+ * const members = await org.getMembers();
1116
+ * const invitedAdmin = members.results.find(m => m.isInvitingAdmin());
1117
+ * console.log("Invitation admin en attente:", invitedAdmin?.data.name);
1118
+ * ```
1119
+ */
1120
+ isInvitingAdmin(): boolean {
1121
+ this._validateRoleCheckPreconditions("isInvitingAdmin", ["organizations", "projects"]);
1122
+ const parentLink = this._getParentLinkForUser();
1123
+ if (!parentLink) return false;
1124
+ return parentLink?.isAdminInviting === true && parentLink?.isAdmin === true;
1125
+ }
1126
+
1127
+ /**
1128
+ * Vérifie si l'utilisateur a une demande de promotion admin en attente de validation.
1129
+ *
1130
+ * Cette méthode vérifie si l'utilisateur a demandé ou a été proposé pour devenir administrateur
1131
+ * de l'organisation ou du projet parent, mais la demande n'a pas encore été validée (`isAdminPending: true`).
1132
+ *
1133
+ * @returns {boolean} `true` si l'utilisateur a une demande admin en attente, `false` sinon
1134
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1135
+ *
1136
+ * @example
1137
+ * ```typescript
1138
+ * const org = await me.organization({ slug: "myOrg" });
1139
+ * const members = await org.getMembers();
1140
+ * const pendingAdmin = members.results.find(m => m.isAdminPending());
1141
+ * if (pendingAdmin) {
1142
+ * await pendingAdmin.validateAdminRequest();
1143
+ * }
1144
+ * ```
1145
+ */
1146
+ isAdminPending(): boolean {
1147
+ this._validateRoleCheckPreconditions("isAdminPending", ["organizations", "projects"]);
1148
+ const parentLink = this._getParentLinkForUser();
1149
+ return parentLink?.isAdmin === true && parentLink?.isAdminPending === true;
1150
+ }
1151
+
1152
+ /**
1153
+ * Vérifie si l'utilisateur a une demande d'adhésion en attente de validation.
1154
+ *
1155
+ * Cette méthode vérifie si l'utilisateur a demandé à rejoindre l'organisation, le projet
1156
+ * ou l'événement parent, mais la demande n'a pas encore été validée par un administrateur (`toBeValidated: true`).
1157
+ *
1158
+ * @returns {boolean} `true` si l'utilisateur a une demande en attente, `false` sinon
1159
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1160
+ *
1161
+ * @example
1162
+ * ```typescript
1163
+ * const org = await me.organization({ slug: "myOrg" });
1164
+ * const members = await org.getMembers();
1165
+ * const pendingUser = members.results.find(m => m.isToBeValidated());
1166
+ * if (pendingUser) {
1167
+ * await pendingUser.validateMemberRequest();
1168
+ * }
1169
+ * ```
1170
+ */
1171
+ isToBeValidated(): boolean {
1172
+ this._validateRoleCheckPreconditions("isToBeValidated", ["organizations", "projects", "events"]);
1173
+ const parentLink = this._getParentLinkForUser();
1174
+ return parentLink?.toBeValidated === true;
1175
+ }
1176
+
1177
+
1178
+ // ────────────────────────────────
1179
+ // Actions d'admin sur l'utilisateur par rapport à l'entité parente
1180
+ // ────────────────────────────────
1181
+
1182
+ /**
1183
+ * Envoie une demande pour rejoindre l'entité parente.
1184
+ *
1185
+ * Cette méthode permet à un admin de créer une demande de connexion pour un utilisateur
1186
+ * vers l'organisation ou le projet parent. Si l'utilisateur n'a pas encore de lien,
1187
+ * une demande de connexion est créée.
1188
+ *
1189
+ * @returns {Promise<unknown>} La réponse de l'API après création de la demande
1190
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1191
+ * @throws {ApiError} Si l'utilisateur est déjà en attente de validation
1192
+ * @throws {ApiError} Si l'utilisateur est déjà connecté à l'entité
1193
+ *
1194
+ * @example
1195
+ * ```typescript
1196
+ * // Un admin récupère les membres et envoie une invitation
1197
+ * const org = await me.organization({ slug: "myOrg" });
1198
+ * const users = await org.getMembers();
1199
+ * const user = users.results[0];
1200
+ * await user.sendRequestToJoinParent();
1201
+ * ```
1202
+ */
1203
+ async sendRequestToJoinParent(): Promise<unknown> {
1204
+ this._validateRoleCheckPreconditions("sendRequestToJoinParent", ["organizations", "projects"]);
1205
+
1206
+ const { connectTypeConnect } = this.parent!._getLinkMeta();
1207
+
1208
+ const parentLink = this._getParentLinkForUser();
1209
+
1210
+ // Cas : aucun lien → on demande à se connecter
1211
+ if (!parentLink) {
1212
+
1213
+ const t = this.parent!.getEntityType();
1214
+
1215
+ // 2) Narrow de type pour TypeScript
1216
+ const parentType = t as ConnectData["parentType"];
1217
+
1218
+ const data: ConnectData = {
1219
+ childId: this.id!,
1220
+ childType: "citoyens",
1221
+ parentType,
1222
+ parentId: this.parent!.id!,
1223
+ connectType: connectTypeConnect
1224
+ };
1225
+
1226
+ const retour = await this.callIsConnected(() => this.endpointApi.connect(data));
1227
+ // Refresh l'utilisateur membre et l'entité parente
1228
+ await this.refresh();
1229
+ if (this.parent) {
1230
+ await this.parent.refresh();
1231
+ }
1232
+ return retour;
1233
+ }
1234
+
1235
+ // Cas : déjà en attente
1236
+ if (parentLink.toBeValidated) {
1237
+ throw new ApiError("Vous êtes déjà en attente de validation.", 400);
1238
+ }
1239
+
1240
+ // Cas par défaut : rien à faire
1241
+ throw new ApiError("Vous êtes déjà connecté à cette entité.", 400);
1242
+ }
1243
+
1244
+ /**
1245
+ * Valide une demande de membre en attente de validation.
1246
+ *
1247
+ * Cette méthode permet à un admin de valider une demande d'adhésion d'un utilisateur
1248
+ * qui est en attente (`toBeValidated: true`). Après validation, l'utilisateur devient
1249
+ * membre actif de l'entité parente.
1250
+ *
1251
+ * @returns {Promise<unknown>} La réponse de l'API après validation
1252
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1253
+ * @throws {ApiError} Si l'utilisateur n'a pas de demande en attente
1254
+ *
1255
+ * @example
1256
+ * ```typescript
1257
+ * // Un admin valide une demande en attente
1258
+ * const org = await me.organization({ slug: "myOrg" });
1259
+ * const members = await org.getMembers();
1260
+ * const pendingUser = members.results.find(u => u.serverData.links?.memberOf?.[org.id]?.toBeValidated);
1261
+ * await pendingUser.validateMemberRequest();
1262
+ * ```
1263
+ */
1264
+ async validateMemberRequest(): Promise<unknown> {
1265
+ this._validateRoleCheckPreconditions("validateMemberRequest", ["organizations", "projects"]);
1266
+
1267
+ const parentLink = this._getParentLinkForUser();
1268
+
1269
+ if (!parentLink?.toBeValidated) {
1270
+ throw new ApiError("Cet utilisateur n'a pas de demande en attente de validation.", 400);
1271
+ }
1272
+
1273
+ const t = this.parent!.getEntityType();
1274
+
1275
+ const parentType = t as LinkValidateData["parentType"];
1276
+
1277
+ const data: LinkValidateData = {
1278
+ childId: this.id!,
1279
+ childType: "citoyens",
1280
+ parentType,
1281
+ parentId: this.parent!.id!,
1282
+ linkOption: "toBeValidated"
1283
+ };
1284
+
1285
+ const retour = await this.callIsConnected(() => this.endpointApi.linkValidate(data));
1286
+ // Refresh l'utilisateur membre et l'entité parente
1287
+ await this.refresh();
1288
+ if (this.parent) {
1289
+ await this.parent.refresh();
1290
+ }
1291
+ return retour;
1292
+ }
1293
+
1294
+ /**
1295
+ * Valide une invitation en attente d'acceptation.
1296
+ *
1297
+ * Cette méthode permet à un admin de valider une invitation envoyée à un utilisateur
1298
+ * qui est en attente (`isInviting: true`). Après validation, l'utilisateur devient
1299
+ * membre actif de l'entité parente.
1300
+ *
1301
+ * @returns {Promise<unknown>} La réponse de l'API après validation
1302
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1303
+ * @throws {ApiError} Si l'utilisateur n'a pas d'invitation en attente
1304
+ *
1305
+ * @example
1306
+ * ```typescript
1307
+ * // Un admin valide une invitation en attente
1308
+ * const org = await me.organization({ slug: "myOrg" });
1309
+ * const members = await org.getMembers();
1310
+ * const invitedUser = members.results.find(u => u.serverData.links?.memberOf?.[org.id]?.isInviting);
1311
+ * await invitedUser.validateInvitation();
1312
+ * ```
1313
+ */
1314
+ // async validateInvitation(): Promise<unknown> {
1315
+ // this._validateRoleCheckPreconditions("validateInvitation", ["organizations", "projects"]);
1316
+
1317
+ // const parentLink = this._getParentLinkForUser();
1318
+
1319
+ // if (!parentLink?.isInviting) {
1320
+ // throw new ApiError("Cet utilisateur n'a pas d'invitation en attente.", 400);
1321
+ // }
1322
+
1323
+ // const t = this.parent!.getEntityType();
1324
+
1325
+ // const parentType = t as LinkValidateData["parentType"];
1326
+
1327
+ // const data: LinkValidateData = {
1328
+ // childId: this.id!,
1329
+ // childType: "citoyens",
1330
+ // parentType,
1331
+ // parentId: this.parent!.id!,
1332
+ // linkOption: "isInviting"
1333
+ // };
1334
+
1335
+ // const retour = await this.callIsConnected(() => this.endpointApi.linkValidate(data));
1336
+ // // Refresh l'utilisateur membre et l'entité parente
1337
+ // await this.refresh();
1338
+ // if (this.parent) {
1339
+ // await this.parent.refresh();
1340
+ // }
1341
+ // return retour;
1342
+ // }
1343
+
1344
+ /**
1345
+ * Valide une demande d'admin en attente.
1346
+ *
1347
+ * Cette méthode permet à un admin de valider une demande de promotion au statut d'admin
1348
+ * pour un utilisateur qui est en attente (`isAdminPending: true`). Après validation,
1349
+ * l'utilisateur devient admin de l'entité parente.
1350
+ *
1351
+ * @returns {Promise<unknown>} La réponse de l'API après validation
1352
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1353
+ * @throws {ApiError} Si l'utilisateur n'a pas de demande d'admin en attente
1354
+ *
1355
+ * @example
1356
+ * ```typescript
1357
+ * // Un admin valide une demande de promotion à admin
1358
+ * const org = await me.organization({ slug: "myOrg" });
1359
+ * const members = await org.getMembers();
1360
+ * const pendingAdmin = members.results.find(u => u.serverData.links?.memberOf?.[org.id]?.isAdminPending);
1361
+ * await pendingAdmin.validateAdminRequest();
1362
+ * ```
1363
+ */
1364
+ async validateAdminRequest(): Promise<unknown> {
1365
+ this._validateRoleCheckPreconditions("validateAdminRequest", ["organizations", "projects"]);
1366
+
1367
+ const parentLink = this._getParentLinkForUser();
1368
+
1369
+ if (!parentLink?.isAdminPending) {
1370
+ throw new ApiError("Cet utilisateur n'a pas de demande d'admin en attente.", 400);
1371
+ }
1372
+
1373
+ const t = this.parent!.getEntityType();
1374
+
1375
+ const parentType = t as LinkValidateData["parentType"];
1376
+
1377
+ const data: LinkValidateData = {
1378
+ childId: this.id!,
1379
+ childType: "citoyens",
1380
+ parentType,
1381
+ parentId: this.parent!.id!,
1382
+ linkOption: "isAdminPending"
1383
+ };
1384
+
1385
+ const retour = await this.callIsConnected(() => this.endpointApi.linkValidate(data));
1386
+ // Refresh l'utilisateur membre et l'entité parente
1387
+ await this.refresh();
1388
+ if (this.parent) {
1389
+ await this.parent.refresh();
1390
+ }
1391
+ return retour;
1392
+ }
1393
+
1394
+ /**
1395
+ * Retire un utilisateur de l'entité parente.
1396
+ *
1397
+ * Cette méthode permet à un admin de déconnecter un membre de l'organisation ou du projet parent.
1398
+ * L'utilisateur perd tous ses liens avec l'entité (membre, admin, etc.).
1399
+ *
1400
+ * @returns {Promise<unknown>} La réponse de l'API après déconnexion
1401
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1402
+ * @throws {ApiError} Si l'utilisateur n'est pas membre de l'entité
1403
+ *
1404
+ * @example
1405
+ * ```typescript
1406
+ * // Un admin retire un membre
1407
+ * const org = await me.organization({ slug: "myOrg" });
1408
+ * const members = await org.getMembers();
1409
+ * const userToRemove = members.results[0];
1410
+ * await userToRemove.removeFromParent();
1411
+ * ```
1412
+ */
1413
+ async removeFromParent(): Promise<unknown> {
1414
+ this._validateRoleCheckPreconditions("removeFromParent", ["organizations", "projects"]);
1415
+
1416
+ const parentLink = this._getParentLinkForUser();
1417
+
1418
+ if (!parentLink) {
1419
+ throw new ApiError("Cet utilisateur n'est pas connecté à cette entité.", 400);
1420
+ }
1421
+
1422
+ const t = this.parent!.getEntityType();
1423
+
1424
+ const parentType = t as DisconnectData["parentType"];
1425
+
1426
+ const { connectTypeDisconnect } = this.parent!._getLinkMeta();
1427
+
1428
+ const data: DisconnectData = {
1429
+ childId: this.id!,
1430
+ childType: "citoyens",
1431
+ parentType,
1432
+ parentId: this.parent!.id!,
1433
+ connectType: connectTypeDisconnect
1434
+ };
1435
+
1436
+ const retour = await this.callIsConnected(() => this.endpointApi.disconnect(data));
1437
+ // Refresh l'utilisateur membre et l'entité parente
1438
+ await this.refresh();
1439
+ if (this.parent) {
1440
+ await this.parent.refresh();
1441
+ }
1442
+ return retour;
1443
+ }
1444
+
1445
+ /**
1446
+ * Promeut un utilisateur au statut d'admin de l'entité parente.
1447
+ *
1448
+ * Cette méthode permet à un admin de promouvoir un membre ordinaire au statut d'admin
1449
+ * de l'organisation ou du projet parent.
1450
+ *
1451
+ * @returns {Promise<unknown>} La réponse de l'API après promotion
1452
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1453
+ * @throws {ApiError} Si l'utilisateur n'est pas membre de l'entité
1454
+ * @throws {ApiError} Si l'utilisateur est déjà admin
1455
+ *
1456
+ * @example
1457
+ * ```typescript
1458
+ * // Un admin promeut un membre au statut d'admin
1459
+ * const org = await me.organization({ slug: "myOrg" });
1460
+ * const members = await org.getMembers();
1461
+ * const userToPromote = members.results.find(u => !u.isAdmin());
1462
+ * await userToPromote.promoteToAdmin();
1463
+ * ```
1464
+ */
1465
+ async promoteToAdmin(): Promise<unknown> {
1466
+ this._validateRoleCheckPreconditions("promoteToAdmin", ["organizations", "projects"]);
1467
+
1468
+ const parentLink = this._getParentLinkForUser();
1469
+
1470
+ if (!parentLink) {
1471
+ throw new ApiError("Cet utilisateur n'est pas membre de cette entité.", 400);
1472
+ }
1473
+
1474
+ if (parentLink.isAdmin && !parentLink.isAdminPending) {
1475
+ throw new ApiError("Cet utilisateur est déjà admin.", 400);
1476
+ }
1477
+
1478
+ const t = this.parent!.getEntityType();
1479
+
1480
+ const parentType = t as ConnectData["parentType"];
1481
+
1482
+ const data: ConnectData = {
1483
+ childId: this.id!,
1484
+ childType: "citoyens",
1485
+ parentType,
1486
+ parentId: this.parent!.id!,
1487
+ connectType: "admin"
1488
+ };
1489
+
1490
+ const retour = await this.callIsConnected(() => this.endpointApi.connect(data));
1491
+ // Refresh l'utilisateur membre et l'entité parente
1492
+ await this.refresh();
1493
+ if (this.parent) {
1494
+ await this.parent.refresh();
1495
+ }
1496
+ return retour;
1497
+ }
1498
+
1499
+ /**
1500
+ * Rétrograde un admin au statut de membre ordinaire.
1501
+ *
1502
+ * Cette méthode permet à un admin de rétrograder un autre admin au statut de membre
1503
+ * ordinaire de l'organisation ou du projet parent. L'utilisateur perd ses privilèges d'admin
1504
+ * mais reste membre de l'entité.
1505
+ *
1506
+ * @returns {Promise<unknown>} La réponse de l'API après rétrogradation
1507
+ * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1508
+ * @throws {ApiError} Si l'utilisateur n'est pas admin de l'entité
1509
+ *
1510
+ * @example
1511
+ * ```typescript
1512
+ * // Un admin rétrograde un autre admin
1513
+ * const org = await me.organization({ slug: "myOrg" });
1514
+ * const admins = await org.getMembers({}, { isAdmin: true });
1515
+ * const adminToDemote = admins.results.find(u => u.isAdmin());
1516
+ * await adminToDemote.demoteFromAdmin();
1517
+ * ```
1518
+ */
1519
+ async demoteFromAdmin(): Promise<unknown> {
1520
+ this._validateRoleCheckPreconditions("demoteFromAdmin", ["organizations", "projects"]);
1521
+
1522
+ const parentLink = this._getParentLinkForUser();
1523
+
1524
+ if (!parentLink?.isAdmin || parentLink.isAdminPending) {
1525
+ throw new ApiError("Cet utilisateur n'est pas admin de cette entité.", 400);
1526
+ }
1527
+
1528
+ const t = this.parent!.getEntityType();
1529
+
1530
+ const { connectTypeDisconnect } = this.parent!._getLinkMeta();
1531
+
1532
+ const parentType = t as DemoteAdminData["parentType"];
1533
+
1534
+ // Pour rétrograder un admin, on déconnecte le lien "admin"
1535
+ // L'utilisateur garde son lien "member"
1536
+ const data: DemoteAdminData = {
1537
+ childId: this.id!,
1538
+ childType: "citoyens",
1539
+ parentType,
1540
+ parentId: this.parent!.id!,
1541
+ connect: connectTypeDisconnect as DemoteAdminData["connect"],
1542
+ isAdmin: false
1543
+ };
1544
+
1545
+ const retour = await this.callIsConnected(() => this.endpointApi.demoteAdmin(data));
1546
+ // Refresh l'utilisateur membre et l'entité parente
1547
+ await this.refresh();
1548
+ if (this.parent) {
1549
+ await this.parent.refresh();
1550
+ }
1551
+ return retour;
1552
+ }
1553
+
891
1554
  }
892
1555
 
893
1556
  // Incorporation des mixins dans User
@@ -18,6 +18,7 @@ export interface LinkRef<TType extends string = string> {
18
18
  isAdmin?: boolean;
19
19
  isAdminPending?: boolean;
20
20
  isInviting?: boolean;
21
+ isAdminInviting?: boolean;
21
22
  invitorId?: string;
22
23
  invitorName?: string;
23
24
  toBeValidated?: boolean;