@communecter/cocolight-api-client 1.0.151 → 1.0.152
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 +1 -1
- 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/BaseEntity.ts +101 -4
- package/src/api/EndpointApi.ts +16 -1
- package/src/api/EndpointApi.types.ts +95 -2
- package/src/api/User.ts +13 -4
- package/src/api/UserApi.ts +16 -5
- package/src/costum/runtime.ts +65 -12
- package/src/endpoints.module.ts +96 -46
- package/types/api/BaseEntity.d.ts +56 -2
- package/types/api/EndpointApi.d.ts +10 -1
- package/types/api/EndpointApi.types.d.ts +73 -2
- package/types/api/User.d.ts +9 -2
- package/types/api/UserApi.d.ts +7 -1
- package/types/costum/runtime.d.ts +14 -5
- package/types/endpoints.module.d.ts +844 -420
package/package.json
CHANGED
package/src/api/BaseEntity.ts
CHANGED
|
@@ -48,6 +48,7 @@ import type {
|
|
|
48
48
|
ProfilBannerData,
|
|
49
49
|
SearchMemberAutocompleteData,
|
|
50
50
|
GetEventsData,
|
|
51
|
+
SearchEventsCostumData,
|
|
51
52
|
CostumFilterCoformData,
|
|
52
53
|
CostumFilterCoformByPathData,
|
|
53
54
|
GetCountriesData,
|
|
@@ -261,6 +262,18 @@ export type CostumContextFields = {
|
|
|
261
262
|
|
|
262
263
|
export type WithCostumContext<T> = T & CostumContextFields;
|
|
263
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Bornes d'agenda acceptant un `Date` (coercé en ISO par la méthode avant l'envoi — le fil reste
|
|
267
|
+
* `type:string`). Partagé par `searchEventsCostum` et `getEvents`.
|
|
268
|
+
*/
|
|
269
|
+
type AgendaDateInputs = { startDateUTC?: Date | string; endDateUTC?: Date | string };
|
|
270
|
+
/** Entrée de `searchEventsCostum` : la request, mais les bornes acceptent un `Date`. */
|
|
271
|
+
export type SearchEventsCostumInput =
|
|
272
|
+
Omit<Partial<SearchEventsCostumData>, keyof AgendaDateInputs> & AgendaDateInputs;
|
|
273
|
+
/** Entrée de `getEvents` : la request, mais les bornes acceptent un `Date`. */
|
|
274
|
+
export type GetEventsInput =
|
|
275
|
+
Omit<Partial<GetEventsData>, keyof AgendaDateInputs> & AgendaDateInputs;
|
|
276
|
+
|
|
264
277
|
/**
|
|
265
278
|
* Une valeur distincte d'une thématique CoForm (retournée par `coformFilterByPath`).
|
|
266
279
|
*/
|
|
@@ -2905,7 +2918,23 @@ export class BaseEntity<TServerData = any> {
|
|
|
2905
2918
|
*/
|
|
2906
2919
|
async entity<T extends keyof EntityTypeMap>(entityType: T, entityData: object = {}, config: BaseEntityConfig = {}): Promise<EntityTypeMap[T]> {
|
|
2907
2920
|
try {
|
|
2908
|
-
|
|
2921
|
+
// Propagation du scope costum aux ENFANTS créés (chaîne `scope.org().project()` / parent-create
|
|
2922
|
+
// `parent.poi()`) : si CE parent porte un `_costumCtx` et que l'appelant n'a PAS fourni de costumCtx
|
|
2923
|
+
// explicite, l'enfant hérite de l'IDENTITÉ (slug/costumId/costumType = source.key) MAIS avec l'overlay
|
|
2924
|
+
// de champs RE-RÉSOLU pour SA collection (un projet ne reçoit pas les champs d'une org). Création
|
|
2925
|
+
// seulement (un load id/slug fait foi via _setData/source.key).
|
|
2926
|
+
let effectiveConfig = config;
|
|
2927
|
+
if (config.costumCtx === undefined && this._costumCtx && !this._hasAtLeastOne(entityData, ["id", "slug"])) {
|
|
2928
|
+
const p = this._costumCtx;
|
|
2929
|
+
const childCollection = entityType as unknown as Collection;
|
|
2930
|
+
const childCtx: CostumRuntimeContext = resolveCostumCtxFromSource(p.slug, childCollection) ?? {
|
|
2931
|
+
slug: p.slug, costumId: p.costumId, costumType: p.costumType, collection: childCollection,
|
|
2932
|
+
schema: { type: "object", additionalProperties: true, properties: {} },
|
|
2933
|
+
fields: [], presets: {}, hidden: [],
|
|
2934
|
+
};
|
|
2935
|
+
effectiveConfig = { ...config, costumCtx: childCtx };
|
|
2936
|
+
}
|
|
2937
|
+
const entity = this._entityInstanceData(entityType, entityData, effectiveConfig);
|
|
2909
2938
|
|
|
2910
2939
|
const fetchKeysByEntity = {
|
|
2911
2940
|
citoyens: ["id", "slug"],
|
|
@@ -4115,16 +4144,24 @@ export class BaseEntity<TServerData = any> {
|
|
|
4115
4144
|
/**
|
|
4116
4145
|
* Récupérer les événements d'une entitée : liste des événements de l'entité ou elle est "organizer" ou "attendee".
|
|
4117
4146
|
* Constant : GET_EVENTS
|
|
4147
|
+
* `startDateUTC`/`endDateUTC` acceptent un `Date` ou une string ISO (un `Date` est coercé en ISO).
|
|
4148
|
+
* `recurrency` est typé `false` (garde parité, comme `searchEventsCostum`) : omettre (récurrents inclus
|
|
4149
|
+
* en mode calendrier) ou passer `false` (exclure) ; jamais `true` (le legacy `=== true` l'exclurait → divergence).
|
|
4118
4150
|
* @param data - Paramètres (partiels) de recherche/pagination.
|
|
4119
4151
|
* @returns - Les données de réponse.
|
|
4120
4152
|
*/
|
|
4121
4153
|
async getEvents(
|
|
4122
|
-
data:
|
|
4154
|
+
data: GetEventsInput = {},
|
|
4123
4155
|
options?: { restoredState?: PaginatorState }
|
|
4124
4156
|
): Promise<PaginatorPage<EventEntity>> {
|
|
4125
|
-
|
|
4157
|
+
// DX : `startDateUTC`/`endDateUTC` acceptent un `Date` → coercé en ISO (un Date brut échouerait l'AJV
|
|
4158
|
+
// `type:string`). Cf. convention R0 de normalisation de champs.
|
|
4159
|
+
const finalInput: Partial<GetEventsData> = { ...(data as Partial<GetEventsData>) };
|
|
4160
|
+
if (data.startDateUTC instanceof Date) finalInput.startDateUTC = data.startDateUTC.toISOString();
|
|
4161
|
+
if (data.endDateUTC instanceof Date) finalInput.endDateUTC = data.endDateUTC.toISOString();
|
|
4162
|
+
finalInput.searchType = this._getDefaultFromEndpoint("GET_EVENTS", "searchType") as GetEventsData["searchType"];
|
|
4126
4163
|
const paginator = this._createPaginatorEngine({
|
|
4127
|
-
initialData:
|
|
4164
|
+
initialData: finalInput,
|
|
4128
4165
|
methodName: "getEvents",
|
|
4129
4166
|
restoredState: options?.restoredState,
|
|
4130
4167
|
finalizer: async (finalData) => {
|
|
@@ -5261,6 +5298,66 @@ export class BaseEntity<TServerData = any> {
|
|
|
5261
5298
|
return page;
|
|
5262
5299
|
}
|
|
5263
5300
|
|
|
5301
|
+
/**
|
|
5302
|
+
* Agenda costum-aware : events scopés au costum (`source.key`) — pendant de `searchCostum` pour les events.
|
|
5303
|
+
* `_withCostumContext` injecte `costumSlug`/`contextId`/`contextType` + `sourceKey=[serverData.slug]` → les
|
|
5304
|
+
* events DU site/hôte costum (au lieu du filtre attendees/organizer FORCÉ de `getEvents`). À appeler sur
|
|
5305
|
+
* l'entité HÔTE du costum (ex. l'entité du déploiement). `sourceKey` surchargeable (agenda multi-sites / réseau).
|
|
5306
|
+
*
|
|
5307
|
+
* **Deux modes** (endpoint `/co2/search/agenda`), selon que tu passes une date ou non :
|
|
5308
|
+
*
|
|
5309
|
+
* 1. **CALENDRIER** — déclenché par `startDateUTC` et/ou `endDateUTC` (l'agenda d'une période) :
|
|
5310
|
+
* ramène **TOUT** ce qui matche la plage — ponctuels **+ récurrents** (`openingHours`, sauf `recurrency:false`) —
|
|
5311
|
+
* trié par **occurrence** (`startDateSort`/`startDateSortFormat` calculés côté serveur), résultat = **tableau**.
|
|
5312
|
+
* **Pas de pagination** serveur : une seule page (`next()`/`prev()` sans effet).
|
|
5313
|
+
* 2. **LISTE** — déclenché par l'**absence** de date (flux paginé) :
|
|
5314
|
+
* **ponctuels uniquement** (récurrents exclus), triés par `startDate` **décroissant** (plus récents d'abord),
|
|
5315
|
+
* paginés via `indexMin`/`indexStep` (défaut 30) — `next()`/`prev()` fonctionnent. (Backend : objet keyé `_id`,
|
|
5316
|
+
* hydraté en tableau d'`Event` côté lib.)
|
|
5317
|
+
*
|
|
5318
|
+
* Le scope costum (`sourceKey`) s'applique dans les **deux** modes.
|
|
5319
|
+
*
|
|
5320
|
+
* `recurrency` est typé `false` (garde parité) : **omettre** (mode calendrier → récurrents inclus) ou passer
|
|
5321
|
+
* `false` (exclure) ; **jamais `true`** — le legacy fait un test strict `=== true` qui exclurait les récurrents
|
|
5322
|
+
* (divergence), valeur donc interdite au type **et** à l'AJV.
|
|
5323
|
+
*
|
|
5324
|
+
* `startDateUTC`/`endDateUTC` acceptent un **`Date` ou une string ISO** : un `Date` est coercé en ISO
|
|
5325
|
+
* (`toISOString()`) avant l'envoi (le fil reste `type:string`). Évite le piège de l'ISO partielle
|
|
5326
|
+
* (`"2026-06-01"` n'est pas un `date-time` AJV valide).
|
|
5327
|
+
*
|
|
5328
|
+
// Mode CALENDRIER — agenda du mois, avec de vrais Date
|
|
5329
|
+
* const debut = new Date("2026-06-01T00:00:00Z");
|
|
5330
|
+
* const page = await org.searchEventsCostum({ startDateUTC: debut, endDateUTC: new Date("2026-06-30T23:59:59Z") });
|
|
5331
|
+
* page.results.forEach((e) => console.log(e.serverData.name, e.serverData.startDateSort));
|
|
5332
|
+
*
|
|
5333
|
+
// Mode LISTE — flux paginé (ponctuels, du plus récent)
|
|
5334
|
+
* let p = await org.searchEventsCostum({ indexStep: 20 });
|
|
5335
|
+
* if (p.hasNext) p = await p.next();
|
|
5336
|
+
*/
|
|
5337
|
+
async searchEventsCostum(
|
|
5338
|
+
data: SearchEventsCostumInput = {},
|
|
5339
|
+
options?: { restoredState?: PaginatorState }
|
|
5340
|
+
): Promise<PaginatorPage<EventEntity>> {
|
|
5341
|
+
// DX : coerce les `Date` en ISO (un objet Date échouerait la validation AJV `type:string`) — suit
|
|
5342
|
+
// la convention de normalisation de champs de la lib (R0 : `Date` → `toISOString()`).
|
|
5343
|
+
const finalInput: Partial<SearchEventsCostumData> = { ...(data as Partial<SearchEventsCostumData>) };
|
|
5344
|
+
if (data.startDateUTC instanceof Date) finalInput.startDateUTC = data.startDateUTC.toISOString();
|
|
5345
|
+
if (data.endDateUTC instanceof Date) finalInput.endDateUTC = data.endDateUTC.toISOString();
|
|
5346
|
+
finalInput.searchType = this._getDefaultFromEndpoint("SEARCH_EVENTS_COSTUM", "searchType") as SearchEventsCostumData["searchType"];
|
|
5347
|
+
const paginator = this._createPaginatorEngine({
|
|
5348
|
+
initialData: finalInput,
|
|
5349
|
+
methodName: "searchEventsCostum",
|
|
5350
|
+
restoredState: options?.restoredState,
|
|
5351
|
+
finalizer: this._withCostumContext(
|
|
5352
|
+
(finalData: SearchEventsCostumData) => {
|
|
5353
|
+
delete (finalData as { pathParams?: unknown }).pathParams;
|
|
5354
|
+
return this.endpointApi.searchEventsCostum(finalData);
|
|
5355
|
+
}
|
|
5356
|
+
),
|
|
5357
|
+
});
|
|
5358
|
+
return paginator.next() as Promise<PaginatorPage<EventEntity>>;
|
|
5359
|
+
}
|
|
5360
|
+
|
|
5264
5361
|
/**
|
|
5265
5362
|
* @param data
|
|
5266
5363
|
* Paramètres de recherche (partiels — les valeurs manquantes sont complétées
|
package/src/api/EndpointApi.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ApiAuthenticationError } from "../error.js";
|
|
3
3
|
|
|
4
4
|
import type ApiClient from "../ApiClient.js";
|
|
5
|
-
import type { DocumentListData, 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";
|
|
5
|
+
import type { DocumentListData, 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, SearchEventsCostumData, 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
|
|
@@ -1366,6 +1366,21 @@ export class EndpointApi {
|
|
|
1366
1366
|
return this.call("GET_EVENTS", data);
|
|
1367
1367
|
}
|
|
1368
1368
|
|
|
1369
|
+
/**
|
|
1370
|
+
* Recherche d'events scopée costum (agenda) : Agenda costum-aware : events filtrés par sourceKey/costum + plage de dates. Même endpoint /co2/search/agenda que GET_EVENTS, mais surface costum (PAS le filtre attendees/organizer forcé de GET_EVENTS). Réponse = cible-co2-search-agenda.
|
|
1371
|
+
* Constant : SEARCH_EVENTS_COSTUM
|
|
1372
|
+
* @param data - Données envoyées à l'API
|
|
1373
|
+
* @returns Les données de réponse.
|
|
1374
|
+
* @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
|
|
1375
|
+
* @throws {Error} - En cas d'erreur inattendue.
|
|
1376
|
+
*/
|
|
1377
|
+
async searchEventsCostum(data: SearchEventsCostumData): Promise<any> {
|
|
1378
|
+
if (!data || typeof data !== "object") {
|
|
1379
|
+
throw new TypeError("Le paramètre data doit être un objet.");
|
|
1380
|
+
}
|
|
1381
|
+
return this.call("SEARCH_EVENTS_COSTUM", data);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1369
1384
|
/**
|
|
1370
1385
|
* Partager un événement : Partage un événement avec d’autres utilisateurs ou entités.
|
|
1371
1386
|
* Constant : SHARE_EVENTS
|
|
@@ -4092,9 +4092,9 @@ export interface GetEventsData {
|
|
|
4092
4092
|
countType?: unknown[];
|
|
4093
4093
|
fediverse: boolean;
|
|
4094
4094
|
/**
|
|
4095
|
-
*
|
|
4095
|
+
* GARDE PARITÉ legacy (identique à SEARCH_EVENTS_COSTUM) : seule valeur autorisée `false` (= exclure les récurrents) OU champ omis (= inclure, défaut). NE JAMAIS envoyer `true` — le legacy teste STRICTEMENT $recurrency === true (AgendaAction.php:65) qui échoue sur la string POST → exclut les récurrents, alors que notre backend les inclurait (divergence assumée). En interdisant `true`, omis→inclus et false→exclus se comportent pareil sur les 2 serveurs.
|
|
4096
4096
|
*/
|
|
4097
|
-
recurrency?:
|
|
4097
|
+
recurrency?: false;
|
|
4098
4098
|
filters?: {
|
|
4099
4099
|
$or?: Record<string, unknown>;
|
|
4100
4100
|
/**
|
|
@@ -4107,6 +4107,99 @@ export interface GetEventsData {
|
|
|
4107
4107
|
}
|
|
4108
4108
|
|
|
4109
4109
|
|
|
4110
|
+
export interface SearchEventsCostumData {
|
|
4111
|
+
searchType: "events"[];
|
|
4112
|
+
indexMin: number;
|
|
4113
|
+
indexStep: number;
|
|
4114
|
+
/**
|
|
4115
|
+
* Date de début (UTC)
|
|
4116
|
+
*/
|
|
4117
|
+
startDateUTC?: string;
|
|
4118
|
+
/**
|
|
4119
|
+
* Date de fin (UTC)
|
|
4120
|
+
*/
|
|
4121
|
+
endDateUTC?: string;
|
|
4122
|
+
/**
|
|
4123
|
+
* Nom ou terme
|
|
4124
|
+
*/
|
|
4125
|
+
name?: string;
|
|
4126
|
+
/**
|
|
4127
|
+
* Type d’événement
|
|
4128
|
+
*/
|
|
4129
|
+
type?:
|
|
4130
|
+
| "workshop"
|
|
4131
|
+
| "competition"
|
|
4132
|
+
| "concert"
|
|
4133
|
+
| "contest"
|
|
4134
|
+
| "conference"
|
|
4135
|
+
| "debate"
|
|
4136
|
+
| "exhibition"
|
|
4137
|
+
| "festival"
|
|
4138
|
+
| "crowdfunding"
|
|
4139
|
+
| "fair"
|
|
4140
|
+
| "course"
|
|
4141
|
+
| "protest"
|
|
4142
|
+
| "market"
|
|
4143
|
+
| "film"
|
|
4144
|
+
| "getTogether"
|
|
4145
|
+
| "meeting"
|
|
4146
|
+
| "spectacle"
|
|
4147
|
+
| "internship"
|
|
4148
|
+
| "stand"
|
|
4149
|
+
| "others";
|
|
4150
|
+
/**
|
|
4151
|
+
* Liste des localités ciblées
|
|
4152
|
+
*/
|
|
4153
|
+
locality?: {
|
|
4154
|
+
/**
|
|
4155
|
+
* This interface was referenced by `undefined`'s JSON-Schema definition
|
|
4156
|
+
* via the `patternProperty` "^[^\s]+$".
|
|
4157
|
+
*/
|
|
4158
|
+
[k: string]: {
|
|
4159
|
+
[k: string]: unknown;
|
|
4160
|
+
};
|
|
4161
|
+
};
|
|
4162
|
+
count?: boolean;
|
|
4163
|
+
countType?: unknown[];
|
|
4164
|
+
fediverse: boolean;
|
|
4165
|
+
/**
|
|
4166
|
+
* GARDE PARITÉ legacy : seule valeur autorisée `false` (= exclure les récurrents) OU champ omis (= inclure, défaut). NE JAMAIS envoyer `true` — le legacy teste STRICTEMENT $recurrency === true (AgendaAction.php:65) qui échoue sur la string POST → exclut les récurrents, alors que notre backend les inclurait (divergence assumée). En interdisant `true`, omis→inclus et false→exclus se comportent pareil sur les 2 serveurs.
|
|
4167
|
+
*/
|
|
4168
|
+
recurrency?: false;
|
|
4169
|
+
/**
|
|
4170
|
+
* Filtres Mongo additionnels (buildFilters).
|
|
4171
|
+
*/
|
|
4172
|
+
filters?: {
|
|
4173
|
+
[k: string]: unknown;
|
|
4174
|
+
};
|
|
4175
|
+
/**
|
|
4176
|
+
* Scope costum : events dont source.keys/reference.costum ∈ sourceKey (agenda multi-sites/réseau). Auto-injecté = [slug de l’entité] si absent.
|
|
4177
|
+
*/
|
|
4178
|
+
sourceKey?: string[];
|
|
4179
|
+
/**
|
|
4180
|
+
* Filtre par tags ($in, ou $all via options.tags.verb).
|
|
4181
|
+
*/
|
|
4182
|
+
searchTags?: string[];
|
|
4183
|
+
/**
|
|
4184
|
+
* Events liés (links.<type>.<id>).
|
|
4185
|
+
*/
|
|
4186
|
+
links?: {
|
|
4187
|
+
type: string;
|
|
4188
|
+
id: string;
|
|
4189
|
+
[k: string]: unknown;
|
|
4190
|
+
}[];
|
|
4191
|
+
subType?: string;
|
|
4192
|
+
category?: string;
|
|
4193
|
+
section?: string | string[];
|
|
4194
|
+
contextId?: string;
|
|
4195
|
+
contextType?: string;
|
|
4196
|
+
costumSlug?: string;
|
|
4197
|
+
costumId?: string;
|
|
4198
|
+
costumType?: string;
|
|
4199
|
+
[k: string]: unknown;
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
|
|
4110
4203
|
export interface ShareEventsData {
|
|
4111
4204
|
/**
|
|
4112
4205
|
* ID de l’événement à partager
|
package/src/api/User.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiError } from "../error.js";
|
|
2
2
|
import { BaseEntity } from "./BaseEntity.js";
|
|
3
3
|
import { Notifications } from "./Notifications.js";
|
|
4
|
-
import { resolveCostumScope, type CostumScope } from "../costum/runtime.js";
|
|
4
|
+
import { resolveCostumScope, costumScopeFromEntity, type CostumScope } from "../costum/runtime.js";
|
|
5
5
|
import { UserMixin } from "../mixin/UserMixin.js";
|
|
6
6
|
import { createSocialTransform } from "../types/transforms.js";
|
|
7
7
|
|
|
@@ -733,10 +733,19 @@ export class User extends BaseEntity<UserItemNormalized> {
|
|
|
733
733
|
* const org = await tl.organization({ name: "Mon TL", manageModel: "Association", buildingSurfaceArea: "120" });
|
|
734
734
|
* await org.save();
|
|
735
735
|
*
|
|
736
|
-
*
|
|
736
|
+
* Deux formes :
|
|
737
|
+
* - `me.costum(slug)` : slug d'un costum du registry (autocomplété) OU n'importe quel slug (`string`) —
|
|
738
|
+
* le backend reste l'autorité de validité (`found`). Slug hors registry sans champ → inc1b (résolution live).
|
|
739
|
+
* - `me.costum(entity)` : ouvre un scope FIELDLESS depuis une entité DÉJÀ CHARGÉE (org/projet/event qui porte
|
|
740
|
+
* un costum), zéro fetch — `source.key` = slug de l'entité. Cas « créer sous ce site nu ».
|
|
741
|
+
*
|
|
742
|
+
* @param arg - slug (`KnownCostumSlug | string`) OU une entité chargée porteuse d'un costum.
|
|
737
743
|
*/
|
|
738
|
-
async costum(slug: KnownCostumSlug): Promise<CostumScope
|
|
739
|
-
|
|
744
|
+
async costum(slug: KnownCostumSlug | (string & {})): Promise<CostumScope>;
|
|
745
|
+
async costum(entity: BaseEntity): Promise<CostumScope>;
|
|
746
|
+
async costum(arg: KnownCostumSlug | (string & {}) | BaseEntity): Promise<CostumScope> {
|
|
747
|
+
if (typeof arg === "string") return resolveCostumScope(this, arg);
|
|
748
|
+
return costumScopeFromEntity(this, arg);
|
|
740
749
|
}
|
|
741
750
|
|
|
742
751
|
/**
|
package/src/api/UserApi.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import ApiClient from "../ApiClient.js";
|
|
2
|
-
import { ApiError, ApiResponseError } from "../error.js";
|
|
3
2
|
import { Action } from "./Action.js";
|
|
4
3
|
import { Answer } from "./Answer.js";
|
|
5
4
|
import { Badge } from "./Badge.js";
|
|
@@ -12,6 +11,8 @@ import { Organization } from "./Organization.js";
|
|
|
12
11
|
import { Poi } from "./Poi.js";
|
|
13
12
|
import { Project } from "./Project.js";
|
|
14
13
|
import { User } from "./User.js";
|
|
14
|
+
import { COSTUM_REQUEST_ENVELOPE } from "../costum/symbol.js";
|
|
15
|
+
import { ApiError, ApiResponseError } from "../error.js";
|
|
15
16
|
|
|
16
17
|
type AxiosResponse = import("axios").AxiosResponse;
|
|
17
18
|
|
|
@@ -105,14 +106,24 @@ export class UserApi {
|
|
|
105
106
|
* @param params.username - Nom d'utilisateur.
|
|
106
107
|
* @param params.email - Adresse email.
|
|
107
108
|
* @param params.pwd - Mot de passe.
|
|
109
|
+
* @param params.costumSlug - (option) slug du costum de déploiement → estampille `source.key` du citoyen
|
|
110
|
+
* côté backend (port Person::insert). `costumId`/`costumType` = entité porteuse (requis si le slug n'est
|
|
111
|
+
* pas un doc `costum`, ex. un site porté par une org).
|
|
108
112
|
* @returns Réponse brute de l'API.
|
|
109
113
|
* @throws {ApiResponseError} Si l'API retourne un échec (`result === false`).
|
|
110
114
|
*/
|
|
111
|
-
async register({ name, username, email, pwd }: { name: string; username: string; email: string; pwd: string }): Promise<any> {
|
|
115
|
+
async register({ name, username, email, pwd, costumSlug, costumId, costumType }: { name: string; username: string; email: string; pwd: string; costumSlug?: string; costumId?: string; costumType?: string }): Promise<any> {
|
|
112
116
|
return this.client.safeCall(async () => {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const data: Record<string, unknown> = { name, username, email, pwd };
|
|
118
|
+
// Contexte costum (déploiement) injecté via l'enveloppe (cf. element/save) : `callEndpoint` fusionne le
|
|
119
|
+
// `context` dans le body + relâche l'AJV. Le backend lit costumSlug/Id/Type → estampille `source` (origin/key/keys).
|
|
120
|
+
if (costumSlug) {
|
|
121
|
+
(data as Record<symbol, unknown>)[COSTUM_REQUEST_ENVELOPE] = {
|
|
122
|
+
properties: {},
|
|
123
|
+
context: { costumSlug, ...(costumId ? { costumId } : {}), ...(costumType ? { costumType } : {}) },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const response = await this.client.callEndpoint("PERSON_REGISTER", data);
|
|
116
127
|
|
|
117
128
|
if (!isAxiosResponse(response)) {
|
|
118
129
|
throw new ApiError("Requête mise en file (offline/circuit breaker)", 503, response);
|
package/src/costum/runtime.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { COSTUM_RUNTIME } from "./runtime-map.js";
|
|
|
19
19
|
|
|
20
20
|
import type { CostumExtensionModule, CostumExtensionField, KnownCostumSlug, Collection } from "./types.js";
|
|
21
21
|
import type { BaseEntity } from "../api/BaseEntity.js";
|
|
22
|
+
import type { GetElementsKeyResponse } from "../types/api-responses.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Contexte costum résolu, attaché à une instance d'entité via `config.costumCtx`.
|
|
@@ -99,13 +100,13 @@ type ScopeCollection = "poi" | "organizations" | "projects" | "events";
|
|
|
99
100
|
* et n'altère jamais l'état partagé — chaque entité reçoit son propre `_costumCtx`.
|
|
100
101
|
*/
|
|
101
102
|
export class CostumScope {
|
|
102
|
-
readonly slug: KnownCostumSlug;
|
|
103
|
+
readonly slug: KnownCostumSlug | (string & {});
|
|
103
104
|
private readonly user: BaseEntity;
|
|
104
105
|
private readonly module: CostumExtensionModule;
|
|
105
106
|
private readonly costumId: string;
|
|
106
107
|
private readonly costumType: string;
|
|
107
108
|
|
|
108
|
-
constructor(user: BaseEntity, slug: KnownCostumSlug, module: CostumExtensionModule, costumId: string, costumType: string) {
|
|
109
|
+
constructor(user: BaseEntity, slug: KnownCostumSlug | (string & {}), module: CostumExtensionModule, costumId: string, costumType: string) {
|
|
109
110
|
this.user = user;
|
|
110
111
|
this.slug = slug;
|
|
111
112
|
this.module = module;
|
|
@@ -191,8 +192,23 @@ export class CostumScope {
|
|
|
191
192
|
export function resolveCostumCtxFromSource(sourceKey: string | undefined | null, collection: Collection): CostumRuntimeContext | null {
|
|
192
193
|
if (!sourceKey) return null;
|
|
193
194
|
const entry = COSTUM_RUNTIME[sourceKey]?.[collection];
|
|
194
|
-
if (!entry) return null;
|
|
195
195
|
const meta = COSTUM_META[sourceKey as KnownCostumSlug];
|
|
196
|
+
if (!entry) {
|
|
197
|
+
// Pas d'overlay de champs pour (sourceKey, collection). Si le costum est CONNU (meta présent), on
|
|
198
|
+
// reconnaît quand même le scope (source.key = identité) via un ctx NU (fields:[]) — l'élément est
|
|
199
|
+
// rattaché au costum sans champ spécifique. Costum hors registry → null (résolution live = inc1b).
|
|
200
|
+
if (!meta) return null;
|
|
201
|
+
return {
|
|
202
|
+
slug: sourceKey,
|
|
203
|
+
costumId: meta.costumId,
|
|
204
|
+
costumType: meta.costumType,
|
|
205
|
+
collection,
|
|
206
|
+
schema: { type: "object", additionalProperties: true, properties: {} },
|
|
207
|
+
fields: [],
|
|
208
|
+
presets: {},
|
|
209
|
+
hidden: [],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
196
212
|
const props = (entry.schema as { properties?: Record<string, unknown> }).properties ?? {};
|
|
197
213
|
return {
|
|
198
214
|
slug: sourceKey,
|
|
@@ -206,16 +222,53 @@ export function resolveCostumCtxFromSource(sourceKey: string | undefined | null,
|
|
|
206
222
|
};
|
|
207
223
|
}
|
|
208
224
|
|
|
225
|
+
/** Module costum « nu » (scope d'IDENTITÉ sans overlay de champs) : `collections` vide → `create()` no-op. */
|
|
226
|
+
function fieldlessModule(slug: string): CostumExtensionModule {
|
|
227
|
+
return { slug, collections: {} };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Ouvre un scope costum FIELDLESS depuis une entité DÉJÀ CHARGÉE (zéro fetch) : `costumSlug` = slug de
|
|
232
|
+
* l'entité (= `source.key` posé par le backend), `costumId/costumType` = l'entité porteuse. Pendant runtime
|
|
233
|
+
* exact de `_withCostumContext` (BaseEntity), pour la voie « entité tenue » (un site nu sans form). Le
|
|
234
|
+
* backend re-gate `found` sur l'existence d'un costum sur l'entité → un site sans costum n'estampille rien.
|
|
235
|
+
*/
|
|
236
|
+
export function costumScopeFromEntity(user: BaseEntity, entity: BaseEntity): CostumScope {
|
|
237
|
+
const sd = entity.serverData as { slug?: string | null; id?: string | null } | undefined;
|
|
238
|
+
const slug = sd?.slug;
|
|
239
|
+
const id = sd?.id;
|
|
240
|
+
if (typeof slug !== "string" || slug.trim() === "") {
|
|
241
|
+
throw new ApiError("me.costum(entity) : l'entité n'a pas de slug (charge-la d'abord).", 400);
|
|
242
|
+
}
|
|
243
|
+
if (typeof id !== "string" || !id) {
|
|
244
|
+
throw new ApiError("me.costum(entity) : l'entité n'a pas d'id.", 400);
|
|
245
|
+
}
|
|
246
|
+
return new CostumScope(user, slug, fieldlessModule(slug), id, entity.getEntityType());
|
|
247
|
+
}
|
|
248
|
+
|
|
209
249
|
/**
|
|
210
|
-
* Résout un `CostumScope` pour un user connecté.
|
|
211
|
-
*
|
|
250
|
+
* Résout un `CostumScope` par SLUG pour un user connecté.
|
|
251
|
+
* - slug à champs (meta + loader) → charge le module généré (code-split via dynamic import).
|
|
252
|
+
* - slug CONNU sans loader (fieldless) → scope d'identité nu (`collections:{}`).
|
|
253
|
+
* - slug HORS registry → résolution LIVE via `slug/getinfo` (contextId/contextType) → scope nu (tier 3b).
|
|
212
254
|
*/
|
|
213
|
-
export async function resolveCostumScope(user: BaseEntity, slug: KnownCostumSlug): Promise<CostumScope> {
|
|
214
|
-
const meta = COSTUM_META[slug];
|
|
215
|
-
const loader = COSTUM_SCHEMAS[slug];
|
|
216
|
-
if (
|
|
217
|
-
|
|
255
|
+
export async function resolveCostumScope(user: BaseEntity, slug: KnownCostumSlug | (string & {})): Promise<CostumScope> {
|
|
256
|
+
const meta = COSTUM_META[slug as KnownCostumSlug];
|
|
257
|
+
const loader = COSTUM_SCHEMAS[slug as KnownCostumSlug];
|
|
258
|
+
if (meta && loader) {
|
|
259
|
+
const { default: module } = await loader();
|
|
260
|
+
return new CostumScope(user, slug, module, meta.costumId, meta.costumType);
|
|
261
|
+
}
|
|
262
|
+
if (meta) {
|
|
263
|
+
// Connu mais sans overlay de champs : scope d'identité pur (estampille source.key, aucun champ costum).
|
|
264
|
+
return new CostumScope(user, slug, fieldlessModule(slug), meta.costumId, meta.costumType);
|
|
265
|
+
}
|
|
266
|
+
// Tier 3b — slug HORS registry : résolution LIVE slug → {contextId, contextType} via la route `slug/getinfo`
|
|
267
|
+
// (GET_ELEMENTS_KEY, « none »). Couvre les sites fieldless/cold/futurs sans rien bundler. costumSlug = le slug
|
|
268
|
+
// demandé (slug ENTITÉ) ; le backend re-gate `found` sur l'existence d'un costum sur l'entité résolue.
|
|
269
|
+
const info = await user.endpointApi.getElementsKey({ pathParams: { slug } }) as GetElementsKeyResponse;
|
|
270
|
+
if (info?.contextId && info?.contextType) {
|
|
271
|
+
return new CostumScope(user, slug, fieldlessModule(slug), String(info.contextId), String(info.contextType));
|
|
218
272
|
}
|
|
219
|
-
|
|
220
|
-
return new CostumScope(user, slug, module, meta.costumId, meta.costumType);
|
|
273
|
+
throw new ApiError(`Costum inconnu : "${slug}" — slug introuvable (résolution live slug/getinfo).`, 404);
|
|
221
274
|
}
|