import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireViewer, viewerMiddleware } from "./middleware";
import { first, run } from "~/server/db";
import { limitBrowse, limitWrite } from "~/server/limits";
import { notFoundError } from "~/server/errors";
import { contributionGraph, contributionYears, privateModeFor } from "~/server/contributions";
import { contributionDayDetail } from "~/server/contribution-day";
const MAX_PAGE = 500;
const dayString = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
export const contributionsInput = z.object({
name: z.string().min(1).max(64),
year: z.number().int().min(1970).max(9999).nullable(),
day: dayString.nullable(),
repoPage: z.number().int().min(1).max(MAX_PAGE).default(1),
activityPage: z.number().int().min(1).max(MAX_PAGE).default(1),
});
/** The profile graph, its year list and (when a day is selected) that day's paginated details. */
export const getContributions = createServerFn({ method: "GET" })
.middleware([viewerMiddleware])
.validator(contributionsInput)
.handler(async ({ data, context }) => {
await limitBrowse(context.viewer);
const account = await first<{ id: string; kind: string; show: number | null }>(
`SELECT a.id, a.kind, u.show_private_contributions AS show
FROM accounts a LEFT JOIN users u ON u.account_id = a.id WHERE a.name = ?`,
data.name,
);
if (!account) throw notFoundError("Account not found");
if (account.kind !== "user") return null;
const mode = await privateModeFor(account.id, context.viewer?.id ?? null);
const day = data.day && !Number.isNaN(Date.parse(`${data.day}T00:00:00Z`)) ? data.day : null;
const [days, years, detail] = await Promise.all([
contributionGraph(account.id, mode, data.year),
contributionYears(account.id, mode),
day ? contributionDayDetail(account.id, day, mode, { repos: data.repoPage, activity: data.activityPage }) : null,
]);