@odla-ai/chapter 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -29
- package/dist/{copy-context-Dp5hvj5W.d.ts → copy-context-DI21CYQ3.d.ts} +3 -4
- package/dist/index.cjs +51 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -24
- package/dist/index.d.ts +50 -24
- package/dist/index.js +51 -14
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +211 -22
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +3 -4
- package/dist/worker/index.d.ts +3 -4
- package/dist/worker/index.js +211 -22
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/adopt-existing.md +38 -9
- package/runbooks/greenfield.md +31 -31
package/dist/ui/admin/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as preact from 'preact';
|
|
2
2
|
import { ComponentChildren, JSX } from 'preact';
|
|
3
3
|
import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
|
|
4
|
-
import { a as Chapter, b as ChapterCopy } from '../../copy-context-
|
|
5
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-
|
|
4
|
+
import { a as Chapter, b as ChapterCopy } from '../../copy-context-DI21CYQ3.js';
|
|
5
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-DI21CYQ3.js';
|
|
6
6
|
import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
|
|
7
7
|
|
|
8
8
|
/** Normalized public auth configuration consumed by the Clerk gate. */
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AdminAccountMenuProps, AdminChrome, AdminNote, AdminPage, AdminResource, AdminRouteState, AdminRouteTarget, AdminRouting, AdminSection, AdminSectionContext, AdminWorkspace, AdminWorkspaceNavProps, AvailabilitySectionOptions, BillingSectionOptions, ChapterAdmin, ChapterAdminAuthAdapter, ChapterAdminAuthConfig, ChapterAdminAuthLoadContext, ChapterAdminProps, ChapterAdminUser, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, applicationLifecycleAdapter, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, loadChapterAdminConfig, loadChapterAdminUser, meetingsSection, peopleSection, useAdminResource } from './admin/index.js';
|
|
2
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-
|
|
2
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-DI21CYQ3.js';
|
|
3
3
|
export { ApiFn, BrandStyle, BrandStyleProps, JoinConfig, JoinFlowState, JoinIsland, JoinIslandProps, JoinStepRenderContext, JoinSubmitRenderContext, MemberProvisionalRenderContext, MembersArea, MembersAreaProps, PaymentPriceLines, PaymentStep, PaymentStepProps, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, timeLabel, tzShort } from './member/index.js';
|
|
4
4
|
import 'preact';
|
|
5
5
|
import '@odla-ai/crm';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as preact from 'preact';
|
|
2
2
|
import { ComponentChildren, JSX } from 'preact';
|
|
3
|
-
import { b as ChapterCopy, c as ChapterBrand } from '../../copy-context-
|
|
4
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-
|
|
3
|
+
import { b as ChapterCopy, c as ChapterBrand } from '../../copy-context-DI21CYQ3.js';
|
|
4
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-DI21CYQ3.js';
|
|
5
5
|
import '@odla-ai/crm';
|
|
6
6
|
|
|
7
7
|
/** A bookable slot: a start instant in epoch milliseconds. */
|
package/dist/worker/index.cjs
CHANGED
|
@@ -546,23 +546,30 @@ function clerkUserRequest(input) {
|
|
|
546
546
|
skip_password_requirement: true,
|
|
547
547
|
...input.firstName ? { first_name: input.firstName } : {},
|
|
548
548
|
...input.lastName ? { last_name: input.lastName } : {},
|
|
549
|
-
...input.publicMetadata ? { public_metadata: input.publicMetadata } : {}
|
|
549
|
+
...input.publicMetadata !== void 0 ? { public_metadata: input.publicMetadata } : {},
|
|
550
|
+
...input.privateMetadata !== void 0 ? { private_metadata: input.privateMetadata } : {}
|
|
550
551
|
}
|
|
551
552
|
};
|
|
552
553
|
}
|
|
553
|
-
async function
|
|
554
|
+
async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fetch) {
|
|
555
|
+
const res = await fetchImpl(`https://api.clerk.com/v1/users/${encodeURIComponent(userId)}/metadata`, {
|
|
556
|
+
method: "PATCH",
|
|
557
|
+
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
558
|
+
body: JSON.stringify({
|
|
559
|
+
...input.publicMetadata !== void 0 ? { public_metadata: input.publicMetadata } : {},
|
|
560
|
+
...input.privateMetadata !== void 0 ? { private_metadata: input.privateMetadata } : {}
|
|
561
|
+
})
|
|
562
|
+
});
|
|
563
|
+
return res.ok;
|
|
564
|
+
}
|
|
565
|
+
async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
|
|
554
566
|
const auth = { authorization: `Bearer ${secretKey}` };
|
|
555
567
|
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
556
568
|
if (!found.ok) return false;
|
|
557
569
|
const users = await found.json().catch(() => null);
|
|
558
570
|
const id = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
559
571
|
if (!id) return false;
|
|
560
|
-
|
|
561
|
-
method: "PATCH",
|
|
562
|
-
headers: { ...auth, "content-type": "application/json" },
|
|
563
|
-
body: JSON.stringify({ public_metadata: publicMetadata })
|
|
564
|
-
});
|
|
565
|
-
return patched.ok;
|
|
572
|
+
return updateClerkUserMetadata(secretKey, id, input, fetchImpl);
|
|
566
573
|
}
|
|
567
574
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
568
575
|
const { path, body } = clerkUserRequest(input);
|
|
@@ -573,8 +580,15 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
573
580
|
});
|
|
574
581
|
if (res.ok) return { ok: true, status: res.status };
|
|
575
582
|
const healed = heal(res.status);
|
|
576
|
-
|
|
577
|
-
|
|
583
|
+
const repair = input.repairMetadata ?? {
|
|
584
|
+
...input.publicMetadata !== void 0 ? { publicMetadata: input.publicMetadata } : {},
|
|
585
|
+
...input.privateMetadata !== void 0 ? { privateMetadata: input.privateMetadata } : {}
|
|
586
|
+
};
|
|
587
|
+
if (!healed.existed || repair.publicMetadata === void 0 && repair.privateMetadata === void 0) return healed;
|
|
588
|
+
const refreshed = await updateClerkUserMetadataByEmail(secretKey, input.email, {
|
|
589
|
+
...repair.publicMetadata !== void 0 ? { publicMetadata: repair.publicMetadata } : {},
|
|
590
|
+
...repair.privateMetadata !== void 0 ? { privateMetadata: repair.privateMetadata } : {}
|
|
591
|
+
}, fetchImpl).catch(() => false);
|
|
578
592
|
return { ...healed, refreshed };
|
|
579
593
|
}
|
|
580
594
|
|
|
@@ -682,6 +696,41 @@ function emailGroupFrom(row) {
|
|
|
682
696
|
}
|
|
683
697
|
|
|
684
698
|
// src/worker-provisioning.ts
|
|
699
|
+
var applicantPrivateMetadata = (chapter, applicationId, fields, removeMissingProfile = false) => {
|
|
700
|
+
const profile = applicantProfile(chapter, fields);
|
|
701
|
+
return {
|
|
702
|
+
applicationId,
|
|
703
|
+
...profile ? { profile } : removeMissingProfile ? { profile: null } : {}
|
|
704
|
+
};
|
|
705
|
+
};
|
|
706
|
+
var publicProfileRemoval = { applicationId: null, profile: null };
|
|
707
|
+
async function markPrivateMetadataSynced(db, applicationId, clerkUserId) {
|
|
708
|
+
await db.transact([{
|
|
709
|
+
t: "update",
|
|
710
|
+
ns: "applications",
|
|
711
|
+
id: applicationId,
|
|
712
|
+
attrs: {
|
|
713
|
+
clerkPrivateMetadataSyncedAt: Date.now(),
|
|
714
|
+
...clerkUserId ? { clerkUserId } : {}
|
|
715
|
+
}
|
|
716
|
+
}]);
|
|
717
|
+
}
|
|
718
|
+
async function syncApplicantPrivateMetadata(db, chapter, applicationId, fields, clerkUserId) {
|
|
719
|
+
if (chapter.account === "none") return false;
|
|
720
|
+
try {
|
|
721
|
+
const secret = await getVaultSecret(db, "clerk_secret_key");
|
|
722
|
+
if (!secret) return false;
|
|
723
|
+
const ok = await updateClerkUserMetadata(secret, clerkUserId, {
|
|
724
|
+
publicMetadata: publicProfileRemoval,
|
|
725
|
+
privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
|
|
726
|
+
});
|
|
727
|
+
if (!ok) return false;
|
|
728
|
+
await markPrivateMetadataSynced(db, applicationId, clerkUserId);
|
|
729
|
+
return true;
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
685
734
|
async function provisionApplicant(db, chapter, applicationId, fields) {
|
|
686
735
|
const email = typeof fields.email === "string" ? fields.email : "";
|
|
687
736
|
if (!email) return;
|
|
@@ -709,17 +758,30 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
|
|
|
709
758
|
try {
|
|
710
759
|
const secret = await getVaultSecret(db, "clerk_secret_key");
|
|
711
760
|
if (!secret) return;
|
|
712
|
-
const
|
|
713
|
-
const publicMetadata = { applicationId, ...profile ? { profile } : {} };
|
|
761
|
+
const privateMetadata = applicantPrivateMetadata(chapter, applicationId, fields);
|
|
714
762
|
if (chapter.account === "create") {
|
|
715
|
-
await createClerkUser(secret, {
|
|
763
|
+
const result = await createClerkUser(secret, {
|
|
716
764
|
email,
|
|
717
765
|
firstName: stringValue(fields.firstName),
|
|
718
766
|
lastName: stringValue(fields.lastName),
|
|
719
|
-
|
|
767
|
+
privateMetadata,
|
|
768
|
+
repairMetadata: {
|
|
769
|
+
publicMetadata: publicProfileRemoval,
|
|
770
|
+
privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
|
|
771
|
+
}
|
|
720
772
|
});
|
|
773
|
+
if (result.ok && (!result.existed || result.refreshed)) {
|
|
774
|
+
await markPrivateMetadataSynced(db, applicationId);
|
|
775
|
+
}
|
|
721
776
|
} else {
|
|
722
|
-
await createClerkInvitation(secret, { email
|
|
777
|
+
const result = await createClerkInvitation(secret, { email });
|
|
778
|
+
if (result.existed) {
|
|
779
|
+
const refreshed = await updateClerkUserMetadataByEmail(secret, email, {
|
|
780
|
+
publicMetadata: publicProfileRemoval,
|
|
781
|
+
privateMetadata: applicantPrivateMetadata(chapter, applicationId, fields, true)
|
|
782
|
+
});
|
|
783
|
+
if (refreshed) await markPrivateMetadataSynced(db, applicationId);
|
|
784
|
+
}
|
|
723
785
|
}
|
|
724
786
|
} catch {
|
|
725
787
|
}
|
|
@@ -768,7 +830,10 @@ async function memberSessionApplication(db, chapterId, email) {
|
|
|
768
830
|
const groups = (await db.query({ groups: { $: { where: { id: chapterId }, limit: 1 } } })).groups;
|
|
769
831
|
const group = Array.isArray(groups) ? groups[0] : void 0;
|
|
770
832
|
const timezone = resolveScheduling(group?.schedulingJson).timezone;
|
|
771
|
-
return
|
|
833
|
+
return {
|
|
834
|
+
application: memberApplication(app, meeting, timezone),
|
|
835
|
+
source: app
|
|
836
|
+
};
|
|
772
837
|
}
|
|
773
838
|
var handleHealth = async (_req, url) => url.pathname === "/api/health" ? json({ ok: true }) : null;
|
|
774
839
|
var handleConfig = async (_req, url, env, ctx) => {
|
|
@@ -789,8 +854,26 @@ var handleMe = async (req, url, env, ctx) => {
|
|
|
789
854
|
const superAdmin = await ctx.isSuperAdminEmail(db, u.email);
|
|
790
855
|
const base = { authorized: isAdminRole(role, ctx.auth), role, superAdmin, email: u.email ?? null };
|
|
791
856
|
if (ctx.chapter.mode !== "chapter" || !u.email) return json(base);
|
|
792
|
-
const
|
|
793
|
-
|
|
857
|
+
const member = await memberSessionApplication(db, ctx.chapter.id, u.email);
|
|
858
|
+
let privateMetadataSynced = false;
|
|
859
|
+
if (member && ctx.chapter.account !== "none" && typeof member.source.clerkPrivateMetadataSyncedAt !== "number") {
|
|
860
|
+
privateMetadataSynced = await syncApplicantPrivateMetadata(
|
|
861
|
+
db,
|
|
862
|
+
ctx.chapter,
|
|
863
|
+
String(member.source.id),
|
|
864
|
+
member.source,
|
|
865
|
+
u.userId
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (member && !privateMetadataSynced && member.source.clerkUserId !== u.userId) {
|
|
869
|
+
await db.transact([{
|
|
870
|
+
t: "update",
|
|
871
|
+
ns: "applications",
|
|
872
|
+
id: String(member.source.id),
|
|
873
|
+
attrs: { clerkUserId: u.userId }
|
|
874
|
+
}]).catch(() => void 0);
|
|
875
|
+
}
|
|
876
|
+
return json({ ...base, application: member?.application ?? null });
|
|
794
877
|
};
|
|
795
878
|
var handleCrm = async (req, url, env, ctx) => {
|
|
796
879
|
const crmBase = ctx.crmBase;
|
|
@@ -1645,9 +1728,21 @@ var PAGE = 100;
|
|
|
1645
1728
|
function toRecord(u) {
|
|
1646
1729
|
if (typeof u.id !== "string") return null;
|
|
1647
1730
|
const pm = u.public_metadata ?? {};
|
|
1731
|
+
const privateMetadata = u.private_metadata ?? {};
|
|
1732
|
+
const rawProfile = privateMetadata.profile;
|
|
1733
|
+
const profile = rawProfile && typeof rawProfile === "object" && !Array.isArray(rawProfile) ? rawProfile : {};
|
|
1734
|
+
const applicationId = typeof privateMetadata.applicationId === "string" ? privateMetadata.applicationId : void 0;
|
|
1648
1735
|
const role = typeof pm.role === "string" && pm.role ? pm.role : DEFAULT_ROLE;
|
|
1649
1736
|
const email = u.email_addresses?.[0]?.email_address;
|
|
1650
|
-
return {
|
|
1737
|
+
return {
|
|
1738
|
+
id: u.id,
|
|
1739
|
+
email: typeof email === "string" ? email : void 0,
|
|
1740
|
+
role,
|
|
1741
|
+
publicMetadata: pm,
|
|
1742
|
+
privateMetadata,
|
|
1743
|
+
profile,
|
|
1744
|
+
...applicationId ? { applicationId } : {}
|
|
1745
|
+
};
|
|
1651
1746
|
}
|
|
1652
1747
|
async function clerkGet(path, secretKey, fetchImpl) {
|
|
1653
1748
|
const res = await fetchImpl(`${CLERK_API}${path}`, { headers: { authorization: `Bearer ${secretKey}` } });
|
|
@@ -1718,17 +1813,20 @@ var handleAdminPeople = async (req, url, env, ctx) => {
|
|
|
1718
1813
|
db.query({ $users: { $: { limit: 200 } } }),
|
|
1719
1814
|
sk ? clerkListUsers(sk).catch(() => []) : Promise.resolve([])
|
|
1720
1815
|
]);
|
|
1721
|
-
const
|
|
1816
|
+
const clerkByUserId = new Map(roleList.map((u) => [u.id, u]));
|
|
1722
1817
|
const people = /* @__PURE__ */ new Map();
|
|
1723
1818
|
for (const u of usersRes.$users ?? []) {
|
|
1724
1819
|
if (u.deleted === true) continue;
|
|
1725
1820
|
const email = typeof u.email === "string" ? u.email : "";
|
|
1726
1821
|
if (!email) continue;
|
|
1822
|
+
const clerk = clerkByUserId.get(String(u.id));
|
|
1727
1823
|
people.set(email.toLowerCase(), {
|
|
1728
1824
|
email,
|
|
1729
1825
|
name: typeof u.name === "string" ? u.name : "",
|
|
1730
1826
|
userId: typeof u.id === "string" ? u.id : null,
|
|
1731
|
-
role:
|
|
1827
|
+
role: clerk?.role ?? "provisional",
|
|
1828
|
+
profile: clerk ? clerk.profile : null,
|
|
1829
|
+
clerkApplicationId: clerk?.applicationId ?? null,
|
|
1732
1830
|
application: null
|
|
1733
1831
|
});
|
|
1734
1832
|
}
|
|
@@ -1741,7 +1839,15 @@ var handleAdminPeople = async (req, url, env, ctx) => {
|
|
|
1741
1839
|
if (!row.application) row.application = applicationSummary(a);
|
|
1742
1840
|
if (!row.name) row.name = name;
|
|
1743
1841
|
} else {
|
|
1744
|
-
people.set(key, {
|
|
1842
|
+
people.set(key, {
|
|
1843
|
+
email: String(a.email),
|
|
1844
|
+
name,
|
|
1845
|
+
userId: null,
|
|
1846
|
+
role: null,
|
|
1847
|
+
profile: null,
|
|
1848
|
+
clerkApplicationId: null,
|
|
1849
|
+
application: applicationSummary(a)
|
|
1850
|
+
});
|
|
1745
1851
|
}
|
|
1746
1852
|
}
|
|
1747
1853
|
const rows2 = [...people.values()].sort(
|
|
@@ -1793,6 +1899,88 @@ var handleAdminPeopleRole = async (req, url, env, ctx) => {
|
|
|
1793
1899
|
return json({ ok: true });
|
|
1794
1900
|
};
|
|
1795
1901
|
|
|
1902
|
+
// src/worker-routes-admin-clerk.ts
|
|
1903
|
+
var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
1904
|
+
function canonical(value) {
|
|
1905
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
1906
|
+
if (value && typeof value === "object") {
|
|
1907
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
1908
|
+
}
|
|
1909
|
+
return JSON.stringify(value) ?? "undefined";
|
|
1910
|
+
}
|
|
1911
|
+
function positiveInt(raw, fallback, max) {
|
|
1912
|
+
const parsed = Number(raw);
|
|
1913
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? Math.min(parsed, max) : fallback;
|
|
1914
|
+
}
|
|
1915
|
+
var handleAdminClerkPrivateProfileSync = async (req, url, env, ctx) => {
|
|
1916
|
+
if (req.method !== "POST" || url.pathname !== "/api/admin/clerk/private-profiles/sync") return null;
|
|
1917
|
+
const rawDb = ctx.makeDb(env);
|
|
1918
|
+
const actor = await ctx.verifyUser(req, env);
|
|
1919
|
+
if (!actor) return json({ error: "unauthorized" }, 401);
|
|
1920
|
+
if (!await ctx.isAdmin(rawDb, actor)) return json({ error: "forbidden" }, 403);
|
|
1921
|
+
if (ctx.chapter.mode !== "chapter" || ctx.chapter.account === "none") {
|
|
1922
|
+
return json({ error: "private profile sync requires a side-effecting chapter account model" }, 409);
|
|
1923
|
+
}
|
|
1924
|
+
const db = rawDb;
|
|
1925
|
+
const secret = await getVaultSecret(db, "clerk_secret_key");
|
|
1926
|
+
if (!secret) return json({ error: "clerk_secret_key missing from vault" }, 503);
|
|
1927
|
+
let all;
|
|
1928
|
+
try {
|
|
1929
|
+
all = await clerkListUsers(secret);
|
|
1930
|
+
} catch {
|
|
1931
|
+
return json({ error: "Clerk user listing failed" }, 502);
|
|
1932
|
+
}
|
|
1933
|
+
const offset = positiveInt(url.searchParams.get("offset"), 0, Number.MAX_SAFE_INTEGER);
|
|
1934
|
+
const limit = positiveInt(url.searchParams.get("limit"), 50, 100) || 50;
|
|
1935
|
+
const users = all.slice(offset, offset + limit);
|
|
1936
|
+
const result = { updated: 0, alreadyCurrent: 0, noApplication: 0, failed: 0 };
|
|
1937
|
+
for (const user of users) {
|
|
1938
|
+
if (!user.email) {
|
|
1939
|
+
result.noApplication += 1;
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
const apps = (await db.query({
|
|
1943
|
+
applications: { $: { where: { email: user.email }, order: { createdAt: "desc" }, limit: 1 } }
|
|
1944
|
+
})).applications;
|
|
1945
|
+
const app = Array.isArray(apps) ? apps[0] : void 0;
|
|
1946
|
+
if (!app || typeof app.id !== "string") {
|
|
1947
|
+
result.noApplication += 1;
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const desired = applicantPrivateMetadata(ctx.chapter, app.id, app);
|
|
1951
|
+
const desiredProfile = desired.profile ?? {};
|
|
1952
|
+
const legacyPublic = own(user.publicMetadata, "applicationId") || own(user.publicMetadata, "profile");
|
|
1953
|
+
const current = user.applicationId === app.id && canonical(user.profile) === canonical(desiredProfile);
|
|
1954
|
+
try {
|
|
1955
|
+
let updated = false;
|
|
1956
|
+
if (!current || legacyPublic) {
|
|
1957
|
+
const ok = await updateClerkUserMetadata(secret, user.id, {
|
|
1958
|
+
publicMetadata: publicProfileRemoval,
|
|
1959
|
+
privateMetadata: applicantPrivateMetadata(ctx.chapter, app.id, app, true)
|
|
1960
|
+
});
|
|
1961
|
+
if (!ok) {
|
|
1962
|
+
result.failed += 1;
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
updated = true;
|
|
1966
|
+
}
|
|
1967
|
+
await markPrivateMetadataSynced(db, app.id, user.id);
|
|
1968
|
+
if (updated) result.updated += 1;
|
|
1969
|
+
else result.alreadyCurrent += 1;
|
|
1970
|
+
} catch {
|
|
1971
|
+
result.failed += 1;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const nextOffset = offset + users.length < all.length ? offset + users.length : null;
|
|
1975
|
+
return json({
|
|
1976
|
+
totalUsers: all.length,
|
|
1977
|
+
offset,
|
|
1978
|
+
processed: users.length,
|
|
1979
|
+
nextOffset,
|
|
1980
|
+
...result
|
|
1981
|
+
});
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1796
1984
|
// src/series.ts
|
|
1797
1985
|
function periodSeries(points, now, count, periodMs) {
|
|
1798
1986
|
const start = now - count * periodMs;
|
|
@@ -2409,6 +2597,7 @@ var BUILTIN_ROUTES = [
|
|
|
2409
2597
|
handleAdminPeopleAccess,
|
|
2410
2598
|
handleAdminPeopleRole,
|
|
2411
2599
|
handleAdminCrmSync,
|
|
2600
|
+
handleAdminClerkPrivateProfileSync,
|
|
2412
2601
|
// Aggregation
|
|
2413
2602
|
handleAdminDashboard,
|
|
2414
2603
|
handleAdminBilling,
|