Skip to content

Commit

Permalink
♻️ Use new backend routes
Browse files Browse the repository at this point in the history
  • Loading branch information
Malte2036 committed Dec 4, 2024
1 parent f1280f7 commit 4bdae46
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 76 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"test": "vitest"
},
"dependencies": {
"@malte2036/thw-tools-components": "^0.2.34"
"@malte2036/thw-tools-components": "^0.2.34",
"zod": "^3.23.8"
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.2.2",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 31 additions & 35 deletions src/lib/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import { PUBLIC_API_URL } from '$env/static/public';
import type { IQuestion, QuestionType } from '$lib/model/question';

export type QuestionsStatsCount = {
questionType: QuestionType;
right: number;
wrong: number;
};

export async function getQuestionStatsCountForType(
questionType: QuestionType,
questionId?: number
): Promise<QuestionsStatsCount> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/${questionType}/stats/count/${questionId ?? ''}`);
import {
questionSchema,
questionsStatsCountSchema,
type Question,
type QuestionsStatsCount,
type QuestionType
} from '$lib/model/question';
import { z } from 'zod';

export async function getQuestionStatsCount(questionId: number): Promise<QuestionsStatsCount> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/stats/count/${questionId}`);

if (!res.ok) {
throw new Error('Failed to fetch question stats count');
}

return await res.json();
const json = await res.json();
return questionsStatsCountSchema.parse(json);
}

export async function addQuestionStatsCountForType(
questionType: QuestionType,
questionId: number,
correct: boolean
): Promise<void> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/${questionType}/stats/count/${questionId}`, {
export async function addQuestionStatsCount(questionId: number, correct: boolean): Promise<void> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/stats/count/${questionId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
Expand All @@ -40,42 +35,42 @@ export async function addQuestionStatsCountForType(
}
}

function mapResponseQuestion(question: any): IQuestion {
// fix strange answers format
const answers: IQuestion['answers'] = new Map();
for (const [key, value] of Object.entries(question.answers)) {
answers.set(parseInt(key), value as string);
export async function getQuestionStatsCountForType(
questionType: QuestionType
): Promise<QuestionsStatsCount> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/${questionType}/stats/count`);

if (!res.ok) {
throw new Error('Failed to fetch question stats count for type');
}

return {
...question,
answers,
correctIndices: question.correctIndices.map((i: any) => parseInt(i))
} satisfies IQuestion;
const json = await res.json();
return questionsStatsCountSchema.parse(json);
}

export async function getQuestion(
questionType: QuestionType,
questionId: number
): Promise<IQuestion> {
): Promise<Question> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/${questionType}/${questionId}`);

if (!res.ok) {
throw new Error('Failed to fetch question');
}

return mapResponseQuestion(await res.json());
const json = await res.json();
return questionSchema.parse(json);
}

export async function getQuestions(questionType: QuestionType): Promise<IQuestion[]> {
export async function getQuestions(questionType: QuestionType): Promise<Question[]> {
const res = await fetch(`${PUBLIC_API_URL}/quiz/${questionType}`);

if (!res.ok) {
throw new Error('Failed to fetch questions');
}

const json = await res.json();
return json.map(mapResponseQuestion);
return json.map((question: Question) => questionSchema.parse(question));
}

export async function getQuestionCount(questionType: QuestionType): Promise<number> {
Expand All @@ -85,5 +80,6 @@ export async function getQuestionCount(questionType: QuestionType): Promise<numb
throw new Error('Failed to fetch question count');
}

return await res.json();
const json = await res.json();
return z.number().parse(json);
}
39 changes: 29 additions & 10 deletions src/lib/model/question.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
import { z } from 'zod';

export enum QuestionType {
GA = 'ga',
AGT = 'agt',
CBRN = 'cbrn',
RADIO = 'radio'
}

export type IQuestion = {
type: QuestionType;
number: number;
text: string;
image: string;
answers: Map<number, string>;
correctIndices: number[];
};
const questionAnswerSchema = z.object({
id: z.number(),
text: z.string(),
isCorrect: z.boolean()
});

export type QuestionAnswer = z.infer<typeof questionAnswerSchema>;

export const questionSchema = z.object({
id: z.number(),
type: z.nativeEnum(QuestionType),
number: z.number(),
text: z.string(),
image: z.string().optional().nullable(),
answers: z.array(questionAnswerSchema)
});

export type ExtendedQuestion = IQuestion & {
checkedIndices: number[];
export type Question = z.infer<typeof questionSchema>;

export type ExtendedQuestion = Question & {
checkedAnswers: number[];
};

export const questionsStatsCountSchema = z.object({
right: z.number(),
wrong: z.number()
});

export type QuestionsStatsCount = z.infer<typeof questionsStatsCountSchema>;
9 changes: 4 additions & 5 deletions src/lib/quiz/AnswerButton.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<script lang="ts">
import { addQuestionStatsCountForType } from '$lib/api/api';
import { addQuestionStatsCount } from '$lib/api/api';
import Button from '$lib/Button.svelte';
import type { ExtendedQuestion, QuestionType } from '$lib/model/question';
import type { AnsweredCountData } from '../../routes/(main)/quiz/[type]/[questionId]/+page.server';
export let questionType: QuestionType;
export let question: ExtendedQuestion;
export let revealAnswers: boolean;
export let completelyRight: boolean;
Expand Down Expand Up @@ -33,11 +32,11 @@
}
}
}
addQuestionStatsCountForType(questionType, question.number, completelyRight);
addQuestionStatsCount(question.id, completelyRight);
}
}}
dataUmamiEvent={`${questionType} quiz question ${revealAnswers ? 'next question' : 'answered'}`}
disabled={!revealAnswers && question.checkedIndices.length == 0}
dataUmamiEvent={`${question.type} quiz question ${revealAnswers ? 'next question' : 'answered'}`}
disabled={!revealAnswers && question.checkedAnswers.length == 0}
>{revealAnswers
? `${completelyRight ? 'Richtig' : 'Falsch'} - Nächste Frage`
: 'Überprüfen'}</Button
Expand Down
12 changes: 7 additions & 5 deletions src/lib/quiz/answer/CheckboxAnswer.svelte
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
<script lang="ts">
export let answer: string;
import type { QuestionAnswer } from '$lib/model/question';
export let answer: QuestionAnswer;
export let checked: boolean;
export let correct: boolean;
export let revealAnswers: boolean;
export let changeCheckedCallback: (value: boolean) => void;
</script>

<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="text-xl flex flex-row items-center p-2 gap-2 bg-thw-50 border shadow-sm rounded-2xl transition-colors hover:cursor-pointer"
class:checked
class:revealAnswerCorrect={revealAnswers && correct}
class:revealAnswerWrong={revealAnswers && checked != correct}
class:revealAnswerCorrect={revealAnswers && answer.isCorrect}
class:revealAnswerWrong={revealAnswers && checked != answer.isCorrect}
on:click={() => {
changeCheckedCallback(!checked);
checked = !checked;
}}
>
<input type="checkbox" bind:checked />
<div>{answer}</div>
<div>{answer.text}</div>
</div>

<style lang="scss">
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const shuffle = (array: any[]) => {
export const shuffle = <T>(array: T[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
Expand Down
1 change: 0 additions & 1 deletion src/routes/(main)/quiz/[type]/[questionId]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export type AnsweredCountData = {
questionType: QuestionType;
right: number;
wrong: number;
};
Expand Down
39 changes: 23 additions & 16 deletions src/routes/(main)/quiz/[type]/[questionId]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<script lang="ts">
import { afterNavigate, goto } from '$app/navigation';
import { getQuestionStatsCountForType } from '$lib/api/api';
import type { ExtendedQuestion, IQuestion, QuestionType } from '$lib/model/question';
import { getQuestionStatsCount, getQuestionStatsCountForType } from '$lib/api/api';
import type {
ExtendedQuestion,
Question,
QuestionAnswer,
QuestionType
} from '$lib/model/question';
import CheckboxAnswer from '$lib/quiz/answer/CheckboxAnswer.svelte';
import AnswerButton from '$lib/quiz/AnswerButton.svelte';
import QuestionStatisticsForQuestion from '$lib/quiz/question/QuestionStatisticsForQuestion.svelte';
Expand All @@ -17,23 +22,23 @@
export let data: PageData;
let question: ExtendedQuestion;
let shuffledAnswers: [number, string][];
let shuffledAnswers: QuestionAnswer[];
let questionType: QuestionType;
let answeredCountData: AnsweredCountData | undefined;
let currentQuestionAnsweredCountData: AnsweredCountData | undefined;
function setQuestion(q: IQuestion) {
function setQuestion(q: Question) {
revealAnswers = false;
question = {
...q,
checkedIndices: []
checkedAnswers: []
};
shuffledAnswers = shuffle(Array.from(q.answers));
if (!import.meta.env.SSR) {
getQuestionStatsCountForType(questionType, q.number)
getQuestionStatsCount(q.id)
.then((data) => (currentQuestionAnsweredCountData = data))
.catch((error) => {
console.warn('Could not get current question stats');
Expand All @@ -51,8 +56,12 @@
let completelyRight = false;
$: completelyRight =
JSON.stringify(question.correctIndices.sort()) ===
JSON.stringify(question.checkedIndices.sort());
JSON.stringify(
question.answers
.filter((a) => a.isCorrect)
.map((a) => a.id)
.sort()
) === JSON.stringify(question.checkedAnswers.sort());
function gotoQuestionNumber(newQuestionNumber: number) {
goto(`/quiz/${questionType}/${newQuestionNumber}`);
Expand Down Expand Up @@ -114,16 +123,15 @@
/>
{/if}
<div class="flex flex-col flex-grow gap-2 w-full">
{#each shuffledAnswers as [index, value]}
{#each shuffledAnswers as answer}
<CheckboxAnswer
bind:answer={value}
checked={question.checkedIndices.includes(index)}
correct={question.correctIndices.includes(index)}
bind:answer
checked={question.checkedAnswers.includes(answer.id)}
bind:revealAnswers
changeCheckedCallback={(value) => {
question.checkedIndices = value
? [...question.checkedIndices, index]
: question.checkedIndices.filter((v) => v != index);
question.checkedAnswers = value
? [...question.checkedAnswers, answer.id]
: question.checkedAnswers.filter((v) => v != answer.id);
}}
/>
{/each}
Expand All @@ -132,7 +140,6 @@
<div class="mx-auto w-3/5 max-md:w-4/6">
<AnswerButton
bind:question
bind:questionType
bind:answeredCountData
bind:completelyRight
bind:currentQuestionAnsweredCountData
Expand Down
4 changes: 2 additions & 2 deletions src/routes/(main)/quiz/[type]/listing/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { getQuestions } from '$lib/api/api';
import type { IQuestion, QuestionType } from '$lib/model/question';
import type { Question, QuestionType } from '$lib/model/question';
import type { PageServerLoad } from './$types';

export const prerender = true;

export const load = (async ({ params }) => {
const questionType: QuestionType | undefined = params.type as QuestionType;

const allQuestions: IQuestion[] = await getQuestions(questionType);
const allQuestions: Question[] = await getQuestions(questionType);

return {
questionType,
Expand Down

0 comments on commit 4bdae46

Please sign in to comment.