42 lines · 2.0 KB
import { backfillAccounts, backfillDone } from "./backfill";
import { all } from "./db";
import { accountDb } from "./scoped-db";
import { locatedByPrefix, registerLocator } from "./scoped-db/locators";

const INVITER_INDEX = "org-invitation-inviters";

function inviterKey(inviterId: string, invitationId: string): string {
  return `${inviterId}:${invitationId}`;
}

/** Indexes an invitation under who sent it, so clearing that user's invitations needn't visit every org. */
export async function registerInviter(inviterId: string, invitationId: string, orgId: string): Promise<void> {
  await registerLocator("org_invitation_by", inviterKey(inviterId, invitationId), orgId);
}

/** Cron step until done: indexes invitations sent before `org_invitation_by` locators existed. */
export function backfillInviters(): Promise<boolean> {
  return backfillAccounts(INVITER_INDEX, async (accountId) => {
    const rows = await accountDb(accountId).all<{ id: string; org_id: string; invited_by: string }>(
      "SELECT id, org_id, invited_by FROM org_invitations WHERE invited_by IS NOT NULL",
    );
    for (const row of rows) await registerInviter(row.invited_by, row.id, row.org_id);
  });
}

/**
 * Organizations that may hold invitations to or from `userId`: those in the invitee's own copies, those it sent
 * from, and `memberOrgIds`. Every org until the inviter index is backfilled.
 */
export async function invitationOrgs(userId: string, memberOrgIds: string[]): Promise<string[]> {
  const [received, sent] = await Promise.all([
    accountDb(userId).all<{ org_id: string }>("SELECT DISTINCT org_id FROM org_invitations WHERE user_id = ?", userId),
    backfillDone(INVITER_INDEX).then((indexed) =>
      indexed
        ? locatedByPrefix("org_invitation_by", `${userId}:`)
        : all<{ id: string }>("SELECT id FROM accounts WHERE kind = 'org'").then((rows) => rows.map((row) => row.id)),
    ),
  ]);
  return [...new Set([...memberOrgIds, ...received.map((row) => row.org_id), ...sent])].filter((id) => id !== userId);