65 lines · 2.6 KB
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,
    ]);