@communecter/cocolight-api-client 1.0.81 → 1.0.83

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.81",
3
+ "version": "1.0.83",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3209,6 +3209,63 @@ export class BaseEntity<TServerData = any> {
3209
3209
  return arrayObjet as Record<string, any>;
3210
3210
  }
3211
3211
 
3212
+
3213
+ /**
3214
+ * Recherche des membres (utilisateurs ou organisations) liés à l'entité courante.
3215
+ *
3216
+ * Cette méthode effectue une recherche par autocomplétion parmi les membres de l'entité
3217
+ * (organisation, projet, événement). Les résultats sont filtrés selon le mode de recherche
3218
+ * et retournés sous forme d'entités User ou Organization liées à l'entité parente.
3219
+ *
3220
+ * @param {SearchMemberAutocompleteData} data - Paramètres de recherche
3221
+ * @param {string} data.search - Terme recherché (nom, slug, etc.)
3222
+ * @param {"personOnly" | "organizationOnly" | "mixte"} data.searchMode - Mode de recherche :
3223
+ * - `"personOnly"` : recherche uniquement parmi les utilisateurs
3224
+ * - `"organizationOnly"` : recherche uniquement parmi les organisations
3225
+ * - `"mixte"` : recherche parmi les utilisateurs et organisations
3226
+ * @returns {Promise<(User | Organization)[]>} Liste des entités correspondantes, liées à l'entité parente
3227
+ * @throws {ApiError} Si le type d'entité n'est pas supporté (badges, news, poi, comments)
3228
+ *
3229
+ * @example
3230
+ * ```typescript
3231
+ * // Rechercher des utilisateurs dans une organisation
3232
+ * const org = await me.organization({ slug: "myOrg" });
3233
+ * const users = await org.searchMembers({ search: "jean", searchMode: "personOnly" });
3234
+ * users.forEach(user => console.log(user.data.name));
3235
+ *
3236
+ * // Rechercher des organisations membres d'un projet
3237
+ * const project = await me.project({ slug: "myProject" });
3238
+ * const orgs = await project.searchMembers({ search: "asso", searchMode: "organizationOnly" });
3239
+ * ```
3240
+ *
3241
+ * @remarks
3242
+ * Les entités retournées sont automatiquement liées à l'entité parente via `_linkEntities`,
3243
+ * ce qui permet d'utiliser les méthodes comme `sendRequestToJoinParent()`, `isAdminPending()`, etc.
3244
+ */
3245
+ async searchMembers(data: SearchMemberAutocompleteData): Promise<(User | Organization)[]> {
3246
+ // TODO: j'ai l'impression que searchMode : "organizationOnly" ne renvois pas que des organisations
3247
+ const result = await this.endpointApi.searchMemberAutocomplete(data);
3248
+ if (!result || !Array.isArray(result)) {
3249
+ return [];
3250
+ }
3251
+
3252
+ const t = this.getEntityType();
3253
+ if (t === "badges" || t === "news" || t === "poi" || t === "comments") {
3254
+ throw new ApiError(`Le type d'entité "${t}" n'est pas supporté par searchMemberAutocomplete.`, 400);
3255
+ }
3256
+
3257
+ result.forEach(item => {
3258
+ for (const key of Object.keys(item)) {
3259
+ if (!["id", "name", "slug", "profilThumbImageUrl", "profilMarkerImageUrl", "type", "collection"].includes(key)) {
3260
+ delete item[key];
3261
+ }
3262
+ }
3263
+ });
3264
+
3265
+ const rawList = this._linkEntities(result);
3266
+ return rawList;
3267
+ }
3268
+
3212
3269
  /**
3213
3270
  * Soumet une demande pour rejoindre l'entité courante (ex. organisation, projet, événement...).
3214
3271
  * Si une invitation est en attente, elle est automatiquement acceptée.
@@ -4088,30 +4145,6 @@ export class BaseEntity<TServerData = any> {
4088
4145
  return paginator.next() as Promise<PaginatorPage<any>>;
4089
4146
  }
4090
4147
 
4091
- async searchMembers(data: SearchMemberAutocompleteData) {
4092
- // TODO: j'ai l'impression que searchMode : "organizationOnly" ne renvois pas que des organisations
4093
- const result = await this.endpointApi.searchMemberAutocomplete(data);
4094
- if (!result || !Array.isArray(result)) {
4095
- return [];
4096
- }
4097
-
4098
- const t = this.getEntityType();
4099
- if (t === "badges" || t === "news" || t === "poi" || t === "comments") {
4100
- throw new ApiError(`Le type d'entité "${t}" n'est pas supporté par searchMemberAutocomplete.`, 400);
4101
- }
4102
-
4103
- result.forEach(item => {
4104
- for (const key of Object.keys(item)) {
4105
- if (!["id", "name", "slug", "profilThumbImageUrl", "profilMarkerImageUrl", "type", "collection"].includes(key)) {
4106
- delete item[key];
4107
- }
4108
- }
4109
- });
4110
-
4111
- const rawList = this._linkEntities(result);
4112
- return rawList;
4113
- }
4114
-
4115
4148
  }
4116
4149
 
4117
4150
  export default BaseEntity;
package/src/api/User.ts CHANGED
@@ -24,6 +24,18 @@ import type { EntityTypes } from "@/types/entities.js";
24
24
  type ApiClient = import("../ApiClient.js").default;
25
25
  type UserItemNormalized = import("./serverDataType/User.js").UserItemNormalized;
26
26
 
27
+ /**
28
+ * Interface représentant le lien entre un utilisateur et une entité parente.
29
+ * Utilisée pour typer les données de relation (membre, admin, invitations, etc.)
30
+ */
31
+ interface ParentLink {
32
+ isAdmin?: boolean;
33
+ isAdminPending?: boolean;
34
+ isInviting?: boolean;
35
+ isAdminInviting?: boolean;
36
+ toBeValidated?: boolean;
37
+ }
38
+
27
39
  export class User extends BaseEntity<UserItemNormalized> {
28
40
 
29
41
  static override entityType = "citoyens" as const;
@@ -890,37 +902,88 @@ export class User extends BaseEntity<UserItemNormalized> {
890
902
  return super.entityBySlug(slug);
891
903
  }
892
904
 
905
+ // ────────────────────────────────
906
+ // Actions sur l'utilisateur par rapport à l'entité parente
907
+ // ────────────────────────────────
908
+
893
909
  /**
894
- * Valide les préconditions communes pour les méthodes de vérification de rôle.
910
+ * Valide les préconditions pour les méthodes nécessitant d'être membre.
895
911
  * @private
896
912
  * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
897
913
  * @param expectedTypes - Types d'entité parent autorisés.
898
914
  * @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
915
  * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
901
916
  * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
917
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas membre de l'entité parente.
902
918
  * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
903
919
  */
904
- private _validateRoleCheckPreconditions(methodName: string, expectedTypes: string[]): void {
920
+ private _validateMemberPreconditions(methodName: string, expectedTypes: string[]): void {
905
921
  if (!this.userId) {
906
922
  throw new ApiError(`Vous devez être connecté pour ${methodName}.`, 401);
907
923
  }
908
924
 
909
- if(!this.parent?.isAdmin()){
910
- throw new ApiError("Vous devez être administrateur pour effectuer cette action.", 401);
911
- }
912
-
913
925
  if (!this.id) {
914
926
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
915
927
  }
916
928
 
917
- if(!this.parent?.id){
929
+ if (!this.parent?.id) {
918
930
  throw new ApiError("L'entité parente n'est pas enregistrée.", 404);
919
931
  }
920
932
 
921
933
  if (!expectedTypes.includes(this.parent.getEntityType())) {
922
934
  throw new ApiError(`L'entité doit être de type : ${expectedTypes.join(", ")}, reçu : ${this.parent.getEntityType()}`, 400);
923
935
  }
936
+
937
+ // Vérifier que l'utilisateur connecté a le bon rôle selon le type d'entité
938
+ // Pour les events, tout utilisateur connecté peut agir (pas besoin d'être participant)
939
+ const parentType = this.parent.getEntityType();
940
+
941
+ if (parentType === "events") {
942
+ // Pour les événements, être connecté suffit (déjà vérifié ci-dessus)
943
+ return;
944
+ }
945
+
946
+ let hasRole = false;
947
+
948
+ switch (parentType) {
949
+ case "organizations":
950
+ hasRole = this.parent.isMember();
951
+ break;
952
+ case "projects":
953
+ hasRole = this.parent.isContributor();
954
+ break;
955
+ default:
956
+ hasRole = false;
957
+ }
958
+
959
+ if (!hasRole) {
960
+ const roleNames: Record<string, string> = {
961
+ organizations: "membre",
962
+ projects: "contributeur"
963
+ };
964
+ const roleName = roleNames[parentType] || "membre";
965
+ throw new ApiError(`Vous devez être ${roleName} pour effectuer cette action.`, 401);
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Valide les préconditions pour les méthodes nécessitant d'être administrateur.
971
+ * @private
972
+ * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
973
+ * @param expectedTypes - Types d'entité parent autorisés.
974
+ * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
975
+ * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
976
+ * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
977
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas membre de l'entité parente.
978
+ * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
979
+ * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
980
+ */
981
+ private _validateAdminPreconditions(methodName: string, expectedTypes: string[]): void {
982
+ this._validateMemberPreconditions(methodName, expectedTypes);
983
+
984
+ if (!this.parent?.isAdmin()) {
985
+ throw new ApiError("Vous devez être administrateur pour effectuer cette action.", 401);
986
+ }
924
987
  }
925
988
 
926
989
 
@@ -930,13 +993,24 @@ export class User extends BaseEntity<UserItemNormalized> {
930
993
  * @returns Le lien parent de l'utilisateur ou `null` s'il n'existe pas.
931
994
  * @private
932
995
  */
933
- private _getParentLinkForUser(): any {
996
+ private _getParentLinkForUser(): ParentLink | null {
934
997
  const { connectTypeDisconnect } = this.parent!._getLinkMeta();
935
998
  const userId = this!.id;
936
999
  if (!userId) return null;
937
1000
  return this.parent?.serverData?.links?.[connectTypeDisconnect]?.[userId] || null;
938
1001
  }
939
1002
 
1003
+ /**
1004
+ * Rafraîchit l'utilisateur et l'entité parente après une action.
1005
+ * @private
1006
+ */
1007
+ private async _refreshWithParent(): Promise<void> {
1008
+ await this.refresh();
1009
+ if (this.parent) {
1010
+ await this.parent.refresh();
1011
+ }
1012
+ }
1013
+
940
1014
  /**
941
1015
  * Vérifie si l'utilisateur est administrateur de l'entité parente.
942
1016
  *
@@ -978,7 +1052,7 @@ export class User extends BaseEntity<UserItemNormalized> {
978
1052
  * }
979
1053
  */
980
1054
  override isAdmin(): boolean {
981
- this._validateRoleCheckPreconditions("isAdmin", ["organizations", "projects"]);
1055
+ this._validateMemberPreconditions("isAdmin", ["organizations", "projects"]);
982
1056
  const parentLink = this._getParentLinkForUser();
983
1057
  return this._validateUserLink(parentLink) && parentLink?.isAdmin === true;
984
1058
  }
@@ -1009,7 +1083,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1009
1083
  * }
1010
1084
  */
1011
1085
  override isMember(): boolean {
1012
- this._validateRoleCheckPreconditions("isMember", ["organizations"]);
1086
+ this._validateMemberPreconditions("isMember", ["organizations"]);
1013
1087
  const parentLink = this._getParentLinkForUser();
1014
1088
  return this._validateUserLink(parentLink);
1015
1089
  }
@@ -1040,7 +1114,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1040
1114
  * }
1041
1115
  */
1042
1116
  override isContributor(): boolean {
1043
- this._validateRoleCheckPreconditions("isContributor", ["projects"]);
1117
+ this._validateMemberPreconditions("isContributor", ["projects"]);
1044
1118
  const parentLink = this._getParentLinkForUser();
1045
1119
  return this._validateUserLink(parentLink);
1046
1120
  }
@@ -1071,7 +1145,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1071
1145
  * }
1072
1146
  */
1073
1147
  override isAttendee(): boolean {
1074
- this._validateRoleCheckPreconditions("isAttendee", ["events"]);
1148
+ this._validateMemberPreconditions("isAttendee", ["events"]);
1075
1149
  const parentLink = this._getParentLinkForUser();
1076
1150
  return this._validateUserLink(parentLink);
1077
1151
  }
@@ -1094,7 +1168,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1094
1168
  * ```
1095
1169
  */
1096
1170
  isInviting(): boolean {
1097
- this._validateRoleCheckPreconditions("isInviting", ["organizations", "projects", "events"]);
1171
+ this._validateMemberPreconditions("isInviting", ["organizations", "projects", "events"]);
1098
1172
  const parentLink = this._getParentLinkForUser();
1099
1173
  if (!parentLink) return false;
1100
1174
  return parentLink?.isInviting === true;
@@ -1118,10 +1192,10 @@ export class User extends BaseEntity<UserItemNormalized> {
1118
1192
  * ```
1119
1193
  */
1120
1194
  isInvitingAdmin(): boolean {
1121
- this._validateRoleCheckPreconditions("isInvitingAdmin", ["organizations", "projects"]);
1195
+ this._validateMemberPreconditions("isInvitingAdmin", ["organizations", "projects"]);
1122
1196
  const parentLink = this._getParentLinkForUser();
1123
1197
  if (!parentLink) return false;
1124
- return parentLink?.isAdminInviting === true && parentLink?.isAdmin === true;
1198
+ return (parentLink?.isAdminInviting === true || parentLink?.isInviting === true) && parentLink?.isAdmin === true;
1125
1199
  }
1126
1200
 
1127
1201
  /**
@@ -1144,7 +1218,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1144
1218
  * ```
1145
1219
  */
1146
1220
  isAdminPending(): boolean {
1147
- this._validateRoleCheckPreconditions("isAdminPending", ["organizations", "projects"]);
1221
+ this._validateMemberPreconditions("isAdminPending", ["organizations", "projects"]);
1148
1222
  const parentLink = this._getParentLinkForUser();
1149
1223
  return parentLink?.isAdmin === true && parentLink?.isAdminPending === true;
1150
1224
  }
@@ -1169,16 +1243,11 @@ export class User extends BaseEntity<UserItemNormalized> {
1169
1243
  * ```
1170
1244
  */
1171
1245
  isToBeValidated(): boolean {
1172
- this._validateRoleCheckPreconditions("isToBeValidated", ["organizations", "projects", "events"]);
1246
+ this._validateMemberPreconditions("isToBeValidated", ["organizations", "projects", "events"]);
1173
1247
  const parentLink = this._getParentLinkForUser();
1174
1248
  return parentLink?.toBeValidated === true;
1175
1249
  }
1176
1250
 
1177
-
1178
- // ────────────────────────────────
1179
- // Actions d'admin sur l'utilisateur par rapport à l'entité parente
1180
- // ────────────────────────────────
1181
-
1182
1251
  /**
1183
1252
  * Envoie une demande pour rejoindre l'entité parente.
1184
1253
  *
@@ -1186,6 +1255,8 @@ export class User extends BaseEntity<UserItemNormalized> {
1186
1255
  * vers l'organisation ou le projet parent. Si l'utilisateur n'a pas encore de lien,
1187
1256
  * une demande de connexion est créée.
1188
1257
  *
1258
+ * @param {Object} [options] - Options de la méthode
1259
+ * @param {boolean} [options.admin=false] - Si true, utilise "admin" comme connectType au lieu du type par défaut (connectTypeConnect)
1189
1260
  * @returns {Promise<unknown>} La réponse de l'API après création de la demande
1190
1261
  * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1191
1262
  * @throws {ApiError} Si l'utilisateur est déjà en attente de validation
@@ -1193,15 +1264,25 @@ export class User extends BaseEntity<UserItemNormalized> {
1193
1264
  *
1194
1265
  * @example
1195
1266
  * ```typescript
1196
- * // Un admin récupère les membres et envoie une invitation
1267
+ * // Un admin récupère les membres et envoie une invitation (type par défaut)
1197
1268
  * const org = await me.organization({ slug: "myOrg" });
1198
1269
  * const users = await org.getMembers();
1199
1270
  * const user = users.results[0];
1200
1271
  * await user.sendRequestToJoinParent();
1272
+ *
1273
+ * // Avec le type admin explicite
1274
+ * await user.sendRequestToJoinParent({ admin: true });
1201
1275
  * ```
1202
1276
  */
1203
- async sendRequestToJoinParent(): Promise<unknown> {
1204
- this._validateRoleCheckPreconditions("sendRequestToJoinParent", ["organizations", "projects"]);
1277
+ async sendRequestToJoinParent({ admin }: { admin: boolean } = { admin: false }): Promise<unknown> {
1278
+ // Validation selon le type d'invitation : admin requis pour inviter en tant qu'admin
1279
+ if (admin) {
1280
+ // Invitation admin : uniquement orga/project, seul un admin peut le faire
1281
+ this._validateAdminPreconditions("sendRequestToJoinParent", ["organizations", "projects"]);
1282
+ } else {
1283
+ // Invitation simple : orga/project/event, membre/contributeur/connecté peut le faire
1284
+ this._validateMemberPreconditions("sendRequestToJoinParent", ["organizations", "projects", "events"]);
1285
+ }
1205
1286
 
1206
1287
  const { connectTypeConnect } = this.parent!._getLinkMeta();
1207
1288
 
@@ -1212,7 +1293,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1212
1293
 
1213
1294
  const t = this.parent!.getEntityType();
1214
1295
 
1215
- // 2) Narrow de type pour TypeScript
1296
+ // Narrow de type pour TypeScript
1216
1297
  const parentType = t as ConnectData["parentType"];
1217
1298
 
1218
1299
  const data: ConnectData = {
@@ -1220,21 +1301,17 @@ export class User extends BaseEntity<UserItemNormalized> {
1220
1301
  childType: "citoyens",
1221
1302
  parentType,
1222
1303
  parentId: this.parent!.id!,
1223
- connectType: connectTypeConnect
1304
+ connectType: admin ? "admin" : connectTypeConnect
1224
1305
  };
1225
1306
 
1226
1307
  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
- }
1308
+ await this._refreshWithParent();
1232
1309
  return retour;
1233
1310
  }
1234
1311
 
1235
- // Cas : déjà en attente
1236
- if (parentLink.toBeValidated) {
1237
- throw new ApiError("Vous êtes déjà en attente de validation.", 400);
1312
+ // Cas : déjà en attente d'acceptation
1313
+ if (parentLink.isInviting || parentLink?.isAdminInviting) {
1314
+ throw new ApiError("Une invitation est déjà en attente d'acceptation.", 400);
1238
1315
  }
1239
1316
 
1240
1317
  // Cas par défaut : rien à faire
@@ -1262,7 +1339,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1262
1339
  * ```
1263
1340
  */
1264
1341
  async validateMemberRequest(): Promise<unknown> {
1265
- this._validateRoleCheckPreconditions("validateMemberRequest", ["organizations", "projects"]);
1342
+ this._validateAdminPreconditions("validateMemberRequest", ["organizations", "projects"]);
1266
1343
 
1267
1344
  const parentLink = this._getParentLinkForUser();
1268
1345
 
@@ -1283,64 +1360,10 @@ export class User extends BaseEntity<UserItemNormalized> {
1283
1360
  };
1284
1361
 
1285
1362
  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
- }
1363
+ await this._refreshWithParent();
1291
1364
  return retour;
1292
1365
  }
1293
1366
 
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
1367
  /**
1345
1368
  * Valide une demande d'admin en attente.
1346
1369
  *
@@ -1362,7 +1385,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1362
1385
  * ```
1363
1386
  */
1364
1387
  async validateAdminRequest(): Promise<unknown> {
1365
- this._validateRoleCheckPreconditions("validateAdminRequest", ["organizations", "projects"]);
1388
+ this._validateAdminPreconditions("validateAdminRequest", ["organizations", "projects"]);
1366
1389
 
1367
1390
  const parentLink = this._getParentLinkForUser();
1368
1391
 
@@ -1383,11 +1406,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1383
1406
  };
1384
1407
 
1385
1408
  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
- }
1409
+ await this._refreshWithParent();
1391
1410
  return retour;
1392
1411
  }
1393
1412
 
@@ -1411,7 +1430,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1411
1430
  * ```
1412
1431
  */
1413
1432
  async removeFromParent(): Promise<unknown> {
1414
- this._validateRoleCheckPreconditions("removeFromParent", ["organizations", "projects"]);
1433
+ this._validateAdminPreconditions("removeFromParent", ["organizations", "projects"]);
1415
1434
 
1416
1435
  const parentLink = this._getParentLinkForUser();
1417
1436
 
@@ -1434,11 +1453,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1434
1453
  };
1435
1454
 
1436
1455
  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
- }
1456
+ await this._refreshWithParent();
1442
1457
  return retour;
1443
1458
  }
1444
1459
 
@@ -1463,7 +1478,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1463
1478
  * ```
1464
1479
  */
1465
1480
  async promoteToAdmin(): Promise<unknown> {
1466
- this._validateRoleCheckPreconditions("promoteToAdmin", ["organizations", "projects"]);
1481
+ this._validateAdminPreconditions("promoteToAdmin", ["organizations", "projects"]);
1467
1482
 
1468
1483
  const parentLink = this._getParentLinkForUser();
1469
1484
 
@@ -1471,7 +1486,11 @@ export class User extends BaseEntity<UserItemNormalized> {
1471
1486
  throw new ApiError("Cet utilisateur n'est pas membre de cette entité.", 400);
1472
1487
  }
1473
1488
 
1474
- if (parentLink.isAdmin && !parentLink.isAdminPending) {
1489
+ if (parentLink?.isAdmin && (parentLink?.isInviting || parentLink?.isAdminInviting)) {
1490
+ throw new ApiError("Cet utilisateur est déjà en cours d'invitation en tant qu'admin.", 400);
1491
+ }
1492
+
1493
+ if (parentLink?.isAdmin && !parentLink?.isInviting && !parentLink?.isAdminInviting) {
1475
1494
  throw new ApiError("Cet utilisateur est déjà admin.", 400);
1476
1495
  }
1477
1496
 
@@ -1488,11 +1507,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1488
1507
  };
1489
1508
 
1490
1509
  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
- }
1510
+ await this._refreshWithParent();
1496
1511
  return retour;
1497
1512
  }
1498
1513
 
@@ -1517,7 +1532,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1517
1532
  * ```
1518
1533
  */
1519
1534
  async demoteFromAdmin(): Promise<unknown> {
1520
- this._validateRoleCheckPreconditions("demoteFromAdmin", ["organizations", "projects"]);
1535
+ this._validateAdminPreconditions("demoteFromAdmin", ["organizations", "projects"]);
1521
1536
 
1522
1537
  const parentLink = this._getParentLinkForUser();
1523
1538
 
@@ -1543,11 +1558,7 @@ export class User extends BaseEntity<UserItemNormalized> {
1543
1558
  };
1544
1559
 
1545
1560
  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
- }
1561
+ await this._refreshWithParent();
1551
1562
  return retour;
1552
1563
  }
1553
1564
 
@@ -1081,6 +1081,39 @@ export declare class BaseEntity<TServerData = any> {
1081
1081
  docType?: "image" | "file";
1082
1082
  };
1083
1083
  }): Promise<Record<string, any>>;
1084
+ /**
1085
+ * Recherche des membres (utilisateurs ou organisations) liés à l'entité courante.
1086
+ *
1087
+ * Cette méthode effectue une recherche par autocomplétion parmi les membres de l'entité
1088
+ * (organisation, projet, événement). Les résultats sont filtrés selon le mode de recherche
1089
+ * et retournés sous forme d'entités User ou Organization liées à l'entité parente.
1090
+ *
1091
+ * @param {SearchMemberAutocompleteData} data - Paramètres de recherche
1092
+ * @param {string} data.search - Terme recherché (nom, slug, etc.)
1093
+ * @param {"personOnly" | "organizationOnly" | "mixte"} data.searchMode - Mode de recherche :
1094
+ * - `"personOnly"` : recherche uniquement parmi les utilisateurs
1095
+ * - `"organizationOnly"` : recherche uniquement parmi les organisations
1096
+ * - `"mixte"` : recherche parmi les utilisateurs et organisations
1097
+ * @returns {Promise<(User | Organization)[]>} Liste des entités correspondantes, liées à l'entité parente
1098
+ * @throws {ApiError} Si le type d'entité n'est pas supporté (badges, news, poi, comments)
1099
+ *
1100
+ * @example
1101
+ * ```typescript
1102
+ * // Rechercher des utilisateurs dans une organisation
1103
+ * const org = await me.organization({ slug: "myOrg" });
1104
+ * const users = await org.searchMembers({ search: "jean", searchMode: "personOnly" });
1105
+ * users.forEach(user => console.log(user.data.name));
1106
+ *
1107
+ * // Rechercher des organisations membres d'un projet
1108
+ * const project = await me.project({ slug: "myProject" });
1109
+ * const orgs = await project.searchMembers({ search: "asso", searchMode: "organizationOnly" });
1110
+ * ```
1111
+ *
1112
+ * @remarks
1113
+ * Les entités retournées sont automatiquement liées à l'entité parente via `_linkEntities`,
1114
+ * ce qui permet d'utiliser les méthodes comme `sendRequestToJoinParent()`, `isAdminPending()`, etc.
1115
+ */
1116
+ searchMembers(data: SearchMemberAutocompleteData): Promise<(User | Organization)[]>;
1084
1117
  /**
1085
1118
  * Soumet une demande pour rejoindre l'entité courante (ex. organisation, projet, événement...).
1086
1119
  * Si une invitation est en attente, elle est automatiquement acceptée.
@@ -1381,6 +1414,5 @@ export declare class BaseEntity<TServerData = any> {
1381
1414
  */
1382
1415
  costumEventRequestLoadContextTag(data?: Partial<Omit<CostumEventRequestLoadContextTagData, "pathParams">>): Promise<unknown>;
1383
1416
  coformAnswersSearch(data?: Partial<CoformAnswersSearchData>): Promise<PaginatorPage<any>>;
1384
- searchMembers(data: SearchMemberAutocompleteData): Promise<any[]>;
1385
1417
  }
1386
1418
  export default BaseEntity;