feat: make changelog, mod changelog, downloads, mods and vulnerability pages server-side rendered

This commit is contained in:
Dorian Niemiec 2024-09-07 20:56:08 +02:00
parent 7c47e05a3a
commit 81950a1fc4
17 changed files with 215 additions and 301 deletions

View file

@ -0,0 +1,66 @@
import { Metadata } from "next";
import clientPromise from "@/lib/db";
interface Page {
title: string;
content: string;
}
// baseURL [ENV]
export async function generateMetadata({
params
}: {
params: { slug: "string" };
}) {
let page: Page = {
title: "unknown mod",
content: "unknown mod"
};
let notFound = false;
try {
const client = await clientPromise;
const db = client.db();
const fetchedPage = (await db
.collection("pages")
.findOne({ slug: params.slug })) as unknown as Page;
if (fetchedPage) {
page = fetchedPage;
} else {
notFound = true;
}
} catch (err) {}
return {
title: `${page.title} change log - SVR.JS`,
description: `Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`,
openGraph: {
title: `${page.title} change log - SVR.JS`,
description: `Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`,
url: `https://svrjs.org/changelog/${params.slug}`,
type: "website",
images: [
{
url: "https://svrjs.vercel.app/metadata/svrjs-cover.png",
width: 800,
height: 600,
alt: `${page.title} change log - SVR.JS`
}
]
},
twitter: {
card: "summary_large_image",
site: "@SVR_JS",
title: `${page.title} change log - SVR.JS`,
description: `Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`,
images: ["https://svrjs.vercel.app/metadata/svrjs-cover.png"],
creator: "@SVR_JS"
}
};
}
const ContactLayout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default ContactLayout;

View file

@ -1,74 +1,38 @@
"use client";
import { Skeleton } from "@/components/ui/skeleton";
import React, { useEffect, useState } from "react";
import React from "react";
import ReactMarkdown from "react-markdown";
import Head from "next/head";
import clientPromise from "@/lib/db";
import { notFound } from "next/navigation";
const Page = ({ params }: { params: { slug: string } }) => {
interface Page {
title: string;
content: string;
}
export const dynamic = "force-static";
const Page = async ({ params }: { params: { slug: string } }) => {
const { slug } = params;
const [page, setPage] = useState<{ title: string; content: string } | null>(
null
);
const [loading, setLoading] = useState(true);
const [notFound, setNotFound] = useState(false);
let page: Page | null = null;
let isNotFound = false;
useEffect(() => {
const fetchPage = async () => {
try {
const response = await fetch(`/api/mdx/pages/${slug}`);
if (response.ok) {
const data = await response.json();
setPage(data);
return (document.title = `${data.title} Change Log - SVR.JS`);
} else {
if (response.status === 404) {
setNotFound(true);
return (document.title = "404 Not Found");
}
}
} catch (error) {
console.error("Failed to load page", error);
setNotFound(true);
} finally {
setLoading(false);
}
};
try {
const client = await clientPromise;
const db = client.db();
fetchPage();
}, [slug]);
const fetchedPage = (await db
.collection("pages")
.findOne({ slug })) as unknown as Page;
if (fetchedPage) {
page = fetchedPage;
} else {
isNotFound = true;
}
} catch (err) {}
if (loading) {
return (
<>
<head>
<title>Mods Change Logs - SVR.JS</title>
</head>
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="mb-3">
<Skeleton className="w-[400px] h-[50px] rounded-md" />
</div>
<div className="flex flex-col gap-4">
<Skeleton className="w-[300px] h-[30px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
</div>
</section>
</>
);
}
if (notFound) {
return (
<section id="404error" className="flex-center flex-col wrapper container">
<h1 className="text-3xl md:text-5xl text-center">
<span className="text-red-500">404</span> Page not Found
</h1>
<p className="text-lg mt-3 text-muted-foreground">
Please return back to Home
</p>
</section>
);
if (isNotFound) {
notFound();
}
if (!page) {
@ -77,47 +41,6 @@ const Page = ({ params }: { params: { slug: string } }) => {
return (
<>
<head>
<title>{page.title} Changelog - SVR.JS</title>
<meta
name="description"
content={`Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`}
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
property="og:title"
content={`${page.title} Changelog - SVR.JS`}
/>
<meta
property="og:description"
content={`Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`}
/>
<meta property="og:type" content="website" />
<meta
property="og:url"
content={`https://svrjs.org/changelog/${slug}`}
/>
<meta
property="og:image"
content="https://svrjs.vercel.app/metadata/svrjs-cover.png"
/>
<title>Documentation - SVR.JS</title>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content={`${page.title} Changelog - SVR.JS`}
/>
<meta
name="twitter:description"
content={`Keep track of the latest updates and improvements for ${page.title} with our comprehensive change log. Discover new features, bug fixes, and enhancements for each release of this SVR.JS mod.`}
/>
<meta
name="twitter:image"
content="https://svrjs.vercel.app/metadata/svrjs-cover.png"
/>
</head>
<section className="wrapper container py-24 md:py-28 gap-2 flex flex-col">
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
{page.title} Change Log
@ -130,4 +53,17 @@ const Page = ({ params }: { params: { slug: string } }) => {
);
};
export async function generateStaticParams() {
try {
const client = await clientPromise;
const db = client.db();
const slugs = await db.collection("pages").find().toArray();
return slugs.map((element) => {
return { slug: element.slug };
});
} catch (err) {
return [];
}
}
export default Page;

View file

@ -1,12 +1,10 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Download } from "lucide-react";
import Link from "next/link";
import ReactMarkdown from "react-markdown";
import { CHANGE_LOGS } from "@/constants/guidelines";
import { Skeleton } from "@/components/ui/skeleton";
import clientPromise from "@/lib/db";
interface Bullet {
point: string;
@ -19,61 +17,25 @@ interface LOGS {
bullets?: Bullet[]; // Make bullets optional
}
const LogsPage: React.FC = () => {
const [downloads, setDownloads] = useState<LOGS[]>([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
export const dynamic = "force-static";
const fetchDownloads = async () => {
try {
const response = await fetch("/api/logs", {
method: "GET"
});
if (response.ok) {
const data: LOGS[] = await response.json();
setDownloads(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch downloads");
} finally {
setLoading(false);
}
};
const LogsPage: React.FC = async () => {
let error: Error | null = null;
let downloads: LOGS[] = [];
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
const reversedDownloads = [...downloads].reverse();
if (loading) {
return (
<>
<head>
<title>SVR.JS change log - SVR.JS</title>
</head>
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="mb-3">
<Skeleton className="w-[400px] h-[50px] rounded-md" />
</div>
<div className="flex flex-col gap-4">
<Skeleton className="w-[300px] h-[30px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
</div>
</section>
</>
);
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
downloads = (await db
.collection("logs")
.find()
.toArray()) as unknown as LOGS[];
} catch (err) {
error = err as Error;
}
const reversedDownloads = [...downloads].reverse();
return (
<section
id="logs"
@ -85,7 +47,7 @@ const LogsPage: React.FC = () => {
<p className="md:text-lg text-muted-foreground text-start mb-6">
See the changes done to SVR.JS web server.
</p>
{error && <p className="text-red-500">{error}</p>}
{error && <p className="text-red-500">{error.message}</p>}
{reversedDownloads.map((download) => (
<div

View file

@ -1,6 +1,3 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
@ -13,7 +10,7 @@ import {
} from "@/components/ui/table";
import { Download } from "lucide-react";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";
import clientPromise from "@/lib/db";
interface Download {
_id: string;
@ -24,35 +21,22 @@ interface Download {
downloadLink?: string; // Optional
}
const DownloadPage: React.FC = () => {
const [downloads, setDownloads] = useState<Download[]>([]);
const [error, setError] = useState("");
export const dynamic = "force-static";
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.message);
}
};
const DownloadPage: React.FC = async () => {
let error: Error | null = null;
let downloads: Download[] = [];
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
downloads = (await db
.collection("downloads")
.find()
.toArray()) as unknown as Download[];
} catch (err) {
error = err as Error;
}
return (
<section
@ -73,7 +57,7 @@ const DownloadPage: React.FC = () => {
</Link>
.
</p>
{error && <p className="text-red-500">{error}</p>}
{error && <p className="text-red-500">{error.message}</p>}
<Table>
<TableCaption>A list of all available downloads.</TableCaption>
<TableHeader>

View file

@ -1,6 +1,3 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
@ -13,6 +10,7 @@ import {
} from "@/components/ui/table";
import { Download } from "lucide-react";
import Link from "next/link";
import clientPromise from "@/lib/db";
interface Mods {
_id: string;
@ -23,35 +21,22 @@ interface Mods {
downloadLink: string;
}
const ModsPage: React.FC = () => {
const [downloads, setDownloads] = useState<Mods[]>([]);
const [error, setError] = useState("");
export const dynamic = "force-static";
const fetchDownloads = async () => {
try {
const response = await fetch("/api/mods", {
method: "GET"
});
if (response.ok) {
const data: Mods[] = await response.json();
setDownloads(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error);
}
};
const ModsPage: React.FC = async () => {
let error: Error | null = null;
let downloads: Mods[] = [];
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
downloads = (await db
.collection("mods")
.find()
.toArray()) as unknown as Mods[];
} catch (err) {
error = err as Error;
}
return (
<section
@ -78,7 +63,7 @@ const ModsPage: React.FC = () => {
</Link>
.
</p>
{error && <p className="text-red-500">{error}</p>}
{error && <p className="text-red-500">{error.message}</p>}
<Table>
<TableCaption>A list of all available downloads.</TableCaption>
<TableHeader>

View file

@ -1,9 +1,8 @@
"use client";
import ReactMarkdown from "react-markdown";
import { VULNERABILITY } from "@/constants/guidelines";
import { useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import clientPromise from "@/lib/db";
interface Bullet {
point: string;
@ -23,89 +22,39 @@ interface ModsVulnerability {
vulnerabilities: string;
}
const Vulnerabilities = () => {
const [loading, setLoading] = useState(true);
const [downloads, setDownloads] = useState<Vulnerabilities[]>([]);
const [mods, setMods] = useState<ModsVulnerability[]>([]);
const [error, setError] = useState("");
const Vulnerabilities = async () => {
let downloads: Vulnerabilities[] = [];
let mods: ModsVulnerability[] = [];
let error: Error | null = null;
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 - SVR.JS");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch downloads");
} finally {
setLoading(false);
}
};
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
downloads = (await db
.collection("vulnerabilities")
.find()
.toArray()) as unknown as Vulnerabilities[];
} catch (err) {
error = err as Error;
}
const fetchMods = async () => {
try {
const response = await fetch(`/api/mdx/pages`, {
method: "GET"
});
if (response.ok) {
const data: ModsVulnerability[] = await response.json();
// Filter out entries where vulnerabilities is undefined or an empty string
const filteredMods = data.filter(
(mod) => mod.vulnerabilities && mod.vulnerabilities.trim() !== ""
);
setMods(filteredMods);
return (document.title = "Vulnerabilities - SVR.JS");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch vulnerabilities");
} finally {
setLoading(false);
}
};
try {
const client = await clientPromise;
const db = client.db();
useEffect(() => {
fetchData();
fetchMods();
const interval = setInterval(() => {
fetchData();
fetchMods();
}, 10000);
return () => clearInterval(interval);
}, []);
const pages = (await db
.collection("pages")
.find()
.toArray()) as unknown as ModsVulnerability[];
mods = pages.filter(
(mod: ModsVulnerability) =>
mod.vulnerabilities && mod.vulnerabilities.trim() !== ""
);
} catch (err) {}
const reversedDownloads = [...downloads].reverse();
const reversedMods = [...mods].reverse();
if (loading) {
return (
<>
<head>
<title>Vulnerabilities - SVR.JS</title>
</head>
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="mb-3">
<Skeleton className="w-[400px] h-[50px] rounded-md" />
</div>
<div className="flex flex-col gap-4">
<Skeleton className="w-[300px] h-[30px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
</div>
</section>
</>
);
}
return (
<section
id="vulnerabilities"
@ -121,7 +70,7 @@ const Vulnerabilities = () => {
vulnerability-reports@svrjs.org. We&apos;ll mitigate that vulnerability
if it is possible.
</p>
{error && <p className="text-red-500">{error}</p>}
{error && <p className="text-red-500">{error.message}</p>}
{reversedDownloads.map((download) => (
<div
@ -161,4 +110,6 @@ const Vulnerabilities = () => {
);
};
export const dynamic = "force-static";
export default Vulnerabilities;

View file

@ -2,6 +2,7 @@
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function DELETE(
request: Request,
@ -17,6 +18,7 @@ export async function DELETE(
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
revalidatePath("/downloads");
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });

View file

@ -2,6 +2,7 @@
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function DELETE(
request: Request,
@ -17,6 +18,7 @@ export async function DELETE(
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
revalidatePath("/changelog");
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });

View file

@ -2,6 +2,7 @@
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function DELETE(
request: Request,
@ -17,6 +18,7 @@ export async function DELETE(
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
revalidatePath("/mods");
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });

View file

@ -1,6 +1,7 @@
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function DELETE(
request: Request,
@ -19,6 +20,7 @@ export async function DELETE(
.collection("vulnerabilities")
.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
revalidatePath("/vulnerabilities");
return NextResponse.json(
{ message: "Vulnerability deleted successfully" },
{ status: 200 }

View file

@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
export const GET = async (
req: NextRequest,
@ -59,6 +60,8 @@ export const PUT = async (
...result.value,
_id: result.value._id.toString() // Convert ObjectId to string
};
revalidatePath(`/changelog/${slug}`);
revalidatePath("/vulnerabilities");
return NextResponse.json(serializedResult, { status: 200 });
} else {
return NextResponse.json({ message: "Page not found" }, { status: 404 });
@ -88,6 +91,8 @@ export const DELETE = async (
const result = await db.collection("pages").deleteOne({ slug });
if (result.deletedCount > 0) {
revalidatePath(`/changelog/${slug}`);
revalidatePath("/vulnerabilities");
return NextResponse.json(
{ message: "Page deleted successfully" },
{ status: 200 }

View file

@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
export const GET = async (req: NextRequest) => {
const client = await clientPromise;
@ -32,6 +33,8 @@ export const POST = async (req: NextRequest) => {
try {
const newPage = { title, slug, content };
const result = await db.collection("pages").insertOne(newPage);
revalidatePath(`/changelog/${slug}`);
revalidatePath("/vulnerabilities");
return NextResponse.json(newPage, { status: 201 });
} catch (error) {
console.error("Error creating page:", error);

View file

@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { revalidatePath } from "next/cache";
export const dynamic = "force-dynamic";
@ -29,6 +30,7 @@ export async function PUT(
);
if (result.modifiedCount > 0) {
revalidatePath("/mods");
return NextResponse.json({ success: true });
} else {
return NextResponse.json({

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
@ -19,5 +20,7 @@ export async function POST(request: Request) {
fileSize
});
revalidatePath("/downloads");
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
@ -17,5 +18,7 @@ export async function POST(request: Request) {
bullets
});
revalidatePath("/changelog");
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
@ -19,5 +20,7 @@ export async function POST(request: Request) {
fileSize
});
revalidatePath("/mods");
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
import { revalidatePath } from "next/cache";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
@ -16,5 +17,7 @@ export async function POST(request: Request) {
bullets
});
revalidatePath("/vulnerabilities");
return NextResponse.json({ success: true, id: result.insertedId });
}