File size: 1,980 Bytes
f64a3bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import { NextResponse } from "next/server";
import { z } from "zod";
import { categories } from "@/lib/tool-types";
import { submitTool } from "@/lib/tools";
const githubRepoUrl = z
.string()
.url()
.refine((value) => {
try {
const url = new URL(value);
return (
url.protocol === "https:" &&
url.hostname === "github.com" &&
!url.username &&
!url.password &&
!url.search &&
!url.hash &&
/^\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?\/?$/.test(url.pathname)
);
} catch {
return false;
}
}, "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) {
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 },
);
}
try {
const tool = await submitTool(parsed.data);
return NextResponse.json({ tool }, { status: 201 });
} catch (error) {
console.error("Tool submission failed", error);
return NextResponse.json(
{ error: "The tool could not be saved. Please try again later." },
{ status: 500 },
);
}
}
|