@odla-ai/chapter 0.34.0 → 0.36.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.
- package/README.md +83 -2
- package/dist/{chunk-UJIVBGL5.js → chunk-I45VYBUV.js} +441 -239
- package/dist/chunk-I45VYBUV.js.map +1 -0
- package/dist/{chunk-JAWTIROV.js → chunk-IAWBE5PX.js} +55 -1
- package/dist/chunk-IAWBE5PX.js.map +1 -0
- package/dist/{chunk-TXE5ZDI4.js → chunk-LWM32REV.js} +166 -59
- package/dist/chunk-LWM32REV.js.map +1 -0
- package/dist/{copy-context-c1tqPXOR.d.ts → copy-context-0k0-yWHL.d.ts} +6 -2
- package/dist/index.cjs +967 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +253 -3
- package/dist/index.d.ts +253 -3
- package/dist/index.js +967 -296
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +10 -3
- package/dist/ui/admin/index.js +6 -2
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +11 -3
- package/dist/ui/member/index.d.ts +86 -7
- package/dist/ui/member/index.js +6 -2
- package/dist/worker/index.cjs +1957 -722
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +200 -3
- package/dist/worker/index.d.ts +200 -3
- package/dist/worker/index.js +1968 -733
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-JAWTIROV.js.map +0 -1
- package/dist/chunk-TXE5ZDI4.js.map +0 -1
- package/dist/chunk-UJIVBGL5.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,213 @@ async function applicationIdForSubmission(submissionId) {
|
|
|
15
15
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// src/admission-grants-contract.ts
|
|
19
|
+
var ADMISSION_GRANTS_SCHEMA_VERSION = 1;
|
|
20
|
+
var ADMISSION_GRANTS_PATH = "/api/admission-grants";
|
|
21
|
+
|
|
22
|
+
// src/signup-control-canonical.ts
|
|
23
|
+
var encoder = new TextEncoder();
|
|
24
|
+
function canonicalValue(value) {
|
|
25
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(value)) return `[${value.map(canonicalValue).join(",")}]`;
|
|
29
|
+
if (typeof value !== "object") throw new TypeError("signup control contains an unsupported value");
|
|
30
|
+
const record2 = value;
|
|
31
|
+
const entries = Object.keys(record2).sort().flatMap((key) => {
|
|
32
|
+
const item = record2[key];
|
|
33
|
+
return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue(item)}`];
|
|
34
|
+
});
|
|
35
|
+
return `{${entries.join(",")}}`;
|
|
36
|
+
}
|
|
37
|
+
function canonicalSignupControl(value) {
|
|
38
|
+
return canonicalValue(value);
|
|
39
|
+
}
|
|
40
|
+
async function signupControlDigest(document) {
|
|
41
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(canonicalSignupControl(document)));
|
|
42
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
43
|
+
}
|
|
44
|
+
async function createSignupControlEnvelope(document) {
|
|
45
|
+
return { document, digest: await signupControlDigest(document) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/admission-grants.ts
|
|
49
|
+
var ID = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
|
|
50
|
+
var MAX_GRANTS = 2e3;
|
|
51
|
+
var MAX_NOTE = 2e3;
|
|
52
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
53
|
+
var isAdmissionEmail = (value) => EMAIL_RE.test(value);
|
|
54
|
+
function normalizeAdmissionEmail(email) {
|
|
55
|
+
return email.trim().toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
function validateAdmissionGrantDocument(input) {
|
|
58
|
+
const errors = [];
|
|
59
|
+
const root = input && typeof input === "object" ? input : {};
|
|
60
|
+
if (root.schemaVersion !== ADMISSION_GRANTS_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
|
|
61
|
+
const revision = Number(root.revision);
|
|
62
|
+
const generatedAt = Number(root.generatedAt);
|
|
63
|
+
if (!Number.isSafeInteger(revision) || revision < 1) errors.push("document.revision must be a positive integer");
|
|
64
|
+
if (!Number.isFinite(generatedAt) || generatedAt <= 0) errors.push("document.generatedAt must be epoch milliseconds");
|
|
65
|
+
const source = root.source && typeof root.source === "object" ? root.source : {};
|
|
66
|
+
const target = root.target && typeof root.target === "object" ? root.target : {};
|
|
67
|
+
const sourceId = typeof source.appId === "string" ? source.appId : "";
|
|
68
|
+
const sourceActor = typeof source.actor === "string" ? source.actor.trim() : "";
|
|
69
|
+
const chapterId = typeof target.chapterId === "string" ? target.chapterId : "";
|
|
70
|
+
const environment = typeof target.environment === "string" ? target.environment : "";
|
|
71
|
+
if (!ID.test(sourceId)) errors.push("document.source.appId must be an identifier");
|
|
72
|
+
if (!sourceActor || sourceActor.length > 256) errors.push("document.source.actor must be 1-256 characters");
|
|
73
|
+
if (!ID.test(chapterId)) errors.push("document.target.chapterId must be an identifier");
|
|
74
|
+
if (!ID.test(environment)) errors.push("document.target.environment must be an identifier");
|
|
75
|
+
if (!Array.isArray(root.grants)) errors.push("document.grants must be an array");
|
|
76
|
+
const rawGrants = Array.isArray(root.grants) ? root.grants : [];
|
|
77
|
+
if (rawGrants.length > MAX_GRANTS) errors.push(`document.grants supports at most ${MAX_GRANTS} entries`);
|
|
78
|
+
const grants = [];
|
|
79
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
80
|
+
const seenEmails = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const [index, raw] of rawGrants.slice(0, MAX_GRANTS).entries()) {
|
|
82
|
+
const grant = raw && typeof raw === "object" ? raw : {};
|
|
83
|
+
const id4 = typeof grant.id === "string" ? grant.id : "";
|
|
84
|
+
const email = typeof grant.email === "string" ? grant.email.trim() : "";
|
|
85
|
+
const normalized = normalizeAdmissionEmail(email);
|
|
86
|
+
const decision = grant.decision;
|
|
87
|
+
const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
|
|
88
|
+
const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
|
|
89
|
+
if (!ID.test(id4)) errors.push(`document.grants[${index}].id must be an identifier`);
|
|
90
|
+
if (seenIds.has(id4)) errors.push(`document.grants repeats id "${id4}"`);
|
|
91
|
+
if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index}].email must be valid`);
|
|
92
|
+
if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
|
|
93
|
+
if (decision !== "waive_interview" && decision !== "require_interview") {
|
|
94
|
+
errors.push(`document.grants[${index}].decision is invalid`);
|
|
95
|
+
}
|
|
96
|
+
if (note && note.length > MAX_NOTE) errors.push(`document.grants[${index}].note is too long`);
|
|
97
|
+
if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
|
|
98
|
+
errors.push(`document.grants[${index}].expiresAt must be epoch milliseconds`);
|
|
99
|
+
}
|
|
100
|
+
seenIds.add(id4);
|
|
101
|
+
seenEmails.add(normalized);
|
|
102
|
+
grants.push({
|
|
103
|
+
id: id4,
|
|
104
|
+
email,
|
|
105
|
+
decision,
|
|
106
|
+
...note ? { note } : {},
|
|
107
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (errors.length) return { ok: false, errors };
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
value: {
|
|
114
|
+
schemaVersion: ADMISSION_GRANTS_SCHEMA_VERSION,
|
|
115
|
+
revision,
|
|
116
|
+
generatedAt,
|
|
117
|
+
source: { appId: sourceId, actor: sourceActor },
|
|
118
|
+
target: { chapterId, environment },
|
|
119
|
+
grants
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function createAdmissionGrantEnvelope(document) {
|
|
124
|
+
const checked = validateAdmissionGrantDocument(document);
|
|
125
|
+
if (!checked.ok) throw new TypeError(`Invalid admission grant document: ${checked.errors.join("; ")}`);
|
|
126
|
+
return { document: checked.value, digest: await signupControlDigest(checked.value) };
|
|
127
|
+
}
|
|
128
|
+
var rows = (value) => Array.isArray(value) ? value : [];
|
|
129
|
+
function asGrant(row) {
|
|
130
|
+
if (typeof row.id !== "string" || typeof row.emailNormalized !== "string") return null;
|
|
131
|
+
if (row.sourceScope !== "local" && row.sourceScope !== "parent") return null;
|
|
132
|
+
if (row.decision !== "waive_interview" && row.decision !== "require_interview") return null;
|
|
133
|
+
return row;
|
|
134
|
+
}
|
|
135
|
+
async function findAdmissionGrant(db, targetId, email, now) {
|
|
136
|
+
const emailNormalized = normalizeAdmissionEmail(email);
|
|
137
|
+
if (!isAdmissionEmail(emailNormalized)) return null;
|
|
138
|
+
const result = await db.query({ admissionGrants: { $: { where: { emailNormalized }, limit: 100 } } });
|
|
139
|
+
const active = rows(result.admissionGrants).map(asGrant).filter((grant) => Boolean(
|
|
140
|
+
grant && grant.targetId === targetId && grant.active === true && !grant.revokedAt && (!grant.expiresAt || grant.expiresAt > now) && !grant.consumedApplicationId
|
|
141
|
+
)).sort((a, b) => {
|
|
142
|
+
const scope = Number(b.sourceScope === "local") - Number(a.sourceScope === "local");
|
|
143
|
+
return scope || b.createdAt - a.createdAt;
|
|
144
|
+
});
|
|
145
|
+
return active[0] ?? null;
|
|
146
|
+
}
|
|
147
|
+
async function hasAdmissionRevision(db, sourceId, targetId, environment) {
|
|
148
|
+
const where = { sourceId, targetId, environment };
|
|
149
|
+
if (db.aggregate) {
|
|
150
|
+
return Number((await db.aggregate("admissionGrantRevisions", { count: true }, { where })).count ?? 0) > 0;
|
|
151
|
+
}
|
|
152
|
+
const found = await db.query({ admissionGrantRevisions: {} });
|
|
153
|
+
return rows(found.admissionGrantRevisions).some((row) => row.sourceId === sourceId && row.targetId === targetId && row.environment === environment);
|
|
154
|
+
}
|
|
155
|
+
async function loadLatestAdmissionRevision(db, sourceId, targetId, environment) {
|
|
156
|
+
if (!await hasAdmissionRevision(db, sourceId, targetId, environment)) return null;
|
|
157
|
+
const result = await db.query({ admissionGrantRevisions: { $: {
|
|
158
|
+
where: { sourceId, targetId, environment },
|
|
159
|
+
order: { revision: "desc" },
|
|
160
|
+
limit: 1
|
|
161
|
+
} } });
|
|
162
|
+
const row = rows(result.admissionGrantRevisions).sort((a, b) => Number(b.revision ?? 0) - Number(a.revision ?? 0))[0];
|
|
163
|
+
return row ? row : null;
|
|
164
|
+
}
|
|
165
|
+
async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
166
|
+
const { document, digest } = envelope;
|
|
167
|
+
const current = rows((await db.query({ admissionGrants: { $: {
|
|
168
|
+
where: { sourceScope: "parent", sourceId: document.source.appId, targetId: document.target.chapterId },
|
|
169
|
+
limit: MAX_GRANTS
|
|
170
|
+
} } })).admissionGrants);
|
|
171
|
+
const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
|
|
172
|
+
const ops = [];
|
|
173
|
+
for (const row of current) {
|
|
174
|
+
const id4 = String(row.id ?? "");
|
|
175
|
+
if (id4 && !incomingIds.has(id4)) ops.push({ t: "update", ns: "admissionGrants", id: id4, attrs: {
|
|
176
|
+
active: false,
|
|
177
|
+
revokedAt: receivedAt,
|
|
178
|
+
revokedBy: document.source.actor,
|
|
179
|
+
updatedAt: receivedAt,
|
|
180
|
+
updatedBy: document.source.actor
|
|
181
|
+
} });
|
|
182
|
+
}
|
|
183
|
+
for (const grant of document.grants) {
|
|
184
|
+
const id4 = `admission:parent:${document.source.appId}:${grant.id}`;
|
|
185
|
+
const existing = current.find((row) => row.id === id4);
|
|
186
|
+
ops.push({ t: "update", ns: "admissionGrants", id: id4, attrs: {
|
|
187
|
+
id: id4,
|
|
188
|
+
sourceGrantId: grant.id,
|
|
189
|
+
sourceScope: "parent",
|
|
190
|
+
sourceId: document.source.appId,
|
|
191
|
+
targetId: document.target.chapterId,
|
|
192
|
+
email: grant.email,
|
|
193
|
+
emailNormalized: normalizeAdmissionEmail(grant.email),
|
|
194
|
+
decision: grant.decision,
|
|
195
|
+
...grant.note ? { note: grant.note } : {},
|
|
196
|
+
...grant.expiresAt ? { expiresAt: grant.expiresAt } : {},
|
|
197
|
+
active: true,
|
|
198
|
+
sourceRevision: document.revision,
|
|
199
|
+
createdAt: Number(existing?.createdAt ?? receivedAt),
|
|
200
|
+
createdBy: String(existing?.createdBy ?? document.source.actor),
|
|
201
|
+
updatedAt: receivedAt,
|
|
202
|
+
updatedBy: document.source.actor
|
|
203
|
+
} });
|
|
204
|
+
const retract = [
|
|
205
|
+
...!grant.note && existing?.note !== void 0 ? ["note"] : [],
|
|
206
|
+
...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
|
|
207
|
+
];
|
|
208
|
+
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id4, attrs: retract });
|
|
209
|
+
}
|
|
210
|
+
if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
|
|
211
|
+
const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
|
|
212
|
+
ops.push({ t: "update", ns: "admissionGrantRevisions", id: revisionId, attrs: {
|
|
213
|
+
id: revisionId,
|
|
214
|
+
sourceId: document.source.appId,
|
|
215
|
+
targetId: document.target.chapterId,
|
|
216
|
+
environment: document.target.environment,
|
|
217
|
+
revision: document.revision,
|
|
218
|
+
digest,
|
|
219
|
+
active: true,
|
|
220
|
+
receivedAt
|
|
221
|
+
} });
|
|
222
|
+
await db.transact(ops, { mutationId: `admission-grants:${document.source.appId}:${document.target.environment}:${document.revision}:${digest}` });
|
|
223
|
+
}
|
|
224
|
+
|
|
18
225
|
// src/member.ts
|
|
19
226
|
var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
|
|
20
227
|
var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
|
|
@@ -66,9 +273,9 @@ function resolveApplication(a) {
|
|
|
66
273
|
validateEmail: a?.validateEmail ?? true
|
|
67
274
|
};
|
|
68
275
|
}
|
|
69
|
-
var
|
|
276
|
+
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
70
277
|
function isValidEmail(value) {
|
|
71
|
-
return typeof value === "string" &&
|
|
278
|
+
return typeof value === "string" && EMAIL_RE2.test(value);
|
|
72
279
|
}
|
|
73
280
|
function clampArray(value, max) {
|
|
74
281
|
if (!Array.isArray(value)) return value;
|
|
@@ -110,14 +317,23 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
110
317
|
if (app.requireDisclaimerAck && !acked) {
|
|
111
318
|
return { ok: false, error: "disclaimerAck is required" };
|
|
112
319
|
}
|
|
113
|
-
const
|
|
114
|
-
const row = { id:
|
|
320
|
+
const id4 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
321
|
+
const row = { id: id4, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
115
322
|
for (const f of [...app.required, ...app.optional]) {
|
|
116
323
|
if (fieldStates[f]?.visible === false) continue;
|
|
117
324
|
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
118
325
|
}
|
|
119
326
|
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
120
327
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
328
|
+
if (typeof fields.email === "string" && isValidEmail(fields.email.trim())) {
|
|
329
|
+
row.emailNormalized = normalizeAdmissionEmail(fields.email);
|
|
330
|
+
}
|
|
331
|
+
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
332
|
+
row.admissionGrantId = opts.admissionGrant.id;
|
|
333
|
+
row.admissionSource = opts.admissionGrant.sourceScope;
|
|
334
|
+
row.interviewWaivedAt = opts.now;
|
|
335
|
+
}
|
|
336
|
+
if (opts.giftOfferId) row.giftOfferId = opts.giftOfferId;
|
|
121
337
|
if (typeof fields.tierId === "string" && fields.tierId) {
|
|
122
338
|
if (opts.tierIds && !opts.tierIds.includes(fields.tierId)) {
|
|
123
339
|
return { ok: false, error: "tierId is not an offered tier" };
|
|
@@ -125,11 +341,20 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
125
341
|
row.tierId = fields.tierId;
|
|
126
342
|
}
|
|
127
343
|
if (acked) row.disclaimerAckAt = opts.now;
|
|
344
|
+
const ops = [
|
|
345
|
+
{ t: "update", ns: "applications", id: id4, attrs: row }
|
|
346
|
+
];
|
|
347
|
+
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
348
|
+
ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
|
|
349
|
+
consumedAt: opts.now,
|
|
350
|
+
consumedApplicationId: id4
|
|
351
|
+
} });
|
|
352
|
+
}
|
|
128
353
|
const { duplicate } = await db.transact(
|
|
129
|
-
|
|
354
|
+
ops,
|
|
130
355
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
131
356
|
);
|
|
132
|
-
return { ok: true, id:
|
|
357
|
+
return { ok: true, id: id4, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
133
358
|
}
|
|
134
359
|
function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
|
|
135
360
|
return {
|
|
@@ -149,93 +374,13 @@ function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
|
|
|
149
374
|
};
|
|
150
375
|
}
|
|
151
376
|
|
|
152
|
-
// src/schema.ts
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
var id = () => attr("string", { unique: true, indexed: true });
|
|
162
|
-
var admins = {
|
|
163
|
-
attrs: {
|
|
164
|
-
id: id(),
|
|
165
|
-
email: attr("string", { unique: true, indexed: true }),
|
|
166
|
-
name: attr("string", { optional: true }),
|
|
167
|
-
note: attr("string", { optional: true })
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
var superAdmins = {
|
|
171
|
-
attrs: {
|
|
172
|
-
id: id(),
|
|
173
|
-
email: attr("string", { unique: true, indexed: true }),
|
|
174
|
-
note: attr("string", { optional: true }),
|
|
175
|
-
createdAt: attr("number", { indexed: true })
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
var networkNotes = {
|
|
179
|
-
attrs: {
|
|
180
|
-
id: id(),
|
|
181
|
-
recordKey: attr("string", { indexed: true }),
|
|
182
|
-
targetId: attr("string", { indexed: true }),
|
|
183
|
-
recordType: attr("string", { indexed: true }),
|
|
184
|
-
recordId: attr("string", { indexed: true }),
|
|
185
|
-
body: attr("string"),
|
|
186
|
-
authorId: attr("string", { indexed: true }),
|
|
187
|
-
authorEmail: attr("string", { optional: true }),
|
|
188
|
-
createdAt: attr("number", { indexed: true })
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
var signupControls = {
|
|
192
|
-
attrs: {
|
|
193
|
-
id: id(),
|
|
194
|
-
sourceId: attr("string", { indexed: true }),
|
|
195
|
-
targetId: attr("string", { indexed: true }),
|
|
196
|
-
environment: attr("string", { indexed: true }),
|
|
197
|
-
revision: attr("number", { indexed: true }),
|
|
198
|
-
digest: attr("string", { indexed: true }),
|
|
199
|
-
document: attr("json"),
|
|
200
|
-
active: attr("boolean", { indexed: true }),
|
|
201
|
-
receivedAt: attr("number", { indexed: true })
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
var applications = {
|
|
205
|
-
attrs: {
|
|
206
|
-
id: id(),
|
|
207
|
-
firstName: attr("string"),
|
|
208
|
-
lastName: attr("string"),
|
|
209
|
-
email: attr("string", { indexed: true }),
|
|
210
|
-
referral: attr("string"),
|
|
211
|
-
referralName: attr("string", { optional: true }),
|
|
212
|
-
whoYouAre: attr("string"),
|
|
213
|
-
focus: attr("json"),
|
|
214
|
-
linkedin: attr("string", { optional: true }),
|
|
215
|
-
message: attr("string"),
|
|
216
|
-
status: attr("string", { indexed: true }),
|
|
217
|
-
createdAt: attr("number", { indexed: true }),
|
|
218
|
-
meetingAt: attr("number", { indexed: true, optional: true }),
|
|
219
|
-
meetingLink: attr("string", { optional: true }),
|
|
220
|
-
clerkUserId: attr("string", { indexed: true, optional: true }),
|
|
221
|
-
// One-time/effect-repair marker. Absent means /api/me should copy the
|
|
222
|
-
// allowlisted application snapshot into Clerk private_metadata and remove
|
|
223
|
-
// the former public_metadata copy.
|
|
224
|
-
clerkPrivateMetadataSyncedAt: attr("number", { optional: true }),
|
|
225
|
-
phone: attr("string", { optional: true }),
|
|
226
|
-
state: attr("string", { optional: true }),
|
|
227
|
-
groupId: attr("string", { indexed: true, optional: true }),
|
|
228
|
-
// The tier this application joined on. Absent on rows that predate tiers.
|
|
229
|
-
tierId: attr("string", { indexed: true, optional: true }),
|
|
230
|
-
stripeCustomerId: attr("string", { indexed: true, optional: true }),
|
|
231
|
-
stripeSubscriptionId: attr("string", { indexed: true, optional: true }),
|
|
232
|
-
renewalAt: attr("number", { optional: true }),
|
|
233
|
-
disclaimerAckAt: attr("number", { optional: true }),
|
|
234
|
-
refundPolicyAckAt: attr("number", { optional: true }),
|
|
235
|
-
prepEmailSentAt: attr("number", { optional: true }),
|
|
236
|
-
canceled: attr("boolean", { optional: true })
|
|
237
|
-
}
|
|
238
|
-
};
|
|
377
|
+
// src/schema-applications.ts
|
|
378
|
+
var attr = (type, flags = {}) => ({
|
|
379
|
+
type,
|
|
380
|
+
unique: flags.unique ?? false,
|
|
381
|
+
indexed: flags.indexed ?? false,
|
|
382
|
+
optional: flags.optional ?? false
|
|
383
|
+
});
|
|
239
384
|
var APPLICATION_INPUT_FIELDS = /* @__PURE__ */ new Set([
|
|
240
385
|
"firstName",
|
|
241
386
|
"lastName",
|
|
@@ -249,25 +394,57 @@ var APPLICATION_INPUT_FIELDS = /* @__PURE__ */ new Set([
|
|
|
249
394
|
"phone",
|
|
250
395
|
"state"
|
|
251
396
|
]);
|
|
397
|
+
var applicationAttrs = {
|
|
398
|
+
id: attr("string", { unique: true, indexed: true }),
|
|
399
|
+
firstName: attr("string"),
|
|
400
|
+
lastName: attr("string"),
|
|
401
|
+
email: attr("string", { indexed: true }),
|
|
402
|
+
referral: attr("string"),
|
|
403
|
+
referralName: attr("string", { optional: true }),
|
|
404
|
+
whoYouAre: attr("string"),
|
|
405
|
+
focus: attr("json"),
|
|
406
|
+
linkedin: attr("string", { optional: true }),
|
|
407
|
+
message: attr("string"),
|
|
408
|
+
status: attr("string", { indexed: true }),
|
|
409
|
+
createdAt: attr("number", { indexed: true }),
|
|
410
|
+
meetingAt: attr("number", { indexed: true, optional: true }),
|
|
411
|
+
meetingLink: attr("string", { optional: true }),
|
|
412
|
+
clerkUserId: attr("string", { indexed: true, optional: true }),
|
|
413
|
+
clerkPrivateMetadataSyncedAt: attr("number", { optional: true }),
|
|
414
|
+
phone: attr("string", { optional: true }),
|
|
415
|
+
state: attr("string", { optional: true }),
|
|
416
|
+
groupId: attr("string", { indexed: true, optional: true }),
|
|
417
|
+
tierId: attr("string", { indexed: true, optional: true }),
|
|
418
|
+
stripeCustomerId: attr("string", { indexed: true, optional: true }),
|
|
419
|
+
stripeSubscriptionId: attr("string", { indexed: true, optional: true }),
|
|
420
|
+
renewalAt: attr("number", { optional: true }),
|
|
421
|
+
disclaimerAckAt: attr("number", { optional: true }),
|
|
422
|
+
refundPolicyAckAt: attr("number", { optional: true }),
|
|
423
|
+
prepEmailSentAt: attr("number", { optional: true }),
|
|
424
|
+
canceled: attr("boolean", { optional: true }),
|
|
425
|
+
emailNormalized: attr("string", { indexed: true, optional: true }),
|
|
426
|
+
admissionGrantId: attr("string", { unique: true, indexed: true, optional: true }),
|
|
427
|
+
admissionSource: attr("string", { indexed: true, optional: true }),
|
|
428
|
+
interviewWaivedAt: attr("number", { optional: true }),
|
|
429
|
+
giftOfferId: attr("string", { optional: true, indexed: true }),
|
|
430
|
+
giftIntentId: attr("string", { optional: true, unique: true, indexed: true }),
|
|
431
|
+
giftClaimId: attr("string", { optional: true, unique: true, indexed: true }),
|
|
432
|
+
giftOrderId: attr("string", { optional: true, indexed: true }),
|
|
433
|
+
giftPaidAt: attr("number", { optional: true, indexed: true }),
|
|
434
|
+
giftRevokedAt: attr("number", { optional: true, indexed: true }),
|
|
435
|
+
giftCheckoutStartedAt: attr("number", { optional: true, indexed: true })
|
|
436
|
+
};
|
|
252
437
|
function applicationsFor(application) {
|
|
253
438
|
const attrs = {};
|
|
254
|
-
for (const [name, spec] of Object.entries(
|
|
255
|
-
attrs[name] = {
|
|
256
|
-
...spec,
|
|
257
|
-
optional: APPLICATION_INPUT_FIELDS.has(name) ? true : spec.optional
|
|
258
|
-
};
|
|
439
|
+
for (const [name, spec] of Object.entries(applicationAttrs)) {
|
|
440
|
+
attrs[name] = { ...spec, optional: APPLICATION_INPUT_FIELDS.has(name) ? true : spec.optional };
|
|
259
441
|
}
|
|
260
442
|
const apply = (field, optional) => {
|
|
261
443
|
const existing = Object.hasOwn(attrs, field) ? attrs[field] : void 0;
|
|
262
|
-
if (existing && !APPLICATION_INPUT_FIELDS.has(field)) {
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
if (existing && existing.type !== "string") {
|
|
266
|
-
throw new Error(`defineChapter.application: field "${field}" is not a configurable string field`);
|
|
267
|
-
}
|
|
268
|
-
const spec = existing ? { ...existing, optional } : attr("string", { optional });
|
|
444
|
+
if (existing && !APPLICATION_INPUT_FIELDS.has(field)) throw new Error(`defineChapter.application: field "${field}" is reserved by the applications schema`);
|
|
445
|
+
if (existing && existing.type !== "string") throw new Error(`defineChapter.application: field "${field}" is not a configurable string field`);
|
|
269
446
|
Object.defineProperty(attrs, field, {
|
|
270
|
-
value:
|
|
447
|
+
value: existing ? { ...existing, optional } : attr("string", { optional }),
|
|
271
448
|
enumerable: true,
|
|
272
449
|
configurable: true,
|
|
273
450
|
writable: true
|
|
@@ -277,75 +454,270 @@ function applicationsFor(application) {
|
|
|
277
454
|
for (const field of application.optional) apply(field, true);
|
|
278
455
|
return { attrs };
|
|
279
456
|
}
|
|
280
|
-
|
|
457
|
+
|
|
458
|
+
// src/schema-admission.ts
|
|
459
|
+
var attr2 = (type, flags = {}) => ({
|
|
460
|
+
type,
|
|
461
|
+
unique: flags.unique ?? false,
|
|
462
|
+
indexed: flags.indexed ?? false,
|
|
463
|
+
optional: flags.optional ?? false
|
|
464
|
+
});
|
|
465
|
+
var id = () => attr2("string", { unique: true, indexed: true });
|
|
466
|
+
var admissionGrantsEntity = {
|
|
281
467
|
attrs: {
|
|
282
468
|
id: id(),
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
469
|
+
sourceGrantId: attr2("string", { indexed: true }),
|
|
470
|
+
sourceScope: attr2("string", { indexed: true }),
|
|
471
|
+
sourceId: attr2("string", { indexed: true }),
|
|
472
|
+
targetId: attr2("string", { indexed: true }),
|
|
473
|
+
email: attr2("string"),
|
|
474
|
+
emailNormalized: attr2("string", { indexed: true }),
|
|
475
|
+
decision: attr2("string", { indexed: true }),
|
|
476
|
+
note: attr2("string", { optional: true }),
|
|
477
|
+
active: attr2("boolean", { indexed: true }),
|
|
478
|
+
sourceRevision: attr2("number", { optional: true, indexed: true }),
|
|
479
|
+
createdAt: attr2("number", { indexed: true }),
|
|
480
|
+
createdBy: attr2("string", { optional: true, indexed: true }),
|
|
481
|
+
updatedAt: attr2("number", { optional: true, indexed: true }),
|
|
482
|
+
updatedBy: attr2("string", { optional: true, indexed: true }),
|
|
483
|
+
expiresAt: attr2("number", { optional: true, indexed: true }),
|
|
484
|
+
revokedAt: attr2("number", { optional: true, indexed: true }),
|
|
485
|
+
revokedBy: attr2("string", { optional: true, indexed: true }),
|
|
486
|
+
consumedAt: attr2("number", { optional: true, indexed: true }),
|
|
487
|
+
consumedApplicationId: attr2("string", { optional: true, indexed: true })
|
|
300
488
|
}
|
|
301
489
|
};
|
|
302
|
-
var
|
|
490
|
+
var admissionGrantRevisionsEntity = {
|
|
303
491
|
attrs: {
|
|
304
492
|
id: id(),
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
493
|
+
sourceId: attr2("string", { indexed: true }),
|
|
494
|
+
targetId: attr2("string", { indexed: true }),
|
|
495
|
+
environment: attr2("string", { indexed: true }),
|
|
496
|
+
revision: attr2("number", { indexed: true }),
|
|
497
|
+
digest: attr2("string", { indexed: true }),
|
|
498
|
+
active: attr2("boolean", { indexed: true }),
|
|
499
|
+
receivedAt: attr2("number", { indexed: true })
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/schema-gifts.ts
|
|
504
|
+
var attr3 = (type, flags = {}) => ({
|
|
505
|
+
type,
|
|
506
|
+
unique: flags.unique ?? false,
|
|
507
|
+
indexed: flags.indexed ?? false,
|
|
508
|
+
optional: flags.optional ?? false
|
|
509
|
+
});
|
|
510
|
+
var id2 = () => attr3("string", { unique: true, indexed: true });
|
|
511
|
+
var giftPurchaseIntentsEntity = { attrs: {
|
|
512
|
+
id: id2(),
|
|
513
|
+
chapterId: attr3("string", { indexed: true }),
|
|
514
|
+
environment: attr3("string", { indexed: true }),
|
|
515
|
+
purchaserType: attr3("string", { indexed: true }),
|
|
516
|
+
purchaserApplicationId: attr3("string", { optional: true, indexed: true }),
|
|
517
|
+
purchaserUserId: attr3("string", { optional: true, indexed: true }),
|
|
518
|
+
purchaserEmailNormalized: attr3("string", { indexed: true }),
|
|
519
|
+
offerId: attr3("string", { indexed: true }),
|
|
520
|
+
offerSnapshot: attr3("json"),
|
|
521
|
+
status: attr3("string", { indexed: true }),
|
|
522
|
+
codeHash: attr3("string", { indexed: true }),
|
|
523
|
+
codeVersion: attr3("number"),
|
|
524
|
+
codeIssuedAt: attr3("number"),
|
|
525
|
+
providerObjectId: attr3("string", { optional: true, unique: true, indexed: true }),
|
|
526
|
+
providerCustomerId: attr3("string", { optional: true, indexed: true }),
|
|
527
|
+
checkoutStartedAt: attr3("number", { optional: true }),
|
|
528
|
+
paidAt: attr3("number", { optional: true, indexed: true }),
|
|
529
|
+
claimId: attr3("string", { optional: true, unique: true, indexed: true }),
|
|
530
|
+
createdAt: attr3("number", { indexed: true }),
|
|
531
|
+
updatedAt: attr3("number", { indexed: true })
|
|
532
|
+
} };
|
|
533
|
+
var giftOrdersEntity = { attrs: {
|
|
534
|
+
id: id2(),
|
|
535
|
+
intentId: attr3("string", { unique: true, indexed: true }),
|
|
536
|
+
chapterId: attr3("string", { indexed: true }),
|
|
537
|
+
environment: attr3("string", { indexed: true }),
|
|
538
|
+
offerId: attr3("string", { indexed: true }),
|
|
539
|
+
offerSnapshot: attr3("json"),
|
|
540
|
+
purchaserType: attr3("string", { indexed: true }),
|
|
541
|
+
purchaserApplicationId: attr3("string", { optional: true, indexed: true }),
|
|
542
|
+
purchaserUserId: attr3("string", { optional: true, indexed: true }),
|
|
543
|
+
purchaserEmailNormalized: attr3("string", { indexed: true }),
|
|
544
|
+
providerEventId: attr3("string", { unique: true, indexed: true }),
|
|
545
|
+
providerObjectId: attr3("string", { indexed: true }),
|
|
546
|
+
amountCents: attr3("number"),
|
|
547
|
+
currency: attr3("string"),
|
|
548
|
+
billingMode: attr3("string"),
|
|
549
|
+
createdAt: attr3("number", { indexed: true })
|
|
550
|
+
} };
|
|
551
|
+
var giftClaimsEntity = { attrs: {
|
|
552
|
+
id: id2(),
|
|
553
|
+
orderId: attr3("string", { unique: true, indexed: true }),
|
|
554
|
+
intentId: attr3("string", { unique: true, indexed: true }),
|
|
555
|
+
chapterId: attr3("string", { indexed: true }),
|
|
556
|
+
offerId: attr3("string", { indexed: true }),
|
|
557
|
+
target: attr3("json"),
|
|
558
|
+
purchaserApplicationId: attr3("string", { optional: true, indexed: true }),
|
|
559
|
+
purchaserUserId: attr3("string", { optional: true, indexed: true }),
|
|
560
|
+
purchaserEmailNormalized: attr3("string", { indexed: true }),
|
|
561
|
+
codeHash: attr3("string", { unique: true, indexed: true }),
|
|
562
|
+
codeVersion: attr3("number"),
|
|
563
|
+
rotationCount: attr3("number"),
|
|
564
|
+
codeIssuedAt: attr3("number"),
|
|
565
|
+
codeExpiresAt: attr3("number", { indexed: true }),
|
|
566
|
+
attemptCount: attr3("number"),
|
|
567
|
+
attemptWindowStartedAt: attr3("number"),
|
|
568
|
+
status: attr3("string", { indexed: true }),
|
|
569
|
+
redeemedAt: attr3("number", { optional: true, indexed: true }),
|
|
570
|
+
beneficiaryEmailNormalized: attr3("string", { optional: true, indexed: true }),
|
|
571
|
+
beneficiaryUserId: attr3("string", { optional: true, indexed: true }),
|
|
572
|
+
applicationId: attr3("string", { optional: true, unique: true, indexed: true }),
|
|
573
|
+
entitlementId: attr3("string", { optional: true, unique: true, indexed: true }),
|
|
574
|
+
rotatedAt: attr3("number", { optional: true }),
|
|
575
|
+
revokedAt: attr3("number", { optional: true, indexed: true }),
|
|
576
|
+
revokedReason: attr3("string", { optional: true }),
|
|
577
|
+
createdAt: attr3("number", { indexed: true })
|
|
578
|
+
} };
|
|
579
|
+
var giftEntitlementsEntity = { attrs: {
|
|
580
|
+
id: id2(),
|
|
581
|
+
claimId: attr3("string", { unique: true, indexed: true }),
|
|
582
|
+
orderId: attr3("string", { indexed: true }),
|
|
583
|
+
chapterId: attr3("string", { indexed: true }),
|
|
584
|
+
entitlementKey: attr3("string", { indexed: true }),
|
|
585
|
+
beneficiaryEmailNormalized: attr3("string", { indexed: true }),
|
|
586
|
+
beneficiaryUserId: attr3("string", { optional: true, indexed: true }),
|
|
587
|
+
active: attr3("boolean", { indexed: true }),
|
|
588
|
+
createdAt: attr3("number", { indexed: true }),
|
|
589
|
+
revokedAt: attr3("number", { optional: true })
|
|
590
|
+
} };
|
|
591
|
+
var giftRedemptionAttemptsEntity = { attrs: {
|
|
592
|
+
id: id2(),
|
|
593
|
+
chapterId: attr3("string", { indexed: true }),
|
|
594
|
+
bucket: attr3("string", { indexed: true }),
|
|
595
|
+
attemptCount: attr3("number"),
|
|
596
|
+
windowStartedAt: attr3("number", { indexed: true }),
|
|
597
|
+
updatedAt: attr3("number")
|
|
598
|
+
} };
|
|
599
|
+
|
|
600
|
+
// src/schema.ts
|
|
601
|
+
function attr4(type, flags = {}) {
|
|
602
|
+
return {
|
|
603
|
+
type,
|
|
604
|
+
unique: flags.unique ?? false,
|
|
605
|
+
indexed: flags.indexed ?? false,
|
|
606
|
+
optional: flags.optional ?? false
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
var id3 = () => attr4("string", { unique: true, indexed: true });
|
|
610
|
+
var admins = {
|
|
611
|
+
attrs: {
|
|
612
|
+
id: id3(),
|
|
613
|
+
email: attr4("string", { unique: true, indexed: true }),
|
|
614
|
+
name: attr4("string", { optional: true }),
|
|
615
|
+
note: attr4("string", { optional: true })
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
var superAdmins = {
|
|
619
|
+
attrs: {
|
|
620
|
+
id: id3(),
|
|
621
|
+
email: attr4("string", { unique: true, indexed: true }),
|
|
622
|
+
note: attr4("string", { optional: true }),
|
|
623
|
+
createdAt: attr4("number", { indexed: true })
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
var networkNotes = {
|
|
627
|
+
attrs: {
|
|
628
|
+
id: id3(),
|
|
629
|
+
recordKey: attr4("string", { indexed: true }),
|
|
630
|
+
targetId: attr4("string", { indexed: true }),
|
|
631
|
+
recordType: attr4("string", { indexed: true }),
|
|
632
|
+
recordId: attr4("string", { indexed: true }),
|
|
633
|
+
body: attr4("string"),
|
|
634
|
+
authorId: attr4("string", { indexed: true }),
|
|
635
|
+
authorEmail: attr4("string", { optional: true }),
|
|
636
|
+
createdAt: attr4("number", { indexed: true })
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
var signupControls = {
|
|
640
|
+
attrs: {
|
|
641
|
+
id: id3(),
|
|
642
|
+
sourceId: attr4("string", { indexed: true }),
|
|
643
|
+
targetId: attr4("string", { indexed: true }),
|
|
644
|
+
environment: attr4("string", { indexed: true }),
|
|
645
|
+
revision: attr4("number", { indexed: true }),
|
|
646
|
+
digest: attr4("string", { indexed: true }),
|
|
647
|
+
document: attr4("json"),
|
|
648
|
+
active: attr4("boolean", { indexed: true }),
|
|
649
|
+
receivedAt: attr4("number", { indexed: true })
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
var groups = {
|
|
653
|
+
attrs: {
|
|
654
|
+
id: id3(),
|
|
655
|
+
name: attr4("string"),
|
|
656
|
+
standardPriceCents: attr4("number"),
|
|
657
|
+
foundingDiscountCents: attr4("number"),
|
|
658
|
+
stripePriceId: attr4("string", { optional: true }),
|
|
659
|
+
stripePublishableKey: attr4("string", { optional: true }),
|
|
660
|
+
notificationEmail: attr4("string"),
|
|
661
|
+
replyTo: attr4("string"),
|
|
662
|
+
debugEmail: attr4("string", { optional: true }),
|
|
663
|
+
calendarLink: attr4("string", { optional: true }),
|
|
664
|
+
disclaimerText: attr4("string"),
|
|
665
|
+
refundPolicyText: attr4("string"),
|
|
666
|
+
trustCopy: attr4("string"),
|
|
667
|
+
commitmentText: attr4("string", { optional: true }),
|
|
668
|
+
normsText: attr4("string", { optional: true }),
|
|
669
|
+
emailTemplates: attr4("json"),
|
|
670
|
+
schedulingJson: attr4("json", { optional: true }),
|
|
671
|
+
createdAt: attr4("number", { indexed: true })
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
var tiers = {
|
|
675
|
+
attrs: {
|
|
676
|
+
id: id3(),
|
|
677
|
+
groupId: attr4("string", { indexed: true }),
|
|
678
|
+
name: attr4("string"),
|
|
679
|
+
priceCents: attr4("number"),
|
|
680
|
+
stripePriceId: attr4("string", { optional: true }),
|
|
681
|
+
blurb: attr4("string", { optional: true }),
|
|
682
|
+
sortOrder: attr4("number", { indexed: true }),
|
|
683
|
+
active: attr4("boolean", { indexed: true }),
|
|
684
|
+
createdAt: attr4("number", { indexed: true })
|
|
313
685
|
}
|
|
314
686
|
};
|
|
315
687
|
var meetings = {
|
|
316
688
|
attrs: {
|
|
317
|
-
id:
|
|
318
|
-
applicationId:
|
|
319
|
-
groupId:
|
|
320
|
-
startAt:
|
|
321
|
-
endAt:
|
|
322
|
-
timezone:
|
|
323
|
-
status:
|
|
324
|
-
googleEventId:
|
|
325
|
-
meetUrl:
|
|
326
|
-
htmlLink:
|
|
327
|
-
drift:
|
|
328
|
-
driftGoogleStartAt:
|
|
329
|
-
driftDetectedAt:
|
|
330
|
-
adoptedFromGoogleAt:
|
|
331
|
-
createdAt:
|
|
689
|
+
id: id3(),
|
|
690
|
+
applicationId: attr4("string", { indexed: true }),
|
|
691
|
+
groupId: attr4("string", { indexed: true }),
|
|
692
|
+
startAt: attr4("number", { indexed: true }),
|
|
693
|
+
endAt: attr4("number"),
|
|
694
|
+
timezone: attr4("string"),
|
|
695
|
+
status: attr4("string", { indexed: true }),
|
|
696
|
+
googleEventId: attr4("string", { indexed: true, optional: true }),
|
|
697
|
+
meetUrl: attr4("string", { optional: true }),
|
|
698
|
+
htmlLink: attr4("string", { optional: true }),
|
|
699
|
+
drift: attr4("string", { indexed: true, optional: true }),
|
|
700
|
+
driftGoogleStartAt: attr4("number", { optional: true }),
|
|
701
|
+
driftDetectedAt: attr4("number", { optional: true }),
|
|
702
|
+
adoptedFromGoogleAt: attr4("number", { optional: true }),
|
|
703
|
+
createdAt: attr4("number", { indexed: true })
|
|
332
704
|
}
|
|
333
705
|
};
|
|
334
706
|
var emailLog = {
|
|
335
707
|
attrs: {
|
|
336
|
-
id:
|
|
337
|
-
groupId:
|
|
338
|
-
applicationId:
|
|
339
|
-
to:
|
|
340
|
-
template:
|
|
341
|
-
subject:
|
|
342
|
-
body:
|
|
343
|
-
transport:
|
|
344
|
-
messageId:
|
|
345
|
-
redirected:
|
|
346
|
-
dedupeKey:
|
|
347
|
-
error:
|
|
348
|
-
sentAt:
|
|
708
|
+
id: id3(),
|
|
709
|
+
groupId: attr4("string", { indexed: true }),
|
|
710
|
+
applicationId: attr4("string", { indexed: true, optional: true }),
|
|
711
|
+
to: attr4("string", { indexed: true }),
|
|
712
|
+
template: attr4("string", { indexed: true }),
|
|
713
|
+
subject: attr4("string"),
|
|
714
|
+
body: attr4("string", { optional: true }),
|
|
715
|
+
transport: attr4("string"),
|
|
716
|
+
messageId: attr4("string", { optional: true }),
|
|
717
|
+
redirected: attr4("boolean", { optional: true }),
|
|
718
|
+
dedupeKey: attr4("string", { indexed: true, optional: true }),
|
|
719
|
+
error: attr4("string", { optional: true }),
|
|
720
|
+
sentAt: attr4("number", { indexed: true })
|
|
349
721
|
}
|
|
350
722
|
};
|
|
351
723
|
function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false) {
|
|
@@ -356,6 +728,13 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
|
|
|
356
728
|
entities.tiers = tiers;
|
|
357
729
|
entities.meetings = meetings;
|
|
358
730
|
entities.emailLog = emailLog;
|
|
731
|
+
entities.admissionGrants = admissionGrantsEntity;
|
|
732
|
+
entities.admissionGrantRevisions = admissionGrantRevisionsEntity;
|
|
733
|
+
entities.giftPurchaseIntents = giftPurchaseIntentsEntity;
|
|
734
|
+
entities.giftOrders = giftOrdersEntity;
|
|
735
|
+
entities.giftClaims = giftClaimsEntity;
|
|
736
|
+
entities.giftEntitlements = giftEntitlementsEntity;
|
|
737
|
+
entities.giftRedemptionAttempts = giftRedemptionAttemptsEntity;
|
|
359
738
|
if (includeSignupControl) entities.signupControls = signupControls;
|
|
360
739
|
}
|
|
361
740
|
if (auth.source === "table") entities.admins = admins;
|
|
@@ -640,6 +1019,7 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
640
1019
|
overview: "Overview",
|
|
641
1020
|
billing: "Billing",
|
|
642
1021
|
people: "People",
|
|
1022
|
+
admission: "Admission",
|
|
643
1023
|
network: "Network",
|
|
644
1024
|
portfolio: "Portfolio",
|
|
645
1025
|
settings: "Settings",
|
|
@@ -750,6 +1130,26 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
750
1130
|
reschedule: "Reschedule",
|
|
751
1131
|
cancel: "Cancel"
|
|
752
1132
|
},
|
|
1133
|
+
admission: {
|
|
1134
|
+
title: "Interview decisions",
|
|
1135
|
+
description: "Local decisions override Parent decisions. Requiring an interview locally suppresses a Parent waiver for the same email.",
|
|
1136
|
+
email: "Email",
|
|
1137
|
+
decision: "Decision",
|
|
1138
|
+
expires: "Expires",
|
|
1139
|
+
note: "Internal note",
|
|
1140
|
+
waive: "Waive interview",
|
|
1141
|
+
require: "Require interview",
|
|
1142
|
+
add: "Add decision",
|
|
1143
|
+
ledger: "Effective grant ledger",
|
|
1144
|
+
loading: "Loading\u2026",
|
|
1145
|
+
consumed: "consumed",
|
|
1146
|
+
revoked: "revoked",
|
|
1147
|
+
expired: "expired",
|
|
1148
|
+
active: "active",
|
|
1149
|
+
local: "local",
|
|
1150
|
+
parent: "Parent",
|
|
1151
|
+
revoke: "Revoke"
|
|
1152
|
+
},
|
|
753
1153
|
network: {
|
|
754
1154
|
title: "Share to a follower",
|
|
755
1155
|
allowlistDescription: "Sends the target's allowlisted fields. Its pipeline and account state remain local.",
|
|
@@ -848,6 +1248,19 @@ var DEFAULT_JOIN_COPY = {
|
|
|
848
1248
|
label: "You're booked",
|
|
849
1249
|
calendarInvite: "A calendar invitation with the video call link is on its way to your email.",
|
|
850
1250
|
memberArea: "Go to your member area"
|
|
1251
|
+
},
|
|
1252
|
+
accepted: {
|
|
1253
|
+
label: "You're approved",
|
|
1254
|
+
body: "Your membership is approved. No introduction call is required."
|
|
1255
|
+
},
|
|
1256
|
+
gift: {
|
|
1257
|
+
chooseSelf: "Buy membership for myself",
|
|
1258
|
+
optionsLabel: "Or give access to someone else",
|
|
1259
|
+
label: "Your gift code",
|
|
1260
|
+
body: "Share this one-time code with the recipient. It becomes active after payment is confirmed.",
|
|
1261
|
+
save: "Save it now. For security, this code is shown only once.",
|
|
1262
|
+
rotate: "Generate a new code",
|
|
1263
|
+
rotateFailed: "A new code could not be generated."
|
|
851
1264
|
}
|
|
852
1265
|
};
|
|
853
1266
|
var DEFAULT_MEMBERS_COPY = {
|
|
@@ -875,6 +1288,26 @@ var DEFAULT_MEMBERS_COPY = {
|
|
|
875
1288
|
full: {
|
|
876
1289
|
welcome: "Welcome back."
|
|
877
1290
|
},
|
|
1291
|
+
gifts: {
|
|
1292
|
+
title: "Give access",
|
|
1293
|
+
body: "Purchase access for someone else and share the one-time claim code.",
|
|
1294
|
+
noneAvailable: "No gift options are available right now.",
|
|
1295
|
+
paid: "Payment submitted. Save the code below while it is visible.",
|
|
1296
|
+
saveCode: "This code is shown once. Save it before leaving this page.",
|
|
1297
|
+
purchases: "Your gifts",
|
|
1298
|
+
noPurchases: "No gift purchases yet.",
|
|
1299
|
+
pending: "Pending",
|
|
1300
|
+
redeemed: "Redeemed",
|
|
1301
|
+
expired: "Expired",
|
|
1302
|
+
revoked: "Revoked",
|
|
1303
|
+
rotate: "Rotate code",
|
|
1304
|
+
rotated: "A new code was generated. Save it now.",
|
|
1305
|
+
rotateFailed: "The code could not be rotated.",
|
|
1306
|
+
redeemTitle: "Redeem a gift",
|
|
1307
|
+
code: "One-time code",
|
|
1308
|
+
redeem: "Redeem",
|
|
1309
|
+
redeemFailed: "The code is invalid or unavailable."
|
|
1310
|
+
},
|
|
878
1311
|
reschedule: {
|
|
879
1312
|
changeTime: "Change your time",
|
|
880
1313
|
unavailable: "Scheduling is briefly unavailable. We'll reach out by email to arrange your call.",
|
|
@@ -990,13 +1423,13 @@ function resolveLeaderFormation(input, crm) {
|
|
|
990
1423
|
}
|
|
991
1424
|
function formationFields(crm, formation) {
|
|
992
1425
|
const def = crm.type(formation.type);
|
|
993
|
-
return [...formation.required, ...formation.optional].map((
|
|
994
|
-
const field = def.fields[
|
|
1426
|
+
return [...formation.required, ...formation.optional].map((id4) => {
|
|
1427
|
+
const field = def.fields[id4];
|
|
995
1428
|
return {
|
|
996
|
-
id:
|
|
997
|
-
label: field.label ??
|
|
1429
|
+
id: id4,
|
|
1430
|
+
label: field.label ?? id4,
|
|
998
1431
|
type: field.type,
|
|
999
|
-
required: formation.required.includes(
|
|
1432
|
+
required: formation.required.includes(id4),
|
|
1000
1433
|
...field.options ? { options: field.options } : {}
|
|
1001
1434
|
};
|
|
1002
1435
|
});
|
|
@@ -1155,9 +1588,9 @@ function isResolvedCrm(x) {
|
|
|
1155
1588
|
}
|
|
1156
1589
|
function defineChapter(config) {
|
|
1157
1590
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
1158
|
-
const { id:
|
|
1159
|
-
if (typeof
|
|
1160
|
-
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(
|
|
1591
|
+
const { id: id4, name } = config;
|
|
1592
|
+
if (typeof id4 !== "string" || !SLUG3.test(id4)) {
|
|
1593
|
+
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id4)}`);
|
|
1161
1594
|
}
|
|
1162
1595
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
1163
1596
|
const mode = config.mode ?? "chapter";
|
|
@@ -1240,7 +1673,7 @@ function defineChapter(config) {
|
|
|
1240
1673
|
};
|
|
1241
1674
|
const chapter = {
|
|
1242
1675
|
config,
|
|
1243
|
-
id:
|
|
1676
|
+
id: id4,
|
|
1244
1677
|
name,
|
|
1245
1678
|
mode,
|
|
1246
1679
|
brand,
|
|
@@ -1454,8 +1887,8 @@ function tierPayable(tier, group, hasSecretKey) {
|
|
|
1454
1887
|
function byOrder(a, b) {
|
|
1455
1888
|
return a.sortOrder - b.sortOrder || a.id.localeCompare(b.id);
|
|
1456
1889
|
}
|
|
1457
|
-
function resolveTiers(group,
|
|
1458
|
-
const stored =
|
|
1890
|
+
function resolveTiers(group, rows2 = []) {
|
|
1891
|
+
const stored = rows2.filter((tier) => tier.active);
|
|
1459
1892
|
if (stored.length) return [...stored].sort(byOrder);
|
|
1460
1893
|
return [{
|
|
1461
1894
|
id: "standard",
|
|
@@ -1577,10 +2010,10 @@ function planDelivery(input) {
|
|
|
1577
2010
|
const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
|
|
1578
2011
|
const to = redirect ? input.group.debugEmail : input.to;
|
|
1579
2012
|
const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
|
|
1580
|
-
const
|
|
2013
|
+
const text3 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
1581
2014
|
|
|
1582
2015
|
` + render(tpl.text, vars) : render(tpl.text, vars);
|
|
1583
|
-
return { deliver: true, transport, to, subject, text:
|
|
2016
|
+
return { deliver: true, transport, to, subject, text: text3, redirected: redirect };
|
|
1584
2017
|
}
|
|
1585
2018
|
|
|
1586
2019
|
// src/notify.ts
|
|
@@ -1615,9 +2048,9 @@ async function sendTemplated(deps, input) {
|
|
|
1615
2048
|
error = e instanceof Error ? e.message : String(e);
|
|
1616
2049
|
}
|
|
1617
2050
|
}
|
|
1618
|
-
const
|
|
2051
|
+
const id4 = deps.newId();
|
|
1619
2052
|
const row = {
|
|
1620
|
-
id:
|
|
2053
|
+
id: id4,
|
|
1621
2054
|
groupId: input.group.id,
|
|
1622
2055
|
to: decision.to,
|
|
1623
2056
|
template: input.template,
|
|
@@ -1631,7 +2064,7 @@ async function sendTemplated(deps, input) {
|
|
|
1631
2064
|
...messageId ? { messageId } : {},
|
|
1632
2065
|
...error ? { error } : {}
|
|
1633
2066
|
};
|
|
1634
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
2067
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id4, attrs: row }], error ? void 0 : { mutationId: `email:${input.dedupeKey}` });
|
|
1635
2068
|
return error ? { sent: false, reason: error } : { sent: true };
|
|
1636
2069
|
}
|
|
1637
2070
|
function emailGroupFrom(row) {
|
|
@@ -1667,8 +2100,26 @@ function findApplicationRef(obj) {
|
|
|
1667
2100
|
const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
|
|
1668
2101
|
return { ...applicationId ? { applicationId } : {}, ...customerId ? { customerId } : {} };
|
|
1669
2102
|
}
|
|
2103
|
+
function findGiftRef(obj) {
|
|
2104
|
+
const metaOf = (value) => value && typeof value === "object" ? value.metadata ?? {} : {};
|
|
2105
|
+
const pick = (metadata) => typeof metadata.giftIntentId === "string" ? metadata.giftIntentId : void 0;
|
|
2106
|
+
const parent = obj.parent;
|
|
2107
|
+
const subscription = obj.subscription_details ?? parent?.subscription_details;
|
|
2108
|
+
const giftIntentId = pick(metaOf(obj)) ?? pick(metaOf(subscription));
|
|
2109
|
+
const subscriptionId = typeof obj.subscription === "string" ? obj.subscription : typeof subscription?.subscription === "string" ? String(subscription.subscription) : void 0;
|
|
2110
|
+
return { ...giftIntentId ? { giftIntentId } : {}, providerObjectId: subscriptionId ?? String(obj.id ?? "") };
|
|
2111
|
+
}
|
|
1670
2112
|
function normalizeWebhookEvent(event) {
|
|
1671
2113
|
const obj = event.data?.object ?? {};
|
|
2114
|
+
const gift = findGiftRef(obj);
|
|
2115
|
+
if (gift.giftIntentId) {
|
|
2116
|
+
if (event.type === "payment_intent.succeeded" || event.type === "invoice.paid" && obj.billing_reason === "subscription_create") {
|
|
2117
|
+
return { kind: "gift_paid", giftIntentId: gift.giftIntentId, providerObjectId: gift.providerObjectId };
|
|
2118
|
+
}
|
|
2119
|
+
if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "refunded" };
|
|
2120
|
+
if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "canceled" };
|
|
2121
|
+
return { kind: "ignored", type: event.type };
|
|
2122
|
+
}
|
|
1672
2123
|
const ref = findApplicationRef(obj);
|
|
1673
2124
|
switch (event.type) {
|
|
1674
2125
|
case "invoice.paid": {
|
|
@@ -1702,6 +2153,112 @@ function canceledPatch() {
|
|
|
1702
2153
|
return { canceled: true };
|
|
1703
2154
|
}
|
|
1704
2155
|
|
|
2156
|
+
// src/join-continuation.ts
|
|
2157
|
+
function resolveJoinContinuation(input) {
|
|
2158
|
+
if (input.status === "approved") return "accepted";
|
|
2159
|
+
if (input.interviewWaived) {
|
|
2160
|
+
if (!input.payable) return "approve";
|
|
2161
|
+
if (input.status === input.initialStatus) {
|
|
2162
|
+
return input.subscriptionStarted ? "paymentPending" : "payment";
|
|
2163
|
+
}
|
|
2164
|
+
return "approve";
|
|
2165
|
+
}
|
|
2166
|
+
if (!input.payable) return "booking";
|
|
2167
|
+
if (input.status === input.initialStatus) {
|
|
2168
|
+
return input.subscriptionStarted ? "paymentPending" : "payment";
|
|
2169
|
+
}
|
|
2170
|
+
if (canBook(input.status, input.pipeline)) return "booking";
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// src/gift-offers.ts
|
|
2175
|
+
var GIFT_OFFER_CATALOG_VERSION = 1;
|
|
2176
|
+
var MAX_GIFT_OFFERS = 20;
|
|
2177
|
+
var ID2 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
2178
|
+
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
2179
|
+
var CURRENCY = /^[a-z]{3}$/;
|
|
2180
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2181
|
+
var text = (value, path, errors, max) => {
|
|
2182
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2183
|
+
errors.push(`${path} is required`);
|
|
2184
|
+
return "";
|
|
2185
|
+
}
|
|
2186
|
+
if (value.trim().length > max) errors.push(`${path} must be at most ${max} characters`);
|
|
2187
|
+
return value.trim();
|
|
2188
|
+
};
|
|
2189
|
+
function validateGiftOfferCatalog(input, tierIds) {
|
|
2190
|
+
const errors = [];
|
|
2191
|
+
const root = record(input);
|
|
2192
|
+
if (!root) return { ok: false, errors: ["document.giftOffers must be an object"] };
|
|
2193
|
+
for (const key of Object.keys(root)) if (key !== "schemaVersion" && key !== "offers") errors.push(`document.giftOffers.${key} is not supported`);
|
|
2194
|
+
if (root.schemaVersion !== GIFT_OFFER_CATALOG_VERSION) errors.push("document.giftOffers.schemaVersion must be 1");
|
|
2195
|
+
if (!Array.isArray(root.offers)) errors.push("document.giftOffers.offers must be an array");
|
|
2196
|
+
const rawOffers = Array.isArray(root.offers) ? root.offers : [];
|
|
2197
|
+
if (rawOffers.length > MAX_GIFT_OFFERS) errors.push(`document.giftOffers.offers supports at most ${MAX_GIFT_OFFERS} entries`);
|
|
2198
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2199
|
+
const offers = [];
|
|
2200
|
+
for (const [index, raw] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
|
|
2201
|
+
const path = `document.giftOffers.offers[${index}]`;
|
|
2202
|
+
const item = record(raw);
|
|
2203
|
+
if (!item) {
|
|
2204
|
+
errors.push(`${path} must be an object`);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const allowed = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
2208
|
+
for (const key of Object.keys(item)) if (!allowed.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
2209
|
+
const id4 = text(item.id, `${path}.id`, errors, 80);
|
|
2210
|
+
if (id4 && !ID2.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
2211
|
+
if (ids.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
|
|
2212
|
+
ids.add(id4);
|
|
2213
|
+
const targetRaw = record(item.target);
|
|
2214
|
+
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
2215
|
+
if (targetRaw?.type === "tier") {
|
|
2216
|
+
const tierId = text(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
2217
|
+
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
2218
|
+
target = { type: "tier", tierId };
|
|
2219
|
+
} else if (targetRaw?.type === "entitlement") {
|
|
2220
|
+
const entitlementKey = text(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
2221
|
+
if (entitlementKey && !ID2.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
2222
|
+
target = { type: "entitlement", entitlementKey };
|
|
2223
|
+
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
2224
|
+
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
2225
|
+
const stripePriceId = text(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
2226
|
+
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
2227
|
+
if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
|
|
2228
|
+
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
2229
|
+
if (!CURRENCY.test(currency)) errors.push(`${path}.currency must be a lowercase ISO currency code`);
|
|
2230
|
+
const lifetime = Number(item.redemptionLifetimeHours);
|
|
2231
|
+
if (!Number.isSafeInteger(lifetime) || lifetime < 1 || lifetime > 8760) errors.push(`${path}.redemptionLifetimeHours must be an integer from 1 to 8760`);
|
|
2232
|
+
const activeFrom = item.activeFrom === void 0 ? void 0 : Number(item.activeFrom);
|
|
2233
|
+
const activeUntil = item.activeUntil === void 0 ? void 0 : Number(item.activeUntil);
|
|
2234
|
+
if (activeFrom !== void 0 && (!Number.isSafeInteger(activeFrom) || activeFrom < 1)) errors.push(`${path}.activeFrom must be epoch milliseconds`);
|
|
2235
|
+
if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
|
|
2236
|
+
if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
|
|
2237
|
+
offers.push({
|
|
2238
|
+
id: id4,
|
|
2239
|
+
target,
|
|
2240
|
+
billingMode: item.billingMode,
|
|
2241
|
+
stripePriceId,
|
|
2242
|
+
priceCents: Number(item.priceCents),
|
|
2243
|
+
currency,
|
|
2244
|
+
redemptionLifetimeHours: lifetime,
|
|
2245
|
+
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
2246
|
+
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
2247
|
+
title: text(item.title, `${path}.title`, errors, 160),
|
|
2248
|
+
description: text(item.description, `${path}.description`, errors, 2e3),
|
|
2249
|
+
ctaLabel: text(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
2253
|
+
}
|
|
2254
|
+
var giftOfferActive = (offer, now) => (!offer.activeFrom || offer.activeFrom <= now) && (!offer.activeUntil || offer.activeUntil > now);
|
|
2255
|
+
function publicGiftOffers(catalog, now) {
|
|
2256
|
+
return (catalog?.offers ?? []).filter((offer) => giftOfferActive(offer, now)).map(({ stripePriceId: _stripe, target: _target, ...offer }) => offer);
|
|
2257
|
+
}
|
|
2258
|
+
function findGiftOffer(catalog, offerId, now) {
|
|
2259
|
+
return catalog?.offers.find((offer) => offer.id === offerId && giftOfferActive(offer, now)) ?? null;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
1705
2262
|
// src/network.ts
|
|
1706
2263
|
import {
|
|
1707
2264
|
createRecord,
|
|
@@ -1746,34 +2303,34 @@ function networkSourceTag(type, hubRecordId, sourceId) {
|
|
|
1746
2303
|
if (readable && raw.length <= 64) return raw;
|
|
1747
2304
|
return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
|
|
1748
2305
|
}
|
|
1749
|
-
function normalizeSharedRecord(
|
|
1750
|
-
if ("input" in
|
|
2306
|
+
function normalizeSharedRecord(record2, fallbackSourceId = "legacy-source") {
|
|
2307
|
+
if ("input" in record2 && record2.version === 2) {
|
|
1751
2308
|
return {
|
|
1752
2309
|
version: 2,
|
|
1753
|
-
sourceId:
|
|
1754
|
-
sourceRecordId:
|
|
1755
|
-
type:
|
|
1756
|
-
input:
|
|
2310
|
+
sourceId: record2.source.siteId,
|
|
2311
|
+
sourceRecordId: record2.source.recordId,
|
|
2312
|
+
type: record2.type,
|
|
2313
|
+
input: record2.input
|
|
1757
2314
|
};
|
|
1758
2315
|
}
|
|
1759
|
-
if ("input" in
|
|
2316
|
+
if ("input" in record2) {
|
|
1760
2317
|
return {
|
|
1761
2318
|
version: 1,
|
|
1762
2319
|
sourceId: fallbackSourceId,
|
|
1763
|
-
sourceRecordId:
|
|
1764
|
-
type:
|
|
1765
|
-
input:
|
|
2320
|
+
sourceRecordId: record2.hubRecordId,
|
|
2321
|
+
type: record2.type,
|
|
2322
|
+
input: record2.input
|
|
1766
2323
|
};
|
|
1767
2324
|
}
|
|
1768
|
-
if ("type" in
|
|
1769
|
-
const input = { name:
|
|
2325
|
+
if ("type" in record2 && record2.type === "company") {
|
|
2326
|
+
const input = { name: record2.name };
|
|
1770
2327
|
for (const key of ["domain", "industry", "location", "linkedin", "notes"]) {
|
|
1771
|
-
if (
|
|
2328
|
+
if (record2[key]) input[key] = record2[key];
|
|
1772
2329
|
}
|
|
1773
2330
|
return {
|
|
1774
2331
|
version: 1,
|
|
1775
2332
|
sourceId: fallbackSourceId,
|
|
1776
|
-
sourceRecordId:
|
|
2333
|
+
sourceRecordId: record2.hubRecordId,
|
|
1777
2334
|
type: "company",
|
|
1778
2335
|
input
|
|
1779
2336
|
};
|
|
@@ -1781,28 +2338,28 @@ function normalizeSharedRecord(record, fallbackSourceId = "legacy-source") {
|
|
|
1781
2338
|
return {
|
|
1782
2339
|
version: 1,
|
|
1783
2340
|
sourceId: fallbackSourceId,
|
|
1784
|
-
sourceRecordId:
|
|
2341
|
+
sourceRecordId: record2.hubRecordId,
|
|
1785
2342
|
type: "person",
|
|
1786
|
-
input: sharedPersonInput(
|
|
2343
|
+
input: sharedPersonInput(record2)
|
|
1787
2344
|
};
|
|
1788
2345
|
}
|
|
1789
|
-
function sharedRecordFromCrm(crm,
|
|
1790
|
-
if (target.fields && !target.fields[
|
|
1791
|
-
throw new Error(`${target.name} does not accept "${
|
|
2346
|
+
function sharedRecordFromCrm(crm, record2, target, sourceId) {
|
|
2347
|
+
if (target.fields && !target.fields[record2.type]) {
|
|
2348
|
+
throw new Error(`${target.name} does not accept "${record2.type}" records`);
|
|
1792
2349
|
}
|
|
1793
|
-
const def = crm.type(
|
|
1794
|
-
const fields = target.fields?.[
|
|
2350
|
+
const def = crm.type(record2.type);
|
|
2351
|
+
const fields = target.fields?.[record2.type] ?? DEFAULT_SHARE_FIELDS[record2.type];
|
|
1795
2352
|
if (!fields) {
|
|
1796
|
-
throw new Error(`${target.name} requires an explicit field allowlist for "${
|
|
2353
|
+
throw new Error(`${target.name} requires an explicit field allowlist for "${record2.type}" records`);
|
|
1797
2354
|
}
|
|
1798
2355
|
const nameField = def.nameField ?? "name";
|
|
1799
2356
|
const input = {};
|
|
1800
2357
|
for (const field of /* @__PURE__ */ new Set([nameField, ...fields])) {
|
|
1801
|
-
const value =
|
|
2358
|
+
const value = record2.fields?.[field];
|
|
1802
2359
|
if (value !== void 0) input[field] = value;
|
|
1803
2360
|
}
|
|
1804
|
-
if (input[nameField] === void 0) input[nameField] =
|
|
1805
|
-
return sourceId ? { version: 2, source: { siteId: sourceId, recordId:
|
|
2361
|
+
if (input[nameField] === void 0) input[nameField] = record2.name;
|
|
2362
|
+
return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record2.id }, type: record2.type, input } : { version: 1, type: record2.type, hubRecordId: record2.id, input };
|
|
1806
2363
|
}
|
|
1807
2364
|
async function upsertPerson(deps, opts) {
|
|
1808
2365
|
const email = opts.email.toLowerCase();
|
|
@@ -1816,11 +2373,11 @@ async function upsertPerson(deps, opts) {
|
|
|
1816
2373
|
const created = await createRecord(crmDeps, { type: "person", input: opts.input, mutationId: opts.mutationId });
|
|
1817
2374
|
return { recordId: created.id };
|
|
1818
2375
|
}
|
|
1819
|
-
async function findSharedRecord(deps,
|
|
2376
|
+
async function findSharedRecord(deps, record2, tag) {
|
|
1820
2377
|
const structured = await getRecordByOrigin(
|
|
1821
2378
|
{ crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId },
|
|
1822
|
-
|
|
1823
|
-
|
|
2379
|
+
record2.sourceId,
|
|
2380
|
+
record2.sourceRecordId
|
|
1824
2381
|
);
|
|
1825
2382
|
if (structured) return structured;
|
|
1826
2383
|
const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });
|
|
@@ -1829,53 +2386,53 @@ async function findSharedRecord(deps, record, tag) {
|
|
|
1829
2386
|
const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });
|
|
1830
2387
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1831
2388
|
}
|
|
1832
|
-
const def = deps.crm.type(
|
|
2389
|
+
const def = deps.crm.type(record2.type);
|
|
1833
2390
|
const emailField = def.emailField;
|
|
1834
|
-
if (emailField && typeof
|
|
1835
|
-
const primaryEmail =
|
|
2391
|
+
if (emailField && typeof record2.input[emailField] === "string") {
|
|
2392
|
+
const primaryEmail = record2.input[emailField].toLowerCase();
|
|
1836
2393
|
const found = await deps.db.query({
|
|
1837
|
-
crm_record: { $: { where: { type:
|
|
2394
|
+
crm_record: { $: { where: { type: record2.type, primaryEmail }, limit: 1 } }
|
|
1838
2395
|
});
|
|
1839
2396
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1840
2397
|
}
|
|
1841
|
-
const domain =
|
|
2398
|
+
const domain = record2.input.domain;
|
|
1842
2399
|
const domainSlot = def.fields.domain?.slot;
|
|
1843
2400
|
if (typeof domain === "string" && domainSlot) {
|
|
1844
2401
|
const found = await deps.db.query({
|
|
1845
|
-
crm_record: { $: { where: { type:
|
|
2402
|
+
crm_record: { $: { where: { type: record2.type, [domainSlot]: domain }, limit: 1 } }
|
|
1846
2403
|
});
|
|
1847
2404
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1848
2405
|
}
|
|
1849
2406
|
const nameField = def.nameField ?? "name";
|
|
1850
|
-
const name =
|
|
1851
|
-
if (
|
|
2407
|
+
const name = record2.input[nameField];
|
|
2408
|
+
if (record2.type === "company" && typeof name === "string" && name.trim()) {
|
|
1852
2409
|
const found = await deps.db.query({
|
|
1853
|
-
crm_record: { $: { where: { type:
|
|
2410
|
+
crm_record: { $: { where: { type: record2.type, name: name.trim() }, limit: 1 } }
|
|
1854
2411
|
});
|
|
1855
2412
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1856
2413
|
}
|
|
1857
2414
|
return void 0;
|
|
1858
2415
|
}
|
|
1859
2416
|
async function projectSharedRecord(deps, shared, options = {}) {
|
|
1860
|
-
const
|
|
1861
|
-
if (!
|
|
2417
|
+
const record2 = normalizeSharedRecord(shared, options.sourceId);
|
|
2418
|
+
if (!record2.type.trim() || !record2.sourceId.trim() || !record2.sourceRecordId.trim()) {
|
|
1862
2419
|
throw new Error("type, source site id, and source record id must be non-empty");
|
|
1863
2420
|
}
|
|
1864
|
-
if (options.sourceId && options.sourceId !==
|
|
2421
|
+
if (options.sourceId && options.sourceId !== record2.sourceId) {
|
|
1865
2422
|
throw new Error("signed sender does not match payload source");
|
|
1866
2423
|
}
|
|
1867
|
-
const tag = networkSourceTag(
|
|
2424
|
+
const tag = networkSourceTag(record2.type, record2.sourceRecordId, record2.version === 2 ? record2.sourceId : void 0);
|
|
1868
2425
|
const crmDeps = { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId };
|
|
1869
|
-
const existing = await findSharedRecord(deps,
|
|
2426
|
+
const existing = await findSharedRecord(deps, record2, tag);
|
|
1870
2427
|
let recordId;
|
|
1871
2428
|
if (existing && typeof existing.id === "string") {
|
|
1872
|
-
await updateRecord(crmDeps, { id: existing.id, input:
|
|
2429
|
+
await updateRecord(crmDeps, { id: existing.id, input: record2.input });
|
|
1873
2430
|
recordId = existing.id;
|
|
1874
2431
|
} else {
|
|
1875
|
-
recordId = `network_${shortHash(`${
|
|
2432
|
+
recordId = `network_${shortHash(`${record2.sourceId}\0${record2.type}\0${record2.sourceRecordId}`)}`;
|
|
1876
2433
|
await createRecord({ ...crmDeps, newId: () => recordId }, {
|
|
1877
|
-
type:
|
|
1878
|
-
input:
|
|
2434
|
+
type: record2.type,
|
|
2435
|
+
input: record2.input,
|
|
1879
2436
|
mutationId: `share-create:${tag}`
|
|
1880
2437
|
});
|
|
1881
2438
|
}
|
|
@@ -1885,10 +2442,10 @@ async function projectSharedRecord(deps, shared, options = {}) {
|
|
|
1885
2442
|
);
|
|
1886
2443
|
await upsertRecordOrigin(crmDeps, {
|
|
1887
2444
|
recordId,
|
|
1888
|
-
sourceId:
|
|
1889
|
-
sourceRecordId:
|
|
2445
|
+
sourceId: record2.sourceId,
|
|
2446
|
+
sourceRecordId: record2.sourceRecordId,
|
|
1890
2447
|
...options.sourceUrl ? { sourceUrl: options.sourceUrl } : {},
|
|
1891
|
-
payloadVersion:
|
|
2448
|
+
payloadVersion: record2.version
|
|
1892
2449
|
});
|
|
1893
2450
|
return { recordId };
|
|
1894
2451
|
}
|
|
@@ -2104,9 +2661,9 @@ async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl
|
|
|
2104
2661
|
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
2105
2662
|
if (!found.ok) return false;
|
|
2106
2663
|
const users = await found.json().catch(() => null);
|
|
2107
|
-
const
|
|
2108
|
-
if (!
|
|
2109
|
-
return updateClerkUserMetadata(secretKey,
|
|
2664
|
+
const id4 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
2665
|
+
if (!id4) return false;
|
|
2666
|
+
return updateClerkUserMetadata(secretKey, id4, input, fetchImpl);
|
|
2110
2667
|
}
|
|
2111
2668
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
2112
2669
|
const { path, body } = clerkUserRequest(input);
|
|
@@ -2162,8 +2719,8 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch) {
|
|
|
2162
2719
|
const user = Array.isArray(data) ? data[0] : void 0;
|
|
2163
2720
|
return user ? toRecord(user) : null;
|
|
2164
2721
|
}
|
|
2165
|
-
async function clerkGetUser(secretKey,
|
|
2166
|
-
const data = await clerkGet(`/v1/users/${encodeURIComponent(
|
|
2722
|
+
async function clerkGetUser(secretKey, id4, fetchImpl = fetch) {
|
|
2723
|
+
const data = await clerkGet(`/v1/users/${encodeURIComponent(id4)}`, secretKey, fetchImpl).catch(() => null);
|
|
2167
2724
|
return data ? toRecord(data) : null;
|
|
2168
2725
|
}
|
|
2169
2726
|
async function clerkListUsers(secretKey, fetchImpl = fetch) {
|
|
@@ -2172,15 +2729,15 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
|
|
|
2172
2729
|
const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);
|
|
2173
2730
|
const page = Array.isArray(data) ? data : [];
|
|
2174
2731
|
for (const u of page) {
|
|
2175
|
-
const
|
|
2176
|
-
if (
|
|
2732
|
+
const record2 = toRecord(u);
|
|
2733
|
+
if (record2) out.push(record2);
|
|
2177
2734
|
}
|
|
2178
2735
|
if (page.length < PAGE) break;
|
|
2179
2736
|
}
|
|
2180
2737
|
return out;
|
|
2181
2738
|
}
|
|
2182
|
-
async function clerkSetRole(secretKey,
|
|
2183
|
-
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(
|
|
2739
|
+
async function clerkSetRole(secretKey, id4, role, fetchImpl = fetch) {
|
|
2740
|
+
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id4)}/metadata`, {
|
|
2184
2741
|
method: "PATCH",
|
|
2185
2742
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
2186
2743
|
body: JSON.stringify({ public_metadata: { role } })
|
|
@@ -2556,15 +3113,15 @@ function rowToTier(row, groupId) {
|
|
|
2556
3113
|
}
|
|
2557
3114
|
async function loadTiers(db, group) {
|
|
2558
3115
|
const groupId = String(group.id ?? "");
|
|
2559
|
-
let
|
|
3116
|
+
let rows2;
|
|
2560
3117
|
try {
|
|
2561
|
-
|
|
3118
|
+
rows2 = (await db.query({
|
|
2562
3119
|
tiers: { $: { where: { groupId }, order: { sortOrder: "asc" }, limit: 100 } }
|
|
2563
3120
|
})).tiers;
|
|
2564
3121
|
} catch {
|
|
2565
|
-
|
|
3122
|
+
rows2 = void 0;
|
|
2566
3123
|
}
|
|
2567
|
-
const list = Array.isArray(
|
|
3124
|
+
const list = Array.isArray(rows2) ? rows2.map((row) => rowToTier(row, groupId)) : [];
|
|
2568
3125
|
return resolveTiers(group, list);
|
|
2569
3126
|
}
|
|
2570
3127
|
async function loadOfferableTiers(db, group, hasSecretKey) {
|
|
@@ -2575,32 +3132,6 @@ async function loadOfferableTiers(db, group, hasSecretKey) {
|
|
|
2575
3132
|
var SIGNUP_CONTROL_SCHEMA_VERSION = 1;
|
|
2576
3133
|
var SIGNUP_CONTROL_PATH = "/api/signup-control";
|
|
2577
3134
|
|
|
2578
|
-
// src/signup-control-canonical.ts
|
|
2579
|
-
var encoder = new TextEncoder();
|
|
2580
|
-
function canonicalValue(value) {
|
|
2581
|
-
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
2582
|
-
return JSON.stringify(value);
|
|
2583
|
-
}
|
|
2584
|
-
if (Array.isArray(value)) return `[${value.map(canonicalValue).join(",")}]`;
|
|
2585
|
-
if (typeof value !== "object") throw new TypeError("signup control contains an unsupported value");
|
|
2586
|
-
const record = value;
|
|
2587
|
-
const entries = Object.keys(record).sort().flatMap((key) => {
|
|
2588
|
-
const item = record[key];
|
|
2589
|
-
return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue(item)}`];
|
|
2590
|
-
});
|
|
2591
|
-
return `{${entries.join(",")}}`;
|
|
2592
|
-
}
|
|
2593
|
-
function canonicalSignupControl(value) {
|
|
2594
|
-
return canonicalValue(value);
|
|
2595
|
-
}
|
|
2596
|
-
async function signupControlDigest(document) {
|
|
2597
|
-
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(canonicalSignupControl(document)));
|
|
2598
|
-
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
2599
|
-
}
|
|
2600
|
-
async function createSignupControlEnvelope(document) {
|
|
2601
|
-
return { document, digest: await signupControlDigest(document) };
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
3135
|
// src/membership-page.ts
|
|
2605
3136
|
var MEMBERSHIP_PAGE_RENDERER = "membership-cards-v1";
|
|
2606
3137
|
var TIER_ID = /^[a-z][a-z0-9-]{0,79}$/;
|
|
@@ -2641,10 +3172,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
2641
3172
|
errors.push(`${path} must be a string`);
|
|
2642
3173
|
return void 0;
|
|
2643
3174
|
}
|
|
2644
|
-
const
|
|
2645
|
-
if (required && !
|
|
2646
|
-
if (
|
|
2647
|
-
return
|
|
3175
|
+
const text3 = value.trim();
|
|
3176
|
+
if (required && !text3) errors.push(`${path} is required`);
|
|
3177
|
+
if (text3.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
3178
|
+
return text3;
|
|
2648
3179
|
}
|
|
2649
3180
|
function validateMembershipPageDocument(input, options = {}) {
|
|
2650
3181
|
const errors = [];
|
|
@@ -2693,8 +3224,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
2693
3224
|
else {
|
|
2694
3225
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
2695
3226
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
2696
|
-
const
|
|
2697
|
-
return
|
|
3227
|
+
const text3 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
3228
|
+
return text3 ? [text3] : [];
|
|
2698
3229
|
});
|
|
2699
3230
|
}
|
|
2700
3231
|
}
|
|
@@ -2773,7 +3304,7 @@ function membershipPageDocument(input, options = {}) {
|
|
|
2773
3304
|
var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
2774
3305
|
var ENVIRONMENT = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
2775
3306
|
var TIER = /^[a-z][a-z0-9-]{0,79}$/;
|
|
2776
|
-
var
|
|
3307
|
+
var PRICE2 = /^price_[A-Za-z0-9]{12,}$/;
|
|
2777
3308
|
var ROOT_KEYS2 = /* @__PURE__ */ new Set([
|
|
2778
3309
|
"schemaVersion",
|
|
2779
3310
|
"revision",
|
|
@@ -2785,14 +3316,15 @@ var ROOT_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
2785
3316
|
"application",
|
|
2786
3317
|
"policy",
|
|
2787
3318
|
"scheduling",
|
|
2788
|
-
"theme"
|
|
3319
|
+
"theme",
|
|
3320
|
+
"giftOffers"
|
|
2789
3321
|
]);
|
|
2790
3322
|
var TIER_KEYS2 = /* @__PURE__ */ new Set(["id", "name", "available", "sortOrder", "priceCents", "stripePriceId", "blurb"]);
|
|
2791
3323
|
var recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2792
3324
|
function unknownKeys(value, allowed, path, errors) {
|
|
2793
3325
|
for (const key of Object.keys(value)) if (!allowed.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
2794
3326
|
}
|
|
2795
|
-
function
|
|
3327
|
+
function text2(value, path, errors, max = 65536) {
|
|
2796
3328
|
if (typeof value !== "string" || !value.trim()) {
|
|
2797
3329
|
errors.push(`${path} is required`);
|
|
2798
3330
|
return "";
|
|
@@ -2815,25 +3347,25 @@ function tiersOf(value, errors) {
|
|
|
2815
3347
|
return [];
|
|
2816
3348
|
}
|
|
2817
3349
|
unknownKeys(item, TIER_KEYS2, path, errors);
|
|
2818
|
-
const
|
|
2819
|
-
if (
|
|
2820
|
-
if (seen.has(
|
|
2821
|
-
seen.add(
|
|
2822
|
-
const name =
|
|
3350
|
+
const id4 = text2(item.id, `${path}.id`, errors, 80);
|
|
3351
|
+
if (id4 && !TIER.test(id4)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
3352
|
+
if (seen.has(id4)) errors.push(`${path}.id duplicates "${id4}"`);
|
|
3353
|
+
seen.add(id4);
|
|
3354
|
+
const name = text2(item.name, `${path}.name`, errors, 160);
|
|
2823
3355
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
2824
3356
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
2825
3357
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
2826
3358
|
errors.push(`${path}.priceCents must be an integer from 0 to 100,000,000`);
|
|
2827
3359
|
}
|
|
2828
|
-
if (item.stripePriceId !== void 0 && (typeof item.stripePriceId !== "string" || !
|
|
3360
|
+
if (item.stripePriceId !== void 0 && (typeof item.stripePriceId !== "string" || !PRICE2.test(item.stripePriceId))) {
|
|
2829
3361
|
errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
2830
3362
|
}
|
|
2831
3363
|
if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
|
|
2832
3364
|
errors.push(`${path}.stripePriceId is required for an available paid tier`);
|
|
2833
3365
|
}
|
|
2834
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
3366
|
+
const blurb = item.blurb === void 0 ? void 0 : text2(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
2835
3367
|
return [{
|
|
2836
|
-
id:
|
|
3368
|
+
id: id4,
|
|
2837
3369
|
name,
|
|
2838
3370
|
available: item.available === true,
|
|
2839
3371
|
sortOrder: Number(item.sortOrder) || 0,
|
|
@@ -2880,7 +3412,7 @@ function policyOf(value, errors) {
|
|
|
2880
3412
|
const result = {};
|
|
2881
3413
|
for (const key of allowed) {
|
|
2882
3414
|
if (input[key] === void 0 && key !== "disclaimerText" && key !== "refundPolicyText") continue;
|
|
2883
|
-
result[key] =
|
|
3415
|
+
result[key] = text2(input[key], `document.policy.${key}`, errors);
|
|
2884
3416
|
}
|
|
2885
3417
|
return result;
|
|
2886
3418
|
}
|
|
@@ -2894,12 +3426,18 @@ function themeOf(value, errors) {
|
|
|
2894
3426
|
}
|
|
2895
3427
|
unknownKeys(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
|
|
2896
3428
|
if (Object.keys(tokens).length > 256) errors.push("document.theme.tokens supports at most 256 entries");
|
|
3429
|
+
let serializedLength = 0;
|
|
2897
3430
|
for (const [key, token] of Object.entries(tokens)) {
|
|
2898
|
-
if (!/^--[a-z0-9-]{
|
|
2899
|
-
|
|
2900
|
-
|
|
3431
|
+
if (!/^--ui-[a-z0-9][a-z0-9-]{0,94}$/.test(key)) {
|
|
3432
|
+
errors.push(`document.theme.tokens.${key} must be an allowlisted --ui-* custom property`);
|
|
3433
|
+
}
|
|
3434
|
+
const rendered = String(token).trim();
|
|
3435
|
+
serializedLength += key.length + rendered.length + 4;
|
|
3436
|
+
if (typeof token !== "string" && typeof token !== "number" || typeof token === "number" && !Number.isFinite(token) || !rendered || rendered.length > 1e3 || /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i.test(rendered)) {
|
|
3437
|
+
errors.push(`document.theme.tokens.${key} must be a bounded breakout-safe CSS value`);
|
|
2901
3438
|
}
|
|
2902
3439
|
}
|
|
3440
|
+
if (serializedLength > 65536) errors.push("document.theme.tokens exceeds the serialized size bound");
|
|
2903
3441
|
return { tokens };
|
|
2904
3442
|
}
|
|
2905
3443
|
function validateSignupControlDocument(input) {
|
|
@@ -2912,9 +3450,9 @@ function validateSignupControlDocument(input) {
|
|
|
2912
3450
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
2913
3451
|
const source = recordOf(root.source);
|
|
2914
3452
|
const target = recordOf(root.target);
|
|
2915
|
-
const appId =
|
|
2916
|
-
const chapterId =
|
|
2917
|
-
const environment =
|
|
3453
|
+
const appId = text2(source?.appId, "document.source.appId", errors, 63);
|
|
3454
|
+
const chapterId = text2(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
3455
|
+
const environment = text2(target?.environment, "document.target.environment", errors, 63);
|
|
2918
3456
|
if (appId && !SLUG4.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
2919
3457
|
if (chapterId && !SLUG4.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
2920
3458
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -2935,6 +3473,8 @@ function validateSignupControlDocument(input) {
|
|
|
2935
3473
|
else scheduling = root.scheduling;
|
|
2936
3474
|
}
|
|
2937
3475
|
const theme = themeOf(root.theme, errors);
|
|
3476
|
+
const giftOffers = root.giftOffers === void 0 ? void 0 : validateGiftOfferCatalog(root.giftOffers, new Set(tiers2.map((tier) => tier.id)));
|
|
3477
|
+
if (giftOffers && !giftOffers.ok) errors.push(...giftOffers.errors);
|
|
2938
3478
|
if (errors.length || !page.ok) return { ok: false, errors };
|
|
2939
3479
|
return { ok: true, value: {
|
|
2940
3480
|
schemaVersion: 1,
|
|
@@ -2947,10 +3487,121 @@ function validateSignupControlDocument(input) {
|
|
|
2947
3487
|
...application ? { application } : {},
|
|
2948
3488
|
...policy ? { policy } : {},
|
|
2949
3489
|
...scheduling ? { scheduling } : {},
|
|
2950
|
-
...theme ? { theme } : {}
|
|
3490
|
+
...theme ? { theme } : {},
|
|
3491
|
+
...giftOffers?.ok ? { giftOffers: giftOffers.value } : {}
|
|
2951
3492
|
} };
|
|
2952
3493
|
}
|
|
2953
3494
|
|
|
3495
|
+
// src/signup-control-theme.ts
|
|
3496
|
+
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
3497
|
+
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
3498
|
+
var TOKEN_NAME = /^--ui-[a-z0-9][a-z0-9-]{0,94}$/;
|
|
3499
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
3500
|
+
var UNSAFE_VALUE = /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i;
|
|
3501
|
+
var MAX_TOKENS = 256;
|
|
3502
|
+
var MAX_VALUE_LENGTH = 1e3;
|
|
3503
|
+
var MAX_SERIALIZED_LENGTH = 65536;
|
|
3504
|
+
function validatedTokens(input) {
|
|
3505
|
+
const entries = Object.entries(input ?? {});
|
|
3506
|
+
if (entries.length > MAX_TOKENS) {
|
|
3507
|
+
throw new TypeError(`signup-control theme supports at most ${MAX_TOKENS} tokens`);
|
|
3508
|
+
}
|
|
3509
|
+
const tokens = {};
|
|
3510
|
+
let serializedLength = 0;
|
|
3511
|
+
for (const [name, raw] of entries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
3512
|
+
if (!TOKEN_NAME.test(name)) {
|
|
3513
|
+
throw new TypeError(`signup-control theme token "${name}" must be an allowlisted --ui-* property`);
|
|
3514
|
+
}
|
|
3515
|
+
if (typeof raw !== "string" && typeof raw !== "number" || typeof raw === "number" && !Number.isFinite(raw)) {
|
|
3516
|
+
throw new TypeError(`signup-control theme token "${name}" must be a finite string or number`);
|
|
3517
|
+
}
|
|
3518
|
+
const value = String(raw).trim();
|
|
3519
|
+
if (!value || value.length > MAX_VALUE_LENGTH || UNSAFE_VALUE.test(value)) {
|
|
3520
|
+
throw new TypeError(`signup-control theme token "${name}" has an unsafe or oversized value`);
|
|
3521
|
+
}
|
|
3522
|
+
serializedLength += name.length + value.length + 4;
|
|
3523
|
+
if (serializedLength > MAX_SERIALIZED_LENGTH) {
|
|
3524
|
+
throw new TypeError("signup-control theme exceeds the serialized size bound");
|
|
3525
|
+
}
|
|
3526
|
+
tokens[name] = value;
|
|
3527
|
+
}
|
|
3528
|
+
return tokens;
|
|
3529
|
+
}
|
|
3530
|
+
function cssFor(tokens) {
|
|
3531
|
+
const declarations = Object.entries(tokens).map(([name, value]) => ` ${name}: ${value};`);
|
|
3532
|
+
return declarations.length ? `:root {
|
|
3533
|
+
${declarations.join("\n")}
|
|
3534
|
+
}` : "";
|
|
3535
|
+
}
|
|
3536
|
+
function stateFor(revision, options) {
|
|
3537
|
+
if (!revision) {
|
|
3538
|
+
return {
|
|
3539
|
+
css: cssFor(validatedTokens(options.bootstrapTokens)),
|
|
3540
|
+
metadata: { source: "bootstrap", revision: null, digest: null, cacheKey: "signup-control:bootstrap" }
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !DIGEST.test(revision.digest)) {
|
|
3544
|
+
throw new TypeError("signup-control theme revision metadata is invalid");
|
|
3545
|
+
}
|
|
3546
|
+
return {
|
|
3547
|
+
css: cssFor(validatedTokens(revision.theme?.tokens)),
|
|
3548
|
+
metadata: {
|
|
3549
|
+
source: "active",
|
|
3550
|
+
revision: revision.revision,
|
|
3551
|
+
digest: revision.digest,
|
|
3552
|
+
cacheKey: `signup-control:${revision.digest}`
|
|
3553
|
+
}
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function attributes(metadata) {
|
|
3557
|
+
return [
|
|
3558
|
+
`id="${SIGNUP_CONTROL_THEME_STYLE_ID}"`,
|
|
3559
|
+
`data-signup-control-source="${metadata.source}"`,
|
|
3560
|
+
`data-signup-control-cache-key="${metadata.cacheKey}"`,
|
|
3561
|
+
...metadata.revision === null ? [] : [`data-signup-control-revision="${metadata.revision}"`],
|
|
3562
|
+
...metadata.digest === null ? [] : [`data-signup-control-digest="${metadata.digest}"`]
|
|
3563
|
+
].join(" ");
|
|
3564
|
+
}
|
|
3565
|
+
function renderSignupControlThemeHead(revision, options = {}) {
|
|
3566
|
+
const { css, metadata } = stateFor(revision, options);
|
|
3567
|
+
return { css, metadata, head: `<style ${attributes(metadata)}>${css}</style>` };
|
|
3568
|
+
}
|
|
3569
|
+
function injectSignupControlThemeHead(html, rendered, marker = SIGNUP_CONTROL_THEME_MARKER) {
|
|
3570
|
+
const existing = /<style\b(?=[^>]*\bid\s*=\s*["']odla-signup-control-theme["'])[^>]*>[\s\S]*?<\/style>/gi;
|
|
3571
|
+
const existingBlocks = html.match(existing) ?? [];
|
|
3572
|
+
if (existingBlocks.length > 1) {
|
|
3573
|
+
throw new TypeError("signup-control theme style block must appear at most once");
|
|
3574
|
+
}
|
|
3575
|
+
if (existingBlocks.length === 1) return html.replace(existing, rendered.head);
|
|
3576
|
+
const markerIndex = html.indexOf(marker);
|
|
3577
|
+
if (markerIndex >= 0) {
|
|
3578
|
+
if (html.indexOf(marker, markerIndex + marker.length) >= 0) {
|
|
3579
|
+
throw new TypeError("signup-control theme marker must appear exactly once");
|
|
3580
|
+
}
|
|
3581
|
+
return `${html.slice(0, markerIndex)}${rendered.head}${html.slice(markerIndex + marker.length)}`;
|
|
3582
|
+
}
|
|
3583
|
+
const closingHead = html.search(/<\/head\s*>/i);
|
|
3584
|
+
if (closingHead < 0) throw new TypeError("signup-control theme requires an HTML head");
|
|
3585
|
+
return `${html.slice(0, closingHead)}${rendered.head}${html.slice(closingHead)}`;
|
|
3586
|
+
}
|
|
3587
|
+
function hydrateSignupControlTheme(target, revision, options = {}) {
|
|
3588
|
+
const next = stateFor(revision, options);
|
|
3589
|
+
const currentSource = target.getAttribute("data-signup-control-source");
|
|
3590
|
+
const currentRevision = target.getAttribute("data-signup-control-revision");
|
|
3591
|
+
const currentDigest = target.getAttribute("data-signup-control-digest");
|
|
3592
|
+
if (currentSource === next.metadata.source && currentRevision === (next.metadata.revision === null ? null : String(next.metadata.revision)) && currentDigest === next.metadata.digest) {
|
|
3593
|
+
return false;
|
|
3594
|
+
}
|
|
3595
|
+
target.textContent = next.css;
|
|
3596
|
+
target.setAttribute("data-signup-control-source", next.metadata.source);
|
|
3597
|
+
target.setAttribute("data-signup-control-cache-key", next.metadata.cacheKey);
|
|
3598
|
+
if (next.metadata.revision === null) target.removeAttribute("data-signup-control-revision");
|
|
3599
|
+
else target.setAttribute("data-signup-control-revision", String(next.metadata.revision));
|
|
3600
|
+
if (next.metadata.digest === null) target.removeAttribute("data-signup-control-digest");
|
|
3601
|
+
else target.setAttribute("data-signup-control-digest", next.metadata.digest);
|
|
3602
|
+
return true;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
2954
3605
|
// src/membership-markdown.ts
|
|
2955
3606
|
var MAX_BENEFITS2 = 24;
|
|
2956
3607
|
var MAX_MARKDOWN2 = 65536;
|
|
@@ -2978,9 +3629,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
2978
3629
|
errors.push(`${path} line ${index + 1} must be a Markdown bullet`);
|
|
2979
3630
|
continue;
|
|
2980
3631
|
}
|
|
2981
|
-
const
|
|
2982
|
-
if (
|
|
2983
|
-
if (
|
|
3632
|
+
const text3 = (match[1] ?? "").trim();
|
|
3633
|
+
if (text3.length > 2e3) errors.push(`${path} line ${index + 1} must be at most 2,000 characters`);
|
|
3634
|
+
if (text3) benefits.push(text3);
|
|
2984
3635
|
}
|
|
2985
3636
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
2986
3637
|
return benefits.slice(0, MAX_BENEFITS2);
|
|
@@ -3082,7 +3733,7 @@ var exclusions = (chapterId, paidTierIds) => [
|
|
|
3082
3733
|
id: chapterId,
|
|
3083
3734
|
reason: "provider_identity",
|
|
3084
3735
|
paths: [
|
|
3085
|
-
...paidTierIds.map((
|
|
3736
|
+
...paidTierIds.map((id4) => `tiers[${id4}].stripePriceId`),
|
|
3086
3737
|
"groups.stripePublishableKey",
|
|
3087
3738
|
"applications.stripeCustomerId",
|
|
3088
3739
|
"applications.stripeSubscriptionId",
|
|
@@ -3124,9 +3775,9 @@ async function exportChapterPromotion(input) {
|
|
|
3124
3775
|
sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
|
|
3125
3776
|
receiptDigests: [input.digest]
|
|
3126
3777
|
};
|
|
3127
|
-
const artifact = (kind,
|
|
3778
|
+
const artifact = (kind, id4, content) => ({
|
|
3128
3779
|
kind,
|
|
3129
|
-
id:
|
|
3780
|
+
id: id4,
|
|
3130
3781
|
sourceRevision: revision,
|
|
3131
3782
|
provenance: promotionProvenance,
|
|
3132
3783
|
content: canonicalPromotionValue(content)
|
|
@@ -3164,13 +3815,19 @@ async function exportChapterPromotion(input) {
|
|
|
3164
3815
|
};
|
|
3165
3816
|
}
|
|
3166
3817
|
export {
|
|
3818
|
+
ADMISSION_GRANTS_PATH,
|
|
3819
|
+
ADMISSION_GRANTS_SCHEMA_VERSION,
|
|
3167
3820
|
CHAPTER_PROMOTION_ADAPTER,
|
|
3168
3821
|
DEFAULT_CHAPTER_COPY,
|
|
3169
3822
|
DEFAULT_SHARE_FIELDS,
|
|
3823
|
+
GIFT_OFFER_CATALOG_VERSION,
|
|
3824
|
+
MAX_GIFT_OFFERS,
|
|
3170
3825
|
MEMBERSHIP_PAGE_RENDERER,
|
|
3171
3826
|
SCHEDULING_DEFAULTS,
|
|
3172
3827
|
SIGNUP_CONTROL_PATH,
|
|
3173
3828
|
SIGNUP_CONTROL_SCHEMA_VERSION,
|
|
3829
|
+
SIGNUP_CONTROL_THEME_MARKER,
|
|
3830
|
+
SIGNUP_CONTROL_THEME_STYLE_ID,
|
|
3174
3831
|
applicantProfile,
|
|
3175
3832
|
applicationBookingUpdate,
|
|
3176
3833
|
applicationSummary,
|
|
@@ -3197,6 +3854,7 @@ export {
|
|
|
3197
3854
|
clerkListUsers,
|
|
3198
3855
|
clerkSetRole,
|
|
3199
3856
|
clerkUserRequest,
|
|
3857
|
+
createAdmissionGrantEnvelope,
|
|
3200
3858
|
createChapterIntegration,
|
|
3201
3859
|
createClerkInvitation,
|
|
3202
3860
|
createClerkUser,
|
|
@@ -3207,13 +3865,18 @@ export {
|
|
|
3207
3865
|
emailGroupFrom,
|
|
3208
3866
|
endForSlot,
|
|
3209
3867
|
exportChapterPromotion,
|
|
3868
|
+
findAdmissionGrant,
|
|
3210
3869
|
findApplicationRef,
|
|
3870
|
+
findGiftOffer,
|
|
3211
3871
|
findTier,
|
|
3212
3872
|
firstPaymentPatch,
|
|
3213
3873
|
formatChapterCopy,
|
|
3214
3874
|
formationFields,
|
|
3215
3875
|
getVaultSecret,
|
|
3876
|
+
giftOfferActive,
|
|
3216
3877
|
hasDisclaimerAck,
|
|
3878
|
+
hydrateSignupControlTheme,
|
|
3879
|
+
injectSignupControlThemeHead,
|
|
3217
3880
|
introIdempotencyKey,
|
|
3218
3881
|
isAdminRole,
|
|
3219
3882
|
isAlreadySent,
|
|
@@ -3223,8 +3886,10 @@ export {
|
|
|
3223
3886
|
joinConfig,
|
|
3224
3887
|
joinConfigTiers,
|
|
3225
3888
|
leaderCrmConfig,
|
|
3889
|
+
loadLatestAdmissionRevision,
|
|
3226
3890
|
loadOfferableTiers,
|
|
3227
3891
|
loadTiers,
|
|
3892
|
+
materializeAdmissionGrants,
|
|
3228
3893
|
meetingCreateRow,
|
|
3229
3894
|
meetingRescheduleUpdate,
|
|
3230
3895
|
memberApplication,
|
|
@@ -3232,6 +3897,7 @@ export {
|
|
|
3232
3897
|
membershipPageDocument,
|
|
3233
3898
|
membershipTierPresentationFromMarkdown,
|
|
3234
3899
|
networkSourceTag,
|
|
3900
|
+
normalizeAdmissionEmail,
|
|
3235
3901
|
normalizeSharedRecord,
|
|
3236
3902
|
normalizeWebhookEvent,
|
|
3237
3903
|
offerableTiers,
|
|
@@ -3240,15 +3906,18 @@ export {
|
|
|
3240
3906
|
planDelivery,
|
|
3241
3907
|
projectApplicant,
|
|
3242
3908
|
projectSharedRecord,
|
|
3909
|
+
publicGiftOffers,
|
|
3243
3910
|
reconcileMeetings,
|
|
3244
3911
|
refundedPatch,
|
|
3245
3912
|
render,
|
|
3913
|
+
renderSignupControlThemeHead,
|
|
3246
3914
|
renderSummary,
|
|
3247
3915
|
renderTemplateBody,
|
|
3248
3916
|
renewalPatch,
|
|
3249
3917
|
resolveApplication,
|
|
3250
3918
|
resolveAuth,
|
|
3251
3919
|
resolveChapterCopy,
|
|
3920
|
+
resolveJoinContinuation,
|
|
3252
3921
|
resolveLeaderFormation,
|
|
3253
3922
|
resolvePipeline,
|
|
3254
3923
|
resolveScheduling,
|
|
@@ -3271,6 +3940,8 @@ export {
|
|
|
3271
3940
|
tierPayable,
|
|
3272
3941
|
updateClerkUserMetadata,
|
|
3273
3942
|
updateClerkUserMetadataByEmail,
|
|
3943
|
+
validateAdmissionGrantDocument,
|
|
3944
|
+
validateGiftOfferCatalog,
|
|
3274
3945
|
validateMembershipPageDocument,
|
|
3275
3946
|
validateScheduling,
|
|
3276
3947
|
validateSignupControlDocument,
|