diff --git a/src/app/api/report-tag/route.ts b/src/app/api/report-tag/route.ts index 3546942..285e16a 100644 --- a/src/app/api/report-tag/route.ts +++ b/src/app/api/report-tag/route.ts @@ -9,29 +9,38 @@ interface ReportedFieldInput { field: string; value?: string; } + +interface ReportTagRequestBody { + paperId: string; + reportedFields?: ReportedFieldInput[]; + comment?: string; + reporterEmail?: string; + reporterId?: string; +} + const ALLOWED_FIELDS = ["subject", "courseCode", "exam", "slot", "year"]; const ratelimit = new Ratelimit({ redis, - limiter: Ratelimit.slidingWindow(3, "1 h"),//per id - 3 request - per hour + limiter: Ratelimit.slidingWindow(3, "1 h"), //per id - 3 request - per hour analytics: true, }); -function getClientIp(req: Request & { ip?: string}): string { - return req.ip || "127.0.0.1"; +function getClientIp(req: Request & { ip?: string }): string { + return req.ip ?? "127.0.0.1"; } export async function POST(req: Request & { ip?: string }) { try { await connectToDatabase(); - const body = await req.json(); + const body = (await req.json()) as ReportTagRequestBody; const { paperId } = body; if (!paperId) { return NextResponse.json( { error: "paperId is required" }, - { status: 400 } + { status: 400 }, ); } const ip = getClientIp(req); @@ -41,39 +50,42 @@ export async function POST(req: Request & { ip?: string }) { if (!success) { return NextResponse.json( { error: "Rate limit exceeded for reporting." }, - { status: 429 } + { status: 429 }, ); } - const MAX_REPORTS_PER_PAPER = 5; + const MAX_REPORTS_PER_PAPER = 5; const count = await TagReport.countDocuments({ paperId }); if (count >= MAX_REPORTS_PER_PAPER) { return NextResponse.json( { error: "Received many reports; we are currently working on it." }, - { status: 429 } + { status: 429 }, ); } - const reportedFields: ReportedFieldInput[] = Array.isArray(body.reportedFields) - ? body.reportedFields - .map((r:Partial) => ({ - field: typeof r.field === "string" ? r.field.trim() : "", - value: typeof r.value === "string" ? r.value.trim() : undefined, - })) - .filter((r:Partial) => r.field) - : []; + + const reportedFields: ReportedFieldInput[] = Array.isArray( + body.reportedFields, + ) + ? body.reportedFields + .map((r: Partial) => ({ + field: typeof r.field === "string" ? r.field.trim() : "", + value: typeof r.value === "string" ? r.value.trim() : undefined, + })) + .filter((r: Partial) => r.field) + : []; for (const rf of reportedFields) { if (!ALLOWED_FIELDS.includes(rf.field)) { return NextResponse.json( { error: `Invalid field: ${rf.field}` }, - { status: 400 } + { status: 400 }, ); } if (rf.field === "exam" && rf.value) { - if (!exams.some(e => e.toLowerCase() === rf.value?.toLowerCase())) { + if (!exams.some((e) => e.toLowerCase() === rf.value?.toLowerCase())) { return NextResponse.json( { error: `Invalid exam value: ${rf.value}` }, - { status: 400 } + { status: 400 }, ); } } @@ -89,13 +101,13 @@ export async function POST(req: Request & { ip?: string }) { return NextResponse.json( { message: "Report submitted.", report: newReport }, - { status: 201 } + { status: 201 }, ); } catch (err) { console.error(err); return NextResponse.json( { error: "Failed to submit tag report." }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 3c8094f..054b28d 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -15,6 +15,12 @@ import { } from "react-icons/fa6"; import { Bold, Mail } from "lucide-react"; import toast from "react-hot-toast"; + +type SubscribeResponse = { + message?: string; + error?: string; +}; + export default function Footer() { const { theme } = useTheme(); const [isDarkMode, setIsDarkMode] = useState(true); @@ -35,16 +41,22 @@ export default function Footer() { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), - }) - .then(async (res) => { - const data = await res.json(); - if (!res.ok) return Promise.reject(data.error || "Something went wrong."); + }).then(async (res) => { + const data = (await res.json()) as SubscribeResponse; + + if (!res.ok) { + return Promise.reject( + new Error(data.error ?? "Something went wrong."), + ); + } + return data; }), { loading: "Subscribing...", success: "You've Successfully Subscribed!", - error: (err: any) => err, + error: (err: unknown) => + err instanceof Error ? err.message : String(err), }, ); @@ -52,8 +64,8 @@ export default function Footer() { }; return ( -