mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
improve feedback feature, remove add-admin feature, fix types of list in gap exercise
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRequest } from "@/shared/hooks/useRequest";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function AddAdmin() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState(null);
|
||||
const [success, setSuccess] = useState(null);
|
||||
const request = useRequest();
|
||||
|
||||
const handleSubmit = async function (e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const res = await request("admin/add-admin", { email });
|
||||
setSuccess(res.data.message || "user added as admin successfully");
|
||||
setError(null); // nollaa virhe jos onnistuu
|
||||
} catch (err) {
|
||||
setError(err.message || "Tuntematon virhe");
|
||||
setSuccess(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="email@example.com"
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<br />
|
||||
<Button type="submit">Submit</Button>
|
||||
|
||||
{error && <p style={{ color: "red", marginTop: "8px" }}>{error}</p>}
|
||||
{success && <p style={{ color: "green", marginTop: "8px" }}>{success}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { POST } from "./route";
|
||||
import { useTestRequest } from "@/backend/test/mock-request";
|
||||
import { useTestDatabase } from "@/backend/test/testdb";
|
||||
import { TestFactory } from "@/backend/test/testfactory";
|
||||
import { checkSession } from "@/backend/auth/session";
|
||||
import { DB } from "@/backend/db";
|
||||
|
||||
vi.mock("@/backend/auth/session", () => ({
|
||||
checkSession: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("GET /api/add-admin", () => {
|
||||
useTestDatabase();
|
||||
|
||||
let adminUser;
|
||||
let normalUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
adminUser = await TestFactory.user({ is_admin: true });
|
||||
normalUser = await TestFactory.user({ is_admin: false });
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const postRequest = async (email, authUser) => {
|
||||
const { mockPost } = useTestRequest();
|
||||
const request = mockPost("/api/add-admin", { email }, authUser);
|
||||
|
||||
checkSession.mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
authUser
|
||||
? {
|
||||
id: authUser.id,
|
||||
email: authUser.email,
|
||||
is_admin: authUser.is_admin,
|
||||
}
|
||||
: null
|
||||
)
|
||||
);
|
||||
|
||||
return POST(request);
|
||||
};
|
||||
|
||||
it("should allow an admin to promote a user to admin", async () => {
|
||||
const response = await postRequest(normalUser.email, adminUser);
|
||||
const result = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe(
|
||||
"Benutzer erfolgreich als administrator hinzugefügt"
|
||||
);
|
||||
|
||||
const dbUser = await DB.pool(`SELECT is_admin FROM users WHERE id = $1`, [
|
||||
normalUser.id,
|
||||
]);
|
||||
expect(dbUser.rows[0].is_admin).toBe(true);
|
||||
});
|
||||
|
||||
it("should return 404 if user with email does not exist", async () => {
|
||||
const response = await postRequest("notfound@example.com", adminUser);
|
||||
const result = await response.json();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(result.error).toBe(
|
||||
"Ein Benutzer mit dieser E-Mail-Adresse existiert nicht."
|
||||
);
|
||||
});
|
||||
|
||||
it("should require authentication", async () => {
|
||||
const response = await postRequest(normalUser.email, null);
|
||||
expect(response.status).toBe(401);
|
||||
expect(checkSession).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw on database errors", async () => {
|
||||
const originalPool = DB.pool;
|
||||
DB.pool = vi.fn().mockRejectedValueOnce(new Error("DB failure"));
|
||||
|
||||
await expect(postRequest(normalUser.email, adminUser)).rejects.toThrow(
|
||||
"DB failure"
|
||||
);
|
||||
|
||||
DB.pool = originalPool;
|
||||
});
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
import { withAuth } from "@/backend/middleware/withAuth";
|
||||
import { DB } from "@/backend/db";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export const POST = withAuth(
|
||||
async (req) => {
|
||||
const body = await req.json();
|
||||
const { email } = body;
|
||||
|
||||
// Check first if a user can be found with the email adress given:
|
||||
|
||||
const userResult = await DB.pool(
|
||||
`
|
||||
SELECT id FROM users
|
||||
WHERE email = $1
|
||||
`,
|
||||
[email]
|
||||
);
|
||||
|
||||
// If not, return an error message
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "Ein Benutzer mit dieser E-Mail-Adresse existiert nicht." },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const userId = userResult.rows[0].id;
|
||||
|
||||
// Below the user is updated to an admin:
|
||||
|
||||
await DB.transaction(async (tx) => {
|
||||
await tx.query(
|
||||
`
|
||||
UPDATE users SET is_admin = true
|
||||
WHERE id = $1
|
||||
`,
|
||||
[userId]
|
||||
);
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Benutzer erfolgreich als administrator hinzugefügt",
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
},
|
||||
{
|
||||
requireAdmin: true,
|
||||
requireAuth: true,
|
||||
}
|
||||
);
|
||||
@@ -33,9 +33,10 @@
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
margin-bottom: var(--u-lg);
|
||||
color: var(--fg1);
|
||||
font-size: 1.9em;
|
||||
}
|
||||
|
||||
.talkback-message-input {
|
||||
@@ -44,6 +45,13 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.talkback-description {
|
||||
color: var(--fg1);
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--u-lg);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -56,7 +64,6 @@
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
/* margin-bottom: var(--u-md); */
|
||||
}
|
||||
|
||||
.form-input {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function ContactPage() {
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
||||
// Show nothing while session is being resolved, or if not logged in.
|
||||
// Middleware already redirects unauthenticated requests to /auth/login.
|
||||
// Middleware already redirects unauthenticated requests to /auth/register.
|
||||
if (!isAuthResolved || !isLoggedIn) return null;
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
@@ -61,18 +61,25 @@ export default function ContactPage() {
|
||||
return (
|
||||
<section className="talkback-container">
|
||||
<div className="talkback-box">
|
||||
<h1 className="auth-title">Kontaktieren Sie unser Team</h1>
|
||||
<h2 className="auth-title talkback-title">
|
||||
Kontaktieren Sie unser Team
|
||||
</h2>
|
||||
<p className="talkback-description">
|
||||
Bitte vergessen Sie Ihre <strong>eigene E-Mail-Adresse</strong> nicht,
|
||||
damit wir Ihnen antworten können.
|
||||
</p>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
|
||||
<form className="talkback-form" onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="contact-message">
|
||||
{/* <label className="form-label" htmlFor="contact-message">
|
||||
Nachricht
|
||||
</label>
|
||||
</label> */}
|
||||
<textarea
|
||||
className="form-input talkback-message-input"
|
||||
id="contact-message"
|
||||
placeholder="Nachricht eingeben..."
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
required
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.rendered-html li>.ql-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Quill editor in dark/light theme */
|
||||
.ql-snow .ql-editor {
|
||||
color: var(--fg1);
|
||||
|
||||
@@ -201,11 +201,6 @@ const adminSidebarLinks = [
|
||||
link: "/admin/users",
|
||||
id: "users",
|
||||
},
|
||||
{
|
||||
title: "Admin hinzufügen",
|
||||
link: "/admin/add-admin",
|
||||
id: "add-admin",
|
||||
},
|
||||
{
|
||||
title: "Neue Seite",
|
||||
link: "/admin/new-page",
|
||||
|
||||
@@ -10,6 +10,9 @@ import { AUTH_COOKIE_NAME } from "@/shared/const";
|
||||
*/
|
||||
const authRequired = ["/grammar/exercises", "/edit_info", "/contact"];
|
||||
|
||||
// Protected paths that should send logged-out users to registration first.
|
||||
const registerFirstPaths = ["/contact"];
|
||||
|
||||
// Restricted paths logged-in users cannot access
|
||||
/**
|
||||
* unauthRequired - array of path prefixes that should not be accessible
|
||||
@@ -45,9 +48,16 @@ export async function middleware(request) {
|
||||
!hasCookie &&
|
||||
authRequired.some((path) => pathnameWithoutBasePath.startsWith(path))
|
||||
) {
|
||||
const loginUrl = new URL(`${basePath}/auth/login`, request.url);
|
||||
loginUrl.searchParams.set("redirect", pathnameWithoutBasePath); // Add the original path as a query parameter
|
||||
return NextResponse.redirect(loginUrl);
|
||||
const shouldRedirectToRegister = registerFirstPaths.some((path) =>
|
||||
pathnameWithoutBasePath.startsWith(path)
|
||||
);
|
||||
const targetPath = shouldRedirectToRegister
|
||||
? "/auth/register"
|
||||
: "/auth/login";
|
||||
|
||||
const authUrl = new URL(`${basePath}${targetPath}`, request.url);
|
||||
authUrl.searchParams.set("redirect", pathnameWithoutBasePath); // Add the original path as a query parameter
|
||||
return NextResponse.redirect(authUrl);
|
||||
}
|
||||
// Otherwise, allow the request to proceed and prevent caching of the response
|
||||
const response = NextResponse.next();
|
||||
|
||||
Reference in New Issue
Block a user