@odla-ai/chapter 0.38.0 → 0.39.0

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.
@@ -1198,10 +1198,10 @@ async function syncApplicationToCrm(deps, opts) {
1198
1198
  return recordId;
1199
1199
  }
1200
1200
  async function backfillCrm(deps) {
1201
- const [appsRes, usersRes] = await Promise.all([
1202
- deps.db.query({ applications: { $: { order: { createdAt: "desc" }, limit: 1e3 } } }),
1203
- deps.db.query({ $users: { $: { limit: 1e3 } } })
1204
- ]);
1201
+ const data = await deps.db.query({
1202
+ applications: { $: { order: { createdAt: "desc" }, limit: 1e3 } },
1203
+ $users: { $: { limit: 1e3 } }
1204
+ });
1205
1205
  const seen = /* @__PURE__ */ new Set();
1206
1206
  let synced = 0;
1207
1207
  const errors = [];
@@ -1216,8 +1216,8 @@ async function backfillCrm(deps) {
1216
1216
  errors.push({ email: key, error: err instanceof Error ? err.message : String(err) });
1217
1217
  }
1218
1218
  };
1219
- for (const a of appsRes.applications ?? []) await run(a, str(a.status));
1220
- for (const u of usersRes.$users ?? []) {
1219
+ for (const a of data.applications ?? []) await run(a, str(a.status));
1220
+ for (const u of data.$users ?? []) {
1221
1221
  if (u.deleted === true) continue;
1222
1222
  await run({ email: u.email, firstName: str(u.name) });
1223
1223
  }
@@ -3346,9 +3346,21 @@ function webhookMutationId(eventId) {
3346
3346
  function findApplicationRef(obj) {
3347
3347
  const metaOf = (v) => v && typeof v === "object" ? v.metadata ?? {} : {};
3348
3348
  const pick = (m) => typeof m.applicationId === "string" ? m.applicationId : void 0;
3349
- const applicationId = pick(metaOf(obj)) ?? pick(metaOf(obj.subscription_details)) ?? pick(metaOf(obj.parent?.subscription_details));
3349
+ const candidates = [
3350
+ metaOf(obj),
3351
+ metaOf(obj.subscription_details),
3352
+ metaOf(obj.parent?.subscription_details)
3353
+ ];
3354
+ const applicationId = candidates.map(pick).find(Boolean);
3355
+ const appId = candidates.map((metadata) => typeof metadata.odlaAppId === "string" ? metadata.odlaAppId : void 0).find(Boolean);
3356
+ const environment = candidates.map((metadata) => typeof metadata.odlaEnvironment === "string" ? metadata.odlaEnvironment : void 0).find(Boolean);
3350
3357
  const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
3351
- return { ...applicationId ? { applicationId } : {}, ...customerId ? { customerId } : {} };
3358
+ return {
3359
+ ...applicationId ? { applicationId } : {},
3360
+ ...customerId ? { customerId } : {},
3361
+ ...appId ? { appId } : {},
3362
+ ...environment ? { environment } : {}
3363
+ };
3352
3364
  }
3353
3365
  function findGiftRef(obj) {
3354
3366
  const metaOf = (value) => value && typeof value === "object" ? value.metadata ?? {} : {};
@@ -3439,7 +3451,13 @@ function createStripeProvider(config) {
3439
3451
  return {
3440
3452
  async createSubscription(input) {
3441
3453
  const sk = requireSecret(secretKey);
3442
- const meta = { applicationId: input.applicationId, groupId: input.groupId, email: input.email };
3454
+ const meta = {
3455
+ applicationId: input.applicationId,
3456
+ groupId: input.groupId,
3457
+ email: input.email,
3458
+ odlaAppId: input.appId,
3459
+ odlaEnvironment: input.environment
3460
+ };
3443
3461
  let customerId = input.existingCustomerId;
3444
3462
  if (!customerId) {
3445
3463
  const cust = await stripeCall(
@@ -3959,6 +3977,8 @@ async function startSubscription(req, env, ctx) {
3959
3977
  let result;
3960
3978
  try {
3961
3979
  result = await provider.createSubscription({
3980
+ appId: env.ODLA_APP_ID,
3981
+ environment: env.ODLA_ENV,
3962
3982
  applicationId,
3963
3983
  groupId: String(group.id),
3964
3984
  email: String(app.email ?? ""),
@@ -3988,6 +4008,10 @@ async function ingestWebhook(req, env, ctx) {
3988
4008
  if (!ingest.ok) return json({ error: "invalid signature" }, 400);
3989
4009
  const { eventId, event } = ingest;
3990
4010
  if (event.kind === "ignored") return json({ ok: true, ignored: event.type });
4011
+ const identifiedEnvironment = event.appId !== void 0 || event.environment !== void 0;
4012
+ if (env.ODLA_APP_ID && identifiedEnvironment && (event.appId !== env.ODLA_APP_ID || event.environment !== env.ODLA_ENV)) {
4013
+ return json({ ok: true, matched: false });
4014
+ }
3991
4015
  if (event.kind === "gift_paid" || event.kind === "gift_revoked") {
3992
4016
  const intent = await firstRow2(db, "giftPurchaseIntents", { where: { id: event.giftIntentId }, limit: 1 });
3993
4017
  if (!intent || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV) {
@@ -4342,14 +4366,16 @@ var handleAdminPeople = async (req, url, env, ctx) => {
4342
4366
  if (gate9 instanceof Response) return gate9;
4343
4367
  const { db } = gate9;
4344
4368
  const sk = await getVaultSecret(db, "clerk_secret_key");
4345
- const [appsRes, usersRes, roleList] = await Promise.all([
4346
- db.query({ applications: { $: { order: { createdAt: "desc" }, limit: 200 } } }),
4347
- db.query({ $users: { $: { limit: 200 } } }),
4369
+ const [data, roleList] = await Promise.all([
4370
+ db.query({
4371
+ applications: { $: { order: { createdAt: "desc" }, limit: 200 } },
4372
+ $users: { $: { limit: 200 } }
4373
+ }),
4348
4374
  sk ? clerkListUsers(sk).catch(() => []) : Promise.resolve([])
4349
4375
  ]);
4350
4376
  const clerkByUserId = new Map(roleList.map((u) => [u.id, u]));
4351
4377
  const people = /* @__PURE__ */ new Map();
4352
- for (const u of usersRes.$users ?? []) {
4378
+ for (const u of data.$users ?? []) {
4353
4379
  if (u.deleted === true) continue;
4354
4380
  const email = typeof u.email === "string" ? u.email : "";
4355
4381
  if (!email) continue;
@@ -4364,7 +4390,7 @@ var handleAdminPeople = async (req, url, env, ctx) => {
4364
4390
  application: null
4365
4391
  });
4366
4392
  }
4367
- for (const a of appsRes.applications ?? []) {
4393
+ for (const a of data.applications ?? []) {
4368
4394
  const key = String(a.email ?? "").toLowerCase();
4369
4395
  if (!key) continue;
4370
4396
  const name = `${a.firstName ?? ""} ${a.lastName ?? ""}`.trim();
@@ -4569,13 +4595,19 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4569
4595
  const now = Date.now();
4570
4596
  const d7 = now - 7 * 864e5;
4571
4597
  const d30 = now - 30 * 864e5;
4572
- const [appsRes, meetingsRes, recsRes, groupRes] = await Promise.all([
4573
- db.query({ applications: { $: { order: { createdAt: "desc" }, limit: 1e3 } } }),
4574
- db.query({ meetings: { $: { where: { status: "scheduled" }, order: { startAt: "asc" }, limit: 500 } } }),
4575
- db.query({ crm_record: { $: { where: { type: "person" }, limit: 1e3 } } }),
4576
- db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })
4577
- ]);
4578
- const apps = rows3(appsRes.applications);
4598
+ const data = await db.query({
4599
+ applications: { $: { order: { createdAt: "desc" }, limit: 1e3 } },
4600
+ meetings: {
4601
+ $: {
4602
+ where: { status: "scheduled" },
4603
+ order: { startAt: "asc" },
4604
+ limit: 500
4605
+ }
4606
+ },
4607
+ crm_record: { $: { where: { type: "person" }, limit: 1e3 } },
4608
+ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } }
4609
+ });
4610
+ const apps = rows3(data.applications);
4579
4611
  const applications = {
4580
4612
  total: apps.length,
4581
4613
  last7: apps.filter((a) => a.createdAt >= d7).length,
@@ -4587,7 +4619,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4587
4619
  pipeline[s] = 0;
4588
4620
  pipelineDelta[s] = 0;
4589
4621
  }
4590
- for (const r of rows3(recsRes.crm_record)) {
4622
+ for (const r of rows3(data.crm_record)) {
4591
4623
  const s = r.stage;
4592
4624
  if (s in pipeline) {
4593
4625
  pipeline[s] = (pipeline[s] ?? 0) + 1;
@@ -4595,7 +4627,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4595
4627
  if (Number.isFinite(sc) && sc >= d7) pipelineDelta[s] = (pipelineDelta[s] ?? 0) + 1;
4596
4628
  }
4597
4629
  }
4598
- const meetings = rows3(meetingsRes.meetings);
4630
+ const meetings = rows3(data.meetings);
4599
4631
  const appById = new Map(apps.map((a) => [a.id, a]));
4600
4632
  const upcoming = meetings.filter((m) => m.startAt >= now - 36e5);
4601
4633
  const calls = { upcoming: upcoming.length, needsAttention: meetings.filter((m) => m.drift && m.drift !== "none").length };
@@ -4611,7 +4643,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
4611
4643
  email: a?.email ?? null
4612
4644
  };
4613
4645
  });
4614
- const group = groupRes.groups?.[0];
4646
+ const group = data.groups?.[0];
4615
4647
  const timezone = resolveScheduling(group?.schedulingJson).timezone;
4616
4648
  let revenue = { billingReady: false };
4617
4649
  let revenueSeries = null;
@@ -4645,16 +4677,18 @@ var handleAdminBilling = async (req, url, env, ctx) => {
4645
4677
  const db = got;
4646
4678
  const sk = await getVaultSecret(db, "stripe_secret_key");
4647
4679
  if (!sk) return json({ billingReady: false, rows: [], summary: null });
4648
- const [appsRes, subsRes, groupRes] = await Promise.all([
4649
- db.query({ applications: { $: { order: { createdAt: "desc" }, limit: 200 } } }),
4650
- stripeCall(sk, "GET", "/v1/subscriptions", { limit: 100, status: "all" }),
4651
- db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })
4680
+ const [data, subsRes] = await Promise.all([
4681
+ db.query({
4682
+ applications: { $: { order: { createdAt: "desc" }, limit: 200 } },
4683
+ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } }
4684
+ }),
4685
+ stripeCall(sk, "GET", "/v1/subscriptions", { limit: 100, status: "all" })
4652
4686
  ]);
4653
- const testMode = String(groupRes.groups?.[0]?.stripePublishableKey ?? "").startsWith("pk_test");
4687
+ const testMode = String(data.groups?.[0]?.stripePublishableKey ?? "").startsWith("pk_test");
4654
4688
  if (!subsRes.ok) return json({ error: "billing lookup failed upstream" }, 502);
4655
4689
  const subById = new Map(rows3(subsRes.body.data).map((s) => [s.id, s]));
4656
4690
  const billingRows = [];
4657
- for (const a of rows3(appsRes.applications)) {
4691
+ for (const a of rows3(data.applications)) {
4658
4692
  if (!a.stripeCustomerId && !a.stripeSubscriptionId) continue;
4659
4693
  const sub = a.stripeSubscriptionId ? subById.get(a.stripeSubscriptionId) : void 0;
4660
4694
  const items = sub?.items?.data ?? [];
@@ -4994,21 +5028,27 @@ var handleAdminComms = async (req, url, env, ctx) => {
4994
5028
  if (got instanceof Response) return got;
4995
5029
  const db = got;
4996
5030
  const appId = m[1];
4997
- const [emailRes, meetingRes, appRes] = await Promise.all([
4998
- db.query({ emailLog: { $: { where: { applicationId: appId }, order: { sentAt: "desc" }, limit: 100 } } }),
4999
- db.query({ meetings: { $: { where: { applicationId: appId } } } }),
5000
- db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })
5001
- ]);
5002
- const app = appRes.applications?.[0] ?? null;
5031
+ const data = await db.query({
5032
+ emailLog: {
5033
+ $: {
5034
+ where: { applicationId: appId },
5035
+ order: { sentAt: "desc" },
5036
+ limit: 100
5037
+ }
5038
+ },
5039
+ meetings: { $: { where: { applicationId: appId } } },
5040
+ applications: { $: { where: { id: appId }, limit: 1 } }
5041
+ });
5042
+ const app = data.applications?.[0] ?? null;
5003
5043
  const group = await loadGroup2(db, str2(app?.groupId, ctx.chapter.id));
5004
5044
  const vars = app ? { firstName: str2(app.firstName), lastName: str2(app.lastName), email: str2(app.email), phone: str2(app.phone), state: str2(app.state), adminUrl: `${url.origin}/admin/`, membersUrl: `${url.origin}/members/` } : null;
5005
- const emails = (emailRes.emailLog ?? []).filter((r) => r.template !== "adminNotification").map((r) => {
5045
+ const emails = (data.emailLog ?? []).filter((r) => r.template !== "adminNotification").map((r) => {
5006
5046
  let mailBody = typeof r.body === "string" ? r.body : null;
5007
5047
  if (!mailBody && group && vars) mailBody = renderTemplateBody(emailGroupFrom(group), String(r.template), vars);
5008
5048
  const channel = r.error ? "email (failed)" : r.redirected ? "email (dev-redirected)" : r.transport === "log-only" ? "email (not delivered)" : "email";
5009
5049
  return { kind: "email", channel, label: String(r.template), subject: str2(r.subject), to: r.to ?? null, body: mailBody, at: r.sentAt, error: r.error ?? null };
5010
5050
  });
5011
- const calendar = (meetingRes.meetings ?? []).map((m2) => ({
5051
+ const calendar = (data.meetings ?? []).map((m2) => ({
5012
5052
  kind: "calendar",
5013
5053
  channel: "Google Calendar",
5014
5054
  label: m2.status === "cancelled" ? "Invitation (call later cancelled)" : "Meeting invitation",