@odla-ai/chapter 0.10.1 → 0.12.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 +48 -8
- package/dist/index.cjs +50 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -8
- package/dist/index.d.ts +63 -8
- package/dist/index.js +50 -9
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +48 -10
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +32 -0
- package/dist/worker/index.d.ts +32 -0
- package/dist/worker/index.js +48 -10
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -184,15 +184,28 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// src/member.ts
|
|
187
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
188
|
+
function isValidEmail(value) {
|
|
189
|
+
return typeof value === "string" && EMAIL_RE.test(value);
|
|
190
|
+
}
|
|
191
|
+
function clampArray(value, max) {
|
|
192
|
+
if (!Array.isArray(value)) return value;
|
|
193
|
+
return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
|
|
194
|
+
}
|
|
195
|
+
function hasDisclaimerAck(fields) {
|
|
196
|
+
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
197
|
+
}
|
|
187
198
|
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
188
199
|
function applicantProfile(chapter, fields) {
|
|
200
|
+
const app = chapter.application;
|
|
201
|
+
const allowed = (f) => app.profileFields === null || app.profileFields.includes(f);
|
|
189
202
|
const profile = {};
|
|
190
|
-
for (const f of [...
|
|
191
|
-
if (IDENTITY_FIELDS.has(f)) continue;
|
|
203
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
204
|
+
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
192
205
|
const v = fields[f];
|
|
193
206
|
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
194
207
|
}
|
|
195
|
-
if (fields.focus !== void 0) profile.focus = fields.focus;
|
|
208
|
+
if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
196
209
|
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
197
210
|
}
|
|
198
211
|
async function submitApplication(db, chapter, fields, opts) {
|
|
@@ -206,19 +219,26 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
206
219
|
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
207
220
|
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
208
221
|
}
|
|
222
|
+
if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
|
|
223
|
+
return { ok: false, error: "email must be a valid email address" };
|
|
224
|
+
}
|
|
225
|
+
const acked = hasDisclaimerAck(fields);
|
|
226
|
+
if (app.requireDisclaimerAck && !acked) {
|
|
227
|
+
return { ok: false, error: "disclaimerAck is required" };
|
|
228
|
+
}
|
|
209
229
|
const id = opts.newId();
|
|
210
230
|
const row = { id, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
211
231
|
for (const f of [...app.required, ...app.optional]) {
|
|
212
232
|
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
213
233
|
}
|
|
214
|
-
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
234
|
+
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
215
235
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
216
|
-
if (
|
|
236
|
+
if (acked) row.disclaimerAckAt = opts.now;
|
|
217
237
|
const { duplicate } = await db.transact(
|
|
218
238
|
[{ t: "update", ns: "applications", id, attrs: row }],
|
|
219
239
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
220
240
|
);
|
|
221
|
-
return { ok: true, id, duplicate, status: chapter.pipeline.initial };
|
|
241
|
+
return { ok: true, id, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
222
242
|
}
|
|
223
243
|
function joinConfig(group, paymentsReady) {
|
|
224
244
|
return {
|
|
@@ -264,7 +284,7 @@ async function projectSharedRecord(deps, person) {
|
|
|
264
284
|
return upsertPerson(deps, { email: person.email, input: sharedPersonInput(person), mutationId: `share:${person.hubRecordId}` });
|
|
265
285
|
}
|
|
266
286
|
async function projectApplicant(deps, applicant) {
|
|
267
|
-
const
|
|
287
|
+
const base = sharedPersonInput({
|
|
268
288
|
email: applicant.email,
|
|
269
289
|
firstName: applicant.firstName,
|
|
270
290
|
lastName: applicant.lastName,
|
|
@@ -272,7 +292,14 @@ async function projectApplicant(deps, applicant) {
|
|
|
272
292
|
linkedin: applicant.linkedin,
|
|
273
293
|
hubRecordId: applicant.applicationId
|
|
274
294
|
});
|
|
275
|
-
|
|
295
|
+
const mutationId = `apply:${applicant.applicationId}`;
|
|
296
|
+
const extra = applicant.extra ?? {};
|
|
297
|
+
if (Object.keys(extra).length === 0) return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
|
|
298
|
+
try {
|
|
299
|
+
return await upsertPerson(deps, { email: applicant.email, input: { ...base, ...extra }, mutationId });
|
|
300
|
+
} catch {
|
|
301
|
+
return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
|
|
302
|
+
}
|
|
276
303
|
}
|
|
277
304
|
|
|
278
305
|
// src/scheduling.ts
|
|
@@ -516,10 +543,14 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
|
|
|
516
543
|
const email = typeof fields.email === "string" ? fields.email : "";
|
|
517
544
|
if (!email) return;
|
|
518
545
|
const s = (v) => typeof v === "string" ? v : void 0;
|
|
546
|
+
const extra = {};
|
|
547
|
+
for (const f of chapter.application.crmFields) {
|
|
548
|
+
if (fields[f] !== void 0) extra[f] = fields[f];
|
|
549
|
+
}
|
|
519
550
|
try {
|
|
520
551
|
await projectApplicant(
|
|
521
552
|
{ crm: chapter.crm, db, now: () => Date.now(), newId: () => crypto.randomUUID() },
|
|
522
|
-
{ applicationId, email, firstName: s(fields.firstName), lastName: s(fields.lastName), phone: s(fields.phone), linkedin: s(fields.linkedin) }
|
|
553
|
+
{ applicationId, email, firstName: s(fields.firstName), lastName: s(fields.lastName), phone: s(fields.phone), linkedin: s(fields.linkedin), extra }
|
|
523
554
|
);
|
|
524
555
|
} catch {
|
|
525
556
|
}
|
|
@@ -674,7 +705,14 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
674
705
|
}
|
|
675
706
|
await provisionApplicant(db, chapter, result.id, parsed);
|
|
676
707
|
}
|
|
677
|
-
return json({
|
|
708
|
+
return json({
|
|
709
|
+
id: result.id,
|
|
710
|
+
duplicate: result.duplicate,
|
|
711
|
+
status: result.status,
|
|
712
|
+
// Echoed so a site can see (and assert in an integration test) whether its
|
|
713
|
+
// join page actually posted the ack. Absent consent is otherwise invisible.
|
|
714
|
+
disclaimerAckAt: result.disclaimerAckAt
|
|
715
|
+
});
|
|
678
716
|
}
|
|
679
717
|
return null;
|
|
680
718
|
};
|