72 lines · 2.9 KB
import { createServerFn } from "@tanstack/react-start";
import { isTakenDown } from "~/lib/account-disable";
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;
      disabled_at: number | null;
      disabled_reason: string | null;
    }>(
      `SELECT a.id, a.kind, u.show_private_contributions AS show, a.disabled_at, a.disabled_reason
       FROM accounts a LEFT JOIN users u ON u.account_id = a.id WHERE a.name = ?`,
      data.name,
    );
    if (!account || (isTakenDown(account) && !context.viewer?.isAdmin)) throw notFoundError("Account not found");
    if (account.kind !== "user") return null;