Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 33 additions & 21 deletions src/app/api/report-tag/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<ReportedFieldInput>) => ({
field: typeof r.field === "string" ? r.field.trim() : "",
value: typeof r.value === "string" ? r.value.trim() : undefined,
}))
.filter((r:Partial<ReportedFieldInput>) => r.field)
: [];

const reportedFields: ReportedFieldInput[] = Array.isArray(
body.reportedFields,
)
? body.reportedFields
.map((r: Partial<ReportedFieldInput>) => ({
field: typeof r.field === "string" ? r.field.trim() : "",
value: typeof r.value === "string" ? r.value.trim() : undefined,
}))
.filter((r: Partial<ReportedFieldInput>) => 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 },
);
}
}
Expand All @@ -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 },
);
}
}
63 changes: 42 additions & 21 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean | null>(true);
Expand All @@ -35,25 +41,31 @@ 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),
},
);

setEmail("");
};

return (
<footer className="w-full overflow-hidden bg-gradient-to-b from-[#F3F5FF] to-[#A599CE] px-6 py-10 pt-16 md:pt-20 lg:pt-36 text-white dark:from-[#070114] dark:to-[#1F0234]">
<div className="mx-auto flex max-w-7xl flex-col lg:flex-row justify-between gap-y-10 text-center lg:text-left mb-16">
<footer className="w-full overflow-hidden bg-gradient-to-b from-[#F3F5FF] to-[#A599CE] px-6 py-10 pt-16 text-white dark:from-[#070114] dark:to-[#1F0234] md:pt-20 lg:pt-36">
<div className="mx-auto mb-16 flex max-w-7xl flex-col justify-between gap-y-10 text-center lg:flex-row lg:text-left">
{/* Branding & Socials */}
<div className="flex w-full flex-col gap-4 lg:w-[30%]">
<h1 className="bg-gradient-to-r from-[#562EE7] to-[rgba(116,128,255,0.8)] bg-clip-text font-jost text-5xl font-bold text-transparent dark:to-[#FFC6E8]">
Expand Down Expand Up @@ -96,33 +108,42 @@ export default function Footer() {
</div>

{/* Events */}
<div className="flex w-full flex-col gap-2 text-black dark:text-white lg:w-[15%]">
<div className="flex w-full flex-col gap-2 text-black dark:text-white lg:w-[15%]">
<h3 className="font-jost text-xl font-semibold">Events</h3>
<Link href="https://devsoc25.codechefvit.com" target="_blank">DevSoc</Link>
<Link href="https://gravitas.codechefvit.com" target="_blank">CookOff</Link>
<Link href="https://gravitas.codechefvit.com" target="_blank">Clueminati</Link>
<Link href="https://devsoc25.codechefvit.com" target="_blank">
DevSoc
</Link>
<Link href="https://gravitas.codechefvit.com" target="_blank">
CookOff
</Link>
<Link href="https://gravitas.codechefvit.com" target="_blank">
Clueminati
</Link>
</div>
{/* Projects */}
<div className="flex w-full flex-col gap-2 text-black dark:text-white lg:w-[20%]">
<div className="flex w-full flex-col gap-2 text-black dark:text-white lg:w-[20%]">
<h3 className="font-jost text-xl font-semibold">Our Projects</h3>
<Link href="https://papers.codechefvit.com" target="_blank">Papers</Link>
<Link href="https://contactify.codechefvit.com" target="_blank">Contactify</Link>
<Link href="https://ffcs.codechefvit.com" target="_blank">FFCS-inator</Link>
<Link href="https://papers.codechefvit.com" target="_blank">
Papers
</Link>
<Link href="https://contactify.codechefvit.com" target="_blank">
Contactify
</Link>
<Link href="https://ffcs.codechefvit.com" target="_blank">
FFCS-inator
</Link>
</div>

{/* Suggestions */}
<div className="flex w-full flex-col gap-1 text-black dark:text-white lg:w-[25%] items-center lg:items-start">
<div className="flex w-full flex-col items-center gap-1 text-black dark:text-white lg:w-[25%] lg:items-start">
<Link
href={`mailto:codechefvit@gmail.com`}
className="flex items-center gap-2 font-jost text-xl font-semibold mb-2"
className="mb-2 flex items-center gap-2 font-jost text-xl font-semibold"
>
<Mail size={20}
fontWeight="Bold"
/>
<Mail size={20} fontWeight="Bold" />
<span>codechefvit@gmail.com</span>
</Link>


<h3 className="my-2 font-jost text-xl font-semibold">
Subscribe For Updates:
</h3>
Expand Down
Loading