import { all, placeholders } from "./db"; import { repoReadableSql } from "./repos"; import { userInbox } from "./user-inbox"; import type { InvolvementQuery } from "./involvement"; import type { Viewer } from "~/lib/types"; export type InvolvedItem = { number: number; title: string; is_pull: number; repo_name: string; owner_name: string }; /** Over-fetch so access checks that drop a few repos still fill the list. */ const OVERFETCH = 2; /** * The viewer's involvement index, resolved against the repository directory: current owner and repo names, * and only repositories the viewer can still read (access may have changed since an entry was published). */ export async function involvedItems(viewer: Viewer, query: InvolvementQuery, limit: number): Promise { const entries = await userInbox(viewer.id).listInvolvement({ ...query, limit: limit * OVERFETCH }); const repoIds = [...new Set(entries.map((entry) => entry.repoId))]; if (repoIds.length === 0) return []; const readable = viewer.isAdmin ? "(?1 = ?1)" : repoReadableSql("r", "?1"); const repos = await all<{ id: string; name: string; owner_name: string }>( `SELECT r.id, r.name, a.name AS owner_name FROM repos r JOIN accounts a ON a.id = r.owner_id WHERE r.id IN (${placeholders(repoIds.length)}) AND r.hidden_at IS NULL AND a.disabled_at IS NULL AND ${readable}`, viewer.id, ...repoIds, ); const byId = new Map(repos.map((repo) => [repo.id, repo])); return entries .flatMap((entry) => { const repo = byId.get(entry.repoId); if (!repo) return []; return [ { number: entry.number, title: entry.title, is_pull: entry.isPull ? 1 : 0, repo_name: repo.name, owner_name: repo.owner_name, }, ]; }) .slice(0, limit); } /** Open items the viewer is assigned to or asked to review: the dashboard's "Assigned to you". */ export function assignedToViewer(viewer: Viewer, limit: number): Promise { return involvedItems(viewer, { roles: ["assignee", "reviewer"], state: "open" }, limit); }