diff --git a/app/(auth)/admin/page.tsx b/app/(auth)/admin/page.tsx
index 7085ad9..81c318f 100644
--- a/app/(auth)/admin/page.tsx
+++ b/app/(auth)/admin/page.tsx
@@ -12,6 +12,7 @@ const AdminPage = () => {
+
>
diff --git a/app/(auth)/admin/vulnerabilities/layout.tsx b/app/(auth)/admin/vulnerabilities/layout.tsx
new file mode 100644
index 0000000..2181c4a
--- /dev/null
+++ b/app/(auth)/admin/vulnerabilities/layout.tsx
@@ -0,0 +1,9 @@
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Admin // Vulnerabilities",
+};
+
+export default function logPages({ children }: { children: React.ReactNode }) {
+ return <>{children}>;
+}
diff --git a/app/(auth)/admin/vulnerabilities/page.tsx b/app/(auth)/admin/vulnerabilities/page.tsx
new file mode 100644
index 0000000..2be8082
--- /dev/null
+++ b/app/(auth)/admin/vulnerabilities/page.tsx
@@ -0,0 +1,217 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import { useForm, SubmitHandler, useFieldArray } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Input } from "@/components/ui/input";
+import { logsSchema } from "@/lib/validations/validation";
+import { z } from "zod";
+import { useToast } from "@/components/ui/use-toast";
+
+interface LogEntry {
+ _id: string;
+ version: string;
+ date: string;
+ bullets: { point: string }[];
+}
+
+type LogsFormValues = z.infer;
+
+const AdminLogPage = () => {
+ const [logs, setLogs] = useState([]);
+ const [error, setError] = useState("");
+ const { toast } = useToast();
+ const [loading, setLoading] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(logsSchema),
+ defaultValues: {
+ version: "",
+ date: "",
+ bullets: [{ point: "" }],
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control: form.control,
+ name: "bullets",
+ });
+
+ const fetchLogs = async () => {
+ try {
+ const response = await fetch("/api/vulnerabilties", { method: "GET" });
+ if (response.ok) {
+ const data: LogEntry[] = await response.json();
+ setLogs(data);
+ } else {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } catch (error: any) {
+ setError(error.message || "Failed to fetch logs");
+ }
+ };
+
+ const onSubmit: SubmitHandler = async (data) => {
+ setLoading(true);
+ const response = await fetch("/api/uploadvulnerabilities", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(data),
+ });
+
+ if (response.ok) {
+ form.reset();
+ fetchLogs();
+ setLoading(false);
+ toast({ description: "Logs successfully added" });
+ } else {
+ setLoading(false);
+ toast({ description: "Upload Failed", variant: "destructive" });
+ }
+ };
+
+ const deleteLog = async (id: string) => {
+ try {
+ const response = await fetch(`/api/delete/logs/${id}`, {
+ method: "DELETE",
+ });
+ if (response.ok) {
+ fetchLogs();
+ } else {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } catch (error: any) {
+ setError(error.message || "Failed to delete log");
+ }
+ };
+
+ useEffect(() => {
+ fetchLogs();
+ const interval = setInterval(() => {
+ fetchLogs();
+ }, 10000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+ Server Vulnerabilties Form
+
+
+
+ {/* Section to list and delete logs */}
+
+
+ Existing Vulnerabilties
+
+ {error && {error}
}
+
+
+
+ Version
+ Actions
+
+
+
+ {logs
+ .slice()
+ .reverse()
+ .map((log) => (
+
+
+ {log.version}
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default AdminLogPage;
diff --git a/app/(root)/downloads/page.tsx b/app/(root)/downloads/page.tsx
index 706eb20..1b10f46 100644
--- a/app/(root)/downloads/page.tsx
+++ b/app/(root)/downloads/page.tsx
@@ -3,103 +3,104 @@
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
- Table,
- TableBody,
- TableCaption,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
} from "@/components/ui/table";
import { Download } from "lucide-react";
import Link from "next/link";
+import { Skeleton } from "@/components/ui/skeleton";
interface Download {
- _id: string;
- date: string;
- fileName: string;
- version: string;
- fileSize: string;
- downloadLink: string;
+ _id: string;
+ date: string;
+ fileName: string;
+ version: string;
+ fileSize: string;
+ downloadLink: string;
}
const DownloadPage: React.FC = () => {
- const [downloads, setDownloads] = useState([]);
- const [error, setError] = useState("");
+ const [downloads, setDownloads] = useState([]);
+ const [error, setError] = useState("");
- const fetchDownloads = async () => {
- try {
- const response = await fetch("/api/downloads", {
- method: "GET",
- });
- if (response.ok) {
- const data: Download[] = await response.json();
- setDownloads(data);
- } else {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- } catch (error: any) {
- setError(error);
- }
- };
+ const fetchDownloads = async () => {
+ try {
+ const response = await fetch("/api/downloads", {
+ method: "GET",
+ });
+ if (response.ok) {
+ const data: Download[] = await response.json();
+ setDownloads(data);
+ } else {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } catch (error: any) {
+ setError(error);
+ }
+ };
- useEffect(() => {
- fetchDownloads();
+ useEffect(() => {
+ fetchDownloads();
- const interval = setInterval(() => {
- fetchDownloads();
- }, 10000);
+ const interval = setInterval(() => {
+ fetchDownloads();
+ }, 10000);
- return () => clearInterval(interval);
- }, []);
+ return () => clearInterval(interval);
+ }, []);
- return (
-
-
- Downloads
-
-
- Get all the latest version of SVRJS download and compiled Files here!
-
- {error && {error}
}
-
- A list of all available downloads.
-
-
- Date
- File Name
- Version
- File Size
- Download Link
-
-
-
- {downloads
- .slice(0, 10)
- .reverse()
- .map((download) => (
-
- {download.date}
- {download.fileName}
- {download.version}
- {download.fileSize}
-
-
-
-
-
-
- ))}
-
-
-
- );
+ return (
+
+
+ Downloads
+
+
+ Get all the latest version of SVRJS download and compiled Files here!
+
+ {error && {error}
}
+
+ A list of all available downloads.
+
+
+ Date
+ File Name
+ Version
+ File Size
+ Download Link
+
+
+
+ {downloads
+ .slice(0, 10)
+ .reverse()
+ .map((download) => (
+
+ {download.date}
+ {download.fileName}
+ {download.version}
+ {download.fileSize}
+
+
+
+
+
+
+ ))}
+
+
+
+ );
};
export default DownloadPage;
diff --git a/app/(root)/vulnerabilities/page.tsx b/app/(root)/vulnerabilities/page.tsx
index fa76ec9..11a83aa 100644
--- a/app/(root)/vulnerabilities/page.tsx
+++ b/app/(root)/vulnerabilities/page.tsx
@@ -1,15 +1,76 @@
+"use client";
+
import ReactMarkdown from "react-markdown";
import { vulnerabilities } from "@/constants/guidelines";
-import { Metadata } from "next";
+import { useEffect, useState } from "react";
+import { Skeleton } from "@/components/ui/skeleton";
-export const metadata: Metadata = {
- title: "Vulnerabilities - SVRJS",
-};
+interface Bullet {
+ point: string;
+}
+
+interface Vulnerabilities {
+ _id: string;
+ date: string;
+ version: string;
+ bullets?: Bullet[]; // Make bullets optional
+}
const Vulnerabilities = () => {
+ const [loading, setLoading] = useState(true);
+ const [downloads, setDownloads] = useState([]);
+ const [error, setError] = useState("");
+
+ const fetchData = async () => {
+ try {
+ const response = await fetch("/api/vulnerabilities", {
+ method: "GET",
+ });
+ if (response.ok) {
+ const data: Vulnerabilities[] = await response.json();
+ setDownloads(data);
+ return (document.title = "Vulnerabilities | SVRJS");
+ } else {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } catch (error: any) {
+ setError(error.message || "Failed to fetch downloads");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+
+ const interval = setInterval(() => {
+ fetchData();
+ }, 10000);
+
+ return () => clearInterval(interval);
+ }, []);
+ const reversedDownloads = [...downloads].reverse();
+
+ // initially loading = true
+ if (loading) {
+ return (
+
+ );
+ }
+
return (
@@ -22,6 +83,21 @@ const Vulnerabilities = () => {
vulnerability-reports[at]svrjs[dot]org. We'll mitigate that
vulnerability if it is possible.
+ {error && {error}
}
+
+ {reversedDownloads.map((download) => (
+
+
{download.version}
+
+ {(download.bullets ?? []).map((bullet, index) => (
+ - {bullet.point}
+ ))}
+
+
+ ))}
{vulnerabilities}
diff --git a/app/api/uploadvulnerabilities/route.ts b/app/api/uploadvulnerabilities/route.ts
new file mode 100644
index 0000000..ef4208f
--- /dev/null
+++ b/app/api/uploadvulnerabilities/route.ts
@@ -0,0 +1,20 @@
+import { NextResponse } from "next/server";
+import clientPromise from "@/lib/db";
+
+// Force the API to use SSR instead of static generation
+export const dynamic = "force-dynamic";
+
+export async function POST(request: Request) {
+ const body = await request.json();
+ const { version, date, bullets } = body;
+
+ const client = await clientPromise;
+ const db = client.db("downloadsDatabase");
+
+ const result = await db.collection("vulnerabilities").insertOne({
+ version,
+ bullets,
+ });
+
+ return NextResponse.json({ success: true, id: result.insertedId });
+}
diff --git a/app/api/vulnerabilities/route.ts b/app/api/vulnerabilities/route.ts
new file mode 100644
index 0000000..6d8419e
--- /dev/null
+++ b/app/api/vulnerabilities/route.ts
@@ -0,0 +1,20 @@
+import { NextRequest, NextResponse } from "next/server";
+import clientPromise from "@/lib/db";
+
+// Force the API to use SSR instead of static generation
+export const dynamic = "force-dynamic";
+
+// Handler for GET requests
+export async function GET(req: NextRequest) {
+ try {
+ const client = await clientPromise;
+ const db = client.db("downloadsDatabase");
+ const downloads = await db.collection("vulnerabilities").find().toArray();
+ return NextResponse.json(downloads, { status: 200 });
+ } catch (error) {
+ return NextResponse.json(
+ { error: "Failed to fetch logs" },
+ { status: 500 }
+ );
+ }
+}