@odla-ai/chapter 0.42.1 → 0.42.2
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 +13 -1
- package/dist/{chunk-MMDWXTJU.js → chunk-HWUTCXBV.js} +44 -1
- package/dist/chunk-HWUTCXBV.js.map +1 -0
- package/dist/{copy-context-BNUzbL5g.d.ts → copy-context-BBPsYe24.d.ts} +9 -1
- package/dist/index.cjs +88 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +88 -10
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +14 -3
- package/dist/ui/admin/index.js +3 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +3 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +477 -144
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +55 -2
- package/dist/worker/index.d.ts +55 -2
- package/dist/worker/index.js +453 -120
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-MMDWXTJU.js.map +0 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/worker.ts
|
|
21
21
|
var worker_exports = {};
|
|
22
22
|
__export(worker_exports, {
|
|
23
|
+
AdmissionGrantDeliveryError: () => AdmissionGrantDeliveryError,
|
|
23
24
|
chapterWorker: () => chapterWorker,
|
|
24
25
|
createWorkerContext: () => createWorkerContext,
|
|
25
26
|
deliverAdmissionGrants: () => deliverAdmissionGrants,
|
|
@@ -27,6 +28,7 @@ __export(worker_exports, {
|
|
|
27
28
|
deliverMemberContent: () => deliverMemberContent,
|
|
28
29
|
deliverSignupControl: () => deliverSignupControl,
|
|
29
30
|
renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
|
|
31
|
+
retryLeaderAdmissionDeliveries: () => retryLeaderAdmissionDeliveries,
|
|
30
32
|
withActiveSignupControlTheme: () => withActiveSignupControlTheme
|
|
31
33
|
});
|
|
32
34
|
module.exports = __toCommonJS(worker_exports);
|
|
@@ -640,6 +642,11 @@ function validateAdmissionGrantDocument(input) {
|
|
|
640
642
|
}
|
|
641
643
|
};
|
|
642
644
|
}
|
|
645
|
+
async function createAdmissionGrantEnvelope(document) {
|
|
646
|
+
const checked = validateAdmissionGrantDocument(document);
|
|
647
|
+
if (!checked.ok) throw new TypeError(`Invalid admission grant document: ${checked.errors.join("; ")}`);
|
|
648
|
+
return { document: checked.value, digest: await signupControlDigest(checked.value) };
|
|
649
|
+
}
|
|
643
650
|
var rows = (value) => Array.isArray(value) ? value : [];
|
|
644
651
|
function asGrant(row) {
|
|
645
652
|
if (typeof row.id !== "string" || typeof row.emailNormalized !== "string") return null;
|
|
@@ -3138,12 +3145,17 @@ var import_db5 = require("@odla-ai/db");
|
|
|
3138
3145
|
var reject3 = (reason, status, errors) => json({ ok: false, reason, ...errors ? { errors } : {} }, status);
|
|
3139
3146
|
var handleAdmissionGrants = async (req, url, env, ctx) => {
|
|
3140
3147
|
if (req.method !== "POST" || url.pathname !== ADMISSION_GRANTS_PATH) return null;
|
|
3141
|
-
const
|
|
3142
|
-
|
|
3148
|
+
const claimedSender = req.headers.get("x-odla-federation-sender") ?? "";
|
|
3149
|
+
const networkReader = ctx.chapter.network.readers.find((reader) => reader.id === claimedSender && reader.admissionGrants);
|
|
3150
|
+
const signupAuthority = ctx.chapter.signupControl?.sourceId === claimedSender ? ctx.chapter.signupControl : void 0;
|
|
3151
|
+
if (!networkReader && !signupAuthority) return json({ error: "not found" }, 404);
|
|
3143
3152
|
const db = ctx.makeDb(env);
|
|
3144
|
-
const secret = await getVaultSecret(
|
|
3153
|
+
const secret = await getVaultSecret(
|
|
3154
|
+
db,
|
|
3155
|
+
networkReader ? "network_share_secret" : signupAuthority.secretName
|
|
3156
|
+
);
|
|
3145
3157
|
if (!secret) return json({ error: "unauthorized" }, 401);
|
|
3146
|
-
const verified = await (0, import_db5.verifyFederatedRequest)(req, { secret, senders: [
|
|
3158
|
+
const verified = await (0, import_db5.verifyFederatedRequest)(req, { secret, senders: [claimedSender] });
|
|
3147
3159
|
if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
|
|
3148
3160
|
let input;
|
|
3149
3161
|
try {
|
|
@@ -3155,11 +3167,11 @@ var handleAdmissionGrants = async (req, url, env, ctx) => {
|
|
|
3155
3167
|
const checked = validateAdmissionGrantDocument(input.document);
|
|
3156
3168
|
if (!checked.ok) return reject3("invalid_document", 400, checked.errors);
|
|
3157
3169
|
const document = checked.value;
|
|
3158
|
-
if (document.source.appId !==
|
|
3170
|
+
if (document.source.appId !== claimedSender) return reject3("wrong_source", 403);
|
|
3159
3171
|
if (document.target.chapterId !== ctx.chapter.id) return reject3("wrong_target", 409);
|
|
3160
3172
|
if (document.target.environment !== env.ODLA_ENV) return reject3("wrong_environment", 409);
|
|
3161
3173
|
if (!digest || await signupControlDigest(document) !== digest) return reject3("invalid_digest", 400);
|
|
3162
|
-
const latest = await loadLatestAdmissionRevision(db,
|
|
3174
|
+
const latest = await loadLatestAdmissionRevision(db, claimedSender, ctx.chapter.id, env.ODLA_ENV);
|
|
3163
3175
|
if (latest && document.revision < latest.revision) return reject3("stale_revision", 409);
|
|
3164
3176
|
if (latest && document.revision === latest.revision) {
|
|
3165
3177
|
if (latest.digest !== digest) return reject3("conflicting_revision", 409);
|
|
@@ -3284,6 +3296,365 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
|
|
|
3284
3296
|
return null;
|
|
3285
3297
|
};
|
|
3286
3298
|
|
|
3299
|
+
// src/worker-routes-network-admission.ts
|
|
3300
|
+
var import_crm6 = require("@odla-ai/crm");
|
|
3301
|
+
|
|
3302
|
+
// src/admission-grants-delivery.ts
|
|
3303
|
+
var import_db6 = require("@odla-ai/db");
|
|
3304
|
+
var AdmissionGrantDeliveryError = class extends Error {
|
|
3305
|
+
constructor(status, reason) {
|
|
3306
|
+
super(`admission grants rejected (${status}): ${reason}`);
|
|
3307
|
+
this.status = status;
|
|
3308
|
+
this.reason = reason;
|
|
3309
|
+
this.name = "AdmissionGrantDeliveryError";
|
|
3310
|
+
}
|
|
3311
|
+
status;
|
|
3312
|
+
reason;
|
|
3313
|
+
};
|
|
3314
|
+
async function deliverAdmissionGrants(options) {
|
|
3315
|
+
const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
|
|
3316
|
+
const body = JSON.stringify(options.envelope);
|
|
3317
|
+
const signed = await (0, import_db6.signFederatedRequest)({
|
|
3318
|
+
secret: options.secret,
|
|
3319
|
+
sender: options.sender,
|
|
3320
|
+
method: "POST",
|
|
3321
|
+
url: destination,
|
|
3322
|
+
body
|
|
3323
|
+
});
|
|
3324
|
+
const response = await (options.fetch ?? globalThis.fetch)(destination, {
|
|
3325
|
+
method: "POST",
|
|
3326
|
+
headers: { ...signed, "content-type": "application/json" },
|
|
3327
|
+
body
|
|
3328
|
+
});
|
|
3329
|
+
const result = await response.json();
|
|
3330
|
+
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
3331
|
+
const failure = result;
|
|
3332
|
+
throw new AdmissionGrantDeliveryError(
|
|
3333
|
+
response.status,
|
|
3334
|
+
failure.reason ?? failure.error ?? "unknown"
|
|
3335
|
+
);
|
|
3336
|
+
}
|
|
3337
|
+
return result;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// src/worker-network-fetch.ts
|
|
3341
|
+
function targetFetcher(env, target) {
|
|
3342
|
+
if (!target.binding) return null;
|
|
3343
|
+
const candidate = env[target.binding];
|
|
3344
|
+
if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
|
|
3345
|
+
throw new Error(`service binding "${target.binding}" is unavailable`);
|
|
3346
|
+
}
|
|
3347
|
+
return candidate;
|
|
3348
|
+
}
|
|
3349
|
+
function fetchNetworkTarget(env, target, destination, init) {
|
|
3350
|
+
const binding = targetFetcher(env, target);
|
|
3351
|
+
return binding ? binding.fetch(new Request(destination, init)) : globalThis.fetch(destination, init);
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
// src/worker-network-admission-state.ts
|
|
3355
|
+
var import_crm5 = require("@odla-ai/crm");
|
|
3356
|
+
var MAX_ASSIGNMENTS = 2e3;
|
|
3357
|
+
var admissionRows = (value) => Array.isArray(value) ? value : [];
|
|
3358
|
+
function assignmentId(targetId, personId) {
|
|
3359
|
+
return `leader-admission:${targetId}:${personId}`;
|
|
3360
|
+
}
|
|
3361
|
+
function deliveryId(targetId, environment) {
|
|
3362
|
+
return `leader-admission-delivery:${targetId}:${environment}`;
|
|
3363
|
+
}
|
|
3364
|
+
async function loadLeaderAdmissionAssignments(db, targetId) {
|
|
3365
|
+
const result = await db.query({ leaderAdmissionAssignments: { $: {
|
|
3366
|
+
where: { targetId },
|
|
3367
|
+
order: { updatedAt: "desc" },
|
|
3368
|
+
limit: MAX_ASSIGNMENTS
|
|
3369
|
+
} } });
|
|
3370
|
+
return admissionRows(result.leaderAdmissionAssignments);
|
|
3371
|
+
}
|
|
3372
|
+
async function loadLeaderAdmissionDelivery(db, targetId, environment) {
|
|
3373
|
+
const id = deliveryId(targetId, environment);
|
|
3374
|
+
const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id }, limit: 1 } } });
|
|
3375
|
+
return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
|
|
3376
|
+
}
|
|
3377
|
+
function personAdmissionEmail(ctx, record4) {
|
|
3378
|
+
if (record4.type !== "person") return null;
|
|
3379
|
+
const field = ctx.chapter.crm.config.types.person?.emailField;
|
|
3380
|
+
if (!field) return null;
|
|
3381
|
+
const raw = record4.fields[field] ?? record4.primaryEmail;
|
|
3382
|
+
if (typeof raw !== "string") return null;
|
|
3383
|
+
const normalized = normalizeAdmissionEmail(raw);
|
|
3384
|
+
return isValidEmail(normalized) ? normalized : null;
|
|
3385
|
+
}
|
|
3386
|
+
async function grantInput(db, ctx, assignment) {
|
|
3387
|
+
const person = await (0, import_crm5.getRecord)({ crm: ctx.chapter.crm, db }, assignment.personId);
|
|
3388
|
+
const email = person ? personAdmissionEmail(ctx, person) : null;
|
|
3389
|
+
if (!email) throw new Error(`person ${assignment.personId} has no valid authoritative email`);
|
|
3390
|
+
return {
|
|
3391
|
+
id: assignment.grantId,
|
|
3392
|
+
email,
|
|
3393
|
+
decision: assignment.decision,
|
|
3394
|
+
...assignment.note ? { note: assignment.note } : {},
|
|
3395
|
+
...assignment.expiresAt !== void 0 ? { expiresAt: assignment.expiresAt } : {}
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
async function persistLeaderAdmissionDesiredSet(opts) {
|
|
3399
|
+
const now = Date.now();
|
|
3400
|
+
const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
|
|
3401
|
+
const id = assignmentId(opts.target.id, opts.personId);
|
|
3402
|
+
const existing = current.find((row) => row.id === id);
|
|
3403
|
+
const digest = await signupControlDigest({
|
|
3404
|
+
targetId: opts.target.id,
|
|
3405
|
+
personId: opts.personId,
|
|
3406
|
+
decision: opts.decision,
|
|
3407
|
+
note: opts.note ?? null,
|
|
3408
|
+
expiresAt: opts.expiresAt ?? null,
|
|
3409
|
+
active: opts.active
|
|
3410
|
+
});
|
|
3411
|
+
const mutationReceiptId = `leader-admission-mutation:${opts.target.id}:${opts.mutationId}`;
|
|
3412
|
+
const priorMutation2 = await opts.db.query({ leaderAdmissionMutations: { $: {
|
|
3413
|
+
where: { id: mutationReceiptId },
|
|
3414
|
+
limit: 1
|
|
3415
|
+
} } });
|
|
3416
|
+
const receipt = admissionRows(priorMutation2.leaderAdmissionMutations)[0];
|
|
3417
|
+
if (receipt) {
|
|
3418
|
+
if (receipt.requestDigest !== digest || receipt.personId !== opts.personId) {
|
|
3419
|
+
throw new Error("mutation_conflict");
|
|
3420
|
+
}
|
|
3421
|
+
const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
|
|
3422
|
+
if (!saved) throw new Error("delivery_missing");
|
|
3423
|
+
return { delivery: saved, duplicate: true };
|
|
3424
|
+
}
|
|
3425
|
+
if (existing?.lastMutationId === opts.mutationId) {
|
|
3426
|
+
if (existing.lastRequestDigest !== digest) throw new Error("mutation_conflict");
|
|
3427
|
+
const saved = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
|
|
3428
|
+
if (!saved) throw new Error("delivery_missing");
|
|
3429
|
+
return { delivery: saved, duplicate: true };
|
|
3430
|
+
}
|
|
3431
|
+
const assignment = {
|
|
3432
|
+
id,
|
|
3433
|
+
targetId: opts.target.id,
|
|
3434
|
+
personId: opts.personId,
|
|
3435
|
+
grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
|
|
3436
|
+
decision: opts.decision,
|
|
3437
|
+
...opts.note ? { note: opts.note } : {},
|
|
3438
|
+
...opts.expiresAt !== void 0 ? { expiresAt: opts.expiresAt } : {},
|
|
3439
|
+
active: opts.active,
|
|
3440
|
+
lastMutationId: opts.mutationId,
|
|
3441
|
+
lastRequestDigest: digest,
|
|
3442
|
+
createdAt: existing?.createdAt ?? now,
|
|
3443
|
+
createdBy: existing?.createdBy ?? opts.actor,
|
|
3444
|
+
updatedAt: now,
|
|
3445
|
+
updatedBy: opts.actor
|
|
3446
|
+
};
|
|
3447
|
+
const desired = current.filter((row) => row.id !== id && row.active).concat(assignment.active ? [assignment] : []);
|
|
3448
|
+
const grants = await Promise.all(desired.map((row) => grantInput(opts.db, opts.ctx, row)));
|
|
3449
|
+
const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
|
|
3450
|
+
const revision = (previous?.revision ?? 0) + 1;
|
|
3451
|
+
const envelope = await createAdmissionGrantEnvelope({
|
|
3452
|
+
schemaVersion: 1,
|
|
3453
|
+
revision,
|
|
3454
|
+
generatedAt: now,
|
|
3455
|
+
source: { appId: opts.ctx.chapter.id, actor: opts.actor },
|
|
3456
|
+
target: { chapterId: opts.target.id, environment: opts.env.ODLA_ENV },
|
|
3457
|
+
grants
|
|
3458
|
+
});
|
|
3459
|
+
const delivery = {
|
|
3460
|
+
id: deliveryId(opts.target.id, opts.env.ODLA_ENV),
|
|
3461
|
+
targetId: opts.target.id,
|
|
3462
|
+
environment: opts.env.ODLA_ENV,
|
|
3463
|
+
revision,
|
|
3464
|
+
digest: envelope.digest,
|
|
3465
|
+
envelope,
|
|
3466
|
+
status: "saved",
|
|
3467
|
+
attempts: 0,
|
|
3468
|
+
updatedAt: now
|
|
3469
|
+
};
|
|
3470
|
+
const ops = [
|
|
3471
|
+
{ t: "update", ns: "leaderAdmissionAssignments", id, attrs: assignment },
|
|
3472
|
+
{ t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
|
|
3473
|
+
{ t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
|
|
3474
|
+
id: mutationReceiptId,
|
|
3475
|
+
targetId: opts.target.id,
|
|
3476
|
+
personId: opts.personId,
|
|
3477
|
+
requestDigest: digest,
|
|
3478
|
+
revision,
|
|
3479
|
+
createdAt: now
|
|
3480
|
+
} }
|
|
3481
|
+
];
|
|
3482
|
+
const retract = [
|
|
3483
|
+
...!opts.note && existing?.note !== void 0 ? ["note"] : [],
|
|
3484
|
+
...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
|
|
3485
|
+
];
|
|
3486
|
+
if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id, attrs: retract });
|
|
3487
|
+
const tx = await opts.db.transact(ops, {
|
|
3488
|
+
mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
|
|
3489
|
+
guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
|
|
3490
|
+
});
|
|
3491
|
+
return { delivery, duplicate: tx.duplicate };
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// src/worker-routes-network-admission.ts
|
|
3495
|
+
var PATH = "/api/admin/network/admissions";
|
|
3496
|
+
var RETRY_PATH = "/api/admin/network/admissions/retry";
|
|
3497
|
+
var REVOKE_PATH = "/api/admin/network/admissions/revoke";
|
|
3498
|
+
var MUTATION_ID = /^[A-Za-z0-9_-]{16,128}$/;
|
|
3499
|
+
var MAX_NOTE2 = 2e3;
|
|
3500
|
+
var reject4 = (status, code, error) => json({ ok: false, code, error }, status);
|
|
3501
|
+
async function gate2(req, env, ctx) {
|
|
3502
|
+
const db = ctx.makeDb(env);
|
|
3503
|
+
const user = await ctx.verifyUser(req, env);
|
|
3504
|
+
if (!user) return { response: reject4(401, "unauthorized", "unauthorized") };
|
|
3505
|
+
if (!await ctx.isAdmin(db, user)) return { response: reject4(403, "forbidden", "forbidden") };
|
|
3506
|
+
return { db, user };
|
|
3507
|
+
}
|
|
3508
|
+
function targetFor(ctx, id) {
|
|
3509
|
+
return ctx.chapter.network.targets.find((target) => target.id === id && target.admissionGrants);
|
|
3510
|
+
}
|
|
3511
|
+
async function deliverSaved(db, env, ctx, target, delivery) {
|
|
3512
|
+
if (delivery.status === "acknowledged") return delivery;
|
|
3513
|
+
const secret = await getVaultSecret(db, target.secretName);
|
|
3514
|
+
const attemptedAt = Date.now();
|
|
3515
|
+
const attempts = delivery.attempts + 1;
|
|
3516
|
+
let patch;
|
|
3517
|
+
if (!secret) {
|
|
3518
|
+
patch = {
|
|
3519
|
+
status: delivery.attempts > 0 ? "retrying" : "pending",
|
|
3520
|
+
attempts,
|
|
3521
|
+
lastAttemptAt: attemptedAt,
|
|
3522
|
+
lastError: "edge secret is missing",
|
|
3523
|
+
updatedAt: attemptedAt
|
|
3524
|
+
};
|
|
3525
|
+
} else {
|
|
3526
|
+
try {
|
|
3527
|
+
const receipt = await deliverAdmissionGrants({
|
|
3528
|
+
secret,
|
|
3529
|
+
sender: ctx.chapter.id,
|
|
3530
|
+
targetUrl: target.url,
|
|
3531
|
+
envelope: delivery.envelope,
|
|
3532
|
+
fetch: ((input, init) => fetchNetworkTarget(env, target, new URL(String(input)), init ?? {}))
|
|
3533
|
+
});
|
|
3534
|
+
patch = {
|
|
3535
|
+
status: "acknowledged",
|
|
3536
|
+
attempts,
|
|
3537
|
+
lastAttemptAt: attemptedAt,
|
|
3538
|
+
acknowledgedAt: receipt.acknowledgedAt,
|
|
3539
|
+
updatedAt: attemptedAt
|
|
3540
|
+
};
|
|
3541
|
+
} catch (error) {
|
|
3542
|
+
const permanent = error instanceof AdmissionGrantDeliveryError && error.status >= 400 && error.status < 500;
|
|
3543
|
+
patch = {
|
|
3544
|
+
status: permanent ? "rejected" : delivery.attempts > 0 ? "retrying" : "pending",
|
|
3545
|
+
attempts,
|
|
3546
|
+
lastAttemptAt: attemptedAt,
|
|
3547
|
+
lastError: error instanceof Error ? error.message.slice(0, 2e3) : "delivery failed",
|
|
3548
|
+
updatedAt: attemptedAt
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
const ops = [{
|
|
3553
|
+
t: "update",
|
|
3554
|
+
ns: "leaderAdmissionDeliveries",
|
|
3555
|
+
id: delivery.id,
|
|
3556
|
+
attrs: patch
|
|
3557
|
+
}];
|
|
3558
|
+
if (patch.status === "acknowledged" && delivery.lastError) {
|
|
3559
|
+
ops.push({ t: "retract", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: ["lastError"] });
|
|
3560
|
+
}
|
|
3561
|
+
await db.transact(ops, {
|
|
3562
|
+
mutationId: `leader-admission-attempt:${delivery.targetId}:${delivery.revision}:${attempts}:${patch.status}`
|
|
3563
|
+
});
|
|
3564
|
+
return { ...delivery, ...patch };
|
|
3565
|
+
}
|
|
3566
|
+
async function parseBody(req) {
|
|
3567
|
+
try {
|
|
3568
|
+
return await req.json();
|
|
3569
|
+
} catch {
|
|
3570
|
+
return null;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
|
|
3574
|
+
if (![PATH, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
|
|
3575
|
+
const auth = await gate2(req, env, ctx);
|
|
3576
|
+
if ("response" in auth) return auth.response;
|
|
3577
|
+
const db = auth.db;
|
|
3578
|
+
if (url.pathname === PATH && req.method === "GET") {
|
|
3579
|
+
const targetId2 = url.searchParams.get("targetId") ?? "";
|
|
3580
|
+
const target2 = targetFor(ctx, targetId2);
|
|
3581
|
+
if (!target2) return reject4(404, "unknown_target", "admission target is not configured");
|
|
3582
|
+
return json({
|
|
3583
|
+
ok: true,
|
|
3584
|
+
target: { id: target2.id, name: target2.name },
|
|
3585
|
+
assignments: await loadLeaderAdmissionAssignments(db, target2.id),
|
|
3586
|
+
delivery: await loadLeaderAdmissionDelivery(db, target2.id, env.ODLA_ENV)
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
if (req.method !== "POST") return null;
|
|
3590
|
+
const body = await parseBody(req);
|
|
3591
|
+
if (!body) return reject4(422, "invalid_request", "request body must be JSON");
|
|
3592
|
+
const targetId = typeof body.targetId === "string" ? body.targetId : "";
|
|
3593
|
+
const target = targetFor(ctx, targetId);
|
|
3594
|
+
if (!target) return reject4(404, "unknown_target", "admission target is not configured");
|
|
3595
|
+
if (url.pathname === RETRY_PATH) {
|
|
3596
|
+
const delivery = await loadLeaderAdmissionDelivery(db, target.id, env.ODLA_ENV);
|
|
3597
|
+
if (!delivery) return reject4(404, "not_found", "no saved admission delivery exists");
|
|
3598
|
+
return json({ ok: true, delivery: await deliverSaved(db, env, ctx, target, delivery) });
|
|
3599
|
+
}
|
|
3600
|
+
const personId = typeof body.personId === "string" ? body.personId : "";
|
|
3601
|
+
const mutationId = typeof body.mutationId === "string" ? body.mutationId : "";
|
|
3602
|
+
if (!personId || !MUTATION_ID.test(mutationId)) {
|
|
3603
|
+
return reject4(422, "invalid_request", "personId and a stable mutationId are required");
|
|
3604
|
+
}
|
|
3605
|
+
const person = await (0, import_crm6.getRecord)({ crm: ctx.chapter.crm, db: auth.db }, personId);
|
|
3606
|
+
if (!person) return reject4(404, "person_not_found", "person not found");
|
|
3607
|
+
if (!personAdmissionEmail(ctx, person)) {
|
|
3608
|
+
return reject4(422, "invalid_email", "person has no valid authoritative email");
|
|
3609
|
+
}
|
|
3610
|
+
const decision = body.decision ?? "waive_interview";
|
|
3611
|
+
if (decision !== "waive_interview" && decision !== "require_interview") {
|
|
3612
|
+
return reject4(422, "invalid_decision", "decision must be waive_interview or require_interview");
|
|
3613
|
+
}
|
|
3614
|
+
const note = typeof body.note === "string" ? body.note.trim() : "";
|
|
3615
|
+
if (note.length > MAX_NOTE2) return reject4(422, "invalid_note", "note is too long");
|
|
3616
|
+
const expiresAt = body.expiresAt == null ? void 0 : Number(body.expiresAt);
|
|
3617
|
+
if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= Date.now())) {
|
|
3618
|
+
return reject4(422, "invalid_expiry", "expiresAt must be a future epoch timestamp");
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
const saved = await persistLeaderAdmissionDesiredSet({
|
|
3622
|
+
db,
|
|
3623
|
+
ctx,
|
|
3624
|
+
env,
|
|
3625
|
+
target,
|
|
3626
|
+
personId,
|
|
3627
|
+
decision,
|
|
3628
|
+
...note ? { note } : {},
|
|
3629
|
+
...expiresAt !== void 0 ? { expiresAt } : {},
|
|
3630
|
+
active: url.pathname !== REVOKE_PATH,
|
|
3631
|
+
mutationId,
|
|
3632
|
+
actor: auth.user.email ?? auth.user.userId
|
|
3633
|
+
});
|
|
3634
|
+
const delivery = await deliverSaved(db, env, ctx, target, saved.delivery);
|
|
3635
|
+
return json({ ok: true, duplicate: saved.duplicate, delivery }, delivery.status === "acknowledged" ? 200 : 202);
|
|
3636
|
+
} catch (error) {
|
|
3637
|
+
if (error instanceof Error && error.message === "mutation_conflict") {
|
|
3638
|
+
return reject4(409, "mutation_conflict", "mutationId was already used for another request");
|
|
3639
|
+
}
|
|
3640
|
+
if (error instanceof Error && /valid authoritative email/.test(error.message)) {
|
|
3641
|
+
return reject4(422, "invalid_email", error.message);
|
|
3642
|
+
}
|
|
3643
|
+
return reject4(409, "revision_conflict", "admission assignments changed; reload and retry");
|
|
3644
|
+
}
|
|
3645
|
+
};
|
|
3646
|
+
async function retryLeaderAdmissionDeliveries(env, ctx, limit = 100) {
|
|
3647
|
+
const db = ctx.makeDb(env);
|
|
3648
|
+
const result = await db.query({ leaderAdmissionDeliveries: { $: { limit } } });
|
|
3649
|
+
const pending = admissionRows(result.leaderAdmissionDeliveries).filter((delivery) => delivery.status !== "acknowledged" && delivery.status !== "rejected").slice(0, Math.max(1, Math.min(limit, 500)));
|
|
3650
|
+
const delivered = [];
|
|
3651
|
+
for (const item of pending) {
|
|
3652
|
+
const target = targetFor(ctx, item.targetId);
|
|
3653
|
+
if (target) delivered.push(await deliverSaved(db, env, ctx, target, item));
|
|
3654
|
+
}
|
|
3655
|
+
return delivered;
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3287
3658
|
// src/worker-routes-schedule.ts
|
|
3288
3659
|
var import_calendar = require("@odla-ai/calendar");
|
|
3289
3660
|
function errCode(err) {
|
|
@@ -4545,16 +4916,16 @@ var crmDeps2 = (db, ctx) => ({
|
|
|
4545
4916
|
});
|
|
4546
4917
|
var handleAdminCrmSync = async (req, url, env, ctx) => {
|
|
4547
4918
|
if (req.method !== "POST" || url.pathname !== "/api/admin/crm/sync") return null;
|
|
4548
|
-
const
|
|
4549
|
-
if (
|
|
4550
|
-
const result = await backfillCrm(crmDeps2(
|
|
4919
|
+
const gate10 = await adminGate(req, env, ctx);
|
|
4920
|
+
if (gate10 instanceof Response) return gate10;
|
|
4921
|
+
const result = await backfillCrm(crmDeps2(gate10.db, ctx));
|
|
4551
4922
|
return json({ ok: true, ...result });
|
|
4552
4923
|
};
|
|
4553
4924
|
var handleAdminPeople = async (req, url, env, ctx) => {
|
|
4554
4925
|
if (req.method !== "GET" || url.pathname !== "/api/admin/people") return null;
|
|
4555
|
-
const
|
|
4556
|
-
if (
|
|
4557
|
-
const { db } =
|
|
4926
|
+
const gate10 = await adminGate(req, env, ctx);
|
|
4927
|
+
if (gate10 instanceof Response) return gate10;
|
|
4928
|
+
const { db } = gate10;
|
|
4558
4929
|
const sk = await getVaultSecret(db, "clerk_secret_key");
|
|
4559
4930
|
const [data, roleList] = await Promise.all([
|
|
4560
4931
|
db.query({
|
|
@@ -4607,9 +4978,9 @@ var handleAdminPeople = async (req, url, env, ctx) => {
|
|
|
4607
4978
|
};
|
|
4608
4979
|
var handleAdminPeopleAccess = async (req, url, env, ctx) => {
|
|
4609
4980
|
if (req.method !== "GET" || url.pathname !== "/api/admin/people/access") return null;
|
|
4610
|
-
const
|
|
4611
|
-
if (
|
|
4612
|
-
const { db } =
|
|
4981
|
+
const gate10 = await adminGate(req, env, ctx);
|
|
4982
|
+
if (gate10 instanceof Response) return gate10;
|
|
4983
|
+
const { db } = gate10;
|
|
4613
4984
|
const targetId = url.searchParams.get("userId") ?? "";
|
|
4614
4985
|
if (!targetId.startsWith("user_")) return json({ error: "invalid userId" }, 400);
|
|
4615
4986
|
const sk = await getVaultSecret(db, "clerk_secret_key");
|
|
@@ -4620,9 +4991,9 @@ var handleAdminPeopleAccess = async (req, url, env, ctx) => {
|
|
|
4620
4991
|
};
|
|
4621
4992
|
var handleAdminPeopleRole = async (req, url, env, ctx) => {
|
|
4622
4993
|
if (req.method !== "POST" || url.pathname !== "/api/admin/people/role") return null;
|
|
4623
|
-
const
|
|
4624
|
-
if (
|
|
4625
|
-
const { db, actor } =
|
|
4994
|
+
const gate10 = await adminGate(req, env, ctx);
|
|
4995
|
+
if (gate10 instanceof Response) return gate10;
|
|
4996
|
+
const { db, actor } = gate10;
|
|
4626
4997
|
let body;
|
|
4627
4998
|
try {
|
|
4628
4999
|
body = await req.json();
|
|
@@ -4769,7 +5140,7 @@ function subAnnualCents(sub) {
|
|
|
4769
5140
|
}
|
|
4770
5141
|
|
|
4771
5142
|
// src/worker-routes-admin-dashboard.ts
|
|
4772
|
-
async function
|
|
5143
|
+
async function gate3(req, env, ctx) {
|
|
4773
5144
|
const rawDb = ctx.makeDb(env);
|
|
4774
5145
|
const u = await ctx.verifyUser(req, env);
|
|
4775
5146
|
if (!u) return json({ error: "unauthorized" }, 401);
|
|
@@ -4779,7 +5150,7 @@ async function gate2(req, env, ctx) {
|
|
|
4779
5150
|
var rows3 = (v) => Array.isArray(v) ? v : [];
|
|
4780
5151
|
var handleAdminDashboard = async (req, url, env, ctx) => {
|
|
4781
5152
|
if (req.method !== "GET" || url.pathname !== "/api/admin/dashboard") return null;
|
|
4782
|
-
const got = await
|
|
5153
|
+
const got = await gate3(req, env, ctx);
|
|
4783
5154
|
if (got instanceof Response) return got;
|
|
4784
5155
|
const db = got;
|
|
4785
5156
|
const now = Date.now();
|
|
@@ -4862,7 +5233,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
|
|
|
4862
5233
|
};
|
|
4863
5234
|
var handleAdminBilling = async (req, url, env, ctx) => {
|
|
4864
5235
|
if (req.method !== "GET" || url.pathname !== "/api/admin/billing") return null;
|
|
4865
|
-
const got = await
|
|
5236
|
+
const got = await gate3(req, env, ctx);
|
|
4866
5237
|
if (got instanceof Response) return got;
|
|
4867
5238
|
const db = got;
|
|
4868
5239
|
const sk = await getVaultSecret(db, "stripe_secret_key");
|
|
@@ -4922,7 +5293,7 @@ var handleAdminBilling = async (req, url, env, ctx) => {
|
|
|
4922
5293
|
|
|
4923
5294
|
// src/worker-routes-admin-lifecycle.ts
|
|
4924
5295
|
var import_calendar3 = require("@odla-ai/calendar");
|
|
4925
|
-
async function
|
|
5296
|
+
async function gate4(req, env, ctx) {
|
|
4926
5297
|
const rawDb = ctx.makeDb(env);
|
|
4927
5298
|
const u = await ctx.verifyUser(req, env);
|
|
4928
5299
|
if (!u) return json({ error: "unauthorized" }, 401);
|
|
@@ -4948,7 +5319,7 @@ async function loadGroup(db, id) {
|
|
|
4948
5319
|
var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
|
|
4949
5320
|
const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/reschedule$/);
|
|
4950
5321
|
if (req.method !== "POST" || !m) return null;
|
|
4951
|
-
const got = await
|
|
5322
|
+
const got = await gate4(req, env, ctx);
|
|
4952
5323
|
if (got instanceof Response) return got;
|
|
4953
5324
|
const db = got;
|
|
4954
5325
|
const body = await readJson(req);
|
|
@@ -4989,7 +5360,7 @@ var handleAdminMeetingReschedule = async (req, url, env, ctx) => {
|
|
|
4989
5360
|
var handleAdminMeetingCancel = async (req, url, env, ctx) => {
|
|
4990
5361
|
const m = url.pathname.match(/^\/api\/admin\/meetings\/([0-9a-f-]+)\/cancel$/);
|
|
4991
5362
|
if (req.method !== "POST" || !m) return null;
|
|
4992
|
-
const got = await
|
|
5363
|
+
const got = await gate4(req, env, ctx);
|
|
4993
5364
|
if (got instanceof Response) return got;
|
|
4994
5365
|
const db = got;
|
|
4995
5366
|
const { meetings } = await db.query({ meetings: { $: { where: { id: m[1] }, limit: 1 } } });
|
|
@@ -5012,7 +5383,7 @@ var handleAdminMeetingCancel = async (req, url, env, ctx) => {
|
|
|
5012
5383
|
var handleAdminApprove = async (req, url, env, ctx) => {
|
|
5013
5384
|
const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/approve$/);
|
|
5014
5385
|
if (req.method !== "POST" || !m) return null;
|
|
5015
|
-
const got = await
|
|
5386
|
+
const got = await gate4(req, env, ctx);
|
|
5016
5387
|
if (got instanceof Response) return got;
|
|
5017
5388
|
const db = got;
|
|
5018
5389
|
const id = m[1];
|
|
@@ -5025,7 +5396,7 @@ var handleAdminApprove = async (req, url, env, ctx) => {
|
|
|
5025
5396
|
var handleAdminRefund = async (req, url, env, ctx) => {
|
|
5026
5397
|
const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/refund$/);
|
|
5027
5398
|
if (req.method !== "POST" || !m) return null;
|
|
5028
|
-
const got = await
|
|
5399
|
+
const got = await gate4(req, env, ctx);
|
|
5029
5400
|
if (got instanceof Response) return got;
|
|
5030
5401
|
const db = got;
|
|
5031
5402
|
const app = await loadApp(db, m[1]);
|
|
@@ -5057,7 +5428,7 @@ var handleAdminRefund = async (req, url, env, ctx) => {
|
|
|
5057
5428
|
var handleAdminApplicationPatch = async (req, url, env, ctx) => {
|
|
5058
5429
|
const m = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)$/);
|
|
5059
5430
|
if (req.method !== "PATCH" || !m) return null;
|
|
5060
|
-
const got = await
|
|
5431
|
+
const got = await gate4(req, env, ctx);
|
|
5061
5432
|
if (got instanceof Response) return got;
|
|
5062
5433
|
const db = got;
|
|
5063
5434
|
const id = m[1];
|
|
@@ -5085,7 +5456,7 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
|
|
|
5085
5456
|
};
|
|
5086
5457
|
|
|
5087
5458
|
// src/worker-routes-admin-comms.ts
|
|
5088
|
-
async function
|
|
5459
|
+
async function gate5(req, env, ctx) {
|
|
5089
5460
|
const rawDb = ctx.makeDb(env);
|
|
5090
5461
|
const u = await ctx.verifyUser(req, env);
|
|
5091
5462
|
if (!u) return json({ error: "unauthorized" }, 401);
|
|
@@ -5107,7 +5478,7 @@ var notifyDeps = (db, env) => ({
|
|
|
5107
5478
|
});
|
|
5108
5479
|
var handleAdminGroupEmail = async (req, url, env, ctx) => {
|
|
5109
5480
|
if (url.pathname !== "/api/admin/group/email" || req.method !== "GET" && req.method !== "PUT") return null;
|
|
5110
|
-
const got = await
|
|
5481
|
+
const got = await gate5(req, env, ctx);
|
|
5111
5482
|
if (got instanceof Response) return got;
|
|
5112
5483
|
const db = got;
|
|
5113
5484
|
const group = await loadGroup2(db, ctx.chapter.id);
|
|
@@ -5169,7 +5540,7 @@ var handleAdminGroupEmail = async (req, url, env, ctx) => {
|
|
|
5169
5540
|
};
|
|
5170
5541
|
var handleAdminEmailLog = async (req, url, env, ctx) => {
|
|
5171
5542
|
if (req.method !== "GET" || url.pathname !== "/api/admin/email/log") return null;
|
|
5172
|
-
const got = await
|
|
5543
|
+
const got = await gate5(req, env, ctx);
|
|
5173
5544
|
if (got instanceof Response) return got;
|
|
5174
5545
|
const { emailLog } = await got.query({ emailLog: { $: { order: { sentAt: "desc" }, limit: 50 } } });
|
|
5175
5546
|
const sends = (emailLog ?? []).map((r) => ({
|
|
@@ -5186,7 +5557,7 @@ var handleAdminEmailLog = async (req, url, env, ctx) => {
|
|
|
5186
5557
|
};
|
|
5187
5558
|
var handleAdminEmailTest = async (req, url, env, ctx) => {
|
|
5188
5559
|
if (req.method !== "POST" || url.pathname !== "/api/admin/email/test") return null;
|
|
5189
|
-
const got = await
|
|
5560
|
+
const got = await gate5(req, env, ctx);
|
|
5190
5561
|
if (got instanceof Response) return got;
|
|
5191
5562
|
const db = got;
|
|
5192
5563
|
let body;
|
|
@@ -5214,7 +5585,7 @@ var handleAdminEmailTest = async (req, url, env, ctx) => {
|
|
|
5214
5585
|
var handleAdminComms = async (req, url, env, ctx) => {
|
|
5215
5586
|
const m = url.pathname.match(/^\/api\/admin\/people\/([0-9a-fA-F-]+)\/comms$/);
|
|
5216
5587
|
if (req.method !== "GET" || !m) return null;
|
|
5217
|
-
const got = await
|
|
5588
|
+
const got = await gate5(req, env, ctx);
|
|
5218
5589
|
if (got instanceof Response) return got;
|
|
5219
5590
|
const db = got;
|
|
5220
5591
|
const appId = m[1];
|
|
@@ -5252,26 +5623,12 @@ var handleAdminComms = async (req, url, env, ctx) => {
|
|
|
5252
5623
|
};
|
|
5253
5624
|
|
|
5254
5625
|
// src/worker-routes-network.ts
|
|
5255
|
-
var
|
|
5256
|
-
var
|
|
5257
|
-
|
|
5258
|
-
// src/worker-network-fetch.ts
|
|
5259
|
-
function targetFetcher(env, target) {
|
|
5260
|
-
if (!target.binding) return null;
|
|
5261
|
-
const candidate = env[target.binding];
|
|
5262
|
-
if (typeof candidate !== "object" || candidate === null || !("fetch" in candidate) || typeof candidate.fetch !== "function") {
|
|
5263
|
-
throw new Error(`service binding "${target.binding}" is unavailable`);
|
|
5264
|
-
}
|
|
5265
|
-
return candidate;
|
|
5266
|
-
}
|
|
5267
|
-
function fetchNetworkTarget(env, target, destination, init) {
|
|
5268
|
-
const binding = targetFetcher(env, target);
|
|
5269
|
-
return binding ? binding.fetch(new Request(destination, init)) : globalThis.fetch(destination, init);
|
|
5270
|
-
}
|
|
5626
|
+
var import_crm8 = require("@odla-ai/crm");
|
|
5627
|
+
var import_db8 = require("@odla-ai/db");
|
|
5271
5628
|
|
|
5272
5629
|
// src/worker-network-push.ts
|
|
5273
|
-
var
|
|
5274
|
-
var
|
|
5630
|
+
var import_crm7 = require("@odla-ai/crm");
|
|
5631
|
+
var import_db7 = require("@odla-ai/db");
|
|
5275
5632
|
async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
5276
5633
|
const secret = await getVaultSecret(db, target.secretName);
|
|
5277
5634
|
const crmDeps3 = {
|
|
@@ -5282,7 +5639,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5282
5639
|
};
|
|
5283
5640
|
if (!secret) {
|
|
5284
5641
|
const error = `vault secret "${target.secretName}" is missing`;
|
|
5285
|
-
await (0,
|
|
5642
|
+
await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
|
|
5286
5643
|
recordId: record4.id,
|
|
5287
5644
|
targetId: target.id,
|
|
5288
5645
|
status: "failed",
|
|
@@ -5305,7 +5662,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5305
5662
|
const payloadBody = JSON.stringify(payload);
|
|
5306
5663
|
const destination = new URL("/api/network/shared", target.url);
|
|
5307
5664
|
try {
|
|
5308
|
-
const signed = await (0,
|
|
5665
|
+
const signed = await (0, import_db7.signFederatedRequest)({
|
|
5309
5666
|
secret,
|
|
5310
5667
|
sender: ctx.chapter.id,
|
|
5311
5668
|
method: "POST",
|
|
@@ -5321,7 +5678,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5321
5678
|
const responseBody = await res.json().catch(() => ({}));
|
|
5322
5679
|
if (!res.ok) {
|
|
5323
5680
|
const error = responseBody.error ?? "follower rejected the record";
|
|
5324
|
-
await (0,
|
|
5681
|
+
await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
|
|
5325
5682
|
recordId: record4.id,
|
|
5326
5683
|
targetId: target.id,
|
|
5327
5684
|
status: "failed",
|
|
@@ -5336,7 +5693,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5336
5693
|
error
|
|
5337
5694
|
};
|
|
5338
5695
|
}
|
|
5339
|
-
await (0,
|
|
5696
|
+
await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
|
|
5340
5697
|
recordId: record4.id,
|
|
5341
5698
|
targetId: target.id,
|
|
5342
5699
|
status: "delivered",
|
|
@@ -5352,7 +5709,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5352
5709
|
};
|
|
5353
5710
|
} catch (err) {
|
|
5354
5711
|
const error = err instanceof Error ? err.message : "delivery failed";
|
|
5355
|
-
await (0,
|
|
5712
|
+
await (0, import_crm7.recordDeliveryAttempt)(crmDeps3, {
|
|
5356
5713
|
recordId: record4.id,
|
|
5357
5714
|
targetId: target.id,
|
|
5358
5715
|
status: "failed",
|
|
@@ -5364,7 +5721,7 @@ async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
|
5364
5721
|
}
|
|
5365
5722
|
|
|
5366
5723
|
// src/worker-routes-network.ts
|
|
5367
|
-
async function
|
|
5724
|
+
async function gate6(req, env, ctx) {
|
|
5368
5725
|
const db = ctx.makeDb(env);
|
|
5369
5726
|
const user = await ctx.verifyUser(req, env);
|
|
5370
5727
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
@@ -5373,7 +5730,7 @@ async function gate5(req, env, ctx) {
|
|
|
5373
5730
|
}
|
|
5374
5731
|
var handleAdminNetworkTargets = async (req, url, env, ctx) => {
|
|
5375
5732
|
if (req.method !== "GET" || url.pathname !== "/api/admin/network/targets") return null;
|
|
5376
|
-
const got = await
|
|
5733
|
+
const got = await gate6(req, env, ctx);
|
|
5377
5734
|
if ("response" in got) return got.response;
|
|
5378
5735
|
return json({
|
|
5379
5736
|
targets: ctx.chapter.network.targets.map(({
|
|
@@ -5400,10 +5757,10 @@ var handleNetworkSnapshot = async (req, url, env, ctx) => {
|
|
|
5400
5757
|
const db = ctx.makeDb(env);
|
|
5401
5758
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
5402
5759
|
if (!secret) return json({ error: "unauthorized" }, 401);
|
|
5403
|
-
const verified = await (0,
|
|
5760
|
+
const verified = await (0, import_db8.verifyFederatedRequest)(req, { secret });
|
|
5404
5761
|
if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
|
|
5405
5762
|
const types = await Promise.all(
|
|
5406
|
-
Object.keys(ctx.chapter.crm.config.types).map((type) => (0,
|
|
5763
|
+
Object.keys(ctx.chapter.crm.config.types).map((type) => (0, import_crm8.typeSummary)({ crm: ctx.chapter.crm, db }, type))
|
|
5407
5764
|
);
|
|
5408
5765
|
const snapshot = {
|
|
5409
5766
|
version: 1,
|
|
@@ -5418,7 +5775,7 @@ async function snapshotOne(db, env, ctx, target) {
|
|
|
5418
5775
|
if (!secret) return { id: target.id, name: target.name, url: target.url, available: false, error: "edge secret is missing" };
|
|
5419
5776
|
const destination = new URL("/api/network/snapshot", target.url);
|
|
5420
5777
|
try {
|
|
5421
|
-
const headers = await (0,
|
|
5778
|
+
const headers = await (0, import_db8.signFederatedRequest)({
|
|
5422
5779
|
secret,
|
|
5423
5780
|
sender: ctx.chapter.id,
|
|
5424
5781
|
method: "GET",
|
|
@@ -5459,7 +5816,7 @@ async function snapshotOne(db, env, ctx, target) {
|
|
|
5459
5816
|
}
|
|
5460
5817
|
var handleAdminNetworkRollup = async (req, url, env, ctx) => {
|
|
5461
5818
|
if (req.method !== "GET" || url.pathname !== "/api/admin/network/rollup") return null;
|
|
5462
|
-
const got = await
|
|
5819
|
+
const got = await gate6(req, env, ctx);
|
|
5463
5820
|
if ("response" in got) return got.response;
|
|
5464
5821
|
const targets = await Promise.all(
|
|
5465
5822
|
ctx.chapter.network.targets.map((target) => snapshotOne(got.db, env, ctx, target))
|
|
@@ -5486,7 +5843,7 @@ var handleAdminNetworkRollup = async (req, url, env, ctx) => {
|
|
|
5486
5843
|
};
|
|
5487
5844
|
var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
5488
5845
|
if (req.method !== "POST" || url.pathname !== "/api/admin/network/push") return null;
|
|
5489
|
-
const got = await
|
|
5846
|
+
const got = await gate6(req, env, ctx);
|
|
5490
5847
|
if ("response" in got) return got.response;
|
|
5491
5848
|
let body;
|
|
5492
5849
|
try {
|
|
@@ -5501,7 +5858,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
|
5501
5858
|
if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
|
|
5502
5859
|
const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
|
|
5503
5860
|
if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
|
|
5504
|
-
const record4 = await (0,
|
|
5861
|
+
const record4 = await (0, import_crm8.getRecord)({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
|
|
5505
5862
|
if (!record4) return json({ error: "record not found" }, 404);
|
|
5506
5863
|
const results = await Promise.all(
|
|
5507
5864
|
targets.map((target) => pushNetworkRecord(got.db, env, ctx, target, record4))
|
|
@@ -5510,8 +5867,8 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
|
5510
5867
|
};
|
|
5511
5868
|
|
|
5512
5869
|
// src/worker-routes-network-records-follower.ts
|
|
5513
|
-
var
|
|
5514
|
-
var
|
|
5870
|
+
var import_crm9 = require("@odla-ai/crm");
|
|
5871
|
+
var import_db9 = require("@odla-ai/db");
|
|
5515
5872
|
|
|
5516
5873
|
// src/worker-routes-network-records-common.ts
|
|
5517
5874
|
var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
|
|
@@ -5520,7 +5877,7 @@ var MAX_OFFSET = 1e4;
|
|
|
5520
5877
|
var MAX_SEARCH = 200;
|
|
5521
5878
|
var MAX_BODY = 32768;
|
|
5522
5879
|
var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
|
|
5523
|
-
var
|
|
5880
|
+
var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
|
|
5524
5881
|
function bodyTooLarge(body) {
|
|
5525
5882
|
return new TextEncoder().encode(body).byteLength > MAX_BODY;
|
|
5526
5883
|
}
|
|
@@ -5594,16 +5951,16 @@ function mutationError(error) {
|
|
|
5594
5951
|
if (error?.code === "transact_guard_failed") {
|
|
5595
5952
|
return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
|
|
5596
5953
|
}
|
|
5597
|
-
if (error instanceof
|
|
5954
|
+
if (error instanceof import_crm9.CrmInputError) {
|
|
5598
5955
|
return json({ error: error.message, code: "invalid_transition_or_fields", ...error.fields ? { fields: error.fields } : {} }, 422);
|
|
5599
5956
|
}
|
|
5600
5957
|
return json({ error: "follower mutation failed", code: "mutation_failed" }, 500);
|
|
5601
5958
|
}
|
|
5602
5959
|
async function priorMutation(db, id) {
|
|
5603
5960
|
const result = await db.query({
|
|
5604
|
-
[
|
|
5961
|
+
[import_crm9.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
|
|
5605
5962
|
});
|
|
5606
|
-
const row = (result[
|
|
5963
|
+
const row = (result[import_crm9.CRM_NS.activity] ?? [])[0];
|
|
5607
5964
|
const meta = row?.meta;
|
|
5608
5965
|
const receipt = meta?.[NETWORK_MUTATION_META];
|
|
5609
5966
|
return typeof receipt?.fingerprint === "string" ? { fingerprint: receipt.fingerprint } : null;
|
|
@@ -5617,7 +5974,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5617
5974
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
5618
5975
|
const readers = ctx.chapter.network.readers;
|
|
5619
5976
|
if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
|
|
5620
|
-
const verified = await (0,
|
|
5977
|
+
const verified = await (0, import_db9.verifyFederatedRequest)(req, {
|
|
5621
5978
|
secret,
|
|
5622
5979
|
senders: readers.map((reader2) => reader2.id)
|
|
5623
5980
|
});
|
|
@@ -5638,7 +5995,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5638
5995
|
const hasFields = Boolean(fields && typeof fields === "object" && !Array.isArray(fields));
|
|
5639
5996
|
const hasStage = typeof input?.stage === "string";
|
|
5640
5997
|
const actor = input?.actor;
|
|
5641
|
-
if (!input || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !
|
|
5998
|
+
if (!input || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID2.test(input.mutationId) || !actor || typeof actor.id !== "string" || !RECORD_ID.test(actor.id) || hasFields === hasStage) {
|
|
5642
5999
|
return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
|
|
5643
6000
|
}
|
|
5644
6001
|
const editable = reader.editableFields[input.type];
|
|
@@ -5657,7 +6014,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5657
6014
|
if (prior.fingerprint !== fingerprint) {
|
|
5658
6015
|
return json({ error: "mutationId was already used for different input", code: "mutation_conflict" }, 409);
|
|
5659
6016
|
}
|
|
5660
|
-
const replayed = await (0,
|
|
6017
|
+
const replayed = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
|
|
5661
6018
|
if (!replayed || replayed.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
|
|
5662
6019
|
const result = {
|
|
5663
6020
|
version: 1,
|
|
@@ -5667,7 +6024,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5667
6024
|
};
|
|
5668
6025
|
return json(result);
|
|
5669
6026
|
}
|
|
5670
|
-
const record4 = await (0,
|
|
6027
|
+
const record4 = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
|
|
5671
6028
|
if (!record4 || record4.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
|
|
5672
6029
|
if (record4.updatedAt !== input.expectedRevision) {
|
|
5673
6030
|
return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
|
|
@@ -5688,7 +6045,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5688
6045
|
};
|
|
5689
6046
|
try {
|
|
5690
6047
|
const deps = { crm: ctx.chapter.crm, db };
|
|
5691
|
-
const updated = hasFields ? await (0,
|
|
6048
|
+
const updated = hasFields ? await (0, import_crm9.updateRecord)(deps, {
|
|
5692
6049
|
id: record4.id,
|
|
5693
6050
|
input: fields,
|
|
5694
6051
|
mutationId: activityId,
|
|
@@ -5699,7 +6056,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5699
6056
|
authorId: `network:${verified.sender}`,
|
|
5700
6057
|
meta: auditMeta
|
|
5701
6058
|
}
|
|
5702
|
-
}) : await (0,
|
|
6059
|
+
}) : await (0, import_crm9.setStage)(deps, {
|
|
5703
6060
|
id: record4.id,
|
|
5704
6061
|
to: input.stage,
|
|
5705
6062
|
authorId: `network:${verified.sender}`,
|
|
@@ -5729,7 +6086,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5729
6086
|
if (limit == null || offset == null || search.length > MAX_SEARCH) {
|
|
5730
6087
|
return json({ error: "invalid pagination or search parameters" }, 400);
|
|
5731
6088
|
}
|
|
5732
|
-
const page = await (0,
|
|
6089
|
+
const page = await (0, import_crm9.listRecords)(
|
|
5733
6090
|
{ crm: ctx.chapter.crm, db },
|
|
5734
6091
|
{
|
|
5735
6092
|
type,
|
|
@@ -5752,15 +6109,15 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5752
6109
|
};
|
|
5753
6110
|
|
|
5754
6111
|
// src/worker-routes-network-records-admin.ts
|
|
5755
|
-
var
|
|
5756
|
-
async function
|
|
6112
|
+
var import_db10 = require("@odla-ai/db");
|
|
6113
|
+
async function gate7(req, env, ctx) {
|
|
5757
6114
|
const db = ctx.makeDb(env);
|
|
5758
6115
|
const user = await ctx.verifyUser(req, env);
|
|
5759
6116
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
5760
6117
|
if (!await ctx.isAdmin(db, user)) return { response: json({ error: "forbidden" }, 403) };
|
|
5761
6118
|
return { db, user };
|
|
5762
6119
|
}
|
|
5763
|
-
function
|
|
6120
|
+
function targetFor2(ctx, id) {
|
|
5764
6121
|
return ctx.chapter.network.targets.find((target) => target.id === id);
|
|
5765
6122
|
}
|
|
5766
6123
|
async function readTargetRecords(db, env, ctx, target, query) {
|
|
@@ -5769,7 +6126,7 @@ async function readTargetRecords(db, env, ctx, target, query) {
|
|
|
5769
6126
|
const destination = new URL("/api/network/records", target.url);
|
|
5770
6127
|
destination.search = query.toString();
|
|
5771
6128
|
try {
|
|
5772
|
-
const headers = await (0,
|
|
6129
|
+
const headers = await (0, import_db10.signFederatedRequest)({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
|
|
5773
6130
|
const response = await fetchNetworkTarget(env, target, destination, {
|
|
5774
6131
|
headers,
|
|
5775
6132
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -5805,7 +6162,7 @@ async function mutateTargetRecord(db, env, ctx, target, input) {
|
|
|
5805
6162
|
const destination = new URL("/api/network/records", target.url);
|
|
5806
6163
|
const body = JSON.stringify(input);
|
|
5807
6164
|
try {
|
|
5808
|
-
const headers = await (0,
|
|
6165
|
+
const headers = await (0, import_db10.signFederatedRequest)({
|
|
5809
6166
|
secret,
|
|
5810
6167
|
sender: ctx.chapter.id,
|
|
5811
6168
|
method: "PATCH",
|
|
@@ -5837,7 +6194,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5837
6194
|
if (req.method === "PATCH" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY) {
|
|
5838
6195
|
return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
|
|
5839
6196
|
}
|
|
5840
|
-
const got = await
|
|
6197
|
+
const got = await gate7(req, env, ctx);
|
|
5841
6198
|
if ("response" in got) return got.response;
|
|
5842
6199
|
if (req.method === "PATCH") {
|
|
5843
6200
|
let input;
|
|
@@ -5850,10 +6207,10 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5850
6207
|
} catch {
|
|
5851
6208
|
return json({ error: "invalid JSON body", code: "invalid_json" }, 422);
|
|
5852
6209
|
}
|
|
5853
|
-
const target2 =
|
|
6210
|
+
const target2 = targetFor2(ctx, input?.targetId);
|
|
5854
6211
|
const hasFields = Boolean(input?.fields && typeof input.fields === "object" && !Array.isArray(input.fields));
|
|
5855
6212
|
const hasStage = typeof input?.stage === "string";
|
|
5856
|
-
if (!target2 || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !
|
|
6213
|
+
if (!target2 || !IDENTIFIER.test(input.type) || !RECORD_ID.test(input.recordId) || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0 || !MUTATION_ID2.test(input.mutationId) || hasFields === hasStage) {
|
|
5857
6214
|
return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
|
|
5858
6215
|
}
|
|
5859
6216
|
return mutateTargetRecord(got.db, env, ctx, target2, {
|
|
@@ -5868,7 +6225,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5868
6225
|
}
|
|
5869
6226
|
const targetId = url.searchParams.get("targetId") ?? "";
|
|
5870
6227
|
const type = url.searchParams.get("type") ?? "";
|
|
5871
|
-
const target =
|
|
6228
|
+
const target = targetFor2(ctx, targetId);
|
|
5872
6229
|
if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
|
|
5873
6230
|
const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
|
|
5874
6231
|
const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
|
|
@@ -5885,8 +6242,8 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5885
6242
|
// src/worker-routes-network-notes.ts
|
|
5886
6243
|
var IDENTIFIER2 = /^[a-z][a-zA-Z0-9_]*$/;
|
|
5887
6244
|
var RECORD_ID2 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
|
|
5888
|
-
var
|
|
5889
|
-
async function
|
|
6245
|
+
var MAX_NOTE3 = 4e3;
|
|
6246
|
+
async function gate8(req, env, ctx) {
|
|
5890
6247
|
const db = ctx.makeDb(env);
|
|
5891
6248
|
const user = await ctx.verifyUser(req, env);
|
|
5892
6249
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
@@ -5920,7 +6277,7 @@ function asNote(row) {
|
|
|
5920
6277
|
}
|
|
5921
6278
|
var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
5922
6279
|
if (url.pathname !== "/api/admin/network/notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
5923
|
-
const got = await
|
|
6280
|
+
const got = await gate8(req, env, ctx);
|
|
5924
6281
|
if ("response" in got) return got.response;
|
|
5925
6282
|
const identity = noteIdentity(url, ctx);
|
|
5926
6283
|
if (!identity) return json({ error: "valid targetId, type, and recordId are required" }, 400);
|
|
@@ -5937,7 +6294,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5937
6294
|
return json({ notes: (result.networkNotes ?? []).map((row) => asNote(row)) });
|
|
5938
6295
|
}
|
|
5939
6296
|
const contentLength = Number(req.headers.get("content-length") ?? 0);
|
|
5940
|
-
if (contentLength >
|
|
6297
|
+
if (contentLength > MAX_NOTE3 * 2) return json({ error: "note is too large" }, 413);
|
|
5941
6298
|
let input;
|
|
5942
6299
|
try {
|
|
5943
6300
|
input = await req.json();
|
|
@@ -5945,7 +6302,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5945
6302
|
return json({ error: "invalid JSON body" }, 400);
|
|
5946
6303
|
}
|
|
5947
6304
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
5948
|
-
if (!body || body.length >
|
|
6305
|
+
if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
|
|
5949
6306
|
const id = crypto.randomUUID();
|
|
5950
6307
|
const note = {
|
|
5951
6308
|
id,
|
|
@@ -5967,14 +6324,14 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5967
6324
|
};
|
|
5968
6325
|
|
|
5969
6326
|
// src/worker-routes-network-shared-notes.ts
|
|
5970
|
-
var
|
|
5971
|
-
var
|
|
6327
|
+
var import_crm10 = require("@odla-ai/crm");
|
|
6328
|
+
var import_db11 = require("@odla-ai/db");
|
|
5972
6329
|
var IDENTIFIER3 = /^[a-z][a-zA-Z0-9_]*$/;
|
|
5973
6330
|
var RECORD_ID3 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
|
|
5974
|
-
var
|
|
5975
|
-
var
|
|
6331
|
+
var MUTATION_ID3 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
6332
|
+
var MAX_NOTE4 = 4e3;
|
|
5976
6333
|
var NETWORK_META = "chapterNetwork";
|
|
5977
|
-
async function
|
|
6334
|
+
async function gate9(req, env, ctx) {
|
|
5978
6335
|
const db = ctx.makeDb(env);
|
|
5979
6336
|
const user = await ctx.verifyUser(req, env);
|
|
5980
6337
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
@@ -5997,7 +6354,7 @@ function sharedNote(activity, sourceId) {
|
|
|
5997
6354
|
}
|
|
5998
6355
|
async function listSharedNotes(db, sourceId, recordId) {
|
|
5999
6356
|
const result = await db.query({
|
|
6000
|
-
[
|
|
6357
|
+
[import_crm10.CRM_NS.activity]: {
|
|
6001
6358
|
$: {
|
|
6002
6359
|
where: {
|
|
6003
6360
|
and: [
|
|
@@ -6010,7 +6367,7 @@ async function listSharedNotes(db, sourceId, recordId) {
|
|
|
6010
6367
|
}
|
|
6011
6368
|
}
|
|
6012
6369
|
});
|
|
6013
|
-
return (result[
|
|
6370
|
+
return (result[import_crm10.CRM_NS.activity] ?? []).flatMap((activity) => {
|
|
6014
6371
|
const note = sharedNote(activity, sourceId);
|
|
6015
6372
|
return note ? [note] : [];
|
|
6016
6373
|
});
|
|
@@ -6020,14 +6377,14 @@ function sourceLabel(sourceId) {
|
|
|
6020
6377
|
}
|
|
6021
6378
|
var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
6022
6379
|
if (url.pathname !== "/api/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
6023
|
-
if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) >
|
|
6380
|
+
if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_NOTE4 * 2) {
|
|
6024
6381
|
return json({ error: "note is too large" }, 413);
|
|
6025
6382
|
}
|
|
6026
6383
|
const db = ctx.makeDb(env);
|
|
6027
6384
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
6028
6385
|
const readers = ctx.chapter.network.readers;
|
|
6029
6386
|
if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
|
|
6030
|
-
const verified = await (0,
|
|
6387
|
+
const verified = await (0, import_db11.verifyFederatedRequest)(req, {
|
|
6031
6388
|
secret,
|
|
6032
6389
|
senders: readers.map((reader2) => reader2.id)
|
|
6033
6390
|
});
|
|
@@ -6038,7 +6395,7 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6038
6395
|
if (!IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
|
|
6039
6396
|
return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
|
|
6040
6397
|
}
|
|
6041
|
-
const record4 = await (0,
|
|
6398
|
+
const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
|
|
6042
6399
|
if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
|
|
6043
6400
|
if (req.method === "GET") {
|
|
6044
6401
|
return json({
|
|
@@ -6055,12 +6412,12 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6055
6412
|
}
|
|
6056
6413
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
6057
6414
|
const mutationId = typeof input.mutationId === "string" ? input.mutationId : "";
|
|
6058
|
-
if (!body || body.length >
|
|
6059
|
-
return json({ error: `body must be 1\u2013${
|
|
6415
|
+
if (!body || body.length > MAX_NOTE4 || !MUTATION_ID3.test(mutationId)) {
|
|
6416
|
+
return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
|
|
6060
6417
|
}
|
|
6061
6418
|
const createdAt = Date.now();
|
|
6062
6419
|
const id = `network:${verified.sender}:${mutationId}`;
|
|
6063
|
-
const result = await (0,
|
|
6420
|
+
const result = await (0, import_crm10.addActivity)(
|
|
6064
6421
|
{
|
|
6065
6422
|
crm: ctx.chapter.crm,
|
|
6066
6423
|
db,
|
|
@@ -6093,17 +6450,17 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6093
6450
|
};
|
|
6094
6451
|
return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
|
|
6095
6452
|
};
|
|
6096
|
-
function
|
|
6453
|
+
function targetFor3(ctx, id) {
|
|
6097
6454
|
return ctx.chapter.network.targets.find((target) => target.id === id);
|
|
6098
6455
|
}
|
|
6099
6456
|
var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
6100
6457
|
if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
6101
|
-
const got = await
|
|
6458
|
+
const got = await gate9(req, env, ctx);
|
|
6102
6459
|
if ("response" in got) return got.response;
|
|
6103
6460
|
const targetId = url.searchParams.get("targetId") ?? "";
|
|
6104
6461
|
const recordType = url.searchParams.get("type") ?? "";
|
|
6105
6462
|
const recordId = url.searchParams.get("recordId") ?? "";
|
|
6106
|
-
const target =
|
|
6463
|
+
const target = targetFor3(ctx, targetId);
|
|
6107
6464
|
if (!target || !target.sharedNotes.includes(recordType) || !IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId)) {
|
|
6108
6465
|
return json({ error: "configured targetId, type, and recordId are required" }, 400);
|
|
6109
6466
|
}
|
|
@@ -6116,7 +6473,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6116
6473
|
return json({ error: "invalid JSON body" }, 400);
|
|
6117
6474
|
}
|
|
6118
6475
|
const note = typeof input.body === "string" ? input.body.trim() : "";
|
|
6119
|
-
if (!note || note.length >
|
|
6476
|
+
if (!note || note.length > MAX_NOTE4) return json({ error: `note must be 1\u2013${MAX_NOTE4} characters` }, 400);
|
|
6120
6477
|
body = JSON.stringify({ body: note, mutationId: crypto.randomUUID() });
|
|
6121
6478
|
}
|
|
6122
6479
|
const secret = await getVaultSecret(got.db, target.secretName);
|
|
@@ -6124,7 +6481,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6124
6481
|
const destination = new URL("/api/network/shared-notes", target.url);
|
|
6125
6482
|
destination.search = new URLSearchParams({ type: recordType, recordId }).toString();
|
|
6126
6483
|
try {
|
|
6127
|
-
const headers = await (0,
|
|
6484
|
+
const headers = await (0, import_db11.signFederatedRequest)({
|
|
6128
6485
|
secret,
|
|
6129
6486
|
sender: ctx.chapter.id,
|
|
6130
6487
|
method: req.method,
|
|
@@ -6151,7 +6508,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6151
6508
|
};
|
|
6152
6509
|
|
|
6153
6510
|
// src/worker-routes-formation.ts
|
|
6154
|
-
var
|
|
6511
|
+
var import_crm11 = require("@odla-ai/crm");
|
|
6155
6512
|
var SUBMISSION = /^[A-Za-z0-9_-]{8,128}$/;
|
|
6156
6513
|
var handleFormation = async (req, url, env, ctx) => {
|
|
6157
6514
|
const formation = ctx.chapter.formation;
|
|
@@ -6203,7 +6560,7 @@ var handleFormation = async (req, url, env, ctx) => {
|
|
|
6203
6560
|
const db = ctx.makeDb(env);
|
|
6204
6561
|
const id = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
|
|
6205
6562
|
try {
|
|
6206
|
-
const created = await (0,
|
|
6563
|
+
const created = await (0, import_crm11.createRecord)(
|
|
6207
6564
|
{
|
|
6208
6565
|
crm: ctx.chapter.crm,
|
|
6209
6566
|
db,
|
|
@@ -6231,7 +6588,7 @@ var handleFormation = async (req, url, env, ctx) => {
|
|
|
6231
6588
|
};
|
|
6232
6589
|
|
|
6233
6590
|
// src/worker-routes-builder-library.ts
|
|
6234
|
-
var
|
|
6591
|
+
var import_db12 = require("@odla-ai/db");
|
|
6235
6592
|
|
|
6236
6593
|
// src/builder-library-validation.ts
|
|
6237
6594
|
var ID4 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -6514,7 +6871,7 @@ async function verifySource(req, db, ctx) {
|
|
|
6514
6871
|
if (ctx.chapter.mode === "chapter") {
|
|
6515
6872
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
6516
6873
|
if (!secret) return null;
|
|
6517
|
-
const verified = await (0,
|
|
6874
|
+
const verified = await (0, import_db12.verifyFederatedRequest)(req, {
|
|
6518
6875
|
secret,
|
|
6519
6876
|
senders: sources.map((source2) => source2.sourceId)
|
|
6520
6877
|
});
|
|
@@ -6527,7 +6884,7 @@ async function verifySource(req, db, ctx) {
|
|
|
6527
6884
|
if (!target) continue;
|
|
6528
6885
|
const secret = await getVaultSecret(db, target.secretName);
|
|
6529
6886
|
if (!secret) continue;
|
|
6530
|
-
const verified = await (0,
|
|
6887
|
+
const verified = await (0, import_db12.verifyFederatedRequest)(req.clone(), { secret, senders: [source.sourceId] });
|
|
6531
6888
|
if (verified.ok) return { source, body: verified.body };
|
|
6532
6889
|
}
|
|
6533
6890
|
return null;
|
|
@@ -6716,11 +7073,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
|
|
|
6716
7073
|
};
|
|
6717
7074
|
|
|
6718
7075
|
// src/signup-control-delivery.ts
|
|
6719
|
-
var
|
|
7076
|
+
var import_db13 = require("@odla-ai/db");
|
|
6720
7077
|
async function deliverSignupControl(options) {
|
|
6721
7078
|
const destination = new URL(SIGNUP_CONTROL_PATH, options.targetUrl);
|
|
6722
7079
|
const body = JSON.stringify(options.envelope);
|
|
6723
|
-
const signed = await (0,
|
|
7080
|
+
const signed = await (0, import_db13.signFederatedRequest)({
|
|
6724
7081
|
secret: options.secret,
|
|
6725
7082
|
sender: options.sender,
|
|
6726
7083
|
method: "POST",
|
|
@@ -6741,11 +7098,11 @@ async function deliverSignupControl(options) {
|
|
|
6741
7098
|
}
|
|
6742
7099
|
|
|
6743
7100
|
// src/member-content-delivery.ts
|
|
6744
|
-
var
|
|
7101
|
+
var import_db14 = require("@odla-ai/db");
|
|
6745
7102
|
async function deliverMemberContent(options) {
|
|
6746
7103
|
const destination = new URL(MEMBER_CONTENT_CONTROL_PATH, options.targetUrl);
|
|
6747
7104
|
const body = JSON.stringify(options.envelope);
|
|
6748
|
-
const signed = await (0,
|
|
7105
|
+
const signed = await (0, import_db14.signFederatedRequest)({
|
|
6749
7106
|
secret: options.secret,
|
|
6750
7107
|
sender: options.sender,
|
|
6751
7108
|
method: "POST",
|
|
@@ -6766,11 +7123,11 @@ async function deliverMemberContent(options) {
|
|
|
6766
7123
|
}
|
|
6767
7124
|
|
|
6768
7125
|
// src/builder-library-delivery.ts
|
|
6769
|
-
var
|
|
7126
|
+
var import_db15 = require("@odla-ai/db");
|
|
6770
7127
|
async function deliverBuilderLibrary(options) {
|
|
6771
7128
|
const destination = new URL(BUILDER_LIBRARY_CONTROL_PATH, options.targetUrl);
|
|
6772
7129
|
const body = JSON.stringify(options.envelope);
|
|
6773
|
-
const signed = await (0,
|
|
7130
|
+
const signed = await (0, import_db15.signFederatedRequest)({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
|
|
6774
7131
|
const response = await (options.fetch ?? globalThis.fetch)(destination, { method: "POST", headers: { ...signed, "content-type": "application/json" }, body });
|
|
6775
7132
|
const result = await response.json();
|
|
6776
7133
|
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
@@ -6780,31 +7137,6 @@ async function deliverBuilderLibrary(options) {
|
|
|
6780
7137
|
return result;
|
|
6781
7138
|
}
|
|
6782
7139
|
|
|
6783
|
-
// src/admission-grants-delivery.ts
|
|
6784
|
-
var import_db15 = require("@odla-ai/db");
|
|
6785
|
-
async function deliverAdmissionGrants(options) {
|
|
6786
|
-
const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
|
|
6787
|
-
const body = JSON.stringify(options.envelope);
|
|
6788
|
-
const signed = await (0, import_db15.signFederatedRequest)({
|
|
6789
|
-
secret: options.secret,
|
|
6790
|
-
sender: options.sender,
|
|
6791
|
-
method: "POST",
|
|
6792
|
-
url: destination,
|
|
6793
|
-
body
|
|
6794
|
-
});
|
|
6795
|
-
const response = await (options.fetch ?? globalThis.fetch)(destination, {
|
|
6796
|
-
method: "POST",
|
|
6797
|
-
headers: { ...signed, "content-type": "application/json" },
|
|
6798
|
-
body
|
|
6799
|
-
});
|
|
6800
|
-
const result = await response.json();
|
|
6801
|
-
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
6802
|
-
const failure = result;
|
|
6803
|
-
throw new Error(`admission grants rejected (${response.status}): ${failure.reason ?? failure.error ?? "unknown"}`);
|
|
6804
|
-
}
|
|
6805
|
-
return result;
|
|
6806
|
-
}
|
|
6807
|
-
|
|
6808
7140
|
// src/signup-control-theme.ts
|
|
6809
7141
|
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
6810
7142
|
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
@@ -6964,6 +7296,7 @@ var BUILTIN_ROUTES = [
|
|
|
6964
7296
|
handleAdminPeopleRole,
|
|
6965
7297
|
handleAdminCrmSync,
|
|
6966
7298
|
handleAdminAdmissionGrants,
|
|
7299
|
+
handleAdminNetworkAdmissions,
|
|
6967
7300
|
handleAdminClerkPrivateProfileSync,
|
|
6968
7301
|
// Aggregation
|
|
6969
7302
|
handleAdminDashboard,
|