Vectrum
Sign In
46 lines · 2.4 KB · TypeScript
import { appAgentKey, parseAppAgentKey, type AgentRef } from "./actor";
import { db, filter, findById, findFirst } from "./db";
import { orgPushersFilter } from "./orgs";

// The agents people approved (connected apps), as something issues can be assigned to and work
// can be traced back to. See lib/actor.ts for how agents are named.

export type AssignableAgent = AgentRef & { userId: string; username: string };

/**
 * Agents that can take on work in a repository: connected apps of its owner, collaborators and
 * organization members that may write to it. An issue assigned to one shows up in that agent's
 * get_my_tasks.
 */
export async function listAssignableAgents(repo: { id: string; ownerId: string }): Promise<AssignableAgent[]> {
  const orgPushers = await orgPushersFilter("user", repo.ownerId);
  const rows = await (await db()).collection("oauth_grants").getFullList({
    filter: filter(
      `permission = 'write' && refresh_expires_at > {:now} && (all_repos = true || repos ?= {:repoId}) && (user = {:ownerId} || user.collaborators_via_user.repo ?= {:repoId}${orgPushers ? ` || ${orgPushers}` : ""})`,
      { now: Date.now(), repoId: repo.id, ownerId: repo.ownerId },
    ),
    expand: "client,user",
    sort: "created_at",
  });
  const agents = new Map<string, AssignableAgent>();
  for (const r of rows) {
    const key = appAgentKey(r.client, r.user);
    agents.set(key, { key, name: r.expand?.client?.name ?? "Agent", userId: r.user, username: r.expand?.user?.username ?? "" });
  }
  return [...agents.values()].sort((a, b) => a.name.localeCompare(b.name) || a.username.localeCompare(b.username));
}

/** An agent by its key, with the person it acts for, whether or not it's still connected. */
export async function findAgent(key: string): Promise<AssignableAgent | null> {
  const app = parseAppAgentKey(key);
  if (app) {
    const [client, user] = await Promise.all([findById("oauth_clients", app.clientId), findById("users", app.userId)]);
    return user ? { key, name: client?.name ?? "Disconnected agent", userId: user.id, username: user.username } : null;
  }
  const token = /^tok_([a-z0-9]{15})$/.exec(key);
  if (token) {
    const row = await findFirst("api_tokens", filter("id = {:id}", { id: token[1] }), { expand: "user" });
    return row ? { key, name: row.name, userId: row.user, username: row.expand?.user?.username ?? "" } : null;
  }
  return null;
}