@communecter/cocolight-api-client 1.0.135 → 1.0.137
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/dist/cocolight-api-client.browser.js +2 -2
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +1 -1
- package/src/Api.ts +26 -0
- package/src/api/Action.ts +4 -8
- package/src/api/Answer.ts +11 -11
- package/src/api/BaseEntity.ts +242 -9
- package/src/api/Comment.ts +26 -1
- package/src/api/EndpointApi.types.ts +12 -0
- package/src/api/News.ts +26 -1
- package/src/api/Organization.ts +1 -10
- package/src/endpoints.module.ts +1 -1
- package/src/index.ts +28 -2
- package/types/Api.d.ts +18 -0
- package/types/api/Action.d.ts +3 -1
- package/types/api/Answer.d.ts +11 -11
- package/types/api/BaseEntity.d.ts +139 -6
- package/types/api/Comment.d.ts +16 -0
- package/types/api/EndpointApi.types.d.ts +12 -0
- package/types/api/News.d.ts +16 -0
- package/types/endpoints.module.d.ts +304 -0
- package/types/index.d.ts +3 -1
package/package.json
CHANGED
package/src/Api.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { User } from "./api/User.js";
|
|
|
16
16
|
import { UserApi } from "./api/UserApi.js";
|
|
17
17
|
import { ApiAuthenticationError, ApiClientError, ApiError, ApiResponseError } from "./error.js";
|
|
18
18
|
|
|
19
|
+
import type { SearchTagsData } from "./api/EndpointApi.types.js";
|
|
19
20
|
import type ApiClient from "./ApiClient.js";
|
|
20
21
|
import type { ApiClientOptions } from "./ApiClient.js";
|
|
21
22
|
import type { GetElementsKeyResponse } from "./types/api-responses.js";
|
|
@@ -310,6 +311,31 @@ export default class Api {
|
|
|
310
311
|
return new EndpointApi(this._client);
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Recherche des tags correspondant à un mot-clé.
|
|
316
|
+
*
|
|
317
|
+
* Wrap de `SEARCH_TAGS` (auth=none) — accessible même non connecté.
|
|
318
|
+
* Méthode exposée sur la facade `Api` (pas d'entité hôte naturelle pour
|
|
319
|
+
* un index global de tags).
|
|
320
|
+
*
|
|
321
|
+
* @param q - Mot-clé de recherche (au moins 1 caractère).
|
|
322
|
+
* @returns Tableau de résultats : soit `[{tag}]` si aucun match (echo du
|
|
323
|
+
* terme), soit `[{_id, tag, field_length}, ...]` avec les tags
|
|
324
|
+
* existants triés par `field_length`.
|
|
325
|
+
* @throws {ApiError} 400 si `q` vide.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* const results = await api.searchTags("permac");
|
|
329
|
+
* results.forEach(t => console.log(t.tag, t.field_length));
|
|
330
|
+
*/
|
|
331
|
+
async searchTags(q: string): Promise<unknown> {
|
|
332
|
+
if (typeof q !== "string" || q.length === 0) {
|
|
333
|
+
throw new ApiError("Le mot-clé `q` est requis pour searchTags.", 400);
|
|
334
|
+
}
|
|
335
|
+
const payload: SearchTagsData = { pathParams: { q } };
|
|
336
|
+
return this.endpointApi.searchTags(payload);
|
|
337
|
+
}
|
|
338
|
+
|
|
313
339
|
/**
|
|
314
340
|
* Déconnecte l'utilisateur et réinitialise la session.
|
|
315
341
|
*/
|
package/src/api/Action.ts
CHANGED
|
@@ -557,16 +557,12 @@ export class Action extends BaseEntity<ActionItemNormalized> {
|
|
|
557
557
|
// ───────────────────────────────────────────────────────────────────────────
|
|
558
558
|
|
|
559
559
|
/**
|
|
560
|
-
* Helper privé :
|
|
560
|
+
* Helper privé : délègue à `BaseEntity.updateField()` pour un path donné.
|
|
561
|
+
* Bénéficie du normalize automatique (R0 Date → isoDate, R1 collapse setType,
|
|
562
|
+
* R2 pull + empty → "", R6 drop setType vide).
|
|
561
563
|
*/
|
|
562
564
|
private async _updatePath(path: string, value: unknown, setType?: string): Promise<unknown> {
|
|
563
|
-
return this.
|
|
564
|
-
id: this.id!,
|
|
565
|
-
collection: "actions",
|
|
566
|
-
path,
|
|
567
|
-
value: value as { [k: string]: unknown },
|
|
568
|
-
...(setType ? { setType } : {})
|
|
569
|
-
});
|
|
565
|
+
return this.updateField(path, value, setType ? { setType } : {});
|
|
570
566
|
}
|
|
571
567
|
|
|
572
568
|
/**
|
package/src/api/Answer.ts
CHANGED
|
@@ -167,17 +167,6 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
167
167
|
"voteCount", "vote", "project"
|
|
168
168
|
];
|
|
169
169
|
|
|
170
|
-
/**
|
|
171
|
-
* {@inheritDoc BaseEntity#isAuthor}
|
|
172
|
-
*
|
|
173
|
-
* Override Answer : le champ d'autorité est `serverData.user` (pas `creator`
|
|
174
|
-
* comme la version par défaut de BaseEntity). Le `user` peut être :
|
|
175
|
-
* - un `string` (ID MongoDB brut renvoyé par le backend)
|
|
176
|
-
* - une instance `User` (après `_transformServerData` qui le link en entité)
|
|
177
|
-
*
|
|
178
|
-
* Les pré-conditions (connexion, id, serverData) sont vérifiées comme dans
|
|
179
|
-
* la version de base — `silent: true` (défaut) renvoie `false` au lieu de throw.
|
|
180
|
-
*/
|
|
181
170
|
/**
|
|
182
171
|
* {@inheritDoc BaseEntity#_isAdminViaHierarchy}
|
|
183
172
|
*
|
|
@@ -214,6 +203,17 @@ export class Answer extends BaseEntity<AnswerItemNormalized> {
|
|
|
214
203
|
return false;
|
|
215
204
|
}
|
|
216
205
|
|
|
206
|
+
/**
|
|
207
|
+
* {@inheritDoc BaseEntity#isAuthor}
|
|
208
|
+
*
|
|
209
|
+
* Override Answer : le champ d'autorité est `serverData.user` (pas `creator`
|
|
210
|
+
* comme la version par défaut de BaseEntity). Le `user` peut être :
|
|
211
|
+
* - un `string` (ID MongoDB brut renvoyé par le backend)
|
|
212
|
+
* - une instance `User` (après `_transformServerData` qui le link en entité)
|
|
213
|
+
*
|
|
214
|
+
* Les pré-conditions (connexion, id, serverData) sont vérifiées comme dans
|
|
215
|
+
* la version de base — `silent: true` (défaut) renvoie `false` au lieu de throw.
|
|
216
|
+
*/
|
|
217
217
|
override isAuthor(options?: { silent?: boolean }): boolean {
|
|
218
218
|
const silent = options?.silent ?? true;
|
|
219
219
|
try {
|
package/src/api/BaseEntity.ts
CHANGED
|
@@ -59,7 +59,8 @@ import type {
|
|
|
59
59
|
UnlinkMediawikiAccountData,
|
|
60
60
|
GetMediawikiContributionsData,
|
|
61
61
|
CoremuOperationData,
|
|
62
|
-
DeleteDocumentByIdData
|
|
62
|
+
DeleteDocumentByIdData,
|
|
63
|
+
UpdatePathValueData
|
|
63
64
|
} from "./EndpointApi.types.js";
|
|
64
65
|
import type { GetElementsKeyResponse } from "../types/api-responses.js";
|
|
65
66
|
import type { TransformsMap } from "../types/entities.js";
|
|
@@ -119,6 +120,52 @@ type ParentLike = BaseEntity<any> & { apiClient: ApiClient, userContext?: User |
|
|
|
119
120
|
*/
|
|
120
121
|
export type SearchCostumVariant = "default" | "navigator-tl";
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Types de coercion backend supportés par `UPDATE_PATH_VALUE.setType`.
|
|
125
|
+
*
|
|
126
|
+
* Référence côté backend : `string_set_type` (PHP) accepte ces 5 valeurs +
|
|
127
|
+
* tout autre string (passthrough — pas de coercion). Le `& Record<never, never>`
|
|
128
|
+
* garde l'autocomplete des literals tout en acceptant des strings custom.
|
|
129
|
+
*
|
|
130
|
+
* - `"int"` → `intval()` (PHP)
|
|
131
|
+
* - `"float"` → `floatval()`
|
|
132
|
+
* - `"boolean"` / `"bool"` → `FILTER_VALIDATE_BOOLEAN` (alias)
|
|
133
|
+
* - `"isoDate"` → parse multi-format → `MongoDate`
|
|
134
|
+
* Formats acceptés : ISO 8601 natif, `m-d-Y`, `d-m-Y`, `{sec, usec}` legacy,
|
|
135
|
+
* string `"now"` (date actuelle), slashes auto-convertis en tirets.
|
|
136
|
+
* - `"timestamp"` → parse → Unix seconds (`int`)
|
|
137
|
+
*/
|
|
138
|
+
export type SetTypeValue =
|
|
139
|
+
| "int"
|
|
140
|
+
| "float"
|
|
141
|
+
| "boolean"
|
|
142
|
+
| "bool"
|
|
143
|
+
| "isoDate"
|
|
144
|
+
| "timestamp"
|
|
145
|
+
| (string & Record<never, never>);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Magic values reconnues par `UPDATE_PATH_VALUE` côté backend.
|
|
149
|
+
* Utilisable comme `value` ou `path` selon le cas.
|
|
150
|
+
*/
|
|
151
|
+
export const UPDATE_PATH_MAGIC = {
|
|
152
|
+
/**
|
|
153
|
+
* `value: "updatedTime"` → backend remplace par `time()` Unix seconds courant
|
|
154
|
+
* (utile pour `lastEditedAt`, audit trail, etc. sans avoir à fournir un timestamp côté client).
|
|
155
|
+
*/
|
|
156
|
+
CURRENT_TIME: "updatedTime",
|
|
157
|
+
/**
|
|
158
|
+
* `value: "now"` avec `setType: "isoDate"` → résolu côté backend en `MongoDate(now)`.
|
|
159
|
+
*/
|
|
160
|
+
NOW: "now",
|
|
161
|
+
/**
|
|
162
|
+
* `path: "allToRoot"` → la `value` (qui doit être un objet) est exposée à la
|
|
163
|
+
* racine de l'entité : chaque clé devient un champ racine. Combiner avec
|
|
164
|
+
* `updatePartial: true` pour un merge avec préservation des autres champs.
|
|
165
|
+
*/
|
|
166
|
+
PATH_ALL_TO_ROOT: "allToRoot",
|
|
167
|
+
} as const;
|
|
168
|
+
|
|
122
169
|
const SEARCH_COSTUM_ENDPOINTS = {
|
|
123
170
|
"default": "globalAutocompleteCostum",
|
|
124
171
|
"navigator-tl": "navigatorGettl",
|
|
@@ -1259,6 +1306,181 @@ export class BaseEntity<TServerData = any> {
|
|
|
1259
1306
|
await this.callIsConnected(() => this.endpointApi.deleteDocumentById(payload));
|
|
1260
1307
|
}
|
|
1261
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* Met à jour un champ unique (ou multi-fields via `updatePartial`) sur cette
|
|
1311
|
+
* entité via `UPDATE_PATH_VALUE`.
|
|
1312
|
+
*
|
|
1313
|
+
* **Cas d'usage couverts** :
|
|
1314
|
+
* - `$set` simple : `entity.updateField("name", "Nouveau nom")`
|
|
1315
|
+
* - `$unset` : `entity.updateField("description", null)`
|
|
1316
|
+
* - `$push` array : `entity.updateField("tags", "x", { arrayForm: true })`
|
|
1317
|
+
* - `$pull` array : `entity.updateField("tags.3", null, { pull: "tags" })`
|
|
1318
|
+
* - `$pullAll` array : `entity.updateField("tags", ["a","b"], { arrayForm: true, pull: "tags" })`
|
|
1319
|
+
* - Coercion type : `entity.updateField("credits", 100, { setType: "int" })`
|
|
1320
|
+
* - Date auto : `entity.updateField("startDate", new Date())` → setType "isoDate" + ISO string
|
|
1321
|
+
* - Multi-field merge : `entity.updateField("address", {...}, { updatePartial: true })`
|
|
1322
|
+
* - Auth sub-form : `entity.updateField("...", value, { formParentId: "..." })`
|
|
1323
|
+
*
|
|
1324
|
+
* **Magic values** :
|
|
1325
|
+
* - `value: "updatedTime"` → backend remplace par `time()` Unix seconds
|
|
1326
|
+
* - `value: "now"` + `setType: "isoDate"` → résout en date actuelle
|
|
1327
|
+
* - `path: "allToRoot"` → value exposée à la racine de l'entité
|
|
1328
|
+
*
|
|
1329
|
+
* Le payload est normalisé via `_normalizeUpdatePathPayload` avant envoi
|
|
1330
|
+
* (cf. les 4 règles documentées sur cette méthode).
|
|
1331
|
+
*
|
|
1332
|
+
* @param path - Chemin MongoDB dot-notation (ex: `"address.street"`, `"answers.aapStep1.depense.3"`).
|
|
1333
|
+
* @param value - Valeur à poser. Accepte `Date` (auto-convertie en ISO + setType isoDate).
|
|
1334
|
+
* @param opts - Options : `setType`, `arrayForm`, `pull`, `updatePartial`, `formParentId`, `edit`.
|
|
1335
|
+
* @returns Réponse brute de l'API.
|
|
1336
|
+
* @throws {ApiError} 404 si l'entité n'a pas d'id.
|
|
1337
|
+
* @throws {ApiError} 400 si `value === undefined`, `path` vide, ou conflit `arrayForm` + `pull` sans value array, ou `updatePartial` sans value object.
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* // Update simple
|
|
1341
|
+
* await org.updateField("name", "Nouvelle organisation");
|
|
1342
|
+
*
|
|
1343
|
+
* @example
|
|
1344
|
+
* // Date instance auto-convertie
|
|
1345
|
+
* await action.updateField("startDate", new Date());
|
|
1346
|
+
*
|
|
1347
|
+
* @example
|
|
1348
|
+
* // Multi-field merge (préserve les autres clés de address)
|
|
1349
|
+
* await org.updateField("address", {
|
|
1350
|
+
* street: "123 rue X",
|
|
1351
|
+
* city: "Paris",
|
|
1352
|
+
* }, { updatePartial: true });
|
|
1353
|
+
*
|
|
1354
|
+
* @example
|
|
1355
|
+
* // Push dans array
|
|
1356
|
+
* await project.updateField("tags", "permaculture", { arrayForm: true });
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* // Pull from array (avec normalisation R2 : null → "")
|
|
1360
|
+
* await project.updateField("oceco.milestones.3", null, { pull: "oceco.milestones" });
|
|
1361
|
+
*/
|
|
1362
|
+
async updateField(
|
|
1363
|
+
path: string,
|
|
1364
|
+
value: unknown,
|
|
1365
|
+
opts: {
|
|
1366
|
+
setType?: SetTypeValue | Array<{ path: string; type: SetTypeValue }>;
|
|
1367
|
+
arrayForm?: boolean;
|
|
1368
|
+
pull?: string;
|
|
1369
|
+
updatePartial?: boolean;
|
|
1370
|
+
formParentId?: string;
|
|
1371
|
+
edit?: boolean;
|
|
1372
|
+
} = {},
|
|
1373
|
+
): Promise<unknown> {
|
|
1374
|
+
// R3 — value: undefined → throw (distinction nette vs null pour $unset)
|
|
1375
|
+
if (value === undefined) {
|
|
1376
|
+
throw new ApiError(
|
|
1377
|
+
"`value` ne peut pas être undefined. Utiliser `null` pour $unset.",
|
|
1378
|
+
400
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
if (!this.id) {
|
|
1382
|
+
throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
|
|
1383
|
+
}
|
|
1384
|
+
// R4 — path validation
|
|
1385
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1386
|
+
throw new ApiError("`path` est requis et doit être une string non vide.", 400);
|
|
1387
|
+
}
|
|
1388
|
+
// R5 — arrayForm + pull : autorisé uniquement si value est array ($pullAll)
|
|
1389
|
+
if (opts.arrayForm && opts.pull) {
|
|
1390
|
+
if (!Array.isArray(value)) {
|
|
1391
|
+
throw new ApiError(
|
|
1392
|
+
"`arrayForm` + `pull` requièrent value: array (mode $pullAll). "
|
|
1393
|
+
+ "Pour un $pull simple, utiliser uniquement `pull` (sans arrayForm).",
|
|
1394
|
+
400
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
// R9 — updatePartial requiert value: object
|
|
1399
|
+
if (opts.updatePartial && (typeof value !== "object" || value === null || Array.isArray(value))) {
|
|
1400
|
+
throw new ApiError(
|
|
1401
|
+
"`updatePartial: true` requiert value: object (les sous-clés deviennent des sub-paths).",
|
|
1402
|
+
400
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
// Vérif collection : UPDATE_PATH_VALUE backend exclut badges/news/comments/classifieds
|
|
1406
|
+
// (ces entités ont leurs propres endpoints update — cf. enum schema).
|
|
1407
|
+
this._assertNotEntityType("updateField", ["badges", "news", "comments", "classifieds"]);
|
|
1408
|
+
|
|
1409
|
+
// Construction du payload
|
|
1410
|
+
const payload: UpdatePathValueData = {
|
|
1411
|
+
id: this.id,
|
|
1412
|
+
collection: this.getEntityType() as UpdatePathValueData["collection"],
|
|
1413
|
+
path,
|
|
1414
|
+
value: value as UpdatePathValueData["value"],
|
|
1415
|
+
};
|
|
1416
|
+
if (opts.setType !== undefined) payload.setType = opts.setType;
|
|
1417
|
+
if (opts.arrayForm) payload.arrayForm = true;
|
|
1418
|
+
if (opts.pull) payload.pull = opts.pull;
|
|
1419
|
+
if (opts.updatePartial) payload.updatePartial = true;
|
|
1420
|
+
if (opts.formParentId) payload.formParentId = opts.formParentId;
|
|
1421
|
+
if (opts.edit) payload.edit = true;
|
|
1422
|
+
|
|
1423
|
+
// Normalisation wire format
|
|
1424
|
+
return this.endpointApi.updatePathValue(this._normalizeUpdatePathPayload(payload));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Normalise le payload `updatePathValue` avant envoi au backend.
|
|
1429
|
+
*
|
|
1430
|
+
* Règles appliquées (4) :
|
|
1431
|
+
* - **R0** : `value instanceof Date` → `value.toISOString()` + `setType: "isoDate"`
|
|
1432
|
+
* (override d'un setType existant — l'intent `Date` est prioritaire).
|
|
1433
|
+
* - **R1** : `setType: [{type:"isoDate"}, ...]` uniforme + `value: string` →
|
|
1434
|
+
* collapse en `setType: "isoDate"` (forme scalaire préférée backend).
|
|
1435
|
+
* - **R2** : `pull` présent + `value: ""|null|undefined` → force `value: ""`
|
|
1436
|
+
* (le backend exige `value: string` quand `pull` est présent — contrainte
|
|
1437
|
+
* schema `allOf`).
|
|
1438
|
+
* - **R6** : `setType: []` array vide → drop silencieusement (évite wire
|
|
1439
|
+
* format inutile, sécurise contre filtres qui ont tout retiré).
|
|
1440
|
+
*
|
|
1441
|
+
* Pour traiter une string ISO comme date, le caller doit passer `setType`
|
|
1442
|
+
* explicitement (`"isoDate"` ou `[{path, type:"isoDate"}]`). Pas d'heuristique
|
|
1443
|
+
* sur les strings pour éviter les faux positifs sur champs texte libre.
|
|
1444
|
+
*
|
|
1445
|
+
* @protected
|
|
1446
|
+
*/
|
|
1447
|
+
protected _normalizeUpdatePathPayload(payload: UpdatePathValueData): UpdatePathValueData {
|
|
1448
|
+
let normalized = payload;
|
|
1449
|
+
|
|
1450
|
+
// R6 — drop setType array vide
|
|
1451
|
+
if (Array.isArray(normalized.setType) && normalized.setType.length === 0) {
|
|
1452
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1453
|
+
const { setType, ...rest } = normalized;
|
|
1454
|
+
normalized = rest as UpdatePathValueData;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// R0 — Date instance → ISO + setType "isoDate"
|
|
1458
|
+
if (normalized.value instanceof Date) {
|
|
1459
|
+
normalized = {
|
|
1460
|
+
...normalized,
|
|
1461
|
+
value: normalized.value.toISOString(),
|
|
1462
|
+
setType: "isoDate",
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// R1 — setType array uniforme isoDate + value string → collapse scalaire
|
|
1467
|
+
const isIsoDateSetTypeArray =
|
|
1468
|
+
Array.isArray(normalized.setType)
|
|
1469
|
+
&& normalized.setType.length > 0
|
|
1470
|
+
&& normalized.setType.every(item => item?.type === "isoDate");
|
|
1471
|
+
if (typeof normalized.value === "string" && isIsoDateSetTypeArray) {
|
|
1472
|
+
normalized = { ...normalized, setType: "isoDate" };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// R2 — pull + value empty → force ""
|
|
1476
|
+
if (normalized.pull
|
|
1477
|
+
&& (normalized.value === "" || normalized.value === null || normalized.value === undefined)) {
|
|
1478
|
+
normalized = { ...normalized, value: "" };
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
return normalized;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1262
1484
|
/**
|
|
1263
1485
|
* Valide les entrées d'upload de fichiers.
|
|
1264
1486
|
*
|
|
@@ -5179,21 +5401,32 @@ export class BaseEntity<TServerData = any> {
|
|
|
5179
5401
|
}
|
|
5180
5402
|
|
|
5181
5403
|
/**
|
|
5182
|
-
*
|
|
5183
|
-
*
|
|
5184
|
-
*
|
|
5185
|
-
*
|
|
5186
|
-
*
|
|
5187
|
-
*
|
|
5404
|
+
* Génère côté backend une nouvelle Answer placeholder rattachée au formulaire
|
|
5405
|
+
* donné (insert en BDD avec nouvel ObjectId) et retourne l'instance Answer
|
|
5406
|
+
* connectée (parent = `this`).
|
|
5407
|
+
*
|
|
5408
|
+
* **Note workflow** : l'Answer renvoyée est en état "placeholder" — pas de
|
|
5409
|
+
* `user` posé côté backend tant que `save()` n'a pas été appelé. Pour
|
|
5410
|
+
* récupérer plus tard via `coformAnswersById`, faire `save()` avec au moins
|
|
5411
|
+
* `answers` posés.
|
|
5412
|
+
*
|
|
5413
|
+
* @param formId - ID du formulaire (24 hex)
|
|
5414
|
+
* @returns Instance `Answer` connectée à `this` (parent = entité courante)
|
|
5415
|
+
* @throws {ApiError} 400 si `formId` est absent ou invalide
|
|
5416
|
+
*
|
|
5417
|
+
* @example
|
|
5418
|
+
* const org = await api.organization({ slug: "monOrga" });
|
|
5419
|
+
* const answer = await org.generateNewAnswerId(formId);
|
|
5420
|
+
* await answer.updateField(`${finder}.${org.id}`, { id: org.id, type: "organizations", name: org.serverData.name });
|
|
5188
5421
|
*/
|
|
5189
5422
|
async generateNewAnswerId(
|
|
5190
5423
|
formId: string
|
|
5191
|
-
): Promise<
|
|
5424
|
+
): Promise<Answer> {
|
|
5192
5425
|
if (!formId || typeof formId !== "string") {
|
|
5193
5426
|
throw new ApiError("formId est requis et doit être une chaîne de caractères.", 400);
|
|
5194
5427
|
}
|
|
5195
5428
|
const result = await this.endpointApi.generateAnswerFromForm({ pathParams: { formId }, action: "new" });
|
|
5196
|
-
return this._linkEntity?.(result.collection, result) ?? result;
|
|
5429
|
+
return (this._linkEntity?.(result.collection, result) ?? result) as Answer;
|
|
5197
5430
|
}
|
|
5198
5431
|
|
|
5199
5432
|
/**
|
package/src/api/Comment.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiError } from "../error.js";
|
|
2
2
|
import { BaseEntity } from "./BaseEntity.js";
|
|
3
3
|
|
|
4
|
-
import type { AddCommentsData, AddReportAbuseData, AddVoteData, DeleteCommentsData, UpdateCommentsData } from "./EndpointApi.types.js";
|
|
4
|
+
import type { AddCommentsData, AddReportAbuseData, AddVoteData, DeleteCommentsData, ShowVoteData, UpdateCommentsData } from "./EndpointApi.types.js";
|
|
5
5
|
import type { CommentItemNormalized } from "./serverDataType/Comment.js";
|
|
6
6
|
|
|
7
7
|
export class Comment extends BaseEntity<CommentItemNormalized> {
|
|
@@ -222,6 +222,31 @@ export class Comment extends BaseEntity<CommentItemNormalized> {
|
|
|
222
222
|
return await this.callIsConnected(() => this.endpointApi.addVote(payload));
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Récupère la liste des votes (like, love, etc.) sur ce commentaire.
|
|
227
|
+
*
|
|
228
|
+
* Wrap de `SHOW_VOTE` avec auto-injection du `type` (`"comments"`) et de
|
|
229
|
+
* `this.id` dans `pathParams`. Le retour contient `vote` (votes individuels
|
|
230
|
+
* indexés par userId) et `voteCount` (compteurs par statut).
|
|
231
|
+
*
|
|
232
|
+
* @returns Réponse API : `{ _id, vote, voteCount }` ou variante d'erreur.
|
|
233
|
+
* @throws {ApiError} 404 si le commentaire n'a pas d'id (non enregistré).
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const votes = await comment.getVotes();
|
|
237
|
+
* votes.voteCount?.like; // nombre de likes
|
|
238
|
+
* votes.vote?.[userId]; // vote détaillé d'un user
|
|
239
|
+
*/
|
|
240
|
+
async getVotes(): Promise<unknown> {
|
|
241
|
+
if (!this.id) {
|
|
242
|
+
throw new ApiError(`${this.constructor.name} non enregistré.`, 404);
|
|
243
|
+
}
|
|
244
|
+
const payload: ShowVoteData = {
|
|
245
|
+
pathParams: { type: "comments", id: this.id },
|
|
246
|
+
};
|
|
247
|
+
return this.endpointApi.showVote(payload);
|
|
248
|
+
}
|
|
249
|
+
|
|
225
250
|
/**
|
|
226
251
|
* Signale un abus sur ce commentaire
|
|
227
252
|
*
|
|
@@ -5639,6 +5639,18 @@ export interface UpdatePathValueData {
|
|
|
5639
5639
|
}
|
|
5640
5640
|
| unknown[]
|
|
5641
5641
|
| null;
|
|
5642
|
+
/**
|
|
5643
|
+
* Mode multi-field merge : `value` doit être un objet, chaque clé devient un sub-path et est mise à jour individuellement (vs `$set` qui écrase l'objet entier). Les sous-clés à `null`/`""` sont supprimées (`$unset`). Combinable avec `setType: Array` pour coercion par sous-clé.
|
|
5644
|
+
*/
|
|
5645
|
+
updatePartial?: boolean;
|
|
5646
|
+
/**
|
|
5647
|
+
* ID du Form parent. Utilisé côté backend pour : (1) authorisation hiérarchique sur sub-forms (admin du Form parent autorisé à modifier les sub-inputs), (2) invalidation du cache de traduction du Form parent après modification.
|
|
5648
|
+
*/
|
|
5649
|
+
formParentId?: string;
|
|
5650
|
+
/**
|
|
5651
|
+
* Avec `arrayForm`, désactive le mode `$push` et force `$set` (écrase l'array entier). Cas legacy — préférer omettre `arrayForm` pour écraser un array.
|
|
5652
|
+
*/
|
|
5653
|
+
edit?: boolean;
|
|
5642
5654
|
[k: string]: unknown;
|
|
5643
5655
|
}
|
|
5644
5656
|
|
package/src/api/News.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ApiError, ApiResponseError } from "../error.js";
|
|
|
2
2
|
import { BaseEntity } from "./BaseEntity.js";
|
|
3
3
|
|
|
4
4
|
import type { Comment } from "./Comment.js";
|
|
5
|
-
import type { AddNewsData, UpdateNewsData, DeleteNewsData, AddImageNewsData, AddFileNewsData, GetCommentsData, AddVoteData, AddReportAbuseData, ShareNewsData } from "./EndpointApi.types.js";
|
|
5
|
+
import type { AddNewsData, UpdateNewsData, DeleteNewsData, AddImageNewsData, AddFileNewsData, GetCommentsData, AddVoteData, AddReportAbuseData, ShareNewsData, ShowVoteData } from "./EndpointApi.types.js";
|
|
6
6
|
import type { NewsItemNormalized } from "./serverDataType/News.js";
|
|
7
7
|
|
|
8
8
|
export class News extends BaseEntity<NewsItemNormalized> {
|
|
@@ -366,6 +366,31 @@ export class News extends BaseEntity<NewsItemNormalized> {
|
|
|
366
366
|
return await this.callIsConnected(() => this.endpointApi.addVote(payload));
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Récupère la liste des votes (like, love, etc.) sur cette news.
|
|
371
|
+
*
|
|
372
|
+
* Wrap de `SHOW_VOTE` avec auto-injection du `type` (`"news"`) et de
|
|
373
|
+
* `this.id` dans `pathParams`. Le retour contient `vote` (votes individuels
|
|
374
|
+
* indexés par userId) et `voteCount` (compteurs par statut).
|
|
375
|
+
*
|
|
376
|
+
* @returns Réponse API : `{ _id, vote, voteCount }` ou variante d'erreur.
|
|
377
|
+
* @throws {ApiError} 404 si la news n'a pas d'id (non enregistrée).
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* const votes = await news.getVotes();
|
|
381
|
+
* votes.voteCount?.like; // nombre de likes
|
|
382
|
+
* votes.vote?.[userId]; // vote détaillé d'un user
|
|
383
|
+
*/
|
|
384
|
+
async getVotes(): Promise<unknown> {
|
|
385
|
+
if (!this.id) {
|
|
386
|
+
throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
|
|
387
|
+
}
|
|
388
|
+
const payload: ShowVoteData = {
|
|
389
|
+
pathParams: { type: "news", id: this.id },
|
|
390
|
+
};
|
|
391
|
+
return this.endpointApi.showVote(payload);
|
|
392
|
+
}
|
|
393
|
+
|
|
369
394
|
/**
|
|
370
395
|
* Signale un abus sur cette news
|
|
371
396
|
*
|
package/src/api/Organization.ts
CHANGED
|
@@ -513,16 +513,7 @@ export class Organization extends BaseEntity<OrganizationItemNormalized> {
|
|
|
513
513
|
* await org.updateOpeningHours(openingHours);
|
|
514
514
|
*/
|
|
515
515
|
async updateOpeningHours(hours: OpeningHoursEntry[]): Promise<unknown> {
|
|
516
|
-
|
|
517
|
-
throw new ApiError("L'organisation n'a pas d'ID, impossible de mettre à jour les horaires d'ouverture.", 400);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const result = await this.endpointApi.updatePathValue({
|
|
521
|
-
id: this.id,
|
|
522
|
-
collection: "organizations",
|
|
523
|
-
path: "openingHours",
|
|
524
|
-
value: hours as unknown as { [k: string]: unknown }
|
|
525
|
-
});
|
|
516
|
+
const result = await this.updateField("openingHours", hours);
|
|
526
517
|
await this._refreshIfDirect();
|
|
527
518
|
return result;
|
|
528
519
|
}
|