diff --git a/gradesa/src/app/api/admin/exercises/click/route.js b/gradesa/src/app/api/admin/exercises/click/route.js index 7cf5619..e768997 100644 --- a/gradesa/src/app/api/admin/exercises/click/route.js +++ b/gradesa/src/app/api/admin/exercises/click/route.js @@ -1,53 +1,60 @@ import { DB } from "@/backend/db"; +import { withAuth } from "@/backend/middleware/withAuth"; -export async function POST(request) { - const json = await request.json(); - const { title, targetCategory, targetWords, allWords } = json; +export const POST = withAuth( + async (request) => { + const json = await request.json(); + const { title, targetCategory, targetWords, allWords } = json; - if (!title || !targetCategory || !targetWords || !allWords) { - return Response.json( - { error: "Alle Felder sind erforderlich." }, - { status: 400 } + if (!title || !targetCategory || !targetWords || !allWords) { + return Response.json( + { error: "Alle Felder sind erforderlich." }, + { status: 400 } + ); + } + if (title.length < 3 || title.length > 50) { + return Response.json( + { error: "Der Titel muss zwischen 3 und 50 Zeichen lang sein." }, + { status: 422 } + ); + } + if (targetCategory.length < 3 || targetCategory.length > 30) { + return Response.json( + { error: "Die Kategorie muss zwischen 3 und 30 Zeichen lang sein." }, + { status: 422 } + ); + } + if (targetWords.length < 1 || targetWords.length > 1000) { + return Response.json( + { error: "Es müssen zwischen 1 und 1000 Zielwörter vorhanden sein." }, + { status: 422 } + ); + } + if (allWords.length < 1 || allWords.length > 1000) { + return Response.json( + { error: "Es müssen zwischen 1 und 1000 Wörter vorhanden sein." }, + { status: 422 } + ); + } + const existingExercise = await DB.pool( + "SELECT * FROM click_exercises WHERE title = $1", + [title] ); - } - if (title.length < 3 || title.length > 50) { - return Response.json( - { error: "Der Titel muss zwischen 3 und 50 Zeichen lang sein." }, - { status: 422 } - ); - } - if (targetCategory.length < 3 || targetCategory.length > 30) { - return Response.json( - { error: "Die Kategorie muss zwischen 3 und 30 Zeichen lang sein." }, - { status: 422 } - ); - } - if (targetWords.length < 1 || targetWords.length > 1000) { - return Response.json( - { error: "Es müssen zwischen 1 und 1000 Zielwörter vorhanden sein." }, - { status: 422 } - ); - } - if (allWords.length < 1 || allWords.length > 1000) { - return Response.json( - { error: "Es müssen zwischen 1 und 1000 Wörter vorhanden sein." }, - { status: 422 } - ); - } - const existingExercise = await DB.pool( - "SELECT * FROM click_exercises WHERE title = $1", - [title] - ); - if (existingExercise.rows.length > 0) { - return Response.json( - { error: "Eine Übung mit diesem Titel existiert bereits." }, - { status: 409 } - ); - } - const id = await DB.pool( - `INSERT INTO click_exercises (title, category, target_words, all_words) + if (existingExercise.rows.length > 0) { + return Response.json( + { error: "Eine Übung mit diesem Titel existiert bereits." }, + { status: 409 } + ); + } + const id = await DB.pool( + `INSERT INTO click_exercises (title, category, target_words, all_words) VALUES ($1, $2, $3, $4) returning id`, - [title, targetCategory, targetWords, allWords] - ); - return Response.json({ id: id.rows[0].id }, { status: 201 }); -} + [title, targetCategory, targetWords, allWords] + ); + return Response.json({ id: id.rows[0].id }, { status: 201 }); + }, + { + requireAdmin: true, + requireAuth: true, + } +); diff --git a/gradesa/src/app/api/admin/exercises/click/route.test.js b/gradesa/src/app/api/admin/exercises/click/route.test.js index 0739d2b..67e1459 100644 --- a/gradesa/src/app/api/admin/exercises/click/route.test.js +++ b/gradesa/src/app/api/admin/exercises/click/route.test.js @@ -9,8 +9,8 @@ describe("create click_exercises API", () => { useTestDatabase(); it("should create a new click_exercise with valid details", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -34,8 +34,8 @@ describe("create click_exercises API", () => { expect(exercise.rows.length).toBe(1); }); it("should return 400 if required fields are missing", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -51,8 +51,8 @@ describe("create click_exercises API", () => { expect(json.error).toBe("Alle Felder sind erforderlich."); }); it("should return 422 if title is too short", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -71,8 +71,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if title is too long", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -91,8 +91,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if targetCategory is too short", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -111,8 +111,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if targetCategory is too long", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -131,8 +131,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if targetWords is empty", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -151,8 +151,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if targetWords is too long", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -170,8 +170,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if allWords is empty", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -190,8 +190,8 @@ describe("create click_exercises API", () => { ); }); it("should return 422 if allWords is too long", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); const response = await POST( mockPost("api/exercises/create/click", { @@ -210,8 +210,8 @@ describe("create click_exercises API", () => { ); }); it("should return 409 if exercise with the same title already exists", async () => { - const user = await TestFactory.user(); - const { mockPost } = useTestRequest(user); + const admin = await TestFactory.user({ is_admin: true }); + const { mockPost } = useTestRequest(admin); // Create the first exercise await POST( diff --git a/gradesa/src/app/api/admin/exercises/dragdrop/route.js b/gradesa/src/app/api/admin/exercises/dragdrop/route.js index 35a8fe4..5068e9e 100644 --- a/gradesa/src/app/api/admin/exercises/dragdrop/route.js +++ b/gradesa/src/app/api/admin/exercises/dragdrop/route.js @@ -1,84 +1,91 @@ import { DB } from "@/backend/db"; +import { withAuth } from "@/backend/middleware/withAuth"; -export async function POST(request) { - try { - const rawBody = await request.json(); - const body = rawBody.body ?? rawBody; - const title = body.title; - const fields = body.fields; +export const POST = withAuth( + async (request) => { + try { + const rawBody = await request.json(); + const body = rawBody.body ?? rawBody; + const title = body.title; + const fields = body.fields; - if (!fields || !Array.isArray(fields) || fields.length === 0) { - return Response.json({ error: "No fields provided" }, { status: 400 }); - } + if (!fields || !Array.isArray(fields) || fields.length === 0) { + return Response.json({ error: "No fields provided" }, { status: 400 }); + } - const created_by = 1; - const exerciseCategory = "dnd"; + const created_by = 1; + const exerciseCategory = "dnd"; - // 1. Insert into exercises - const exRes = await DB.pool( - `INSERT INTO exercises (created_by, category) + // 1. Insert into exercises + const exRes = await DB.pool( + `INSERT INTO exercises (created_by, category) VALUES ($1, $2) RETURNING id`, - [created_by, exerciseCategory] - ); - const exercise_id = exRes.rows[0].id; + [created_by, exerciseCategory] + ); + const exercise_id = exRes.rows[0].id; - // 2. Insert into dnd_exercises - const dndRes = await DB.pool( - `INSERT INTO dnd_exercises (created_by, exercise_id, title) + // 2. Insert into dnd_exercises + const dndRes = await DB.pool( + `INSERT INTO dnd_exercises (created_by, exercise_id, title) VALUES ($1, $2, $3) RETURNING id`, - [created_by, exercise_id, title] - ); - const dnd_id = dndRes.rows[0].id; - // Process fields - for (const field of fields) { - // Insert category - const catRes = await DB.pool( - `INSERT INTO dnd_categories (category, color) + [created_by, exercise_id, title] + ); + const dnd_id = dndRes.rows[0].id; + // Process fields + for (const field of fields) { + // Insert category + const catRes = await DB.pool( + `INSERT INTO dnd_categories (category, color) VALUES ($1, $2) ON CONFLICT (category, color) DO NOTHING RETURNING id`, - [field.category, field.color] - ); + [field.category, field.color] + ); - const category_id = - catRes.rows[0]?.id || - ( - await DB.pool( - `SELECT id FROM dnd_categories WHERE category = $1 AND color = $2`, - [field.category, field.color] - ) - ).rows[0].id; + const category_id = + catRes.rows[0]?.id || + ( + await DB.pool( + `SELECT id FROM dnd_categories WHERE category = $1 AND color = $2`, + [field.category, field.color] + ) + ).rows[0].id; - // Insert words - const words = field.content.split(",").map((word) => word.trim()); - for (const word of words) { - const wordRes = await DB.pool( - `INSERT INTO draggable_words (word) + // Insert words + const words = field.content.split(",").map((word) => word.trim()); + for (const word of words) { + const wordRes = await DB.pool( + `INSERT INTO draggable_words (word) VALUES ($1) ON CONFLICT (word) DO UPDATE SET word = EXCLUDED.word RETURNING id`, - [word] - ); + [word] + ); - const word_id = wordRes.rows[0]?.id; + const word_id = wordRes.rows[0]?.id; - // Insert mapping - await DB.pool( - `INSERT INTO word_category_mappings (word_id, category_id, exercise_id) + // Insert mapping + await DB.pool( + `INSERT INTO word_category_mappings (word_id, category_id, exercise_id) VALUES ($1, $2, $3)`, - [word_id, category_id, dnd_id] - ); + [word_id, category_id, dnd_id] + ); + } } - } - return Response.json({ success: true, exerciseId: exercise_id }); - } catch (err) { - console.error("Error creating dragdrop exercise:", err); - return Response.json( - { error: "Fehler beim Erstellen der Übung." }, - { status: 500 } - ); + return Response.json({ success: true, exerciseId: exercise_id }); + } catch (err) { + console.error("Error creating dragdrop exercise:", err); + return Response.json( + { error: "Fehler beim Erstellen der Übung." }, + { status: 500 } + ); + } + }, + { + requireAdmin: true, + requireAuth: true, } -} +); diff --git a/gradesa/src/app/api/admin/exercises/multichoice/route.js b/gradesa/src/app/api/admin/exercises/multichoice/route.js index 30a5d46..62afe09 100644 --- a/gradesa/src/app/api/admin/exercises/multichoice/route.js +++ b/gradesa/src/app/api/admin/exercises/multichoice/route.js @@ -1,87 +1,99 @@ import { DB } from "@/backend/db"; +import { withAuth } from "@/backend/middleware/withAuth"; -export async function POST(request) { - try { - const json = await request.json(); - const { title, description, content } = json; +export const POST = withAuth( + async (request) => { + try { + const json = await request.json(); + const { title, description, content } = json; - // Validation - if (!title || !content || !Array.isArray(content) || content.length === 0) { - return Response.json( - { error: "Titel und Inhalt erforderlich." }, - { status: 400 } + // Validation + if ( + !title || + !content || + !Array.isArray(content) || + content.length === 0 + ) { + return Response.json( + { error: "Titel und Inhalt erforderlich." }, + { status: 400 } + ); + } + + // Prevent duplicates + const exists = await DB.pool( + "SELECT id FROM multichoice_exercises WHERE title = $1", + [title] ); - } + if (exists.rows.length > 0) { + return Response.json( + { error: "Eine Übung mit diesem Titel existiert bereits." }, + { status: 409 } + ); + } - // Prevent duplicates - const exists = await DB.pool( - "SELECT id FROM multichoice_exercises WHERE title = $1", - [title] - ); - if (exists.rows.length > 0) { - return Response.json( - { error: "Eine Übung mit diesem Titel existiert bereits." }, - { status: 409 } + // Insert into exercises table + const exResult = await DB.pool( + `INSERT INTO exercises (category) VALUES ('multichoice') RETURNING id` ); - } + const exercise_id = exResult.rows[0].id; - // Insert into exercises table - const exResult = await DB.pool( - `INSERT INTO exercises (category) VALUES ('multichoice') RETURNING id` - ); - const exercise_id = exResult.rows[0].id; - - // Insert into multichoice_exercises metadata table - const mcRes = await DB.pool( - `INSERT INTO multichoice_exercises (exercise_id, title, exercise_description) + // Insert into multichoice_exercises metadata table + const mcRes = await DB.pool( + `INSERT INTO multichoice_exercises (exercise_id, title, exercise_description) VALUES ($1, $2, $3) RETURNING id`, - [exercise_id, title, description ?? null] - ); - const multichoice_id = mcRes.rows[0].id; + [exercise_id, title, description ?? null] + ); + const multichoice_id = mcRes.rows[0].id; - // Insert content rows - let contentOrder = 1; - for (const item of content) { - const contentRes = await DB.pool( - `INSERT INTO multichoice_content + // Insert content rows + let contentOrder = 1; + for (const item of content) { + const contentRes = await DB.pool( + `INSERT INTO multichoice_content (multichoice_exercise_id, content_type, content_value, content_order, correct_answer) VALUES ($1, $2, $3, $4, $5) RETURNING id`, - [ - multichoice_id, - item.type, - item.value ?? "", - contentOrder, - item.type === "multichoice" ? item.correct : null, - ] - ); + [ + multichoice_id, + item.type, + item.value ?? "", + contentOrder, + item.type === "multichoice" ? item.correct : null, + ] + ); - const content_id = contentRes.rows[0].id; + const content_id = contentRes.rows[0].id; - // Insert options if it is a selection field - if (item.type === "multichoice" && item.options?.length > 0) { - for (const option of item.options) { - await DB.pool( - `INSERT INTO multichoice_options (multichoice_content_id, option_value) + // Insert options if it is a selection field + if (item.type === "multichoice" && item.options?.length > 0) { + for (const option of item.options) { + await DB.pool( + `INSERT INTO multichoice_options (multichoice_content_id, option_value) VALUES ($1, $2)`, - [content_id, option] - ); + [content_id, option] + ); + } } + + contentOrder++; } - contentOrder++; + return Response.json( + { success: true, exerciseId: exercise_id }, + { status: 201 } + ); + } catch (err) { + console.error("Error saving multichoice exercise:", err); + return Response.json( + { error: "Fehler beim Speichern der Übung." }, + { status: 500 } + ); } - - return Response.json( - { success: true, exerciseId: exercise_id }, - { status: 201 } - ); - } catch (err) { - console.error("Error saving multichoice exercise:", err); - return Response.json( - { error: "Fehler beim Speichern der Übung." }, - { status: 500 } - ); + }, + { + requireAdmin: true, + requireAuth: true, } -} +);