@odla-ai/chapter 0.42.0 → 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 +32 -4
- package/dist/{chunk-MMDWXTJU.js → chunk-HWUTCXBV.js} +44 -1
- package/dist/chunk-HWUTCXBV.js.map +1 -0
- package/dist/{copy-context-DuJkH4U9.d.ts → copy-context-BBPsYe24.d.ts} +27 -1
- package/dist/index.cjs +144 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -3
- package/dist/index.d.ts +75 -3
- package/dist/index.js +144 -16
- 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 +745 -163
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +73 -2
- package/dist/worker/index.d.ts +73 -2
- package/dist/worker/index.js +730 -148
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -2
- package/runbooks/greenfield.md +3 -0
- 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,15 +5730,25 @@ 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
|
-
targets: ctx.chapter.network.targets.map(({
|
|
5736
|
+
targets: ctx.chapter.network.targets.map(({
|
|
5737
|
+
id,
|
|
5738
|
+
name,
|
|
5739
|
+
url: targetUrl,
|
|
5740
|
+
fields,
|
|
5741
|
+
sharedNotes,
|
|
5742
|
+
editableFields,
|
|
5743
|
+
stageTransitions
|
|
5744
|
+
}) => ({
|
|
5380
5745
|
id,
|
|
5381
5746
|
name,
|
|
5382
5747
|
url: targetUrl,
|
|
5383
5748
|
types: Object.keys(fields ?? DEFAULT_SHARE_FIELDS),
|
|
5384
|
-
sharedNotes
|
|
5749
|
+
sharedNotes,
|
|
5750
|
+
editableFields,
|
|
5751
|
+
stageTransitions
|
|
5385
5752
|
}))
|
|
5386
5753
|
});
|
|
5387
5754
|
};
|
|
@@ -5390,10 +5757,10 @@ var handleNetworkSnapshot = async (req, url, env, ctx) => {
|
|
|
5390
5757
|
const db = ctx.makeDb(env);
|
|
5391
5758
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
5392
5759
|
if (!secret) return json({ error: "unauthorized" }, 401);
|
|
5393
|
-
const verified = await (0,
|
|
5760
|
+
const verified = await (0, import_db8.verifyFederatedRequest)(req, { secret });
|
|
5394
5761
|
if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
|
|
5395
5762
|
const types = await Promise.all(
|
|
5396
|
-
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))
|
|
5397
5764
|
);
|
|
5398
5765
|
const snapshot = {
|
|
5399
5766
|
version: 1,
|
|
@@ -5408,7 +5775,7 @@ async function snapshotOne(db, env, ctx, target) {
|
|
|
5408
5775
|
if (!secret) return { id: target.id, name: target.name, url: target.url, available: false, error: "edge secret is missing" };
|
|
5409
5776
|
const destination = new URL("/api/network/snapshot", target.url);
|
|
5410
5777
|
try {
|
|
5411
|
-
const headers = await (0,
|
|
5778
|
+
const headers = await (0, import_db8.signFederatedRequest)({
|
|
5412
5779
|
secret,
|
|
5413
5780
|
sender: ctx.chapter.id,
|
|
5414
5781
|
method: "GET",
|
|
@@ -5449,7 +5816,7 @@ async function snapshotOne(db, env, ctx, target) {
|
|
|
5449
5816
|
}
|
|
5450
5817
|
var handleAdminNetworkRollup = async (req, url, env, ctx) => {
|
|
5451
5818
|
if (req.method !== "GET" || url.pathname !== "/api/admin/network/rollup") return null;
|
|
5452
|
-
const got = await
|
|
5819
|
+
const got = await gate6(req, env, ctx);
|
|
5453
5820
|
if ("response" in got) return got.response;
|
|
5454
5821
|
const targets = await Promise.all(
|
|
5455
5822
|
ctx.chapter.network.targets.map((target) => snapshotOne(got.db, env, ctx, target))
|
|
@@ -5476,7 +5843,7 @@ var handleAdminNetworkRollup = async (req, url, env, ctx) => {
|
|
|
5476
5843
|
};
|
|
5477
5844
|
var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
5478
5845
|
if (req.method !== "POST" || url.pathname !== "/api/admin/network/push") return null;
|
|
5479
|
-
const got = await
|
|
5846
|
+
const got = await gate6(req, env, ctx);
|
|
5480
5847
|
if ("response" in got) return got.response;
|
|
5481
5848
|
let body;
|
|
5482
5849
|
try {
|
|
@@ -5491,7 +5858,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
|
5491
5858
|
if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
|
|
5492
5859
|
const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
|
|
5493
5860
|
if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
|
|
5494
|
-
const record4 = await (0,
|
|
5861
|
+
const record4 = await (0, import_crm8.getRecord)({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
|
|
5495
5862
|
if (!record4) return json({ error: "record not found" }, 404);
|
|
5496
5863
|
const results = await Promise.all(
|
|
5497
5864
|
targets.map((target) => pushNetworkRecord(got.db, env, ctx, target, record4))
|
|
@@ -5499,19 +5866,20 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
|
5499
5866
|
return json({ ok: results.every((result) => result.ok), results });
|
|
5500
5867
|
};
|
|
5501
5868
|
|
|
5502
|
-
// src/worker-routes-network-records.ts
|
|
5503
|
-
var
|
|
5504
|
-
var
|
|
5869
|
+
// src/worker-routes-network-records-follower.ts
|
|
5870
|
+
var import_crm9 = require("@odla-ai/crm");
|
|
5871
|
+
var import_db9 = require("@odla-ai/db");
|
|
5872
|
+
|
|
5873
|
+
// src/worker-routes-network-records-common.ts
|
|
5505
5874
|
var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
|
|
5506
5875
|
var MAX_PAGE = 50;
|
|
5507
5876
|
var MAX_OFFSET = 1e4;
|
|
5508
5877
|
var MAX_SEARCH = 200;
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
return { db, user };
|
|
5878
|
+
var MAX_BODY = 32768;
|
|
5879
|
+
var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
|
|
5880
|
+
var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
|
|
5881
|
+
function bodyTooLarge(body) {
|
|
5882
|
+
return new TextEncoder().encode(body).byteLength > MAX_BODY;
|
|
5515
5883
|
}
|
|
5516
5884
|
function integer(value, fallback, min, max) {
|
|
5517
5885
|
if (value == null || value === "") return fallback;
|
|
@@ -5519,10 +5887,22 @@ function integer(value, fallback, min, max) {
|
|
|
5519
5887
|
const parsed = Number(value);
|
|
5520
5888
|
return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : null;
|
|
5521
5889
|
}
|
|
5522
|
-
function projectRecord(record4, allowed2) {
|
|
5890
|
+
function projectRecord(record4, allowed2, editable, stagesEnabled, crm) {
|
|
5523
5891
|
const fields = Object.fromEntries(
|
|
5524
5892
|
allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
|
|
5525
5893
|
);
|
|
5894
|
+
const def = crm.type(record4.type);
|
|
5895
|
+
const editFields = editable.map((id) => {
|
|
5896
|
+
const field = def.fields[id];
|
|
5897
|
+
return {
|
|
5898
|
+
id,
|
|
5899
|
+
label: field.label ?? id,
|
|
5900
|
+
type: field.type,
|
|
5901
|
+
required: field.required ?? false,
|
|
5902
|
+
...field.options ? { options: field.options } : {}
|
|
5903
|
+
};
|
|
5904
|
+
});
|
|
5905
|
+
const allowedStages = stagesEnabled ? (def.pipeline?.stages ?? []).flatMap((stage) => crm.canTransition(record4.type, record4, stage.id) ? [] : [{ id: stage.id, label: stage.label ?? stage.id }]) : [];
|
|
5526
5906
|
return {
|
|
5527
5907
|
id: record4.id,
|
|
5528
5908
|
type: record4.type,
|
|
@@ -5530,23 +5910,175 @@ function projectRecord(record4, allowed2) {
|
|
|
5530
5910
|
...record4.stage ? { stage: record4.stage } : {},
|
|
5531
5911
|
fields,
|
|
5532
5912
|
createdAt: record4.createdAt,
|
|
5533
|
-
updatedAt: record4.updatedAt
|
|
5913
|
+
updatedAt: record4.updatedAt,
|
|
5914
|
+
revision: record4.updatedAt,
|
|
5915
|
+
...editFields.length || stagesEnabled ? { edit: { fields: editFields, allowedStages } } : {}
|
|
5534
5916
|
};
|
|
5535
5917
|
}
|
|
5918
|
+
function readerRecord(record4, reader, crm) {
|
|
5919
|
+
return projectRecord(
|
|
5920
|
+
record4,
|
|
5921
|
+
reader.fields[record4.type] ?? [],
|
|
5922
|
+
reader.editableFields[record4.type] ?? [],
|
|
5923
|
+
reader.stageTransitions.includes(record4.type),
|
|
5924
|
+
crm
|
|
5925
|
+
);
|
|
5926
|
+
}
|
|
5927
|
+
function restrictedRecord(record4, target) {
|
|
5928
|
+
if (!record4.edit) return record4;
|
|
5929
|
+
const fields = record4.edit.fields.filter((field) => target.editableFields[record4.type]?.includes(field.id));
|
|
5930
|
+
const allowedStages = target.stageTransitions.includes(record4.type) ? record4.edit.allowedStages : [];
|
|
5931
|
+
const edit = fields.length || target.stageTransitions.includes(record4.type) ? { fields, allowedStages } : void 0;
|
|
5932
|
+
return { ...record4, ...edit ? { edit } : { edit: void 0 } };
|
|
5933
|
+
}
|
|
5934
|
+
|
|
5935
|
+
// src/worker-routes-network-records-follower.ts
|
|
5936
|
+
var NETWORK_MUTATION_META = "chapterNetworkMutation";
|
|
5937
|
+
function mutationFingerprint(input) {
|
|
5938
|
+
const fields = input.fields ? Object.fromEntries(Object.entries(input.fields).sort(([a], [b]) => a.localeCompare(b))) : void 0;
|
|
5939
|
+
return JSON.stringify({
|
|
5940
|
+
recordId: input.recordId,
|
|
5941
|
+
type: input.type,
|
|
5942
|
+
expectedRevision: input.expectedRevision,
|
|
5943
|
+
expectedStage: input.expectedStage,
|
|
5944
|
+
mutationId: input.mutationId,
|
|
5945
|
+
fields,
|
|
5946
|
+
stage: input.stage,
|
|
5947
|
+
actor: input.actor
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
function mutationError(error) {
|
|
5951
|
+
if (error?.code === "transact_guard_failed") {
|
|
5952
|
+
return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
|
|
5953
|
+
}
|
|
5954
|
+
if (error instanceof import_crm9.CrmInputError) {
|
|
5955
|
+
return json({ error: error.message, code: "invalid_transition_or_fields", ...error.fields ? { fields: error.fields } : {} }, 422);
|
|
5956
|
+
}
|
|
5957
|
+
return json({ error: "follower mutation failed", code: "mutation_failed" }, 500);
|
|
5958
|
+
}
|
|
5959
|
+
async function priorMutation(db, id) {
|
|
5960
|
+
const result = await db.query({
|
|
5961
|
+
[import_crm9.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
|
|
5962
|
+
});
|
|
5963
|
+
const row = (result[import_crm9.CRM_NS.activity] ?? [])[0];
|
|
5964
|
+
const meta = row?.meta;
|
|
5965
|
+
const receipt = meta?.[NETWORK_MUTATION_META];
|
|
5966
|
+
return typeof receipt?.fingerprint === "string" ? { fingerprint: receipt.fingerprint } : null;
|
|
5967
|
+
}
|
|
5536
5968
|
var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
5537
|
-
if (req.method !== "GET"
|
|
5969
|
+
if (url.pathname !== "/api/network/records" || req.method !== "GET" && req.method !== "PATCH") return null;
|
|
5970
|
+
if (req.method === "PATCH" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY) {
|
|
5971
|
+
return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
|
|
5972
|
+
}
|
|
5538
5973
|
const db = ctx.makeDb(env);
|
|
5539
5974
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
5540
5975
|
const readers = ctx.chapter.network.readers;
|
|
5541
5976
|
if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
|
|
5542
|
-
const verified = await (0,
|
|
5977
|
+
const verified = await (0, import_db9.verifyFederatedRequest)(req, {
|
|
5543
5978
|
secret,
|
|
5544
5979
|
senders: readers.map((reader2) => reader2.id)
|
|
5545
5980
|
});
|
|
5546
5981
|
if (!verified.ok) return json({ error: "unauthorized", reason: verified.reason }, 401);
|
|
5547
5982
|
const reader = readers.find((candidate) => candidate.id === verified.sender);
|
|
5983
|
+
if (!reader) return json({ error: "unauthorized" }, 401);
|
|
5984
|
+
if (req.method === "PATCH") {
|
|
5985
|
+
if (bodyTooLarge(verified.body)) {
|
|
5986
|
+
return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
|
|
5987
|
+
}
|
|
5988
|
+
let input;
|
|
5989
|
+
try {
|
|
5990
|
+
input = JSON.parse(verified.body);
|
|
5991
|
+
} catch {
|
|
5992
|
+
return json({ error: "invalid JSON body", code: "invalid_json" }, 422);
|
|
5993
|
+
}
|
|
5994
|
+
const fields = input?.fields;
|
|
5995
|
+
const hasFields = Boolean(fields && typeof fields === "object" && !Array.isArray(fields));
|
|
5996
|
+
const hasStage = typeof input?.stage === "string";
|
|
5997
|
+
const actor = input?.actor;
|
|
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) {
|
|
5999
|
+
return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
|
|
6000
|
+
}
|
|
6001
|
+
const editable = reader.editableFields[input.type];
|
|
6002
|
+
if (hasFields) {
|
|
6003
|
+
const names = Object.keys(fields);
|
|
6004
|
+
if (!editable || names.length === 0 || names.length > 32 || names.some((name) => !editable.includes(name))) {
|
|
6005
|
+
return json({ error: "profile fields are not delegated for this reader", code: "field_forbidden" }, 403);
|
|
6006
|
+
}
|
|
6007
|
+
} else if (!reader.stageTransitions.includes(input.type)) {
|
|
6008
|
+
return json({ error: "stage transitions are not delegated for this reader", code: "stage_forbidden" }, 403);
|
|
6009
|
+
}
|
|
6010
|
+
const activityId = `network-edit:${verified.sender}:${input.mutationId}`;
|
|
6011
|
+
const fingerprint = mutationFingerprint(input);
|
|
6012
|
+
const prior = await priorMutation(db, activityId);
|
|
6013
|
+
if (prior) {
|
|
6014
|
+
if (prior.fingerprint !== fingerprint) {
|
|
6015
|
+
return json({ error: "mutationId was already used for different input", code: "mutation_conflict" }, 409);
|
|
6016
|
+
}
|
|
6017
|
+
const replayed = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
|
|
6018
|
+
if (!replayed || replayed.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
|
|
6019
|
+
const result = {
|
|
6020
|
+
version: 1,
|
|
6021
|
+
site: { id: ctx.chapter.id, name: ctx.chapter.name },
|
|
6022
|
+
record: readerRecord(replayed, reader, ctx.chapter.crm),
|
|
6023
|
+
duplicate: true
|
|
6024
|
+
};
|
|
6025
|
+
return json(result);
|
|
6026
|
+
}
|
|
6027
|
+
const record4 = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
|
|
6028
|
+
if (!record4 || record4.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
|
|
6029
|
+
if (record4.updatedAt !== input.expectedRevision) {
|
|
6030
|
+
return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
|
|
6031
|
+
}
|
|
6032
|
+
if (hasStage && (input.expectedStage ?? null) !== (record4.stage ?? null)) {
|
|
6033
|
+
return json({ error: "stage changed since it was reviewed", code: "stale_stage" }, 409);
|
|
6034
|
+
}
|
|
6035
|
+
const actorEmail = typeof actor.email === "string" && actor.email.length <= 320 ? actor.email : void 0;
|
|
6036
|
+
const auditMeta = {
|
|
6037
|
+
[NETWORK_MUTATION_META]: {
|
|
6038
|
+
version: 1,
|
|
6039
|
+
sourceId: verified.sender,
|
|
6040
|
+
actorId: actor.id,
|
|
6041
|
+
...actorEmail ? { actorEmail } : {},
|
|
6042
|
+
mutationId: input.mutationId,
|
|
6043
|
+
fingerprint
|
|
6044
|
+
}
|
|
6045
|
+
};
|
|
6046
|
+
try {
|
|
6047
|
+
const deps = { crm: ctx.chapter.crm, db };
|
|
6048
|
+
const updated = hasFields ? await (0, import_crm9.updateRecord)(deps, {
|
|
6049
|
+
id: record4.id,
|
|
6050
|
+
input: fields,
|
|
6051
|
+
mutationId: activityId,
|
|
6052
|
+
expectedUpdatedAt: input.expectedRevision,
|
|
6053
|
+
audit: {
|
|
6054
|
+
id: activityId,
|
|
6055
|
+
body: `Profile edited remotely by ${verified.sender}`,
|
|
6056
|
+
authorId: `network:${verified.sender}`,
|
|
6057
|
+
meta: auditMeta
|
|
6058
|
+
}
|
|
6059
|
+
}) : await (0, import_crm9.setStage)(deps, {
|
|
6060
|
+
id: record4.id,
|
|
6061
|
+
to: input.stage,
|
|
6062
|
+
authorId: `network:${verified.sender}`,
|
|
6063
|
+
mutationId: activityId,
|
|
6064
|
+
expectedUpdatedAt: input.expectedRevision,
|
|
6065
|
+
expectedStage: input.expectedStage ?? null,
|
|
6066
|
+
activityId,
|
|
6067
|
+
activityMeta: auditMeta
|
|
6068
|
+
});
|
|
6069
|
+
const result = {
|
|
6070
|
+
version: 1,
|
|
6071
|
+
site: { id: ctx.chapter.id, name: ctx.chapter.name },
|
|
6072
|
+
record: readerRecord(updated.record, reader, ctx.chapter.crm),
|
|
6073
|
+
duplicate: updated.duplicate
|
|
6074
|
+
};
|
|
6075
|
+
return json(result);
|
|
6076
|
+
} catch (error) {
|
|
6077
|
+
return mutationError(error);
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
5548
6080
|
const type = url.searchParams.get("type") ?? "";
|
|
5549
|
-
const allowed2 = reader
|
|
6081
|
+
const allowed2 = reader.fields[type];
|
|
5550
6082
|
if (!IDENTIFIER.test(type) || !allowed2) return json({ error: "record type is not shared with this reader" }, 403);
|
|
5551
6083
|
const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
|
|
5552
6084
|
const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
|
|
@@ -5554,7 +6086,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5554
6086
|
if (limit == null || offset == null || search.length > MAX_SEARCH) {
|
|
5555
6087
|
return json({ error: "invalid pagination or search parameters" }, 400);
|
|
5556
6088
|
}
|
|
5557
|
-
const page = await (0,
|
|
6089
|
+
const page = await (0, import_crm9.listRecords)(
|
|
5558
6090
|
{ crm: ctx.chapter.crm, db },
|
|
5559
6091
|
{
|
|
5560
6092
|
type,
|
|
@@ -5568,14 +6100,24 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5568
6100
|
version: 1,
|
|
5569
6101
|
site: { id: ctx.chapter.id, name: ctx.chapter.name },
|
|
5570
6102
|
type,
|
|
5571
|
-
records: page.records.map((record4) =>
|
|
6103
|
+
records: page.records.map((record4) => readerRecord(record4, reader, ctx.chapter.crm)),
|
|
5572
6104
|
total: page.total,
|
|
5573
6105
|
limit: page.limit,
|
|
5574
6106
|
offset: page.offset
|
|
5575
6107
|
};
|
|
5576
6108
|
return json(response);
|
|
5577
6109
|
};
|
|
5578
|
-
|
|
6110
|
+
|
|
6111
|
+
// src/worker-routes-network-records-admin.ts
|
|
6112
|
+
var import_db10 = require("@odla-ai/db");
|
|
6113
|
+
async function gate7(req, env, ctx) {
|
|
6114
|
+
const db = ctx.makeDb(env);
|
|
6115
|
+
const user = await ctx.verifyUser(req, env);
|
|
6116
|
+
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
6117
|
+
if (!await ctx.isAdmin(db, user)) return { response: json({ error: "forbidden" }, 403) };
|
|
6118
|
+
return { db, user };
|
|
6119
|
+
}
|
|
6120
|
+
function targetFor2(ctx, id) {
|
|
5579
6121
|
return ctx.chapter.network.targets.find((target) => target.id === id);
|
|
5580
6122
|
}
|
|
5581
6123
|
async function readTargetRecords(db, env, ctx, target, query) {
|
|
@@ -5584,12 +6126,7 @@ async function readTargetRecords(db, env, ctx, target, query) {
|
|
|
5584
6126
|
const destination = new URL("/api/network/records", target.url);
|
|
5585
6127
|
destination.search = query.toString();
|
|
5586
6128
|
try {
|
|
5587
|
-
const headers = await (0,
|
|
5588
|
-
secret,
|
|
5589
|
-
sender: ctx.chapter.id,
|
|
5590
|
-
method: "GET",
|
|
5591
|
-
url: destination
|
|
5592
|
-
});
|
|
6129
|
+
const headers = await (0, import_db10.signFederatedRequest)({ secret, sender: ctx.chapter.id, method: "GET", url: destination });
|
|
5593
6130
|
const response = await fetchNetworkTarget(env, target, destination, {
|
|
5594
6131
|
headers,
|
|
5595
6132
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -5602,18 +6139,93 @@ async function readTargetRecords(db, env, ctx, target, query) {
|
|
|
5602
6139
|
error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`)
|
|
5603
6140
|
};
|
|
5604
6141
|
}
|
|
5605
|
-
return {
|
|
6142
|
+
return {
|
|
6143
|
+
ok: true,
|
|
6144
|
+
page: { ...body, records: body.records.map((record4) => restrictedRecord(record4, target)) }
|
|
6145
|
+
};
|
|
5606
6146
|
} catch (error) {
|
|
5607
6147
|
return { ok: false, error: error instanceof Error ? error.message : "record read failed" };
|
|
5608
6148
|
}
|
|
5609
6149
|
}
|
|
6150
|
+
async function mutateTargetRecord(db, env, ctx, target, input) {
|
|
6151
|
+
const fields = input.fields;
|
|
6152
|
+
if (fields) {
|
|
6153
|
+
const allowed2 = target.editableFields[input.type];
|
|
6154
|
+
if (!allowed2 || Object.keys(fields).some((name) => !allowed2.includes(name))) {
|
|
6155
|
+
return json({ error: "profile fields are not enabled for this target", code: "field_forbidden" }, 403);
|
|
6156
|
+
}
|
|
6157
|
+
} else if (!target.stageTransitions.includes(input.type)) {
|
|
6158
|
+
return json({ error: "stage transitions are not enabled for this target", code: "stage_forbidden" }, 403);
|
|
6159
|
+
}
|
|
6160
|
+
const secret = await getVaultSecret(db, target.secretName);
|
|
6161
|
+
if (!secret) return json({ error: "edge secret is missing", code: "edge_unavailable" }, 502);
|
|
6162
|
+
const destination = new URL("/api/network/records", target.url);
|
|
6163
|
+
const body = JSON.stringify(input);
|
|
6164
|
+
try {
|
|
6165
|
+
const headers = await (0, import_db10.signFederatedRequest)({
|
|
6166
|
+
secret,
|
|
6167
|
+
sender: ctx.chapter.id,
|
|
6168
|
+
method: "PATCH",
|
|
6169
|
+
url: destination,
|
|
6170
|
+
body
|
|
6171
|
+
});
|
|
6172
|
+
const response = await fetchNetworkTarget(env, target, destination, {
|
|
6173
|
+
method: "PATCH",
|
|
6174
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
6175
|
+
body,
|
|
6176
|
+
signal: AbortSignal.timeout(1e4)
|
|
6177
|
+
});
|
|
6178
|
+
const payload = await response.json().catch(() => null);
|
|
6179
|
+
const site = payload && "site" in payload ? payload.site : void 0;
|
|
6180
|
+
if (!response.ok || !payload || !("record" in payload) || site && site.id !== target.id) {
|
|
6181
|
+
const status = [401, 403, 404, 409, 422].includes(response.status) ? response.status : 502;
|
|
6182
|
+
return json({
|
|
6183
|
+
error: payload && "error" in payload && typeof payload.error === "string" ? payload.error : `follower returned ${response.status}`,
|
|
6184
|
+
...payload && "code" in payload && typeof payload.code === "string" ? { code: payload.code } : {}
|
|
6185
|
+
}, status);
|
|
6186
|
+
}
|
|
6187
|
+
return json({ ...payload, record: restrictedRecord(payload.record, target) });
|
|
6188
|
+
} catch {
|
|
6189
|
+
return json({ error: "follower mutation request failed", code: "follower_unavailable" }, 502);
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
5610
6192
|
var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
5611
|
-
if (req.method !== "GET"
|
|
5612
|
-
|
|
6193
|
+
if (url.pathname !== "/api/admin/network/records" || req.method !== "GET" && req.method !== "PATCH") return null;
|
|
6194
|
+
if (req.method === "PATCH" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY) {
|
|
6195
|
+
return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
|
|
6196
|
+
}
|
|
6197
|
+
const got = await gate7(req, env, ctx);
|
|
5613
6198
|
if ("response" in got) return got.response;
|
|
6199
|
+
if (req.method === "PATCH") {
|
|
6200
|
+
let input;
|
|
6201
|
+
try {
|
|
6202
|
+
const body = await req.text();
|
|
6203
|
+
if (bodyTooLarge(body)) {
|
|
6204
|
+
return json({ error: "mutation body is too large", code: "body_too_large" }, 413);
|
|
6205
|
+
}
|
|
6206
|
+
input = JSON.parse(body);
|
|
6207
|
+
} catch {
|
|
6208
|
+
return json({ error: "invalid JSON body", code: "invalid_json" }, 422);
|
|
6209
|
+
}
|
|
6210
|
+
const target2 = targetFor2(ctx, input?.targetId);
|
|
6211
|
+
const hasFields = Boolean(input?.fields && typeof input.fields === "object" && !Array.isArray(input.fields));
|
|
6212
|
+
const hasStage = typeof input?.stage === "string";
|
|
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) {
|
|
6214
|
+
return json({ error: "invalid network mutation", code: "invalid_mutation" }, 422);
|
|
6215
|
+
}
|
|
6216
|
+
return mutateTargetRecord(got.db, env, ctx, target2, {
|
|
6217
|
+
type: input.type,
|
|
6218
|
+
recordId: input.recordId,
|
|
6219
|
+
expectedRevision: input.expectedRevision,
|
|
6220
|
+
mutationId: input.mutationId,
|
|
6221
|
+
...input.expectedStage !== void 0 ? { expectedStage: input.expectedStage } : {},
|
|
6222
|
+
...hasFields ? { fields: input.fields } : { stage: input.stage },
|
|
6223
|
+
actor: { id: got.user.userId, ...got.user.email ? { email: got.user.email } : {} }
|
|
6224
|
+
});
|
|
6225
|
+
}
|
|
5614
6226
|
const targetId = url.searchParams.get("targetId") ?? "";
|
|
5615
6227
|
const type = url.searchParams.get("type") ?? "";
|
|
5616
|
-
const target =
|
|
6228
|
+
const target = targetFor2(ctx, targetId);
|
|
5617
6229
|
if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
|
|
5618
6230
|
const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
|
|
5619
6231
|
const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
|
|
@@ -5623,21 +6235,15 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5623
6235
|
}
|
|
5624
6236
|
const query = new URLSearchParams({ type, limit: String(limit), offset: String(offset) });
|
|
5625
6237
|
if (search) query.set("search", search);
|
|
5626
|
-
const result = await readTargetRecords(
|
|
5627
|
-
got.db,
|
|
5628
|
-
env,
|
|
5629
|
-
ctx,
|
|
5630
|
-
target,
|
|
5631
|
-
query
|
|
5632
|
-
);
|
|
6238
|
+
const result = await readTargetRecords(got.db, env, ctx, target, query);
|
|
5633
6239
|
return result.ok ? json(result.page) : json({ error: result.error }, 502);
|
|
5634
6240
|
};
|
|
5635
6241
|
|
|
5636
6242
|
// src/worker-routes-network-notes.ts
|
|
5637
6243
|
var IDENTIFIER2 = /^[a-z][a-zA-Z0-9_]*$/;
|
|
5638
|
-
var
|
|
5639
|
-
var
|
|
5640
|
-
async function
|
|
6244
|
+
var RECORD_ID2 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
|
|
6245
|
+
var MAX_NOTE3 = 4e3;
|
|
6246
|
+
async function gate8(req, env, ctx) {
|
|
5641
6247
|
const db = ctx.makeDb(env);
|
|
5642
6248
|
const user = await ctx.verifyUser(req, env);
|
|
5643
6249
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
@@ -5649,7 +6255,7 @@ function noteIdentity(url, ctx) {
|
|
|
5649
6255
|
const recordType = url.searchParams.get("type") ?? "";
|
|
5650
6256
|
const recordId = url.searchParams.get("recordId") ?? "";
|
|
5651
6257
|
const configured = ctx.chapter.network.targets.some((target) => target.id === targetId);
|
|
5652
|
-
if (!configured || !IDENTIFIER2.test(recordType) || !
|
|
6258
|
+
if (!configured || !IDENTIFIER2.test(recordType) || !RECORD_ID2.test(recordId)) return null;
|
|
5653
6259
|
return {
|
|
5654
6260
|
targetId,
|
|
5655
6261
|
recordType,
|
|
@@ -5671,7 +6277,7 @@ function asNote(row) {
|
|
|
5671
6277
|
}
|
|
5672
6278
|
var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
5673
6279
|
if (url.pathname !== "/api/admin/network/notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
5674
|
-
const got = await
|
|
6280
|
+
const got = await gate8(req, env, ctx);
|
|
5675
6281
|
if ("response" in got) return got.response;
|
|
5676
6282
|
const identity = noteIdentity(url, ctx);
|
|
5677
6283
|
if (!identity) return json({ error: "valid targetId, type, and recordId are required" }, 400);
|
|
@@ -5688,7 +6294,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5688
6294
|
return json({ notes: (result.networkNotes ?? []).map((row) => asNote(row)) });
|
|
5689
6295
|
}
|
|
5690
6296
|
const contentLength = Number(req.headers.get("content-length") ?? 0);
|
|
5691
|
-
if (contentLength >
|
|
6297
|
+
if (contentLength > MAX_NOTE3 * 2) return json({ error: "note is too large" }, 413);
|
|
5692
6298
|
let input;
|
|
5693
6299
|
try {
|
|
5694
6300
|
input = await req.json();
|
|
@@ -5696,7 +6302,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5696
6302
|
return json({ error: "invalid JSON body" }, 400);
|
|
5697
6303
|
}
|
|
5698
6304
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
5699
|
-
if (!body || body.length >
|
|
6305
|
+
if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
|
|
5700
6306
|
const id = crypto.randomUUID();
|
|
5701
6307
|
const note = {
|
|
5702
6308
|
id,
|
|
@@ -5718,14 +6324,14 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
|
|
|
5718
6324
|
};
|
|
5719
6325
|
|
|
5720
6326
|
// src/worker-routes-network-shared-notes.ts
|
|
5721
|
-
var
|
|
5722
|
-
var
|
|
6327
|
+
var import_crm10 = require("@odla-ai/crm");
|
|
6328
|
+
var import_db11 = require("@odla-ai/db");
|
|
5723
6329
|
var IDENTIFIER3 = /^[a-z][a-zA-Z0-9_]*$/;
|
|
5724
|
-
var
|
|
5725
|
-
var
|
|
5726
|
-
var
|
|
6330
|
+
var RECORD_ID3 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
|
|
6331
|
+
var MUTATION_ID3 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
6332
|
+
var MAX_NOTE4 = 4e3;
|
|
5727
6333
|
var NETWORK_META = "chapterNetwork";
|
|
5728
|
-
async function
|
|
6334
|
+
async function gate9(req, env, ctx) {
|
|
5729
6335
|
const db = ctx.makeDb(env);
|
|
5730
6336
|
const user = await ctx.verifyUser(req, env);
|
|
5731
6337
|
if (!user) return { response: json({ error: "unauthorized" }, 401) };
|
|
@@ -5748,7 +6354,7 @@ function sharedNote(activity, sourceId) {
|
|
|
5748
6354
|
}
|
|
5749
6355
|
async function listSharedNotes(db, sourceId, recordId) {
|
|
5750
6356
|
const result = await db.query({
|
|
5751
|
-
[
|
|
6357
|
+
[import_crm10.CRM_NS.activity]: {
|
|
5752
6358
|
$: {
|
|
5753
6359
|
where: {
|
|
5754
6360
|
and: [
|
|
@@ -5761,7 +6367,7 @@ async function listSharedNotes(db, sourceId, recordId) {
|
|
|
5761
6367
|
}
|
|
5762
6368
|
}
|
|
5763
6369
|
});
|
|
5764
|
-
return (result[
|
|
6370
|
+
return (result[import_crm10.CRM_NS.activity] ?? []).flatMap((activity) => {
|
|
5765
6371
|
const note = sharedNote(activity, sourceId);
|
|
5766
6372
|
return note ? [note] : [];
|
|
5767
6373
|
});
|
|
@@ -5771,14 +6377,14 @@ function sourceLabel(sourceId) {
|
|
|
5771
6377
|
}
|
|
5772
6378
|
var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
5773
6379
|
if (url.pathname !== "/api/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
5774
|
-
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) {
|
|
5775
6381
|
return json({ error: "note is too large" }, 413);
|
|
5776
6382
|
}
|
|
5777
6383
|
const db = ctx.makeDb(env);
|
|
5778
6384
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
5779
6385
|
const readers = ctx.chapter.network.readers;
|
|
5780
6386
|
if (!secret || readers.length === 0) return json({ error: "unauthorized" }, 401);
|
|
5781
|
-
const verified = await (0,
|
|
6387
|
+
const verified = await (0, import_db11.verifyFederatedRequest)(req, {
|
|
5782
6388
|
secret,
|
|
5783
6389
|
senders: readers.map((reader2) => reader2.id)
|
|
5784
6390
|
});
|
|
@@ -5786,10 +6392,10 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5786
6392
|
const reader = readers.find((candidate) => candidate.id === verified.sender);
|
|
5787
6393
|
const recordType = url.searchParams.get("type") ?? "";
|
|
5788
6394
|
const recordId = url.searchParams.get("recordId") ?? "";
|
|
5789
|
-
if (!IDENTIFIER3.test(recordType) || !
|
|
6395
|
+
if (!IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
|
|
5790
6396
|
return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
|
|
5791
6397
|
}
|
|
5792
|
-
const record4 = await (0,
|
|
6398
|
+
const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
|
|
5793
6399
|
if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
|
|
5794
6400
|
if (req.method === "GET") {
|
|
5795
6401
|
return json({
|
|
@@ -5806,12 +6412,12 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5806
6412
|
}
|
|
5807
6413
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
5808
6414
|
const mutationId = typeof input.mutationId === "string" ? input.mutationId : "";
|
|
5809
|
-
if (!body || body.length >
|
|
5810
|
-
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);
|
|
5811
6417
|
}
|
|
5812
6418
|
const createdAt = Date.now();
|
|
5813
6419
|
const id = `network:${verified.sender}:${mutationId}`;
|
|
5814
|
-
const result = await (0,
|
|
6420
|
+
const result = await (0, import_crm10.addActivity)(
|
|
5815
6421
|
{
|
|
5816
6422
|
crm: ctx.chapter.crm,
|
|
5817
6423
|
db,
|
|
@@ -5844,18 +6450,18 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5844
6450
|
};
|
|
5845
6451
|
return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
|
|
5846
6452
|
};
|
|
5847
|
-
function
|
|
6453
|
+
function targetFor3(ctx, id) {
|
|
5848
6454
|
return ctx.chapter.network.targets.find((target) => target.id === id);
|
|
5849
6455
|
}
|
|
5850
6456
|
var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
5851
6457
|
if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
|
|
5852
|
-
const got = await
|
|
6458
|
+
const got = await gate9(req, env, ctx);
|
|
5853
6459
|
if ("response" in got) return got.response;
|
|
5854
6460
|
const targetId = url.searchParams.get("targetId") ?? "";
|
|
5855
6461
|
const recordType = url.searchParams.get("type") ?? "";
|
|
5856
6462
|
const recordId = url.searchParams.get("recordId") ?? "";
|
|
5857
|
-
const target =
|
|
5858
|
-
if (!target || !target.sharedNotes.includes(recordType) || !IDENTIFIER3.test(recordType) || !
|
|
6463
|
+
const target = targetFor3(ctx, targetId);
|
|
6464
|
+
if (!target || !target.sharedNotes.includes(recordType) || !IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId)) {
|
|
5859
6465
|
return json({ error: "configured targetId, type, and recordId are required" }, 400);
|
|
5860
6466
|
}
|
|
5861
6467
|
let body = "";
|
|
@@ -5867,7 +6473,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5867
6473
|
return json({ error: "invalid JSON body" }, 400);
|
|
5868
6474
|
}
|
|
5869
6475
|
const note = typeof input.body === "string" ? input.body.trim() : "";
|
|
5870
|
-
if (!note || note.length >
|
|
6476
|
+
if (!note || note.length > MAX_NOTE4) return json({ error: `note must be 1\u2013${MAX_NOTE4} characters` }, 400);
|
|
5871
6477
|
body = JSON.stringify({ body: note, mutationId: crypto.randomUUID() });
|
|
5872
6478
|
}
|
|
5873
6479
|
const secret = await getVaultSecret(got.db, target.secretName);
|
|
@@ -5875,7 +6481,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5875
6481
|
const destination = new URL("/api/network/shared-notes", target.url);
|
|
5876
6482
|
destination.search = new URLSearchParams({ type: recordType, recordId }).toString();
|
|
5877
6483
|
try {
|
|
5878
|
-
const headers = await (0,
|
|
6484
|
+
const headers = await (0, import_db11.signFederatedRequest)({
|
|
5879
6485
|
secret,
|
|
5880
6486
|
sender: ctx.chapter.id,
|
|
5881
6487
|
method: req.method,
|
|
@@ -5902,7 +6508,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5902
6508
|
};
|
|
5903
6509
|
|
|
5904
6510
|
// src/worker-routes-formation.ts
|
|
5905
|
-
var
|
|
6511
|
+
var import_crm11 = require("@odla-ai/crm");
|
|
5906
6512
|
var SUBMISSION = /^[A-Za-z0-9_-]{8,128}$/;
|
|
5907
6513
|
var handleFormation = async (req, url, env, ctx) => {
|
|
5908
6514
|
const formation = ctx.chapter.formation;
|
|
@@ -5954,7 +6560,7 @@ var handleFormation = async (req, url, env, ctx) => {
|
|
|
5954
6560
|
const db = ctx.makeDb(env);
|
|
5955
6561
|
const id = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
|
|
5956
6562
|
try {
|
|
5957
|
-
const created = await (0,
|
|
6563
|
+
const created = await (0, import_crm11.createRecord)(
|
|
5958
6564
|
{
|
|
5959
6565
|
crm: ctx.chapter.crm,
|
|
5960
6566
|
db,
|
|
@@ -5982,7 +6588,7 @@ var handleFormation = async (req, url, env, ctx) => {
|
|
|
5982
6588
|
};
|
|
5983
6589
|
|
|
5984
6590
|
// src/worker-routes-builder-library.ts
|
|
5985
|
-
var
|
|
6591
|
+
var import_db12 = require("@odla-ai/db");
|
|
5986
6592
|
|
|
5987
6593
|
// src/builder-library-validation.ts
|
|
5988
6594
|
var ID4 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -6265,7 +6871,7 @@ async function verifySource(req, db, ctx) {
|
|
|
6265
6871
|
if (ctx.chapter.mode === "chapter") {
|
|
6266
6872
|
const secret = await getVaultSecret(db, "network_share_secret");
|
|
6267
6873
|
if (!secret) return null;
|
|
6268
|
-
const verified = await (0,
|
|
6874
|
+
const verified = await (0, import_db12.verifyFederatedRequest)(req, {
|
|
6269
6875
|
secret,
|
|
6270
6876
|
senders: sources.map((source2) => source2.sourceId)
|
|
6271
6877
|
});
|
|
@@ -6278,7 +6884,7 @@ async function verifySource(req, db, ctx) {
|
|
|
6278
6884
|
if (!target) continue;
|
|
6279
6885
|
const secret = await getVaultSecret(db, target.secretName);
|
|
6280
6886
|
if (!secret) continue;
|
|
6281
|
-
const verified = await (0,
|
|
6887
|
+
const verified = await (0, import_db12.verifyFederatedRequest)(req.clone(), { secret, senders: [source.sourceId] });
|
|
6282
6888
|
if (verified.ok) return { source, body: verified.body };
|
|
6283
6889
|
}
|
|
6284
6890
|
return null;
|
|
@@ -6467,11 +7073,11 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
|
|
|
6467
7073
|
};
|
|
6468
7074
|
|
|
6469
7075
|
// src/signup-control-delivery.ts
|
|
6470
|
-
var
|
|
7076
|
+
var import_db13 = require("@odla-ai/db");
|
|
6471
7077
|
async function deliverSignupControl(options) {
|
|
6472
7078
|
const destination = new URL(SIGNUP_CONTROL_PATH, options.targetUrl);
|
|
6473
7079
|
const body = JSON.stringify(options.envelope);
|
|
6474
|
-
const signed = await (0,
|
|
7080
|
+
const signed = await (0, import_db13.signFederatedRequest)({
|
|
6475
7081
|
secret: options.secret,
|
|
6476
7082
|
sender: options.sender,
|
|
6477
7083
|
method: "POST",
|
|
@@ -6492,11 +7098,11 @@ async function deliverSignupControl(options) {
|
|
|
6492
7098
|
}
|
|
6493
7099
|
|
|
6494
7100
|
// src/member-content-delivery.ts
|
|
6495
|
-
var
|
|
7101
|
+
var import_db14 = require("@odla-ai/db");
|
|
6496
7102
|
async function deliverMemberContent(options) {
|
|
6497
7103
|
const destination = new URL(MEMBER_CONTENT_CONTROL_PATH, options.targetUrl);
|
|
6498
7104
|
const body = JSON.stringify(options.envelope);
|
|
6499
|
-
const signed = await (0,
|
|
7105
|
+
const signed = await (0, import_db14.signFederatedRequest)({
|
|
6500
7106
|
secret: options.secret,
|
|
6501
7107
|
sender: options.sender,
|
|
6502
7108
|
method: "POST",
|
|
@@ -6517,11 +7123,11 @@ async function deliverMemberContent(options) {
|
|
|
6517
7123
|
}
|
|
6518
7124
|
|
|
6519
7125
|
// src/builder-library-delivery.ts
|
|
6520
|
-
var
|
|
7126
|
+
var import_db15 = require("@odla-ai/db");
|
|
6521
7127
|
async function deliverBuilderLibrary(options) {
|
|
6522
7128
|
const destination = new URL(BUILDER_LIBRARY_CONTROL_PATH, options.targetUrl);
|
|
6523
7129
|
const body = JSON.stringify(options.envelope);
|
|
6524
|
-
const signed = await (0,
|
|
7130
|
+
const signed = await (0, import_db15.signFederatedRequest)({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
|
|
6525
7131
|
const response = await (options.fetch ?? globalThis.fetch)(destination, { method: "POST", headers: { ...signed, "content-type": "application/json" }, body });
|
|
6526
7132
|
const result = await response.json();
|
|
6527
7133
|
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
@@ -6531,31 +7137,6 @@ async function deliverBuilderLibrary(options) {
|
|
|
6531
7137
|
return result;
|
|
6532
7138
|
}
|
|
6533
7139
|
|
|
6534
|
-
// src/admission-grants-delivery.ts
|
|
6535
|
-
var import_db14 = require("@odla-ai/db");
|
|
6536
|
-
async function deliverAdmissionGrants(options) {
|
|
6537
|
-
const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
|
|
6538
|
-
const body = JSON.stringify(options.envelope);
|
|
6539
|
-
const signed = await (0, import_db14.signFederatedRequest)({
|
|
6540
|
-
secret: options.secret,
|
|
6541
|
-
sender: options.sender,
|
|
6542
|
-
method: "POST",
|
|
6543
|
-
url: destination,
|
|
6544
|
-
body
|
|
6545
|
-
});
|
|
6546
|
-
const response = await (options.fetch ?? globalThis.fetch)(destination, {
|
|
6547
|
-
method: "POST",
|
|
6548
|
-
headers: { ...signed, "content-type": "application/json" },
|
|
6549
|
-
body
|
|
6550
|
-
});
|
|
6551
|
-
const result = await response.json();
|
|
6552
|
-
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
6553
|
-
const failure = result;
|
|
6554
|
-
throw new Error(`admission grants rejected (${response.status}): ${failure.reason ?? failure.error ?? "unknown"}`);
|
|
6555
|
-
}
|
|
6556
|
-
return result;
|
|
6557
|
-
}
|
|
6558
|
-
|
|
6559
7140
|
// src/signup-control-theme.ts
|
|
6560
7141
|
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
6561
7142
|
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
@@ -6715,6 +7296,7 @@ var BUILTIN_ROUTES = [
|
|
|
6715
7296
|
handleAdminPeopleRole,
|
|
6716
7297
|
handleAdminCrmSync,
|
|
6717
7298
|
handleAdminAdmissionGrants,
|
|
7299
|
+
handleAdminNetworkAdmissions,
|
|
6718
7300
|
handleAdminClerkPrivateProfileSync,
|
|
6719
7301
|
// Aggregation
|
|
6720
7302
|
handleAdminDashboard,
|