@communecter/cocolight-api-client 1.0.126 → 1.0.128

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.126",
3
+ "version": "1.0.128",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
package/src/Api.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // Api.ts
2
+ import { Action } from "./api/Action.js";
2
3
  import { Answer } from "./api/Answer.js";
3
4
  import { Badge } from "./api/Badge.js";
4
5
  import { Classified } from "./api/Classified.js";
@@ -33,6 +34,7 @@ registerEntity("Comment", Comment);
33
34
  registerEntity("Answer", Answer);
34
35
  registerEntity("Form", Form);
35
36
  registerEntity("Classified", Classified);
37
+ registerEntity("Action", Action);
36
38
 
37
39
 
38
40
 
@@ -130,9 +132,9 @@ export default class Api {
130
132
  async user(userData: EntityData): Promise<User> {
131
133
  try {
132
134
  if (!userData.id && !userData.slug) {
133
- return new User(this._client, userData, { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified });
135
+ return new User(this._client, userData, { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified, Action });
134
136
  } else {
135
- const user = new User(this._client, userData, { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified });
137
+ const user = new User(this._client, userData, { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified, Action });
136
138
  await user.get();
137
139
  return user;
138
140
  }
@@ -148,7 +150,7 @@ export default class Api {
148
150
  */
149
151
  async organization(organizationData: EntityData): Promise<Organization> {
150
152
  try {
151
- const organization = new Organization(this._client, organizationData, { EndpointApi, User, Project, Event, Poi, Badge, News, Comment, Answer, Classified });
153
+ const organization = new Organization(this._client, organizationData, { EndpointApi, User, Project, Event, Poi, Badge, News, Comment, Answer, Classified, Action });
152
154
  if (!organizationData.id && !organizationData.slug) {
153
155
  throw new Error("Vous devez fournir un id ou un slug pour créer une instance Organization.");
154
156
  }
@@ -165,7 +167,7 @@ export default class Api {
165
167
  */
166
168
  async project(projectData: EntityData): Promise<Project> {
167
169
  try {
168
- const project = new Project(this._client, projectData, { EndpointApi, User, Organization, Event, Poi, Badge, News, Comment, Classified });
170
+ const project = new Project(this._client, projectData, { EndpointApi, User, Organization, Event, Poi, Badge, News, Comment, Classified, Action });
169
171
  if (!projectData.id && !projectData.slug) {
170
172
  throw new Error("Vous devez fournir un id ou un slug pour créer une instance Project.");
171
173
  }
package/src/ApiClient.ts CHANGED
@@ -1659,7 +1659,7 @@ export default class ApiClient extends EventEmitter {
1659
1659
  * via x-www-form-urlencoded ; sans cette coercion, JSON.stringify diff
1660
1660
  * produit des faux positifs dans `hasChanges()`).
1661
1661
  */
1662
- private _numberFields = ["buildingSurfaceArea", "siteSurfaceArea"];
1662
+ private _numberFields = ["buildingSurfaceArea", "siteSurfaceArea", "credits", "min", "max", "timeSpent"];
1663
1663
 
1664
1664
  /**
1665
1665
  * Normalise récursivement un objet, un tableau ou une valeur simple.
@@ -0,0 +1,76 @@
1
+ import { ApiError } from "../error.js";
2
+ import { BaseEntity } from "./BaseEntity.js";
3
+
4
+ import type { CostumProjectActionRequestNewData } from "./EndpointApi.types.js";
5
+ import type { ActionItemNormalized } from "./serverDataType/Action.js";
6
+
7
+ /**
8
+ * Entité Action — tâche/jalon attaché à un Project.
9
+ *
10
+ * Toujours liée à un Project parent (parentType="projects" est const côté backend).
11
+ * S'instancie via `project.action(...)` plutôt que directement.
12
+ *
13
+ * Endpoints utilisés :
14
+ * - `get()` : hérité (GET_ELEMENTS_ABOUT via `/co2/element/about/type/actions/id/{id}`)
15
+ * - `_add()` : COSTUM_PROJECT_ACTION_REQUEST_NEW (parentId/parentType injectés depuis le parent)
16
+ */
17
+ export class Action extends BaseEntity<ActionItemNormalized> {
18
+ static override entityType = "actions";
19
+
20
+ static override entityTag = "Action";
21
+
22
+ static override SCHEMA_CONSTANTS: string[] = [
23
+ "COSTUM_PROJECT_ACTION_REQUEST_NEW"
24
+ ];
25
+
26
+ static ADD_BLOCKS = new Map([
27
+ ["COSTUM_PROJECT_ACTION_REQUEST_NEW", "addAction"]
28
+ ] as const);
29
+
30
+ override defaultFields: Record<string, any> = {
31
+ parentType: "projects"
32
+ };
33
+
34
+ override removeFields: string[] = [];
35
+
36
+ override _add = async (payload: Record<string, any>): Promise<void> => {
37
+ if (!this._calledFromSave) {
38
+ throw new ApiError("utilisation invalide de _add, utilisez save", 400);
39
+ }
40
+
41
+ if (!this.parent?.id) {
42
+ throw new ApiError("Le parent (Project) doit être défini pour créer une action.", 400);
43
+ }
44
+
45
+ if (this.parent.getEntityType() !== "projects") {
46
+ throw new ApiError(`Une Action ne peut être créée que depuis un Project (parent reçu: ${this.parent.getEntityType()}).`, 400);
47
+ }
48
+
49
+ payload.parentId = this.parent.id;
50
+
51
+ for (const [constant, methodName] of Array.from(Action.ADD_BLOCKS)) {
52
+ const blockData = this._extractChangedFieldsFromSchema(
53
+ this.apiClient,
54
+ constant,
55
+ { ...payload, ...this.defaultFields },
56
+ () => {}
57
+ );
58
+ if (blockData && Object.keys(blockData).length > 0) {
59
+ const data = await this._invokeBlockMethod(Action.ADD_BLOCKS, methodName, blockData);
60
+ const content = data?.content;
61
+ if (!this.id && content?.id) {
62
+ this._draftData.id = content.id;
63
+ this._setData(content as ActionItemNormalized, { forceInitialDraftReset: true });
64
+ }
65
+ }
66
+ }
67
+ };
68
+
69
+ /**
70
+ * Crée une nouvelle action via /costum/project/action/request/new.
71
+ * Méthode de bas niveau — utiliser `save()` après modification du draft à la place.
72
+ */
73
+ async addAction(data: Partial<CostumProjectActionRequestNewData> = {}): Promise<unknown> {
74
+ return this.callIsConnected(() => this.endpointApi.costumProjectActionRequestNew(data as CostumProjectActionRequestNewData));
75
+ }
76
+ }
@@ -77,6 +77,7 @@ type EventInput = { id: string } | { slug: string } | Record<string, any>;
77
77
  type ClassifiedInput = { id: string } | Record<string, any>;
78
78
  type BadgeInput = { id: string } | Record<string, any>;
79
79
  type NewsInput = { id: string } | Record<string, any>;
80
+ type ActionInput = { id: string } | Record<string, any>;
80
81
 
81
82
  /**
82
83
  * On force le type de l'import comme une fabrique qui renvoie un objet
@@ -99,9 +100,10 @@ type Comment = import("./Comment.js").Comment;
99
100
  type Answer = import("./Answer.js").Answer;
100
101
  type Form = import("./Form.js").Form;
101
102
  type Classified = import("./Classified.js").Classified;
103
+ type Action = import("./Action.js").Action;
102
104
 
103
105
  // Types d'union
104
- type AnyEntity = User | Organization | Project | Poi | EventEntity | Badge | News | Comment | Answer | Form | Classified;
106
+ type AnyEntity = User | Organization | Project | Poi | EventEntity | Badge | News | Comment | Answer | Form | Classified | Action;
105
107
  type ParentLike = BaseEntity<any> & { apiClient: ApiClient, userContext?: User | null };
106
108
 
107
109
  // Types pour les dépendances
@@ -120,7 +122,8 @@ interface Deps {
120
122
  Comment?: any;
121
123
  Answer?: any;
122
124
  Form?: any;
123
- Classified?: any
125
+ Classified?: any;
126
+ Action?: any;
124
127
  }
125
128
 
126
129
  interface BaseEntityConfig {
@@ -140,6 +143,7 @@ interface EntityTypeMap {
140
143
  answers: Answer;
141
144
  forms: Form;
142
145
  classifieds: Classified;
146
+ actions: Action;
143
147
  }
144
148
 
145
149
  // Types pour les streams et uploads
@@ -250,7 +254,7 @@ interface LinkMeta {
250
254
  // Type du constructeur de BaseEntity avec ses propriétés statiques
251
255
  type BaseEntityCtor = typeof BaseEntity & {
252
256
  entityTag: string;
253
- entityType: "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds";
257
+ entityType: "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds" | "actions";
254
258
  SCHEMA_CONSTANTS: string | string[];
255
259
  };
256
260
 
@@ -344,7 +348,7 @@ interface FinalizerResult<TOut> {
344
348
  }
345
349
 
346
350
  // Types pour les entités
347
- export type EntityType = "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds";
351
+ export type EntityType = "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds" | "actions";
348
352
 
349
353
  // ============================================================================
350
354
 
@@ -525,7 +529,7 @@ export class BaseEntity<TServerData = any> {
525
529
  }
526
530
 
527
531
  /** @returns Type de l'entité (ex: 'citoyens') */
528
- getEntityType(): "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds" {
532
+ getEntityType(): "citoyens" | "organizations" | "projects" | "events" | "poi" | "badges" | "news" | "comments" | "answers" | "forms" | "classifieds" | "actions" {
529
533
  return this._getCtor().entityType;
530
534
  }
531
535
 
@@ -1947,6 +1951,7 @@ export class BaseEntity<TServerData = any> {
1947
1951
  Answer: selfTag === "Answer" ? selfClass : this.deps.Answer,
1948
1952
  Form: selfTag === "Form" ? selfClass : this.deps.Form,
1949
1953
  Classified: selfTag === "Classified" ? selfClass : this.deps.Classified,
1954
+ Action: selfTag === "Action" ? selfClass : this.deps.Action,
1950
1955
  };
1951
1956
 
1952
1957
  const map = {
@@ -1968,6 +1973,7 @@ export class BaseEntity<TServerData = any> {
1968
1973
  comments: { entityClass: commonDeps.Comment, deps: { ...commonDeps } },
1969
1974
  answers: { entityClass: commonDeps.Answer, deps: { ...commonDeps } },
1970
1975
  forms: { entityClass: commonDeps.Form, deps: { ...commonDeps } },
1976
+ actions: { entityClass: commonDeps.Action, deps: { ...commonDeps } },
1971
1977
  };
1972
1978
 
1973
1979
  return (map as Record<string, EntityMeta | undefined>)[entityType] || null;
@@ -2200,6 +2206,7 @@ export class BaseEntity<TServerData = any> {
2200
2206
  comments: [], // Pas de get() pour les commentaires
2201
2207
  answers: ["id"],
2202
2208
  classifieds: ["id"],
2209
+ actions: ["id"],
2203
2210
  };
2204
2211
 
2205
2212
  const fetchKeys = (fetchKeysByEntity as Record<string, string[] | undefined>)[entityType];
@@ -2622,7 +2629,7 @@ export class BaseEntity<TServerData = any> {
2622
2629
  if (!userLink) {
2623
2630
 
2624
2631
  // 1) Garde runtime pour exclure les types non pris en charge par ConnectData
2625
- this._assertNotEntityType("connect", ["poi", "badges", "news"]);
2632
+ this._assertNotEntityType("connect", ["poi", "badges", "news", "actions"]);
2626
2633
 
2627
2634
  // 2) Narrow de type pour TypeScript
2628
2635
  const parentType = this.getEntityType() as ConnectData["parentType"];
@@ -2678,7 +2685,7 @@ export class BaseEntity<TServerData = any> {
2678
2685
  if (!userLink) {
2679
2686
 
2680
2687
  // 1) Garde runtime pour exclure les types non pris en charge par ConnectData
2681
- this._assertNotEntityType("connect", ["poi", "badges", "news"]);
2688
+ this._assertNotEntityType("connect", ["poi", "badges", "news", "actions"]);
2682
2689
 
2683
2690
  // 2) Narrow de type pour TypeScript
2684
2691
  const parentType = this.getEntityType() as ConnectData["parentType"];
@@ -2737,7 +2744,7 @@ export class BaseEntity<TServerData = any> {
2737
2744
 
2738
2745
  if (userLink && (userLink.isInviting || userLink.isAdminInviting)) {
2739
2746
  // 1) Garde runtime pour exclure les types non pris en charge par LinkValidateData
2740
- this._assertNotEntityType("linkValidate", ["poi", "badges", "news"]);
2747
+ this._assertNotEntityType("linkValidate", ["poi", "badges", "news", "actions"]);
2741
2748
 
2742
2749
  // 2) Narrow de type pour TypeScript
2743
2750
  const parentType = this.getEntityType() as LinkValidateData["parentType"];
@@ -2784,7 +2791,7 @@ export class BaseEntity<TServerData = any> {
2784
2791
  }
2785
2792
 
2786
2793
  // 1) Garde runtime pour exclure les types non pris en charge par DisconnectData
2787
- this._assertNotEntityType("disconnect", ["poi", "badges", "news"]);
2794
+ this._assertNotEntityType("disconnect", ["poi", "badges", "news", "actions"]);
2788
2795
 
2789
2796
  // 2) Narrow de type pour TypeScript
2790
2797
  const parentType = this.getEntityType() as DisconnectData["parentType"];
@@ -2839,7 +2846,7 @@ export class BaseEntity<TServerData = any> {
2839
2846
  }
2840
2847
 
2841
2848
  // Réduit le type de l'entité aux seuls autorisés par UpdateSettingsData
2842
- this._assertNotEntityType("updateSettings", ["poi", "badges", "news"]);
2849
+ this._assertNotEntityType("updateSettings", ["poi", "badges", "news", "actions"]);
2843
2850
  const typeEntity = this.getEntityType() as UpdateSettingsData["typeEntity"];
2844
2851
 
2845
2852
  // Garde runtime pour s'assurer que 'type' et 'value' sont bien fournis
@@ -2874,7 +2881,7 @@ export class BaseEntity<TServerData = any> {
2874
2881
  }
2875
2882
 
2876
2883
  // Réduit le type de l'entité aux seuls autorisés par UpdateBlockDescriptionData
2877
- this._assertNotEntityType("updateDescription", ["badges", "news"]);
2884
+ this._assertNotEntityType("updateDescription", ["badges", "news", "actions"]);
2878
2885
  const typeElement = this.getEntityType() as UpdateBlockDescriptionData["typeElement"];
2879
2886
 
2880
2887
  const payload: UpdateBlockDescriptionData = {
@@ -2903,7 +2910,7 @@ export class BaseEntity<TServerData = any> {
2903
2910
  }
2904
2911
 
2905
2912
  // Réduit le type de l'entité aux seuls autorisés par UpdateBlockInfoData
2906
- this._assertNotEntityType("updateInfo", ["badges", "news"]);
2913
+ this._assertNotEntityType("updateInfo", ["badges", "news", "actions"]);
2907
2914
  const typeElement = this.getEntityType() as UpdateBlockInfoData["typeElement"];
2908
2915
 
2909
2916
  const payload: UpdateBlockInfoData = {
@@ -2930,7 +2937,7 @@ export class BaseEntity<TServerData = any> {
2930
2937
  }
2931
2938
 
2932
2939
  // Réduit le type de l'entité aux seuls autorisés par UpdateBlockSocialData
2933
- this._assertNotEntityType("updateSocial", ["badges", "news", "poi", "events"]);
2940
+ this._assertNotEntityType("updateSocial", ["badges", "news", "poi", "events", "actions"]);
2934
2941
  const typeElement = this.getEntityType() as UpdateBlockSocialData["typeElement"];
2935
2942
 
2936
2943
  const payload: UpdateBlockSocialData = {
@@ -2996,7 +3003,7 @@ export class BaseEntity<TServerData = any> {
2996
3003
  }
2997
3004
 
2998
3005
  // Réduit le type de l'entité aux seuls autorisés par UpdateBlockLocalityData
2999
- this._assertNotEntityType("updateLocality", ["badges", "news", "comments", "answers"]);
3006
+ this._assertNotEntityType("updateLocality", ["badges", "news", "comments", "answers", "actions"]);
3000
3007
 
3001
3008
  // Garde runtime pour s'assurer que 'address' est bien fourni
3002
3009
  if (!("address" in data)) {
@@ -3040,7 +3047,7 @@ export class BaseEntity<TServerData = any> {
3040
3047
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
3041
3048
  }
3042
3049
 
3043
- this._assertNotEntityType("updateSlug", ["badges", "news", "poi", "comments"]);
3050
+ this._assertNotEntityType("updateSlug", ["badges", "news", "poi", "comments", "actions"]);
3044
3051
 
3045
3052
  const type = this.getEntityType() as CheckData["type"];
3046
3053
 
@@ -3071,7 +3078,7 @@ export class BaseEntity<TServerData = any> {
3071
3078
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
3072
3079
  }
3073
3080
 
3074
- this._assertNotEntityType("updateImageProfil", ["badges", "news", "comments", "answers"]);
3081
+ this._assertNotEntityType("updateImageProfil", ["badges", "news", "comments", "answers", "actions"]);
3075
3082
 
3076
3083
  const folder = this.getEntityType() as NonNullable<ProfilImageData["pathParams"]>["folder"];
3077
3084
 
@@ -3098,7 +3105,7 @@ export class BaseEntity<TServerData = any> {
3098
3105
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
3099
3106
  }
3100
3107
 
3101
- this._assertNotEntityType("updateImageBanner", ["badges", "news", "comments", "answers"]);
3108
+ this._assertNotEntityType("updateImageBanner", ["badges", "news", "comments", "answers", "actions"]);
3102
3109
 
3103
3110
  const folder = this.getEntityType() as NonNullable<ProfilBannerData["pathParams"]>["folder"];
3104
3111
 
@@ -3235,6 +3242,20 @@ export class BaseEntity<TServerData = any> {
3235
3242
  return entity as News;
3236
3243
  }
3237
3244
 
3245
+ /**
3246
+ * Crée une instance d'Action et la récupère si nécessaire.
3247
+ *
3248
+ * Par défaut une action ne peut être créée que depuis un Project — les autres
3249
+ * entités lèvent une erreur. Override sur Project pour activer le comportement.
3250
+ *
3251
+ * @param actionData - Les données pour initialiser l'action.
3252
+ * @returns Une promesse qui résout l'objet Action créé.
3253
+ * @throws {ApiError} Sur les entités non supportées.
3254
+ */
3255
+ async action(_actionData: ActionInput = {}): Promise<Action> {
3256
+ throw new ApiError(`action n'existe pas dans ${this.constructor.name}`, 501);
3257
+ }
3258
+
3238
3259
  /**
3239
3260
  * Récupérer les organisations d'une entitée : la liste des organisations dont l'entité est membre ou admin valide.
3240
3261
  * Constant : GET_ORGANIZATIONS_ADMIN | GET_ORGANIZATIONS_NO_ADMIN
@@ -3481,7 +3502,7 @@ export class BaseEntity<TServerData = any> {
3481
3502
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
3482
3503
  }
3483
3504
 
3484
- this._assertNotEntityType("getNews", ["badges", "news", "poi", "events", "comments", "answers"]);
3505
+ this._assertNotEntityType("getNews", ["badges", "news", "poi", "events", "comments", "answers", "actions"]);
3485
3506
 
3486
3507
  const type = this.getEntityType() as NonNullable<GetNewsData["pathParams"]>["type"];
3487
3508
 
@@ -3520,7 +3541,7 @@ export class BaseEntity<TServerData = any> {
3520
3541
  throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
3521
3542
  }
3522
3543
 
3523
- this._assertNotEntityType("getGallery", ["badges", "news", "poi", "events", "comments", "answers"]);
3544
+ this._assertNotEntityType("getGallery", ["badges", "news", "poi", "events", "comments", "answers", "actions"]);
3524
3545
 
3525
3546
  const type = this.getEntityType() as NonNullable<GetGalleryData["pathParams"]>["type"];
3526
3547
 
@@ -3573,7 +3594,7 @@ export class BaseEntity<TServerData = any> {
3573
3594
  return [];
3574
3595
  }
3575
3596
 
3576
- this._assertNotEntityType("searchMembers", ["badges", "news", "poi", "comments"]);
3597
+ this._assertNotEntityType("searchMembers", ["badges", "news", "poi", "comments", "actions"]);
3577
3598
 
3578
3599
  result.forEach(item => {
3579
3600
  for (const key of Object.keys(item)) {
@@ -3733,7 +3754,7 @@ export class BaseEntity<TServerData = any> {
3733
3754
  throw new ApiError("Utilisateur non connecté.", 401);
3734
3755
  }
3735
3756
 
3736
- this._assertNotEntityType("follow", ["badges", "news", "poi", "comments"]);
3757
+ this._assertNotEntityType("follow", ["badges", "news", "poi", "comments", "actions"]);
3737
3758
 
3738
3759
  const parentType = this.getEntityType() as FollowData["parentType"];
3739
3760
 
@@ -3777,7 +3798,7 @@ export class BaseEntity<TServerData = any> {
3777
3798
 
3778
3799
  const userLink = this.userContext?.serverData?.links?.["follows"]?.[this.id] || null;
3779
3800
 
3780
- this._assertNotEntityType("unfollow", ["badges", "news", "poi"]);
3801
+ this._assertNotEntityType("unfollow", ["badges", "news", "poi", "actions"]);
3781
3802
 
3782
3803
  const parentType = this.getEntityType() as DisconnectData["parentType"];
3783
3804
 
@@ -442,7 +442,17 @@ export interface GetElementsAboutData {
442
442
  /**
443
443
  * Type d'entité
444
444
  */
445
- type: "citoyens" | "projects" | "organizations" | "events" | "poi" | "badges" | "answers" | "classifieds" | "forms";
445
+ type:
446
+ | "citoyens"
447
+ | "projects"
448
+ | "organizations"
449
+ | "events"
450
+ | "poi"
451
+ | "badges"
452
+ | "answers"
453
+ | "classifieds"
454
+ | "forms"
455
+ | "actions";
446
456
  /**
447
457
  * ID de l'utilisateur ou de l'entité
448
458
  */
@@ -5269,9 +5279,15 @@ export interface UpdatePathValueData {
5269
5279
  /**
5270
5280
  * Valeur à mettre à jour (peut être un objet, un tableau, une chaîne, etc.)
5271
5281
  */
5272
- value: {
5273
- [k: string]: unknown;
5274
- };
5282
+ value:
5283
+ | string
5284
+ | number
5285
+ | boolean
5286
+ | {
5287
+ [k: string]: unknown;
5288
+ }
5289
+ | unknown[]
5290
+ | null;
5275
5291
  [k: string]: unknown;
5276
5292
  }
5277
5293
 
@@ -5500,9 +5516,9 @@ export interface CostumProjectActionRequestNewData {
5500
5516
  */
5501
5517
  name: string;
5502
5518
  /**
5503
- * Statut de l'action
5519
+ * Statut de l'action. Sert aussi de trigger côté backend : 'done' ajoute startDate/endDate auto, 'tracking' active le mode tracking, 'discuter'/'next'/'totest' ajoutent un tag.
5504
5520
  */
5505
- status: string;
5521
+ status: "todo" | "done" | "tracking" | "discuter" | "next" | "totest" | "disabled" | "closed";
5506
5522
  /**
5507
5523
  * ID du projet parent
5508
5524
  */
@@ -5512,18 +5528,74 @@ export interface CostumProjectActionRequestNewData {
5512
5528
  */
5513
5529
  parentType: "projects";
5514
5530
  /**
5515
- * Montant financé
5531
+ * Montant financé (default: 1)
5516
5532
  */
5517
- credits: number;
5533
+ credits?: number;
5518
5534
  /**
5519
5535
  * Jalon associé à l'action
5520
5536
  */
5521
- milestone: {
5537
+ milestone?: {
5522
5538
  /**
5523
5539
  * ID du jalon associé
5524
5540
  */
5525
5541
  milestoneId: string;
5542
+ /**
5543
+ * Date de début du jalon (ISO 8601, convertie en MongoDate UTC côté backend)
5544
+ */
5545
+ startDate?: string;
5546
+ /**
5547
+ * Date de fin du jalon (ISO 8601, convertie en MongoDate UTC côté backend)
5548
+ */
5549
+ endDate?: string;
5526
5550
  };
5551
+ /**
5552
+ * Date et heure de début (ISO 8601 avec offset)
5553
+ */
5554
+ startDate?: string;
5555
+ /**
5556
+ * Date et heure de fin (ISO 8601 avec offset)
5557
+ */
5558
+ endDate?: string;
5559
+ /**
5560
+ * Temps passé en minutes (utilisé pour calculer endDate quand status=done)
5561
+ */
5562
+ timeSpent?: number;
5563
+ /**
5564
+ * Nombre minimum de contributeurs
5565
+ */
5566
+ min?: number;
5567
+ /**
5568
+ * Nombre maximum de contributeurs (doit être >= min côté backend)
5569
+ */
5570
+ max?: number;
5571
+ /**
5572
+ * Niveau d'importance de l'action
5573
+ */
5574
+ importance?: string;
5575
+ /**
5576
+ * ID de la room parente. Si absent, auto-créée par Room::getOrCreateActionRoom côté backend
5577
+ */
5578
+ idParentRoom?: string;
5579
+ /**
5580
+ * Si true, ajoute l'auteur courant comme contributor admin. ATTENTION : écrasé si 'mentions' est aussi fourni.
5581
+ */
5582
+ is_contributor?: boolean;
5583
+ /**
5584
+ * userId à assigner comme contributor admin. ATTENTION : écrasé si 'mentions' est aussi fourni.
5585
+ */
5586
+ assign?: string;
5587
+ /**
5588
+ * URLs externes associées à l'action (tableau de strings, ou "" pour vider). Passthrough côté backend.
5589
+ */
5590
+ urls?: string[] | "";
5591
+ /**
5592
+ * Liste des contributeurs assignés à l'action par username. Résolu côté backend en links.contributors.{userId}. ATTENTION : ÉCRASE 'assign' et 'is_contributor' si fournis ensemble.
5593
+ */
5594
+ mentions?: string[];
5595
+ /**
5596
+ * Liste des tags associés à l'action. BUG CONNU BACKEND : les tags sont dupliqués dans la réponse (array_merge avec soi-même côté PHP).
5597
+ */
5598
+ tags?: string[];
5527
5599
  [k: string]: unknown;
5528
5600
  }
5529
5601
 
@@ -1,7 +1,7 @@
1
1
  // TypeScript native types
2
2
  import type { CollectionType } from "../types/entities.js";
3
3
 
4
- export type EntityTag = "User" | "Organization" | "Project" | "Event" | "Poi" | "Badge" | "News" | "Comment" | "Answer" | "Form" | "Classified";
4
+ export type EntityTag = "User" | "Organization" | "Project" | "Event" | "Poi" | "Badge" | "News" | "Comment" | "Answer" | "Form" | "Classified" | "Action";
5
5
  export type CollectionKey = CollectionType; // Alias pour compatibilité
6
6
  type BaseEntity = import("./BaseEntity.js").BaseEntity<any>;
7
7
  type ApiClient = import("../ApiClient.js").default;
@@ -113,7 +113,8 @@ function _getCollectionFromTag(tag: EntityTag): CollectionKey | null {
113
113
  "Comment": "comments",
114
114
  "Answer": "answers",
115
115
  "Form": "forms",
116
- "Classified": "classifieds"
116
+ "Classified": "classifieds",
117
+ "Action": "actions"
117
118
  };
118
119
  return map[tag] || null;
119
120
  }
@@ -138,6 +139,7 @@ function _getEntityMeta(entityType: CollectionKey, __entityTag: EntityTag): Enti
138
139
  Answer: selfTag === "Answer" ? selfClass : EntityRegistry.get("Answer"),
139
140
  Form: selfTag === "Form" ? selfClass : EntityRegistry.get("Form"),
140
141
  Classified: selfTag === "Classified" ? selfClass : EntityRegistry.get("Classified"),
142
+ Action: selfTag === "Action" ? selfClass : EntityRegistry.get("Action"),
141
143
  };
142
144
 
143
145
  const map: Record<CollectionKey, EntityMeta> = {
@@ -147,16 +149,17 @@ function _getEntityMeta(entityType: CollectionKey, __entityTag: EntityTag): Enti
147
149
  events: { entityClass: commonDeps.Event as EntityClass, deps: { ...commonDeps, Badge: undefined } },
148
150
  poi: { entityClass: commonDeps.Poi as EntityClass, deps: { ...commonDeps, Badge: undefined, News: undefined, Comment: undefined } },
149
151
  news: { entityClass: commonDeps.News as EntityClass, deps: { ...commonDeps } },
150
- badges: { entityClass: commonDeps.Badge as EntityClass, deps: {
151
- EndpointApi: commonDeps.EndpointApi,
152
- User: commonDeps.User,
153
- Organization: commonDeps.Organization,
154
- Project: commonDeps.Project
152
+ badges: { entityClass: commonDeps.Badge as EntityClass, deps: {
153
+ EndpointApi: commonDeps.EndpointApi,
154
+ User: commonDeps.User,
155
+ Organization: commonDeps.Organization,
156
+ Project: commonDeps.Project
155
157
  } },
156
158
  comments: { entityClass: commonDeps.Comment as EntityClass, deps: { ...commonDeps } },
157
159
  answers: { entityClass: commonDeps.Answer as EntityClass, deps: { ...commonDeps } },
158
160
  forms: { entityClass: commonDeps.Form as EntityClass, deps: { ...commonDeps } },
159
161
  classifieds: { entityClass: commonDeps.Classified as EntityClass, deps: { ...commonDeps, Badge: undefined, News: undefined } },
162
+ actions: { entityClass: commonDeps.Action as EntityClass, deps: { ...commonDeps } },
160
163
  };
161
164
 
162
165
  return map[entityType] || null;
@@ -215,6 +218,10 @@ export function createFromCollection(collection: CollectionKey, parent: ApiClien
215
218
  classifieds: {
216
219
  entityTag: "Classified",
217
220
  meta: tag => _buildMeta(tag, { remove: ["Badge","News"] })
221
+ },
222
+ actions: {
223
+ entityTag: "Action",
224
+ meta: tag => _buildMeta(tag, { remove: [] })
218
225
  }
219
226
  };
220
227
  const entry = _collectionMap[collection];
@@ -248,7 +255,8 @@ function _buildMeta(tag: EntityTag, options: { remove: string[] }): EntityMeta {
248
255
  Comment: EntityRegistry.get("Comment"),
249
256
  Answer: EntityRegistry.get("Answer"),
250
257
  Form: EntityRegistry.get("Form"),
251
- Classified: EntityRegistry.get("Classified")
258
+ Classified: EntityRegistry.get("Classified"),
259
+ Action: EntityRegistry.get("Action")
252
260
  };
253
261
  // inject self
254
262
  (allDeps as any)[tag] = EntityClass;
@@ -3,6 +3,7 @@ import { BaseEntity } from "./BaseEntity.js";
3
3
  import { createSocialTransform, transformEntityRefs } from "../types/transforms.js";
4
4
 
5
5
 
6
+ import type { Action } from "./Action.js";
6
7
  import type { PaginatorPage, PaginatorState } from "./BaseEntity.js";
7
8
  import type { AddProjectData, GetContributorsAdminData, GetContributorsNoAdminData } from "./EndpointApi.types.js";
8
9
  import type { Organization } from "./Organization.js";
@@ -342,10 +343,25 @@ export class Project extends BaseEntity<ProjectItemNormalized> {
342
343
  * Crée une instance de news et la récupère si nécessaire.
343
344
  */
344
345
  override async news(newsData: Parameters<BaseEntity<ProjectItemNormalized>["news"]>[0] = {}) {
345
- // TODO: qui peut créer une news sur le projet ?
346
+ if(!newsData?.id && !this.isContributor()){
347
+ throw new ApiError("Vous n'avez pas les droits pour créer une news dans ce projet", 403);
348
+ }
346
349
  return super.news(newsData);
347
350
  }
348
351
 
352
+ /**
353
+ * {@inheritDoc BaseEntity#action}
354
+ *
355
+ * Crée une instance d'Action liée à ce projet et la récupère si nécessaire.
356
+ */
357
+ override async action(actionData: Parameters<BaseEntity<ProjectItemNormalized>["action"]>[0] = {}) {
358
+ if(!actionData?.id && !this.isAdmin()){
359
+ throw new ApiError("Vous n'avez pas les droits pour créer une action dans ce projet", 403);
360
+ }
361
+ const entity = await this.entity("actions", actionData);
362
+ return entity as Action;
363
+ }
364
+
349
365
  /**
350
366
  * ───────────────────────────────
351
367
  * Lien utilisateur ↔ projet
package/src/api/User.ts CHANGED
@@ -113,6 +113,7 @@ export class User extends BaseEntity<UserItemNormalized> {
113
113
  Comment: typeof import("./Comment.js").Comment;
114
114
  Answer: typeof import("./Answer.js").Answer;
115
115
  Classified?: typeof import("./Classified.js").Classified;
116
+ Action?: typeof import("./Action.js").Action;
116
117
  }
117
118
  ) {
118
119
  if(!deps.EndpointApi){
@@ -1,5 +1,6 @@
1
1
  import ApiClient from "../ApiClient.js";
2
2
  import { ApiError, ApiResponseError } from "../error.js";
3
+ import { Action } from "./Action.js";
3
4
  import { Answer } from "./Answer.js";
4
5
  import { Badge } from "./Badge.js";
5
6
  import { Classified } from "./Classified.js";
@@ -68,7 +69,7 @@ export class UserApi {
68
69
  this.loggedUser = new User(
69
70
  this.client,
70
71
  response.data.user,
71
- { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified }
72
+ { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified, Action }
72
73
  );
73
74
  return this.loggedUser;
74
75
  });
@@ -90,7 +91,7 @@ export class UserApi {
90
91
  this.loggedUser = new User(
91
92
  this.client,
92
93
  { id: this.client.userId },
93
- { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified }
94
+ { EndpointApi, Organization, Project, Event, Poi, Badge, News, Comment, Answer, Classified, Action }
94
95
  );
95
96
 
96
97
  return this.loggedUser;