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, limitAccountWrite } 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; 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, ]); return { days, years, year: data.year, detail, isSelf: mode === "full", showPrivateToVisitors: account.show === 1, }; }); /** Lets a user show their private contributions to visitors as anonymous counts, or hide them. */ export const setContributionPrivacy = createServerFn({ method: "POST" }) .middleware([viewerMiddleware]) .validator(z.object({ showPrivate: z.boolean() })) .handler(async ({ data, context }) => { const viewer = requireViewer(context.viewer); await limitAccountWrite(viewer); await run( "UPDATE users SET show_private_contributions = ? WHERE account_id = ?", data.showPrivate ? 1 : 0, viewer.id, ); return { ok: true }; });