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<InvolvedItem[]> {
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,
},
];