60 lines · 2.1 KB
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireViewer, viewerMiddleware } from "./middleware";
import { enforceRateLimit, limitWrite, requestIp } from "~/server/limits";
import { actOnAbuseReport, listAbuseReports, recordAbuseReport } from "~/server/abuse";
import { requireSiteAdmin } from "~/server/metering/access";
import { ABUSE_ACTIONS, ABUSE_CATEGORIES } from "~/lib/abuse";

const status = z.enum(["open", "actioned", "dismissed"]);

export const submitAbuseReport = createServerFn({ method: "POST" })
  .middleware([viewerMiddleware])
  .validator(
    z.object({
      url: z.url({ protocol: /^https?$/ }).max(2000),
      category: z.enum(ABUSE_CATEGORIES),
      details: z.string().max(4000),
      email: z.union([z.literal(""), z.email().max(320)]),
    }),
  )
  .handler(async ({ data, context }) => {
    const ip = requestIp();
    await enforceRateLimit("REPORT_LIMITER", `abuse-report:${ip}`);
    await recordAbuseReport({
      source: "web",
      targetUrl: data.url,
      category: data.category,
      details: data.details,
      reporterEmail: data.email || null,
      reporterKey: context.viewer ? `user:${context.viewer.id}` : `ip:${ip}`,
      reporterKind: context.viewer ? "user" : "ip",
    });
    return { ok: true };
  });

export const getAbuseQueue = createServerFn({ method: "GET" })
  .middleware([viewerMiddleware])
  .validator(z.object({ status }))
  .handler(async ({ data, context }) => {
    requireSiteAdmin(requireViewer(context.viewer));