diff --git a/backend/app/crud/tests.py b/backend/app/crud/tests.py index c84cde2d594ec58ea9bd52acae09da18102abe8c..a3ff16dbc9829058b58fe82730882320202608dd 100644 --- a/backend/app/crud/tests.py +++ b/backend/app/crud/tests.py @@ -90,13 +90,44 @@ def remove_group_from_test_task( def create_group( db: Session, group: schemas.TestTaskGroupCreate ) -> models.TestTaskGroup: - db_group = models.TestTaskGroup(**group.model_dump()) + db_group = models.TestTaskGroup( + **group.model_dump(exclude_unset=True, exclude={"questions"}) + ) + + if group.questions: + db_group.questions = ( + db.query(models.TestTaskQuestion) + .filter(models.TestTaskQuestion.id.in_(group.questions)) + .all() + ) db.add(db_group) db.commit() db.refresh(db_group) return db_group +def update_group( + db: Session, group: schemas.TestTaskGroupCreate, group_id: int +) -> None: + current = db.query(models.TestTaskGroup).filter(models.TestTaskGroup.id == group_id) + current.update( + { + **group.model_dump(exclude_unset=True, exclude={"questions"}), + } + ) + + first = current.first() + + if first: + first.questions = ( + db.query(models.TestTaskQuestion) + .filter(models.TestTaskQuestion.id.in_(group.questions)) + .all() + ) + + db.commit() + + def get_group(db: Session, group_id: int) -> models.TestTaskGroup | None: return ( db.query(models.TestTaskGroup) diff --git a/backend/app/routes/tests.py b/backend/app/routes/tests.py index a8c7c13750dff17402d59517c9a171b76c9ac706..15db5c490b70448dbf1e8eb7a661eea918ce93e7 100644 --- a/backend/app/routes/tests.py +++ b/backend/app/routes/tests.py @@ -49,6 +49,35 @@ def get_groups( return crud.get_groups(db) +@require_admin("You do not have permission to get a group.") +@testRouter.get("/groups/{group_id}", response_model=schemas.TestTaskGroup) +def get_group( + group_id: int, + db: Session = Depends(get_db), +): + group = crud.get_group(db, group_id) + if group is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Group not found" + ) + return group + + +@require_admin("You do not have permission to edit a group.") +@testRouter.put("/groups/{group_id}", status_code=status.HTTP_204_NO_CONTENT) +def update_group( + group_id: int, + group: schemas.TestTaskGroupCreate, + db: Session = Depends(get_db), +): + db_group = crud.get_group(db, group_id) + if db_group is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Group not found" + ) + crud.update_group(db, group, group_id) + + @require_admin("You do not have permission to get all the questions.") @testRouter.get("/questions", response_model=list[schemas.TestTaskQuestion]) def get_questions( diff --git a/backend/app/schemas/tests.py b/backend/app/schemas/tests.py index 41ea6af57544bbb2fcb47bcc086752270c95dfdb..2b92fe1411b1028a6f3a4647851791fec257bccc 100644 --- a/backend/app/schemas/tests.py +++ b/backend/app/schemas/tests.py @@ -65,6 +65,7 @@ class TestTaskGroupCreate(BaseModel): title: str demo: bool = False randomize: bool = True + questions: list[int] = [] class TestTypingCreate(BaseModel): @@ -74,8 +75,11 @@ class TestTypingCreate(BaseModel): duration: int | None = None -class TestTaskGroup(TestTaskGroupCreate): - # id: int +class TestTaskGroup(BaseModel): + id: int | None = None + title: str + demo: bool = False + randomize: bool = True questions: list[TestTaskQuestion] = [] diff --git a/frontend/src/lib/api/tests.ts b/frontend/src/lib/api/tests.ts index 1f4b2e8d1a9b75c21a11f3446f650f756c077e97..6bdd3ef24bc7c055517b93c645fec4518933416b 100644 --- a/frontend/src/lib/api/tests.ts +++ b/frontend/src/lib/api/tests.ts @@ -195,6 +195,13 @@ export async function getTestGroupsAPI(fetch: fetchType): Promise<any> { return groups; } +export async function getTestTaskGroupAPI(fetch: fetchType, id: number): Promise<any> { + const response = await fetch(`/api/tests/groups/${id}`); + if (!response.ok) return null; + const group = await response.json(); + return group; +} + export async function getTestQuestionsAPI(fetch: fetchType): Promise<any> { const response = await fetch(`/api/tests/questions`); if (!response.ok) return null; @@ -240,3 +247,46 @@ export async function updateTestTaskAPI( }); return response.ok; } + +export async function createTestTaskGroupAPI( + fetch: fetchType, + title: string, + demo: boolean, + randomize: boolean, + questions: number[] +): Promise<number | null> { + const response = await fetch(`/api/tests/groups`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title, + demo, + randomize, + questions + }) + }); + if (!response.ok) return null; + const group = await response.json(); + return group.id; +} + +export async function updateTestTaskGroupAPI( + fetch: fetchType, + id: number, + title: string, + demo: boolean, + randomize: boolean, + questions: number[] +): Promise<boolean> { + const response = await fetch(`/api/tests/groups/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title, + demo, + randomize, + questions + }) + }); + return response.ok; +} diff --git a/frontend/src/routes/admin/tests/+page.svelte b/frontend/src/routes/admin/tests/+page.svelte index 117b2364d0765181caecf653b0990867d96174a9..ca7147d00e5ea52bf5939ac62c64764d7d799de5 100644 --- a/frontend/src/routes/admin/tests/+page.svelte +++ b/frontend/src/routes/admin/tests/+page.svelte @@ -31,7 +31,7 @@ {/each} </tbody> </table> -<div class="mt-8 mx-auto w-[64rem] flex justify-between"> +<div class="mt-8 mx-auto w-[64rem] flex justify-between pb-8"> <a class="button" href="/admin/tests/new">{$t('tests.create')}</a> <span> <a class="btn" href="/admin/studies">⎠{$t('tests.backtostudies')}</a> diff --git a/frontend/src/routes/admin/tests/groups/+page.svelte b/frontend/src/routes/admin/tests/groups/+page.svelte index b1b79de37821cf205a2722de992f2c8bb32b6b93..eaf8322fb72635ada4cb2c3b54b031bde116e6ce 100644 --- a/frontend/src/routes/admin/tests/groups/+page.svelte +++ b/frontend/src/routes/admin/tests/groups/+page.svelte @@ -35,7 +35,7 @@ {/each} </tbody> </table> -<div class="mt-8 mx-auto w-[64rem] flex justify-between"> +<div class="mt-8 mx-auto w-[64rem] flex justify-between pb-8"> <a class="button" href="/admin/tests/groups/new">{$t('tests.groups.create')}</a> <span> <a class="btn" href="/admin/tests">⎠{$t('tests.groups.backtotests')}</a> diff --git a/frontend/src/routes/admin/tests/groups/[id]/+page.server.ts b/frontend/src/routes/admin/tests/groups/[id]/+page.server.ts index 5ecbb67f23bc3139a80d8d2a2cfb3eede42a23a7..ec22d98a967220e474347ccd7ad6e0bd71bfb4bb 100644 --- a/frontend/src/routes/admin/tests/groups/[id]/+page.server.ts +++ b/frontend/src/routes/admin/tests/groups/[id]/+page.server.ts @@ -1,5 +1,5 @@ import { redirect, type Actions } from '@sveltejs/kit'; -import { updateTestTypingAPI } from '$lib/api/tests'; +import { updateTestTaskGroupAPI } from '$lib/api/tests'; export const actions: Actions = { default: async ({ request, fetch }) => { @@ -7,9 +7,10 @@ export const actions: Actions = { const idStr = formData.get('id')?.toString(); const title = formData.get('title')?.toString(); - const type = formData.get('type')?.toString(); + const demo = formData.get('demo')?.toString() == 'on'; + const randomize = formData.get('randomize')?.toString() == 'on'; - if (!title || !type || !idStr || (type !== 'typing' && type !== 'task')) { + if (!idStr || !title) { return { message: 'Invalid request: Missing required fields' }; @@ -23,36 +24,19 @@ export const actions: Actions = { }; } - if (type === 'typing') { - const explanation = formData.get('explanation')?.toString(); - const text = formData.get('text')?.toString(); - const repeatStr = formData.get('repeat')?.toString(); - const durationStr = formData.get('duration')?.toString(); + const questions = formData + .getAll('questions[]') + .map((question) => parseInt(question.toString(), 10)) + .filter((question) => !isNaN(question)); - if (!explanation || !text || !repeatStr || !durationStr) { - return { - message: 'Invalid request: Missing required fields' - }; - } + const ok = await updateTestTaskGroupAPI(fetch, id, title, demo, randomize, questions); - const repeat = parseInt(repeatStr, 10); - const duration = parseInt(durationStr, 10); - - if (isNaN(repeat) || isNaN(duration)) { - return { - message: 'Invalid request: Invalid format for numbers' - }; - } - - const ok = await updateTestTypingAPI(fetch, id, title, explanation, text, repeat, duration); - - if (!ok) { - return { - message: 'Invalid request: Failed to update test' - }; - } - - return redirect(303, `/admin/tests`); + if (!ok) { + return { + message: 'Error updating test task group' + }; } + + return redirect(303, `/admin/tests/groups`); } }; diff --git a/frontend/src/routes/admin/tests/groups/[id]/+page.svelte b/frontend/src/routes/admin/tests/groups/[id]/+page.svelte index 97ba7436319082212d3affbc96851cfd510d15bb..f2e0a43c4e4ef8841005d5cb25f11ebe7aec5d37 100644 --- a/frontend/src/routes/admin/tests/groups/[id]/+page.svelte +++ b/frontend/src/routes/admin/tests/groups/[id]/+page.svelte @@ -1,9 +1,9 @@ <script lang="ts"> - import TestForm from '$lib/components/tests/TestForm.svelte'; + import GroupForm from '$lib/components/tests/GroupForm.svelte'; import type { PageData } from './$types'; - const { data }: { data: PageData } = $props(); - const { test, possibleGroups } = data; + const { data, form }: { data: PageData; form: FormData } = $props(); + const { group, possibleQuestions } = data; </script> -<TestForm {test} {possibleGroups} /> +<GroupForm {group} {possibleQuestions} message={form?.message} /> diff --git a/frontend/src/routes/admin/tests/groups/[id]/+page.ts b/frontend/src/routes/admin/tests/groups/[id]/+page.ts index c49f9ecbdebc98feff80f8cb59ee5c82d38e1f90..e33e6f88c12a8126f04455b8f3e0b815ee0b0ac1 100644 --- a/frontend/src/routes/admin/tests/groups/[id]/+page.ts +++ b/frontend/src/routes/admin/tests/groups/[id]/+page.ts @@ -1,20 +1,31 @@ -import { getTestAPI, getTestGroupsAPI } from '$lib/api/tests'; -import { Test } from '$lib/types/tests'; +import { getTestQuestionsAPI, getTestTaskGroupAPI } from '$lib/api/tests'; import TestTaskGroup from '$lib/types/testTaskGroups'; +import { TestTaskQuestion } from '$lib/types/testTaskQuestions'; import { error, type Load } from '@sveltejs/kit'; export const load: Load = async ({ fetch, params }) => { - const id = Number(params.id); + const idStr = params?.id; + + if (!idStr) { + return error(400, 'Invalid ID'); + } + + const id = parseInt(idStr, 10); if (isNaN(id)) { return error(400, 'Invalid ID'); } - const testRaw = await getTestAPI(fetch, id); - const test = Test.parse(testRaw); + const groupRaw = await getTestTaskGroupAPI(fetch, id); + + if (!groupRaw) { + return error(404, 'Group not found'); + } + + const group = TestTaskGroup.parse(groupRaw); - const groupsRaw = await getTestGroupsAPI(fetch); - const groups = TestTaskGroup.parseAll(groupsRaw); + const questionsRaw = await getTestQuestionsAPI(fetch); + const questions = TestTaskQuestion.parseAll(questionsRaw); - return { test, possibleGroups: groups }; + return { group, possibleQuestions: questions }; }; diff --git a/frontend/src/routes/admin/tests/groups/new/+page.server.ts b/frontend/src/routes/admin/tests/groups/new/+page.server.ts index 2fe5e2218cd89c20a8fac9bfa9c8eec46bd4e196..fd54c87ab26d19ce43b697a5ffefc8c4321ffc90 100644 --- a/frontend/src/routes/admin/tests/groups/new/+page.server.ts +++ b/frontend/src/routes/admin/tests/groups/new/+page.server.ts @@ -1,5 +1,5 @@ import { redirect, type Actions } from '@sveltejs/kit'; -import { createTestTypingAPI } from '$lib/api/tests'; +import { createTestTaskGroupAPI } from '$lib/api/tests'; export const actions: Actions = { default: async ({ request, fetch }) => { @@ -9,7 +9,7 @@ export const actions: Actions = { const demo = formData.get('demo')?.toString() == 'on'; const randomize = formData.get('randomize')?.toString() == 'on'; - if (!title || !demo || !randomize) { + if (!title) { return { message: 'Invalid request: Missing required fields' }; @@ -22,6 +22,12 @@ export const actions: Actions = { const id = await createTestTaskGroupAPI(fetch, title, demo, randomize, questions); - return redirect(303, `/admin/tests`); + if (id === null) { + return { + message: 'Error creating test task group' + }; + } + + return redirect(303, `/admin/tests/groups`); } };