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

+
+ + ( + + Version Name + + + + + + )} + /> + + {fields.map((field, index) => ( + ( + + Key Point {index + 1} + + + + + + + )} + /> + ))} + + + + + + {/* 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 } + ); + } +}