@communecter/cocolight-api-client 1.0.147 → 1.0.148

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.147",
3
+ "version": "1.0.148",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,6 +10,7 @@ import { isReactive, isSignal, reactive } from "../utils/reactive.js";
10
10
 
11
11
  import type {
12
12
  ConnectData,
13
+ DeleteElementData,
13
14
  DisconnectData,
14
15
  LinkValidateData,
15
16
  FollowData,
@@ -1175,6 +1176,8 @@ export class BaseEntity<TServerData = any> {
1175
1176
  * @throws {ApiClientError} - Si une erreur se produit lors de l'appel de l'API.
1176
1177
  */
1177
1178
  async callIsConnected(param: string | (() => Promise<any>), data: object = {}): Promise<unknown> {
1179
+ // Tombstone : une entité supprimée ne peut plus déclencher d'opération serveur.
1180
+ this._checkNotDeleted();
1178
1181
  if (!this.isConnected) {
1179
1182
  throw new ApiAuthenticationError("Vous devez être connecté pour faire cette action.", 401);
1180
1183
  }
@@ -1197,6 +1200,8 @@ export class BaseEntity<TServerData = any> {
1197
1200
  * @throws {ApiClientError} - Si une erreur se produit lors de l'appel de l'API.
1198
1201
  */
1199
1202
  async callIsMe(param: string | (() => Promise<any>), data: object = {}): Promise<unknown> {
1203
+ // Tombstone : une entité supprimée ne peut plus déclencher d'opération serveur.
1204
+ this._checkNotDeleted();
1200
1205
  if (!this.isMe) {
1201
1206
  throw new ApiAuthenticationError("Vous devez être vous-même pour faire cette action.", 403);
1202
1207
  }
@@ -1339,6 +1344,58 @@ export class BaseEntity<TServerData = any> {
1339
1344
  await this.callIsConnected(() => this.endpointApi.deleteDocumentById(payload));
1340
1345
  }
1341
1346
 
1347
+ /**
1348
+ * Vide les objets réactifs et marque l'entité comme supprimée (tombstone),
1349
+ * sans casser la réactivité. Pattern partagé par tous les `delete()`.
1350
+ *
1351
+ * Après appel : `serverData`/`draftData` vidés et `_isDeleted = true` → toute
1352
+ * opération serveur ultérieure lève le tombstone (cf. `_checkNotDeleted`).
1353
+ * @protected
1354
+ */
1355
+ protected _markDeletedLocally(): void {
1356
+ Object.keys(this._draftData).forEach(key => delete this._draftData[key]);
1357
+ Object.keys(this._serverData).forEach(key => delete (this._serverData as any)[key]);
1358
+ this._isDeleted = true;
1359
+ }
1360
+
1361
+ /**
1362
+ * Suppression générique d'un élément via `DELETE_ELEMENT`
1363
+ * (`/co2/element/delete/type/{type}/id/{id}`).
1364
+ *
1365
+ * **Garde client** : `isAuthorOrAdmin({ checkHierarchy })` — auteur (`creator`)
1366
+ * OU administrateur (lien direct, ou via la hiérarchie parent org/projet
1367
+ * résolue par `_isAdminViaHierarchy`). Le serveur reste la source de vérité.
1368
+ *
1369
+ * Après succès : `serverData`/`draftData` vidés + `_isDeleted = true`
1370
+ * (via `_markDeletedLocally`).
1371
+ *
1372
+ * @param type - Type d'élément backend (typiquement `this.getEntityType()`).
1373
+ * @param reason - Raison transmise au backend (audit).
1374
+ * @param options.checkHierarchy - Propage la vérif admin à la hiérarchie parent. Défaut `true`.
1375
+ * @throws {ApiError} 400 si l'entité n'a pas d'id.
1376
+ * @throws {ApiError} 403 si ni auteur ni admin (selon la garde).
1377
+ * @protected
1378
+ */
1379
+ protected async _deleteViaElement(
1380
+ type: string,
1381
+ reason: string,
1382
+ { checkHierarchy = true }: { checkHierarchy?: boolean } = {},
1383
+ ): Promise<void> {
1384
+ if (!this.id) {
1385
+ throw new ApiError(`Vous devez fournir un id pour supprimer ${this.constructor.name}.`, 400);
1386
+ }
1387
+ if (!this.isAuthorOrAdmin({ checkHierarchy })) {
1388
+ throw new ApiError(
1389
+ "Suppression refusée — vous devez être l'auteur ou administrateur "
1390
+ + "(ou administrateur du parent).",
1391
+ 403,
1392
+ );
1393
+ }
1394
+ const data: DeleteElementData = { reason, pathParams: { type, id: this.id } };
1395
+ await this.callIsConnected(() => this.endpointApi.deleteElement(data));
1396
+ this._markDeletedLocally();
1397
+ }
1398
+
1342
1399
  /**
1343
1400
  * Met à jour un champ unique (ou multi-fields via `updatePartial`) sur cette
1344
1401
  * entité via `UPDATE_PATH_VALUE`.
@@ -2,7 +2,7 @@
2
2
  import { ApiAuthenticationError } from "../error.js";
3
3
 
4
4
  import type ApiClient from "../ApiClient.js";
5
- import type { PersonRegisterData, AuthenticateUrlData, RefreshTokenUrlData, PasswordRecoveryData, ServerExchangeTokenData, ChangePasswordData, DeleteAccountData, UpdateSettingsData, UpdateBlockDescriptionData, UpdateBlockInfoData, UpdateBlockSocialData, UpdateBlockLocalityData, UpdateBlockSlugData, CheckData, ProfilImageData, ProfilBannerData, GetElementsAboutData, MulticonnectData, GetNewsData, GetNewsByIdData, AddNewsData, AddImageNewsData, AddFileNewsData, DeleteNewsData, UpdateNewsData, ShareNewsData, GetCommentsData, AddCommentsData, DeleteCommentsData, UpdateCommentsData, SearchTagsData, ShowVoteData, GlobalAutocompleteData, CityAutocompleteData, CityAutocompleteByCountryData, SuggestionInputData, GetProjectsNoAdminData, GetProjectsAdminData, GetPoisNoAdminData, GetPoisAdminData, GetOrganizationsNoAdminData, GetOrganizationsAdminData, GetUserEligiblePlacesData, GetMembersNoAdminData, GetMembersAdminData, GetFriendsAdminData, GetSubscriptionsData, GetSubscriptionsAdminData, GetSubscribersData, GetSubscribersAdminData, GetContributorsNoAdminData, GetContributorsAdminData, GetBadgesData, GetBadgesFiltersData, ConnectData, DisconnectData, GetElementsKeyData, GetFavorisData, DeleteFavorisData, AddFavorisData, AddOrganizationData, AddProjectData, AddPoiData, AddEventData, DeletePoiData, DeleteEventData, DeleteElementData, AddImageElementData, LinkValidateData, SearchMemberAutocompleteData, GetNotificationsData, GetNotificationsCountData, NotificationUpdateData, MarkNotificationAsReadData, ActivitypubSearchData, ActivitypubLinkData, ActivitypubGetCommunityData, GetBadgeData, AddBadgesData, AssignBadgesData, GetEventsData, ShareEventsData, InviteEventData, FollowData, GetCostumJsonData, GlobalAutocompleteCostumData, NavigatorGettlData, CostumEventRequestActorsData, CostumEventRequestSubeventsData, CostumEventRequestElementEventData, CostumEventRequestCategoriesData, CostumEventRequestDatesData, CostumEventRequestEventData, CostumEventRequestLinkTlToEventData, CostumEventRequestLoadContextTagData, GetGalleryData, GetAttendeesNoAdminData, GetAttendeesAdminData, CoformAnswersSearchData, CoformAnswersByIdData, GetCoformByIdData, CoformUploadAnswerFileData, CoformGetAnswerFilesData, GetCoformCatalogsData, SaveCoformAnswerData, GetCoformAnswerHistoryData, GetCoformMultievalDataData, GetCoformCommontableContributorsData, AddVoteData, AddReportAbuseData, UpdatePathValueData, DeleteDocumentByContextData, DeleteDocumentByIdData, DemoteAdminData, CostumFilterCoformData, CostumFilterCoformByPathData, GetCountriesData, SearchZonesData, CoformAnswersByFormsData, GenerateAnswerFromFormData, FundingEnvelopeData, CoremuOperationData, CostumProjectActionRequestNewData, CostumProjectActionRequestSetStatusData, CostumProjectActionRequestSetDateData, CostumProjectActionRequestSetContributorsData, CostumProjectActionRequestCancelData, CostumProjectActionRequestArchiveData, LinkDiscourseAccountData, UnlinkDiscourseAccountData, DiscourseProfileData, DiscourseCheckEmailData, DiscourseDismissLinkData, LinkMediawikiAccountData, UnlinkMediawikiAccountData, GetMediawikiContributionsData, AddClassifiedData, PersonActivateData } from "./EndpointApi.types.js";
5
+ import type { PersonRegisterData, AuthenticateUrlData, RefreshTokenUrlData, PasswordRecoveryData, ServerExchangeTokenData, ChangePasswordData, DeleteAccountData, UpdateSettingsData, UpdateBlockDescriptionData, UpdateBlockInfoData, UpdateBlockSocialData, UpdateBlockLocalityData, UpdateBlockSlugData, CheckData, ProfilImageData, ProfilBannerData, GetElementsAboutData, MulticonnectData, GetNewsData, GetNewsByIdData, AddNewsData, AddImageNewsData, AddFileNewsData, DeleteNewsData, UpdateNewsData, ShareNewsData, GetCommentsData, AddCommentsData, DeleteCommentsData, UpdateCommentsData, SearchTagsData, ShowVoteData, GlobalAutocompleteData, CityAutocompleteData, CityAutocompleteByCountryData, SuggestionInputData, GetProjectsNoAdminData, GetProjectsAdminData, GetPoisNoAdminData, GetPoisAdminData, GetOrganizationsNoAdminData, GetOrganizationsAdminData, GetUserEligiblePlacesData, GetMembersNoAdminData, GetMembersAdminData, GetFriendsAdminData, GetSubscriptionsData, GetSubscriptionsAdminData, GetSubscribersData, GetSubscribersAdminData, GetContributorsNoAdminData, GetContributorsAdminData, GetBadgesData, GetBadgesFiltersData, ConnectData, DisconnectData, GetElementsKeyData, GetFavorisData, DeleteFavorisData, AddFavorisData, AddOrganizationData, AddProjectData, AddPoiData, AddEventData, DeleteElementData, AddImageElementData, LinkValidateData, SearchMemberAutocompleteData, GetNotificationsData, GetNotificationsCountData, NotificationUpdateData, MarkNotificationAsReadData, ActivitypubSearchData, ActivitypubLinkData, ActivitypubGetCommunityData, GetBadgeData, AddBadgesData, AssignBadgesData, GetEventsData, ShareEventsData, InviteEventData, FollowData, GetCostumJsonData, GlobalAutocompleteCostumData, NavigatorGettlData, CostumEventRequestActorsData, CostumEventRequestSubeventsData, CostumEventRequestElementEventData, CostumEventRequestCategoriesData, CostumEventRequestDatesData, CostumEventRequestEventData, CostumEventRequestLinkTlToEventData, CostumEventRequestLoadContextTagData, GetGalleryData, GetAttendeesNoAdminData, GetAttendeesAdminData, CoformAnswersSearchData, CoformAnswersByIdData, GetCoformByIdData, CoformUploadAnswerFileData, CoformGetAnswerFilesData, GetCoformCatalogsData, SaveCoformAnswerData, GetCoformAnswerHistoryData, GetCoformMultievalDataData, GetCoformCommontableContributorsData, AddVoteData, AddReportAbuseData, UpdatePathValueData, DeleteDocumentByContextData, DeleteDocumentByIdData, DemoteAdminData, CostumFilterCoformData, CostumFilterCoformByPathData, GetCountriesData, SearchZonesData, CoformAnswersByFormsData, GenerateAnswerFromFormData, FundingEnvelopeData, CoremuOperationData, CostumProjectActionRequestNewData, CostumProjectActionRequestSetStatusData, CostumProjectActionRequestSetDateData, CostumProjectActionRequestSetContributorsData, CostumProjectActionRequestCancelData, CostumProjectActionRequestArchiveData, LinkDiscourseAccountData, UnlinkDiscourseAccountData, DiscourseProfileData, DiscourseCheckEmailData, DiscourseDismissLinkData, LinkMediawikiAccountData, UnlinkMediawikiAccountData, GetMediawikiContributionsData, AddClassifiedData, PersonActivateData } from "./EndpointApi.types.js";
6
6
 
7
7
  /**
8
8
  * Classe EndpointApi générée automatiquement depuis endpoints-copie.json
@@ -1092,38 +1092,6 @@ export class EndpointApi {
1092
1092
  return this.callIsConnected("ADD_EVENT", data);
1093
1093
  }
1094
1094
 
1095
- /**
1096
- * Supprimer un POI : Supprime un POI existant.
1097
- * Constant : DELETE_POI
1098
- * @param data - Données envoyées à l'API
1099
- * @returns Les données de réponse.
1100
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1101
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1102
- * @throws {Error} - En cas d'erreur inattendue.
1103
- */
1104
- async deletePoi(data: DeletePoiData): Promise<any> {
1105
- if (!data || typeof data !== "object") {
1106
- throw new TypeError("Le paramètre data doit être un objet.");
1107
- }
1108
- return this.callIsConnected("DELETE_POI", data);
1109
- }
1110
-
1111
- /**
1112
- * Supprimer un événement : Supprime un événement spécifique.
1113
- * Constant : DELETE_EVENT
1114
- * @param data - Données envoyées à l'API
1115
- * @returns Les données de réponse.
1116
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1117
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1118
- * @throws {Error} - En cas d'erreur inattendue.
1119
- */
1120
- async deleteEvent(data: DeleteEventData): Promise<any> {
1121
- if (!data || typeof data !== "object") {
1122
- throw new TypeError("Le paramètre data doit être un objet.");
1123
- }
1124
- return this.callIsConnected("DELETE_EVENT", data);
1125
- }
1126
-
1127
1095
  /**
1128
1096
  * Supprimer un élément : Supprime un élément existant.
1129
1097
  * Constant : DELETE_ELEMENT
@@ -2819,22 +2819,6 @@ export interface AddOrganizationData {
2819
2819
  float: true;
2820
2820
  [k: string]: unknown;
2821
2821
  };
2822
- costum?: {
2823
- bannerImageUrl?: string;
2824
- bannerLogoUrl?: string;
2825
- bannerText?: string;
2826
- /**
2827
- * Pour bien indiquer que l'orga est un commune transparente
2828
- */
2829
- transparentCommune?: boolean;
2830
- /**
2831
- * Pour bien indiquer que l'orga est un centre ville
2832
- */
2833
- cocity?: boolean;
2834
- slug?: string;
2835
- typeCocity?: string;
2836
- [k: string]: unknown;
2837
- };
2838
2822
  /**
2839
2823
  * Thématiques de l’organisation
2840
2824
  */
@@ -3079,15 +3063,6 @@ export interface AddOrganizationData {
3079
3063
  * Description longue (markdown supporté)
3080
3064
  */
3081
3065
  description?: string;
3082
- /**
3083
- * Source d'insertion (costum)
3084
- */
3085
- source?: {
3086
- insertOrign?: string;
3087
- keys?: string[];
3088
- key?: string;
3089
- [k: string]: unknown;
3090
- };
3091
3066
  /**
3092
3067
  * Tag principal (ex: TiersLieux)
3093
3068
  */
@@ -3104,10 +3079,6 @@ export interface AddOrganizationData {
3104
3079
  * Type d'entité du costum (ex: projects)
3105
3080
  */
3106
3081
  costumType?: string;
3107
- /**
3108
- * Mode édition costum
3109
- */
3110
- costumEditMode?: boolean;
3111
3082
  [k: string]: unknown;
3112
3083
  }
3113
3084
 
@@ -3230,6 +3201,18 @@ export interface AddProjectData {
3230
3201
  streetAddress?: string;
3231
3202
  };
3232
3203
  scope: "";
3204
+ /**
3205
+ * Slug du costum d'origine
3206
+ */
3207
+ costumSlug?: string;
3208
+ /**
3209
+ * ID Mongo du costum d'origine
3210
+ */
3211
+ costumId?: string;
3212
+ /**
3213
+ * Type d'entité du costum (ex: projects)
3214
+ */
3215
+ costumType?: string;
3233
3216
  [k: string]: unknown;
3234
3217
  }
3235
3218
 
@@ -3518,14 +3501,17 @@ export interface AddPoiData {
3518
3501
  */
3519
3502
  equip_utilisateur?: string[];
3520
3503
  /**
3521
- * Source d'insertion (costum)
3504
+ * Slug du costum d'origine
3522
3505
  */
3523
- source?: {
3524
- insertOrign?: string;
3525
- keys?: string[];
3526
- key?: string;
3527
- [k: string]: unknown;
3528
- };
3506
+ costumSlug?: string;
3507
+ /**
3508
+ * ID Mongo du costum d'origine
3509
+ */
3510
+ costumId?: string;
3511
+ /**
3512
+ * Type d'entité du costum (ex: projects)
3513
+ */
3514
+ costumType?: string;
3529
3515
  [k: string]: unknown;
3530
3516
  }
3531
3517
 
@@ -3873,38 +3859,18 @@ export interface AddEventData {
3873
3859
  streetAddress?: string;
3874
3860
  };
3875
3861
  scope: "";
3876
- [k: string]: unknown;
3877
- }
3878
-
3879
-
3880
- export interface DeletePoiData {
3881
3862
  /**
3882
- * Raison de la suppression
3863
+ * Slug du costum d'origine
3883
3864
  */
3884
- reason: string;
3885
- pathParams?: {
3886
- /**
3887
- * ID de l'élément à supprimer
3888
- */
3889
- id: string;
3890
- [k: string]: unknown;
3891
- };
3892
- [k: string]: unknown;
3893
- }
3894
-
3895
-
3896
- export interface DeleteEventData {
3865
+ costumSlug?: string;
3897
3866
  /**
3898
- * Raison de la suppression
3867
+ * ID Mongo du costum d'origine
3899
3868
  */
3900
- reason: string;
3901
- pathParams?: {
3902
- /**
3903
- * ID de l'élément à supprimer
3904
- */
3905
- id: string;
3906
- [k: string]: unknown;
3907
- };
3869
+ costumId?: string;
3870
+ /**
3871
+ * Type d'entité du costum (ex: projects)
3872
+ */
3873
+ costumType?: string;
3908
3874
  [k: string]: unknown;
3909
3875
  }
3910
3876
 
@@ -4209,6 +4175,18 @@ export interface AddBadgesData {
4209
4175
  name?: string;
4210
4176
  };
4211
4177
  };
4178
+ /**
4179
+ * Slug du costum d'origine
4180
+ */
4181
+ costumSlug?: string;
4182
+ /**
4183
+ * ID Mongo du costum d'origine
4184
+ */
4185
+ costumId?: string;
4186
+ /**
4187
+ * Type d'entité du costum (ex: projects)
4188
+ */
4189
+ costumType?: string;
4212
4190
  [k: string]: unknown;
4213
4191
  }
4214
4192
 
@@ -6661,6 +6639,18 @@ export interface AddClassifiedData {
6661
6639
  streetAddress?: string;
6662
6640
  };
6663
6641
  scope: "";
6642
+ /**
6643
+ * Slug du costum d'origine
6644
+ */
6645
+ costumSlug?: string;
6646
+ /**
6647
+ * ID Mongo du costum d'origine
6648
+ */
6649
+ costumId?: string;
6650
+ /**
6651
+ * Type d'entité du costum (ex: projects)
6652
+ */
6653
+ costumType?: string;
6664
6654
  [k: string]: unknown;
6665
6655
  }
6666
6656
 
package/src/api/Event.ts CHANGED
@@ -126,6 +126,23 @@ export class Event extends BaseEntity<EventItemNormalized> {
126
126
  return this.callIsConnected(() => this.endpointApi.addEvent(data as AddEventData));
127
127
  }
128
128
 
129
+ /**
130
+ * Supprime cet événement via `DELETE_ELEMENT` (`type=events`).
131
+ *
132
+ * **Garde** : auteur de l'événement (`serverData.creator`) OU administrateur
133
+ * de l'organisation/projet organisateur (via `serverData.organizer` × hiérarchie).
134
+ * cf. {@link BaseEntity#_deleteViaElement}.
135
+ *
136
+ * Après succès : données vidées + `_isDeleted = true`.
137
+ *
138
+ * @param reason - Raison transmise au backend (audit). Défaut `"delete event"`.
139
+ * @throws {ApiError} 400 si l'événement n'a pas d'id.
140
+ * @throws {ApiError} 403 si ni auteur ni admin de l'organisateur.
141
+ */
142
+ async delete(reason: string = "delete event"): Promise<void> {
143
+ await this._deleteViaElement(this.getEntityType(), reason);
144
+ }
145
+
129
146
 
130
147
  override async getOrganizations(): Promise<never> {
131
148
  throw new ApiError(`getOrganizations n'existe pas dans ${this.constructor.name}`, 501);
@@ -287,6 +287,26 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
287
287
  return this.callIsConnected(() => this.endpointApi.addOrganization(data));
288
288
  }
289
289
 
290
+ /**
291
+ * Supprime cette organisation via `DELETE_ELEMENT` (`type=organizations`).
292
+ *
293
+ * **Garde** : auteur (`serverData.creator`) OU administrateur direct de l'org
294
+ * (`userContext.links.memberOf[id].isAdmin`). Pas de hiérarchie parent (une org
295
+ * est racine). cf. {@link BaseEntity#_deleteViaElement}.
296
+ *
297
+ * ⚠️ **Destructif** : supprime l'organisation et potentiellement ses rattachements
298
+ * (projets/events/membres) côté serveur. À utiliser avec précaution.
299
+ *
300
+ * Après succès : données vidées + `_isDeleted = true`.
301
+ *
302
+ * @param reason - Raison transmise au backend (audit). Défaut `"delete organization"`.
303
+ * @throws {ApiError} 400 si l'org n'a pas d'id.
304
+ * @throws {ApiError} 403 si ni auteur ni admin.
305
+ */
306
+ async delete(reason: string = "delete organization"): Promise<void> {
307
+ await this._deleteViaElement(this.getEntityType(), reason);
308
+ }
309
+
290
310
  override async getOrganizations(): Promise<never> {
291
311
  throw new ApiError("getOrganizations n'existe pas dans Organization", 404);
292
312
  }
package/src/api/Poi.ts CHANGED
@@ -219,6 +219,23 @@ export class Poi extends BaseEntity<PoiItemNormalized> {
219
219
  return this.callIsConnected(() => this.endpointApi.addPoi(data as AddPoiData));
220
220
  }
221
221
 
222
+ /**
223
+ * Supprime ce POI via `DELETE_ELEMENT` (`type=poi`).
224
+ *
225
+ * **Garde** : auteur du POI (`serverData.creator`) OU administrateur de
226
+ * l'organisation/projet parent (via `serverData.parent` × hiérarchie).
227
+ * cf. {@link BaseEntity#_deleteViaElement}.
228
+ *
229
+ * Après succès : données vidées + `_isDeleted = true`.
230
+ *
231
+ * @param reason - Raison transmise au backend (audit). Défaut `"delete poi"`.
232
+ * @throws {ApiError} 400 si le POI n'a pas d'id.
233
+ * @throws {ApiError} 403 si ni auteur ni admin du parent.
234
+ */
235
+ async delete(reason: string = "delete poi"): Promise<void> {
236
+ await this._deleteViaElement(this.getEntityType(), reason);
237
+ }
238
+
222
239
  override async getOrganizations(): Promise<never> {
223
240
  throw new ApiError(`getOrganizations n'existe pas dans ${this.constructor.name}`, 501);
224
241
  }
@@ -170,6 +170,26 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
170
170
  return this.callIsConnected(() => this.endpointApi.addProject(data as AddProjectData));
171
171
  }
172
172
 
173
+ /**
174
+ * Supprime ce projet via `DELETE_ELEMENT` (`type=projects`).
175
+ *
176
+ * **Garde** : auteur (`serverData.creator`) OU administrateur — direct
177
+ * (`links.projects[id].isAdmin`) ou de l'organisation parente (via
178
+ * `serverData.parent` × hiérarchie). cf. {@link BaseEntity#_deleteViaElement}.
179
+ *
180
+ * ⚠️ **Destructif** : supprime le projet et potentiellement ses rattachements
181
+ * (actions/events/membres) côté serveur. À utiliser avec précaution.
182
+ *
183
+ * Après succès : données vidées + `_isDeleted = true`.
184
+ *
185
+ * @param reason - Raison transmise au backend (audit). Défaut `"delete project"`.
186
+ * @throws {ApiError} 400 si le projet n'a pas d'id.
187
+ * @throws {ApiError} 403 si ni auteur ni admin.
188
+ */
189
+ async delete(reason: string = "delete project"): Promise<void> {
190
+ await this._deleteViaElement(this.getEntityType(), reason);
191
+ }
192
+
173
193
 
174
194
  override async getOrganizations(): Promise<never> {
175
195
  throw new ApiError(`getOrganizations n'existe pas dans ${this.constructor.name}`, 501);
package/src/api/User.ts CHANGED
@@ -169,6 +169,9 @@ export class User extends BaseEntity<UserItemNormalized> {
169
169
  * @returns Le profil complet.
170
170
  */
171
171
  override async get(): Promise<Record<string, any>> {
172
+ // Tombstone : `meInfoUrl()` est basé sur le token (ignore `this.id`), donc cette
173
+ // lecture ne lèverait pas via le garde `!this.id` après un delete — on garde ici.
174
+ this._checkNotDeleted();
172
175
  return this.apiClient.safeCall(async () => {
173
176
  if (this.isMe) {
174
177
  const data = await this.endpointApi.meInfoUrl();
@@ -205,7 +208,15 @@ export class User extends BaseEntity<UserItemNormalized> {
205
208
  * @throws {Error} - En cas d'erreur inattendue.
206
209
  */
207
210
  async delete(data: DeleteAccountData): Promise<unknown> {
208
- return this.callIsMe(() => this.endpointApi.deleteAccount(data));
211
+ const result = await this.callIsMe(() => this.endpointApi.deleteAccount(data));
212
+
213
+ // Vider les objets réactifs sans casser la réactivité (cf. Action/Answer/News/Comment.delete)
214
+ Object.keys(this._draftData).forEach(key => delete this._draftData[key]);
215
+ Object.keys(this._serverData).forEach(key => delete (this._serverData as any)[key]);
216
+
217
+ // Marquer comme supprimé → toute opération ultérieure lève le tombstone.
218
+ this._isDeleted = true;
219
+ return result;
209
220
  }
210
221
 
211
222
  /**