@odla-ai/chapter 0.46.0 → 0.47.1

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.
@@ -1,8 +1,8 @@
1
1
  import * as preact from 'preact';
2
2
  import { ComponentChildren, JSX } from 'preact';
3
3
  import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
4
- import { a as Chapter, b as ChapterCopy, M as MemberContentDocument, E as EffectiveMemberContent, c as MemberContentTierAuthority } from '../../copy-context-GaxckY3T.js';
5
- export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-GaxckY3T.js';
4
+ import { a as Chapter, b as ChapterCopy, M as MemberContentDocument, E as EffectiveMemberContent, c as MemberContentTierAuthority } from '../../copy-context-20WFI5TI.js';
5
+ export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-20WFI5TI.js';
6
6
  import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
7
7
 
8
8
  /** Normalized public auth configuration consumed by the Clerk gate. */
@@ -1,5 +1,5 @@
1
1
  export { AdminAccountMenuProps, AdminBuilderLibraryData, AdminChrome, AdminMemberContentData, AdminNote, AdminPage, AdminResource, AdminRouteState, AdminRouteTarget, AdminRouting, AdminSection, AdminSectionContext, AdminWorkspace, AdminWorkspaceNavProps, AdmissionGrantsWorkspace, AvailabilitySectionOptions, BillingSectionOptions, BuilderLibraryAdmin, ChapterAdmin, ChapterAdminAuthAdapter, ChapterAdminAuthConfig, ChapterAdminAuthLoadContext, ChapterAdminProps, ChapterAdminUser, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, LeaderAdmissionAction, LeaderAdmissionActionProps, LeaderNetworkOverview, MeetingsSectionOptions, MemberContentAdmin, NetworkRecordsWorkspace, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, admissionGrantsWorkspace, applicationLifecycleAdapter, availabilitySection, billingSection, builderLibrarySection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, loadChapterAdminConfig, loadChapterAdminUser, meetingsSection, memberContentSection, networkRecordsWorkspace, peopleSection, useAdminResource } from './admin/index.js';
2
- export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-GaxckY3T.js';
2
+ export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-20WFI5TI.js';
3
3
  export { ApiFn, BrandStyle, BrandStyleProps, GiftCodeRedeemResult, GiftCodeRedeemer, GiftCodeRedeemerCopy, GiftCodeRedeemerProps, GiftOfferCards, GiftOfferCardsMode, GiftOfferCardsProps, GiftPurchases, GiftPurchasesProps, JoinBookingSimulation, JoinConfig, JoinFlowState, JoinIsland, JoinIslandProps, JoinSimulation, JoinStepRenderContext, JoinSubmitRenderContext, JoinTier, JoinTierRenderContext, MEMBERSHIP_CARD_TOKEN_DEFAULTS, MemberContentArea, MemberContentAreaProps, MemberGifts, MemberProvisionalRenderContext, MembersArea, MembersAreaProps, MembershipCardTokenName, MembershipCards, MembershipCardsMode, MembershipCardsProps, PaymentPriceLines, PaymentStep, PaymentStepProps, PaymentStepSimulation, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, SubResponse, TierPlaceholder, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, membershipApplicationHref, tierNeedsPayment, timeLabel, tzShort, useTierSelection } from './member/index.js';
4
4
  import 'preact';
5
5
  import '@odla-ai/crm';
@@ -1,7 +1,7 @@
1
1
  import * as preact from 'preact';
2
2
  import { ComponentChildren, JSX } from 'preact';
3
- import { d as MemberContentProjection, e as MemberContentPageProjection, b as ChapterCopy, f as ChapterBrand } from '../../copy-context-GaxckY3T.js';
4
- export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-GaxckY3T.js';
3
+ import { d as MemberContentProjection, e as MemberContentPageProjection, b as ChapterCopy, f as ChapterBrand } from '../../copy-context-20WFI5TI.js';
4
+ export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-20WFI5TI.js';
5
5
  import { FieldType, FieldConditions } from '@odla-ai/crm';
6
6
 
7
7
  /** A bookable slot: a start instant in epoch milliseconds. */
@@ -115,12 +115,16 @@ function createWorkerContext(options) {
115
115
  }
116
116
  async function isAdminEmail(db, email) {
117
117
  if (!email) return false;
118
- const { admins } = await db.query({ admins: { $: { where: { email: email.toLowerCase() }, limit: 1 } } });
118
+ const { admins } = await db.query({ admins: { $: { where: { email: email.trim().toLowerCase() }, limit: 1 } } });
119
119
  return Array.isArray(admins) && admins.length > 0;
120
120
  }
121
121
  async function isSuperAdminEmail(db, email) {
122
122
  if (!auth.superAdmins || !email) return false;
123
- const { superAdmins } = await db.query({ superAdmins: { $: { where: { email: email.toLowerCase() }, limit: 1 } } });
123
+ if (auth.superAdminSource === "odla") {
124
+ const { $users } = await db.query({ $users: { $: { where: { email: email.trim().toLowerCase(), role: "super_admin" }, limit: 1 } } });
125
+ return Array.isArray($users) && $users.length > 0;
126
+ }
127
+ const { superAdmins } = await db.query({ superAdmins: { $: { where: { email: email.trim().toLowerCase() }, limit: 1 } } });
124
128
  return Array.isArray(superAdmins) && superAdmins.length > 0;
125
129
  }
126
130
  async function roleFor(db, u) {
@@ -6121,9 +6125,39 @@ var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
6121
6125
  var MAX_PAGE = 50;
6122
6126
  var MAX_OFFSET = 1e4;
6123
6127
  var MAX_SEARCH = 200;
6128
+ var MAX_STAGE = 100;
6124
6129
  var MAX_BODY = 32768;
6125
6130
  var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
6126
6131
  var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
6132
+ var NETWORK_RECORD_SORTS = [
6133
+ { id: "updated_desc", label: "Recently updated" },
6134
+ { id: "updated_asc", label: "Least recently updated" },
6135
+ { id: "name_asc", label: "Name A\u2013Z" },
6136
+ { id: "name_desc", label: "Name Z\u2013A" }
6137
+ ];
6138
+ var NETWORK_RECORD_SORT_COLUMNS = {
6139
+ updated_desc: { field: "updatedAt", dir: "desc" },
6140
+ updated_asc: { field: "updatedAt", dir: "asc" },
6141
+ name_asc: { field: "name", dir: "asc" },
6142
+ name_desc: { field: "name", dir: "desc" }
6143
+ };
6144
+ function networkRecordWindow(params) {
6145
+ const limit = integer(params.get("limit"), 25, 1, MAX_PAGE);
6146
+ const offset = integer(params.get("offset"), 0, 0, MAX_OFFSET);
6147
+ const search = (params.get("search") ?? "").trim();
6148
+ const stage = (params.get("stage") ?? "").trim();
6149
+ const requestedSort = (params.get("sort") ?? "updated_desc").trim();
6150
+ const sortId = NETWORK_RECORD_SORTS.some((candidate) => candidate.id === requestedSort) ? requestedSort : null;
6151
+ if (limit == null || offset == null || search.length > MAX_SEARCH || stage.length > 0 && (stage.length > MAX_STAGE || /[\u0000-\u001f\u007f]/.test(stage)) || !sortId) return null;
6152
+ return {
6153
+ limit,
6154
+ offset,
6155
+ ...search ? { search } : {},
6156
+ ...stage ? { stage } : {},
6157
+ sortId,
6158
+ sort: NETWORK_RECORD_SORT_COLUMNS[sortId]
6159
+ };
6160
+ }
6127
6161
  function bodyTooLarge(body) {
6128
6162
  return new TextEncoder().encode(body).byteLength > MAX_BODY;
6129
6163
  }
@@ -6326,20 +6360,22 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6326
6360
  const type = url.searchParams.get("type") ?? "";
6327
6361
  const allowed2 = reader.fields[type];
6328
6362
  if (!IDENTIFIER.test(type) || !allowed2) return json({ error: "record type is not shared with this reader" }, 403);
6329
- const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
6330
- const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
6331
- const search = (url.searchParams.get("search") ?? "").trim();
6332
- if (limit == null || offset == null || search.length > MAX_SEARCH) {
6333
- return json({ error: "invalid pagination or search parameters" }, 400);
6363
+ const window = networkRecordWindow(url.searchParams);
6364
+ if (!window) return json({ error: "invalid record query parameters" }, 400);
6365
+ const typeDef = ctx.chapter.crm.type(type);
6366
+ const stages = typeDef.pipeline?.stages ?? [];
6367
+ if (window.stage && !stages.some((stage) => stage.id === window.stage)) {
6368
+ return json({ error: "stage is not defined for this record type" }, 400);
6334
6369
  }
6335
6370
  const page = await (0, import_crm9.listRecords)(
6336
6371
  { crm: ctx.chapter.crm, db },
6337
6372
  {
6338
6373
  type,
6339
- limit,
6340
- offset,
6341
- ...search ? { search } : {},
6342
- sort: { field: "updatedAt", dir: "desc" }
6374
+ limit: window.limit,
6375
+ offset: window.offset,
6376
+ ...window.search ? { search: window.search } : {},
6377
+ ...window.stage ? { stage: [window.stage] } : {},
6378
+ sort: window.sort
6343
6379
  }
6344
6380
  );
6345
6381
  const response = {
@@ -6349,7 +6385,15 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6349
6385
  records: page.records.map((record4) => readerRecord(record4, reader, ctx.chapter.crm)),
6350
6386
  total: page.total,
6351
6387
  limit: page.limit,
6352
- offset: page.offset
6388
+ offset: page.offset,
6389
+ filters: {
6390
+ stages: stages.map((stage) => ({ id: stage.id, label: stage.label ?? stage.id })),
6391
+ sorts: [...NETWORK_RECORD_SORTS]
6392
+ },
6393
+ appliedQuery: {
6394
+ ...window.stage ? { stage: window.stage } : {},
6395
+ sort: window.sortId
6396
+ }
6353
6397
  };
6354
6398
  return json(response);
6355
6399
  };
@@ -6382,7 +6426,8 @@ async function readTargetRecords(db, env, ctx, target, query) {
6382
6426
  const upstream = body && "error" in body && typeof body.error === "string" ? body.error : void 0;
6383
6427
  return {
6384
6428
  ok: false,
6385
- error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`)
6429
+ error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`),
6430
+ ...response.status === 400 ? { status: 400 } : {}
6386
6431
  };
6387
6432
  }
6388
6433
  return {
@@ -6473,16 +6518,22 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
6473
6518
  const type = url.searchParams.get("type") ?? "";
6474
6519
  const target = targetFor2(ctx, targetId);
6475
6520
  if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
6476
- const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
6477
- const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
6478
- const search = (url.searchParams.get("search") ?? "").trim();
6479
- if (limit == null || offset == null || search.length > MAX_SEARCH) {
6480
- return json({ error: "invalid pagination or search parameters" }, 400);
6481
- }
6482
- const query = new URLSearchParams({ type, limit: String(limit), offset: String(offset) });
6483
- if (search) query.set("search", search);
6521
+ const window = networkRecordWindow(url.searchParams);
6522
+ if (!window) return json({ error: "invalid record query parameters" }, 400);
6523
+ const query = new URLSearchParams({
6524
+ type,
6525
+ limit: String(window.limit),
6526
+ offset: String(window.offset),
6527
+ sort: window.sortId
6528
+ });
6529
+ if (window.search) query.set("search", window.search);
6530
+ if (window.stage) query.set("stage", window.stage);
6484
6531
  const result = await readTargetRecords(got.db, env, ctx, target, query);
6485
- return result.ok ? json(result.page) : json({ error: result.error }, 502);
6532
+ if (!result.ok) return json({ error: result.error }, result.status ?? 502);
6533
+ if ((window.stage || url.searchParams.has("sort")) && (!result.page.appliedQuery || result.page.appliedQuery.sort !== window.sortId || result.page.appliedQuery.stage !== window.stage)) {
6534
+ return json({ error: "follower did not apply the requested record query" }, 502);
6535
+ }
6536
+ return json(result.page);
6486
6537
  };
6487
6538
 
6488
6539
  // src/worker-routes-network-notes.ts