mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
create new page functionality
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { Column, Row } from "@/components/ui/layout/container";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import layout from "@/shared/styles/layout.module.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function NewPage() {
|
||||
const [type, setType] = useState("resources");
|
||||
const [title, setTitle] = useState("");
|
||||
const [slug, setSlug] = useState("");
|
||||
const [creationMessage, setCreationMessage] = useState("");
|
||||
const router = useRouter();
|
||||
const submit = async () => {
|
||||
const response = await fetch(`/api/admin/pages/${type}/${slug}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ title }),
|
||||
});
|
||||
if (response.ok) {
|
||||
router.push(`/admin/edit-page/${type}/${slug}`);
|
||||
} else {
|
||||
setTimeout(() => setCreationMessage(""), 1000);
|
||||
setCreationMessage("Slug already in use");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Column className={layout.viewContent}>
|
||||
<h1>New page</h1>
|
||||
{!!creationMessage && <p className="error-message">{creationMessage}</p>}
|
||||
<Row gap="1rem">
|
||||
<label htmlFor="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Row>
|
||||
<Row gap="1rem">
|
||||
<label htmlFor="title">Slug</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
value={slug}
|
||||
onInput={(e) => {
|
||||
setSlug(e.target.value.replace(/[^A-Za-z0-9\-\_\+]/g, ""));
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<label htmlFor="type">Page group</label>
|
||||
<select value={type} onChange={(e) => setType(e.target.value)}>
|
||||
<option value="resources">Learning</option>
|
||||
<option value="grammar">Grammar</option>
|
||||
<option value="communications">Communication</option>
|
||||
</select>
|
||||
</Row>
|
||||
<Button onClick={submit}>Create</Button>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { NextResponse } from "next/server";
|
||||
import { DB } from "@/backend/db";
|
||||
|
||||
// Allowed page_group values stored in the consolidated `html_pages` table.
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications"]);
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications", "grammar"]);
|
||||
|
||||
/*
|
||||
Route behavior (summary):
|
||||
@@ -27,7 +27,6 @@ export const DELETE = withAuth(
|
||||
async (req, { params }) => {
|
||||
const { type, slug } = await params;
|
||||
|
||||
// const query = `DELETE FROM html_pages WHERE slug = $1 AND page_group = $2 RETURNING *`;
|
||||
const query = `DELETE FROM html_pages WHERE slug = $1 AND page_group = $2 RETURNING *`;
|
||||
const result = await DB.pool(query, [slug, type]);
|
||||
|
||||
@@ -62,7 +61,7 @@ export const PUT = withAuth(
|
||||
// Make sure that the slug only contains characters that do not need escaping
|
||||
// in the url. This ensures that the slug the user types can be used as-is
|
||||
// in the browser address bar.
|
||||
if (data.slug) newData.slug = data.slug.replace(/[^A-Za-z0-9\-\_\+]/g, "");
|
||||
if (data.slug) newData.slug = sanitizeSlug(data.slug);
|
||||
|
||||
if (newData.slug !== slug && (await slugIsInUse(type, newData.slug))) {
|
||||
return new NextResponse("Page slug already in use", {
|
||||
@@ -83,6 +82,33 @@ export const PUT = withAuth(
|
||||
}
|
||||
);
|
||||
|
||||
export const POST = withAuth(
|
||||
async (req, { params }) => {
|
||||
const { type, slug } = await params;
|
||||
if (
|
||||
slug !== sanitizeSlug(slug) ||
|
||||
(await slugIsInUse(type, slug)) ||
|
||||
!VALID_PAGE_GROUPS.has(type)
|
||||
) {
|
||||
return new NextResponse("Invalid slug", {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const title = (await req.json()).title;
|
||||
const query = `INSERT INTO html_pages (title, content, slug, page_group) VALUES ($1, $2, $3, $4)`;
|
||||
const result = await DB.pool(query, [title, "", slug, type]);
|
||||
|
||||
if (result.rowCount === 0)
|
||||
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
|
||||
return NextResponse.json({ message: "Created" }, { status: 200 });
|
||||
},
|
||||
{
|
||||
requireAdmin: true,
|
||||
requireAuth: true,
|
||||
}
|
||||
);
|
||||
|
||||
function sanitize(data) {
|
||||
const window = new JSDOM("").window;
|
||||
const purify = DOMPurify(window);
|
||||
@@ -99,3 +125,7 @@ async function slugIsInUse(type, slug) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeSlug(slug) {
|
||||
return slug.replace(/[^A-Za-z0-9\-\_\+]/g, "");
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import DOMPurify from "dompurify";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// Allowed page_group values stored in the consolidated `html_pages` table.
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications"]);
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications", "grammar"]);
|
||||
|
||||
/*
|
||||
Route behavior (summary):
|
||||
|
||||
@@ -172,6 +172,11 @@ const adminSidebarLinks = [
|
||||
link: "/admin/add-admin",
|
||||
id: "add-admin",
|
||||
},
|
||||
{
|
||||
title: "New page",
|
||||
link: "/admin/new-page",
|
||||
id: "new-page",
|
||||
},
|
||||
];
|
||||
|
||||
function AdminSideBar() {
|
||||
|
||||
Reference in New Issue
Block a user