import { NextResponse } from "next/server"; import { z } from "zod"; import { categories } from "@/lib/tool-types"; import { parseGithubRepoUrl, verifyRepo } from "@/lib/github"; import { checkRateLimit, getClientIp, rateLimitResponse } from "@/lib/rate-limit"; import { DuplicateRepoError, submitTool } from "@/lib/tools"; const SUBMISSIONS_PER_HOUR = 3; const githubRepoUrl = z .string() .url() .refine((value) => parseGithubRepoUrl(value) !== undefined, "Enter a public GitHub repository URL."); const optionalWebsiteUrl = z .union([z.literal(""), z.string().url()]) .refine((value) => { if (!value) return true; const url = new URL(value); return url.protocol === "https:" && !url.username && !url.password; }, "Website must be a public HTTPS URL."); const submissionSchema = z.object({ name: z.string().trim().min(2).max(80), summary: z.string().trim().min(20).max(300), category: z.enum(categories), repoUrl: githubRepoUrl, websiteUrl: optionalWebsiteUrl, submittedBy: z.string().trim().min(2).max(60), }); export async function POST(request: Request) { const limit = checkRateLimit(`tools:${getClientIp(request)}`, SUBMISSIONS_PER_HOUR, 60 * 60 * 1000); if (!limit.allowed) return rateLimitResponse(limit); let body: unknown; try { body = await request.json(); } catch { return NextResponse.json({ error: "Request body must be valid JSON." }, { status: 400 }); } const parsed = submissionSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: "Please review the submission fields and try again.", fields: parsed.error.flatten().fieldErrors }, { status: 400 }, ); } const repo = parseGithubRepoUrl(parsed.data.repoUrl); if (!repo) { return NextResponse.json({ error: "Enter a public GitHub repository URL." }, { status: 400 }); } // A failed lookup (rate limit, timeout) must not block the submission: it stays // pending and a moderator checks it. Only a definite "not found" is rejected. const verification = await verifyRepo(repo); if (verification.status === "not_found") { return NextResponse.json( { error: "That GitHub repository could not be found. Make sure it exists and is public.", fields: { repoUrl: ["That GitHub repository could not be found. Make sure it exists and is public."] }, }, { status: 422 }, ); } try { await submitTool(parsed.data); return NextResponse.json( { status: "pending", message: "Thanks! Your submission is awaiting review." }, { status: 201 }, ); } catch (error) { if (error instanceof DuplicateRepoError) { return NextResponse.json( { error: error.message, fields: { repoUrl: [error.message] }, }, { status: 409 }, ); } console.error("Tool submission failed", error); return NextResponse.json( { error: "The tool could not be saved. Please try again later." }, { status: 500 }, ); } }