@communecter/cocolight-api-client 1.0.75 → 1.0.77

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.75",
3
+ "version": "1.0.77",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -320,6 +320,8 @@ export class BaseEntity<TServerData = any> {
320
320
  static entityTag = "BaseEntity";
321
321
  static entityType?: string;
322
322
  static SCHEMA_CONSTANTS?: string | string[];
323
+ static VIRTUAL_SCHEMAS?: Record<string, any>;
324
+ static CUSTOM_FIELD_HANDLERS?: Map<string, { updateMethod: string; schemaConstant?: string }>;
323
325
 
324
326
  /**
325
327
  * Vérifie que l'objet n'a pas été supprimé.
@@ -1487,7 +1489,15 @@ export class BaseEntity<TServerData = any> {
1487
1489
  };
1488
1490
 
1489
1491
  for (const key of constants) {
1490
- const sch = apiClient.getRequestSchema(key);
1492
+ let sch = apiClient.getRequestSchema(key);
1493
+
1494
+ // Injecter le schéma virtuel si disponible
1495
+ const Ctor = this._getCtor();
1496
+ const virtualSchema = Ctor.VIRTUAL_SCHEMAS?.[key];
1497
+ if (virtualSchema) {
1498
+ sch = this._mergeSchemas(sch, virtualSchema);
1499
+ }
1500
+
1491
1501
  if (!sch) throw new ApiError(`Unable to find schema for ${key}.`, 404);
1492
1502
 
1493
1503
  // Extraire et fusionner les $defs
@@ -1623,6 +1633,58 @@ export class BaseEntity<TServerData = any> {
1623
1633
  return JSON.stringify(this._toRawDeep(current)) !== JSON.stringify(this._toRawDeep(initial));
1624
1634
  }
1625
1635
 
1636
+ /**
1637
+ * Merge deux schémas JSON en combinant leurs propriétés.
1638
+ * @param base - Schéma de base (peut être null/undefined)
1639
+ * @param virtual - Schéma virtuel à fusionner
1640
+ * @returns Schéma combiné
1641
+ * @private
1642
+ */
1643
+ private _mergeSchemas(base: any, virtual: any): any {
1644
+ if (!base) return virtual;
1645
+ if (!virtual) return base;
1646
+
1647
+ return {
1648
+ allOf: [base, virtual],
1649
+ $defs: { ...base.$defs, ...virtual.$defs }
1650
+ };
1651
+ }
1652
+
1653
+ /**
1654
+ * Vérifie si un champ a changé par rapport à l'état initial.
1655
+ * @param fieldName - Nom du champ
1656
+ * @returns true si le champ a changé
1657
+ * @protected
1658
+ */
1659
+ protected _hasFieldChanged(fieldName: string): boolean {
1660
+ const current = this._draftData[fieldName];
1661
+ const initial = this._initialDraftData[fieldName];
1662
+ return this._compareValues(current, initial);
1663
+ }
1664
+
1665
+ /**
1666
+ * Invoque un handler personnalisé pour un champ spécifique.
1667
+ * @param methodName - Nom de la méthode à appeler
1668
+ * @param value - Valeur du champ
1669
+ * @param fieldName - Nom du champ (pour les messages d'erreur)
1670
+ * @returns Résultat de l'appel de la méthode
1671
+ * @protected
1672
+ */
1673
+ protected async _invokeCustomFieldHandler(
1674
+ methodName: string,
1675
+ value: any,
1676
+ fieldName: string
1677
+ ): Promise<any> {
1678
+ const method = (this as any)[methodName];
1679
+ if (typeof method !== "function") {
1680
+ throw new ApiError(
1681
+ `Custom handler "${methodName}" not found for field "${fieldName}"`,
1682
+ 500
1683
+ );
1684
+ }
1685
+ return await method.call(this, value);
1686
+ }
1687
+
1626
1688
  _extractChangedFieldsFromSchema(apiClient: ApiClient, constant: string, data: Record<string, any> = {}, getInitialDraft: () => Record<string, any> | void, removeFields: string[] = []): Record<string, any> | null {
1627
1689
  const schema = apiClient.getRequestSchema(constant);
1628
1690
  let allowed = this._extractWritableFields(schema, data);
@@ -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, 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, CostumEventRequestActorsData, CostumEventRequestSubeventsData, CostumEventRequestElementEventData, CostumEventRequestCategoriesData, CostumEventRequestDatesData, CostumEventRequestEventData, CostumEventRequestLinkTlToEventData, CostumEventRequestLoadContextTagData, GetGalleryData, GetAttendeesNoAdminData, GetAttendeesAdminData, CoformAnswersSearchData, CoformAnswersByIdData, AddVoteData, AddReportAbuseData } 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, 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, CostumEventRequestActorsData, CostumEventRequestSubeventsData, CostumEventRequestElementEventData, CostumEventRequestCategoriesData, CostumEventRequestDatesData, CostumEventRequestEventData, CostumEventRequestLinkTlToEventData, CostumEventRequestLoadContextTagData, GetGalleryData, GetAttendeesNoAdminData, GetAttendeesAdminData, CoformAnswersSearchData, CoformAnswersByIdData, AddVoteData, AddReportAbuseData, UpdatePathValueData, DeleteDocumentByContextData } from "./EndpointApi.types.js";
6
6
 
7
7
  /**
8
8
  * Classe EndpointApi générée automatiquement depuis endpoints-copie.json
@@ -1672,6 +1672,38 @@ export class EndpointApi {
1672
1672
  return this.callIsConnected("ADD_REPORT_ABUSE", data);
1673
1673
  }
1674
1674
 
1675
+ /**
1676
+ * Mettre à jour une valeur de chemin pour une entité : Met à jour une valeur spécifique dans le chemin donné pour une entité.
1677
+ * Constant : UPDATE_PATH_VALUE
1678
+ * @param data - Données envoyées à l'API
1679
+ * @returns Les données de réponse.
1680
+ * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1681
+ * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1682
+ * @throws {Error} - En cas d'erreur inattendue.
1683
+ */
1684
+ async updatePathValue(data: UpdatePathValueData): Promise<any> {
1685
+ if (!data || typeof data !== "object") {
1686
+ throw new TypeError("Le paramètre data doit être un objet.");
1687
+ }
1688
+ return this.callIsConnected("UPDATE_PATH_VALUE", data);
1689
+ }
1690
+
1691
+ /**
1692
+ * Supprimer une image/document par contexte d’entité : Supprimer des documents associés à une entité dans un contexte spécifique
1693
+ * Constant : DELETE_DOCUMENT_BY_CONTEXT
1694
+ * @param data - Données envoyées à l'API
1695
+ * @returns Les données de réponse.
1696
+ * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
1697
+ * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
1698
+ * @throws {Error} - En cas d'erreur inattendue.
1699
+ */
1700
+ async deleteDocumentByContext(data: DeleteDocumentByContextData): Promise<any> {
1701
+ if (!data || typeof data !== "object") {
1702
+ throw new TypeError("Le paramètre data doit être un objet.");
1703
+ }
1704
+ return this.callIsConnected("DELETE_DOCUMENT_BY_CONTEXT", data);
1705
+ }
1706
+
1675
1707
  }
1676
1708
 
1677
1709
  export default EndpointApi;
@@ -4846,3 +4846,56 @@ export interface AddReportAbuseData {
4846
4846
  };
4847
4847
  [k: string]: unknown;
4848
4848
  }
4849
+
4850
+
4851
+ export interface UpdatePathValueData {
4852
+ /**
4853
+ * ID de l’utilisateur
4854
+ */
4855
+ id: string;
4856
+ /**
4857
+ * Collection de l’entité
4858
+ */
4859
+ collection: "citoyens" | "organizations" | "projects" | "events" | "poi";
4860
+ /**
4861
+ * Chemin de la valeur à mettre à jour
4862
+ */
4863
+ path: string;
4864
+ arrayForm?: boolean;
4865
+ /**
4866
+ * Valeur à mettre à jour (peut être un objet, un tableau, une chaîne, etc.)
4867
+ */
4868
+ value: {
4869
+ [k: string]: unknown;
4870
+ };
4871
+ [k: string]: unknown;
4872
+ }
4873
+
4874
+
4875
+ export interface DeleteDocumentByContextData {
4876
+ /**
4877
+ * ID de l’utilisateur
4878
+ */
4879
+ parentId?: string;
4880
+ /**
4881
+ * Type de contexte de l’entité
4882
+ */
4883
+ parentType?: "citoyens" | "organizations" | "projects" | "events" | "poi";
4884
+ path: "communecter";
4885
+ /**
4886
+ * ID image/document à supprimer
4887
+ */
4888
+ ids?: string;
4889
+ pathParams?: {
4890
+ /**
4891
+ * ID du contexte de l’entité
4892
+ */
4893
+ contextId: string;
4894
+ /**
4895
+ * Type de contexte de l’entité
4896
+ */
4897
+ contextType: "citoyens" | "organizations" | "projects" | "events" | "poi";
4898
+ [k: string]: unknown;
4899
+ };
4900
+ [k: string]: unknown;
4901
+ }
package/src/api/News.ts CHANGED
@@ -163,12 +163,26 @@ export class News extends BaseEntity<NewsItemNormalized> {
163
163
  payload.idNews = this.id;
164
164
 
165
165
  let hasChanged = false;
166
- await this.callIsConnected(() => this.endpointApi.updateNews(payload as UpdateNewsData)) as any;
167
- // TODO : voir si j'ai ce qui faut dans data de UPDATE_NEWS pour mettre à jour #serverData
168
- // c'est dans data.object
169
- // if(data?.object){
170
- // this._serverData = { ...data.object };
166
+
167
+
168
+ // il faut que j'ai les ids des images et fichiers avant la mise à jour venant de serverData
169
+ // if (this._serverData?.mediaImg?.images && this._serverData?.mediaImg?.images.length > 0) {
170
+ // imagesIds = this._serverData.mediaImg.images.map((img: any) => img.id);
171
171
  // }
172
+
173
+ // if (this._serverData?.mediaFile?.files && this._serverData?.mediaFile?.files.length > 0) {
174
+ // filesIds = this._serverData.mediaFile.files.map((f: any) => f.id);
175
+ // }
176
+
177
+ // comparer avec this._draftData.mediaImg.images pour voir si des images ont été supprimées
178
+ // comparer avec this._draftData.mediaFile.files pour voir si des fichiers ont été supprimés
179
+
180
+
181
+ await this.callIsConnected(() => this.endpointApi.updateNews(payload as UpdateNewsData)) as any;
182
+
183
+ // TODO : je voudrais pouvoir comparer les images et fichiers pour supprimer ceux qui ont été enlevés
184
+ // avec deleteDocumentByContext() mais il faut que j'ai les ids des images et fichiers avant la mise à jour
185
+
172
186
  hasChanged = true;
173
187
  return hasChanged;
174
188
  };
@@ -262,6 +276,8 @@ export class News extends BaseEntity<NewsItemNormalized> {
262
276
  return dataFile;
263
277
  }
264
278
 
279
+
280
+
265
281
  /**
266
282
  * Supprimer une actualité : Supprime une actualité existante.
267
283
  * Constant : DELETE_NEWS
@@ -7,6 +7,7 @@ import type {
7
7
  GetMembersAdminData,
8
8
  GetMembersNoAdminData
9
9
  } from "./EndpointApi.types.js";
10
+ import type { OpeningHoursEntry } from "./serverDataType/common.js";
10
11
  import type { OrganizationItemNormalized } from "./serverDataType/Organization.js";
11
12
  import type { User } from "./User.js";
12
13
 
@@ -23,9 +24,72 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
23
24
  "UPDATE_BLOCK_SOCIAL",
24
25
  "UPDATE_BLOCK_LOCALITY",
25
26
  "UPDATE_BLOCK_SLUG",
26
- "PROFIL_IMAGE"
27
+ "PROFIL_IMAGE",
28
+ "VIRTUAL_OPENING_HOURS"
27
29
  ];
28
30
 
31
+ static override VIRTUAL_SCHEMAS = {
32
+ VIRTUAL_OPENING_HOURS: {
33
+ type: "object",
34
+ properties: {
35
+ openingHours: {
36
+ type: "array",
37
+ description: "Horaires d'ouverture (7 jours de la semaine)",
38
+ minItems: 7,
39
+ maxItems: 7,
40
+ items: {
41
+ oneOf: [
42
+ {
43
+ type: "string",
44
+ const: ""
45
+ },
46
+ {
47
+ type: "object",
48
+ properties: {
49
+ dayOfWeek: {
50
+ type: "string",
51
+ enum: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
52
+ description: "Jour de la semaine"
53
+ },
54
+ hours: {
55
+ type: "array",
56
+ description: "Liste des créneaux horaires",
57
+ items: {
58
+ type: "object",
59
+ properties: {
60
+ opens: {
61
+ type: "string",
62
+ pattern: "^([01]\\d|2[0-3]):[0-5]\\d$",
63
+ description: "Heure d'ouverture (HH:MM)"
64
+ },
65
+ closes: {
66
+ type: "string",
67
+ pattern: "^([01]\\d|2[0-3]):[0-5]\\d$",
68
+ description: "Heure de fermeture (HH:MM)"
69
+ }
70
+ },
71
+ required: ["opens", "closes"],
72
+ additionalProperties: false
73
+ }
74
+ }
75
+ },
76
+ required: ["dayOfWeek", "hours"],
77
+ additionalProperties: false
78
+ }
79
+ ]
80
+ }
81
+ }
82
+ }
83
+ }
84
+ };
85
+
86
+ static override CUSTOM_FIELD_HANDLERS = new Map([
87
+ ["openingHours", {
88
+ updateMethod: "updateOpeningHours",
89
+ schemaConstant: "VIRTUAL_OPENING_HOURS"
90
+ }]
91
+ ] as const);
92
+
29
93
  static ADD_BLOCKS = new Map([
30
94
  ["ADD_ORGANIZATION", "addOrganization"],
31
95
  ["PROFIL_IMAGE", "updateImageProfil"]
@@ -99,6 +163,28 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
99
163
  if (payload.id) delete payload.id;
100
164
  let hasChanged = false;
101
165
 
166
+ // 1. Traiter les champs personnalisés AVANT les block updates
167
+ const customHandlers = Organization.CUSTOM_FIELD_HANDLERS;
168
+ if (customHandlers) {
169
+ const processedFields = new Set<string>();
170
+
171
+ for (const [fieldName, config] of customHandlers) {
172
+ if (fieldName in payload && this._hasFieldChanged(fieldName)) {
173
+ await this._invokeCustomFieldHandler(
174
+ config.updateMethod,
175
+ payload[fieldName],
176
+ fieldName
177
+ );
178
+ processedFields.add(fieldName);
179
+ hasChanged = true;
180
+ }
181
+ }
182
+
183
+ // Retirer les champs traités du payload pour éviter qu'ils soient traités par les blocks
184
+ processedFields.forEach(f => delete payload[f]);
185
+ }
186
+
187
+ // 2. Traiter les block updates normaux
102
188
  for (const [constant, methodName] of Array.from(Organization.UPDATE_BLOCKS)) {
103
189
  const blockData = this._extractChangedFieldsFromSchema(
104
190
  this.apiClient,
@@ -249,7 +335,7 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
249
335
  const { toBeValidated, isAdmin, isAdminPending, isInviting, roles = [] } = options;
250
336
 
251
337
  if(this.isMe){
252
- finalData.pathParams = { type: this.getEntityType(), id: this.id };
338
+ finalData.pathParams = { id: this.id };
253
339
  // finalData.filters = {
254
340
  // [`links.memberOf.${this.id}`]: { "$exists": true },
255
341
  // [`links.memberOf.${this.id}.toBeValidated`]: { "$exists": false },
@@ -338,7 +424,36 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
338
424
  // TODO: qui peut créer une news sur l'organisation ?
339
425
  return super.news(newsData);
340
426
  }
341
-
427
+
428
+ /**
429
+ * Met à jour les horaires d'ouverture de l'organisation.
430
+ * Utilise UPDATE_PATH_VALUE pour modifier le champ openingHours.
431
+ *
432
+ * @param hours - Tableau de 7 entrées (une par jour de la semaine)
433
+ * @returns Résultat de l'appel API
434
+ * @throws {ApiError} Si l'organisation n'a pas d'ID
435
+ *
436
+ * @example
437
+ * const openingHours = [
438
+ * { dayOfWeek: "Mo", hours: [{ opens: "09:00", closes: "18:00" }] },
439
+ * { dayOfWeek: "Tu", hours: [{ opens: "09:00", closes: "18:00" }] },
440
+ * // ... pour les 7 jours
441
+ * ];
442
+ * await org.updateOpeningHours(openingHours);
443
+ */
444
+ async updateOpeningHours(hours: OpeningHoursEntry[]): Promise<unknown> {
445
+ if (!this.id) {
446
+ throw new ApiError("L'organisation n'a pas d'ID, impossible de mettre à jour les horaires d'ouverture.", 400);
447
+ }
448
+
449
+ return this.endpointApi.updatePathValue({
450
+ id: this.id,
451
+ collection: "organizations",
452
+ path: "openingHours",
453
+ value: hours as unknown as { [k: string]: unknown }
454
+ });
455
+ }
456
+
342
457
  /**
343
458
  * ───────────────────────────────
344
459
  * Lien utilisateur ↔ organisation