Compare commits

..

No commits in common. "c05db22b6bf10492e589f26562f298aaace92715" and "c99576617bd49002d13929fcdaf9e323c9d58fd1" have entirely different histories.

28 changed files with 337 additions and 1051 deletions

View file

@ -1,2 +1,2 @@
DATABASE_URL="file:./db/database.db"
DATABASE_URL="postgres://USERNAME:PASSWORD@HOST:PORT/DATABASE"
CDN_URL="http://DOMAIN.TLD/FOLDER"

View file

@ -1,48 +1,5 @@
# nuxt-prawo-jazdy
## Required
The [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) project is designed for this one in mind, so use it in conjunction with this - visit it for more details
You also need the exam media files from the (Ministry of Infrasture)[https://www.gov.pl/web/infrastruktura/prawo-jazdy] - the latest files should be there
The newest at the moment of me writing this (December 13th 2025) are the (visualisations for questions from November 2025)[https://www.gov.pl/pliki/mi/pytania_egzaminacyjne_na_prawo_jazdy_11_2025.zip]
# To-do:
- [x] re-forge database structure (good for now)
- [x] choose category (good for now)
- [x] come up with how to show results appropriately
- [x] better answer click recognition
- [x] beautify website (good for now)
- [x] <b>Fixed?</b> Needs testing, but should be fine question-mark? - fix pinia middleware between pages, MAJOR ISSUE - finishing exam sometimes redirects to homepage instead of results
- [x] question timers
- [x] exam (& results?) warning leave message on exit and timer end (and definitely on refresh)
- [x] add keybinds:
- S - start
- D - nast.pyt
- X - koniec egzaminu (na pewno chcesz zakonczyc egzamin?)
- T - Tak
- N - Nie
- A - A
- B - B
- C - C
- [ ] i18n - pl, en, de, ua (not all questions are available in ua, api handle)
- UI i18n
- db: examstore add language field, api handle languages
- [ ] db: (revise) script for processing, (revise and) share appropriate files
- [ ] clean up js code in exam.vue and result.vue (currently a little bit of a mess)
## Some information about the project
My intention is, to share access to test exams free of charge, you don't have to pay me - although you can, I greatly appreciate if you donate!
In the future I will host this project publicly `aaS`, and will probably put non-invasive, privacy friendly ads if it gains enough traction
All data used by this software is public information by definition provided in the Polish Constitution - (article 61.)[https://www.sejm.gov.pl/prawo/konst/polski/kon1.htm], and can be acquired by either checking above links on the gov website, or by writing to the Ministry ((if something happened to be missing))[placeholder_for_post_about_missing_points_column]
This project is a website mimicking an official driver's license theoritical exam (for different license categories) with a seperate media server, connected using drizzle ORM to a SQLite database
## Setup
This project utilizes `pnpm`, thus it is recommended
@ -51,6 +8,32 @@ This project utilizes `pnpm`, thus it is recommended
pnpm install
```
## Required
The [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) project is designed for this one in mind, so use it in conjunction with this - visit it for more details
You also need the exam media files from the (Ministry of Infrasture)[https://www.gov.pl/web/infrastruktura/prawo-jazdy]. The newest at the moment of me writing this (19th of April 2025) are the (visualisations for questions from the 18th of January of 2024)[https://www.gov.pl/pliki/mi/wizualizacje_do_pytan_18_01_2024.zip]
# To-do:
- [x] re-forge database structure (good for now)
- [x] choose category (good for now)
- [x] come up with how to show results appropriately
- [x] db: script for processing, share appropriate files
- [x] better answer click recognition
- [x] beautify website (good for now)
- [ ] <b>fix pinia middleware between pages, MAJOR ISSUE - finishing exam sometimes redirects to homepage instead of results, help appreciated</b>
- [ ] exam (& results?) warning leave message on exit and timer end (and definitely on refresh)
- [ ] question timers
- [ ] lazy loading
- [ ] i18n - pl, en, de, ua (not all questions are not available in ua, api handle)
## Some info
My intention is, to share access to test exams free of charge - all data is free of charge and is already available as public information, either on the gov website, or by writing to the MI
This project is an SSR website mimicking an official driver's license exam (for different categories) with a seperate CDN for media, connected using an ORM to a postgres DB
## Development Server
Start the development server on `http://localhost:3000`:

View file

@ -3,9 +3,3 @@
<NuxtPage />
</div>
</template>
<style>
.outline-set-solid {
outline-style: solid;
}
</style>

View file

@ -12,33 +12,3 @@ export default [
'D1',
'PT',
];
export const opis = [
'motocykle bez ograniczeń mocy',
'⭐ samochody osobowe do 3,5 t',
'pojazdy ciężarowe powyżej 3,5 t',
'autobusy',
'ciągniki rolnicze i pojazdy wolnobieżne',
'motorowery i lekkie czterokołowce',
'motocykle do 125 cm³ i 11 kW',
'motocykle do 35 kW',
'czterokołowce (np. quady)',
'pojazdy od 3,5 t do 7,5 t',
'autobusy do 16 pasażerów',
'tramwaje',
];
export const wiek = [
'(24 lata; lub 20 lat jeśli masz kat. A2 min. 2 lata)',
'(18 lat)',
'(21 lat; lub 18 lat z kwalifikacją wstępną)',
'(24 lata; lub 21 lat z kwalifikacją wstępną)',
'(16 lat)',
'(14 lat)',
'(16 lat)',
'(18 lat)',
'(16 lat)',
'(18 lat)',
'(21 lat; lub 18 lat z kwalifikacją wstępną)',
'(21 lat)',
];

View file

@ -1,29 +0,0 @@
<script setup lang="ts">
const props = defineProps<{ showModal: boolean }>();
const endModal = useTemplateRef('endModal');
const emit = defineEmits(['hideEndModal', 'endExam']);
watchEffect(() => {
if (props.showModal && endModal.value?.open == false) {
endModal.value?.showModal();
emit('hideEndModal');
}
});
</script>
<template>
<dialog
ref="endModal"
class="flex justify-center items-center backdrop-blur-sm modal"
>
<div class="flex flex-col p-3 bg-base rounded-md gap-3 modal-box min-w-fit">
<h1 class="text-[1.5rem]">Koniec egzaminu</h1>
<div class="*:inline">Czy na pewno chcesz zakończyć egzamin?</div>
<div class="flex flex-row gap-2 justify-around">
<div class="btn btn-lg btn-success" @click="emit('endExam')">Tak</div>
<div class="btn btn-lg btn-error" @click="endModal?.close()">Nie</div>
</div>
</div>
</dialog>
</template>

View file

@ -6,11 +6,8 @@ const cdnUrl = runtimeConfig.public.cdn_url;
const route = useRoute();
const emit = defineEmits(['mediaload']);
const props = defineProps<{
mediaPath: string | null | undefined;
phase: string;
}>();
const media = computed(() => {
@ -24,21 +21,6 @@ const media = computed(() => {
}
return { name: dotSplit?.join('.'), type };
});
const video = ref();
function onVideoLoad() {
if (route.path === '/exam') {
video.value.play();
let duration = video.value.duration;
if (isNaN(duration) || duration == Infinity) {
duration = 0;
}
setTimeout(() => {
emit('mediaload');
}, duration * 1000);
}
}
</script>
<template>
@ -46,26 +28,23 @@ function onVideoLoad() {
class="select-none flex-auto w-full *:object-contain *:w-full *:h-full *:max-h-full relative *:absolute"
:class="route.path === '/exam' ? 'z-[-1]' : ''"
>
<img v-if="phase == 'set-basic'" src="/placeholder.svg" alt="placeholder" />
<NuxtImg
v-else-if="media.type === 'image'"
v-if="media.type === 'image'"
:key="`${mediaPath}-image`"
provider="selfhost"
:src="'/' + mediaPath"
:alt="mediaPath ?? ''"
@load="emit('mediaload')"
/>
<video
v-else-if="media.type === 'video'"
:key="`${mediaPath}-video`"
ref="video"
:autoplay="route.path === '/exam'"
:controls="route.path === '/result'"
@canplaythrough="onVideoLoad()"
>
<source :src="joinURL(cdnUrl, media.name + '.mp4')" type="video/mp4" />
</video>
<span v-else class="text-5xl font-bold flex items-center justify-center"
>Pytanie bez wizualizacji</span
>Brak mediów</span
>
</div>
</template>

View file

@ -4,8 +4,6 @@ const myModal = useTemplateRef('myModal');
onMounted(() => {
myModal.value?.showModal();
});
defineEmits(['again', 'home']);
</script>
<template>
@ -21,12 +19,10 @@ defineEmits(['again', 'home']);
<div class="*:inline">Punkty: <slot name="points" /> / 74</div>
<div class="*:inline">Wynik: <slot name="resultTrueFalse" /></div>
<div class="flex flex-row gap-2">
<div class="btn btn-soft" @click="$emit('home')">
Wróć na stronę główną
</div>
<div class="btn btn-outline" @click="$emit('again')">
<NuxtLink to="/" class="btn btn-soft">Wróć na stronę główną</NuxtLink>
<NuxtLink to="/exam" class="btn btn-outline">
Rozpocznij jeszcze raz
</div>
</NuxtLink>
<button class="btn btn-neutral" @click="myModal?.close()">
Przejrzyj odpowiedzi
</button>

View file

@ -1,59 +1,22 @@
<script setup lang="ts">
import { onKeyStroke } from '@vueuse/core';
const props = defineProps<{
defineProps<{
countBasic: number;
countAdvanced: number;
now: string | null | undefined;
ending: boolean;
setBasic: boolean;
time: number;
phase: string;
}>();
const emit = defineEmits<{
endExam: [];
nextQuestion: [];
nextTime: [];
showEndModal: [];
}>();
function tryEndExam() {
if (props.ending == false) {
emit('showEndModal');
} else {
emit('endExam');
}
}
const startButton = useTemplateRef('start-button');
onKeyStroke(['S', 's'], () => {
startButton.value?.click();
});
const nextButton = useTemplateRef('next-button');
onKeyStroke(['D', 'd'], () => {
nextButton.value?.click();
});
const endButton = useTemplateRef('end-button');
onKeyStroke(['X', 'x'], () => {
endButton.value?.click();
});
</script>
<template>
<div
class="flex flex-col items-stretch p-4 gap-10 border-l border-base-300 bg-base-100"
>
<button
ref="end-button"
class="btn btn-warning btn-xl"
@click="tryEndExam()"
>
<button class="btn btn-warning btn-xl" @click="emit('endExam')">
Zakończ egzamin
</button>
@ -73,90 +36,38 @@ onKeyStroke(['X', 'x'], () => {
</CurrentQuestionCount>
</div>
<div
v-if="phase == 'set-basic'"
class="text-center text-xl flex flex-col gap-2"
>
<div class="text-center text-xl flex flex-col gap-2">
<span>Czas na zapoznanie się z pytaniem</span>
<div class="flex flex-row items-stretch gap-2">
<div
ref="start-button"
class="btn btn-primary"
@click="emit('nextTime')"
>
START
</div>
<div class="btn btn-primary">START</div>
<div class="h-full flex-1 relative">
<progress
class="progress progress-warning w-full h-full"
:value="time"
max="20"
></progress>
<span class="block set-translate z-10 text-black text-2xl">
{{ time >= 0 ? time : 0 }}s
</span>
value="50"
max="100"
/>
<span class="block set-translate z-10 text-black text-2xl">20s</span>
</div>
</div>
</div>
<div v-else class="text-center text-xl flex flex-col gap-2">
<div class="text-center text-xl flex flex-col gap-2">
<span>Czas na udzielenie odpowiedzi</span>
<div class="h-9 relative">
<progress
class="progress progress-warning w-full h-full"
:value="time"
:max="phase == 'start-basic' ? 15 : 45"
></progress>
<span class="block set-translate z-10 text-black text-2xl">
{{ time >= 0 ? time : 0 }}s
</span>
value="50"
max="100"
/>
<span class="block set-translate z-10 text-black text-2xl">15s</span>
</div>
</div>
<div class="flex-1">
<span class="text-xl">Skróty klawiszowe</span>
<table class="table table-sm">
<tbody>
<tr>
<td>S</td>
<td>niebieski przycisk start</td>
</tr>
<tr>
<td>D</td>
<td>następne pytanie</td>
</tr>
<tr>
<td>X</td>
<td>zakończ egzamin</td>
</tr>
<tr>
<td>T / Y</td>
<td>Tak</td>
</tr>
<tr>
<td>N</td>
<td>Nie</td>
</tr>
<tr>
<td>A</td>
<td>A</td>
</tr>
<tr>
<td>B</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
<td>C</td>
</tr>
</tbody>
</table>
</div>
<div class="flex-1" />
<button
ref="next-button"
class="btn btn-warning btn-xl"
:disabled="ending || setBasic"
:disabled="ending"
@click="emit('nextQuestion')"
>
Następne pytanie
@ -165,12 +76,16 @@ onKeyStroke(['X', 'x'], () => {
</template>
<style>
.progressive {
width: calc(100% / v-bind('time'));
transition: all 1s linear;
/*.progressive {
animation: progressZapoznanie 20s linear;
}
progress[value]::-webkit-progress-value {
transition: width 0.5s;
}
@keyframes progressZapoznanie {
0% {
width: 0;
}
100% {
width: 100%;
}
}*/
</style>

View file

@ -11,8 +11,6 @@ defineProps<{
const emit = defineEmits<{
changeNow: [value: string];
changeCount: [num: number];
again: [];
home: [];
}>();
</script>
@ -20,9 +18,9 @@ const emit = defineEmits<{
<div
class="flex flex-col items-stretch p-4 gap-6 border-l border-base-300 bg-base-100"
>
<div class="btn btn-warning btn-xl" @click="$emit('home')">
<NuxtLink to="/" class="btn btn-warning btn-xl">
Wróć na stronę główną
</div>
</NuxtLink>
<button class="btn btn-info btn-lg" @click="emit('changeNow', 'basic')">
Pytania podstawowe
@ -39,9 +37,7 @@ const emit = defineEmits<{
class="btn btn-md"
name="chooser"
:class="`${
result.basic[num].chosen_answer == ''
? 'btn-warning'
: result.basic[num].question?.correct_answer ===
result.basic[num].question?.correct_answer ===
result.basic[num].chosen_answer
? 'btn-success'
: 'btn-error'
@ -69,9 +65,7 @@ const emit = defineEmits<{
class="btn btn-md"
name="chooser"
:class="`${
result.advanced[num].chosen_answer == ''
? 'btn-warning'
: result.advanced[num].question?.correct_answer ===
result.advanced[num].question?.correct_answer ===
result.advanced[num].chosen_answer
? 'btn-success'
: 'btn-error'
@ -87,8 +81,14 @@ const emit = defineEmits<{
<div class="*:inline">Punkty: <slot name="points" /> / 74</div>
<div class="*:inline">Wynik: <slot name="resultTrueFalse" /></div>
</div>
<div class="btn btn-warning btn-xl" @click="$emit('again')">
<NuxtLink to="/exam" class="btn btn-warning btn-xl">
Rozpocznij jeszcze raz
</div>
</NuxtLink>
</div>
</template>
<style scoped>
.outline-set-solid {
outline-style: solid;
}
</style>

View file

@ -1,36 +1,9 @@
<script lang="ts" setup>
import { onKeyStroke } from '@vueuse/core';
const props = defineProps<{
question: Question;
phase: string;
defineProps<{
question: AdvancedQuestion | undefined;
}>();
const answer = defineModel<string | null | undefined>();
const aButton = useTemplateRef('A-button');
onKeyStroke(['A', 'a'], () => {
if (props.phase != 'result') {
aButton.value[0].click();
}
});
const bButton = useTemplateRef('B-button');
onKeyStroke(['B', 'b'], () => {
if (props.phase != 'result') {
bButton.value[0].click();
}
});
const cButton = useTemplateRef('C-button');
onKeyStroke(['C', 'c'], () => {
if (props.phase != 'result') {
cButton.value[0].click();
}
});
</script>
<template>
@ -43,15 +16,14 @@ onKeyStroke(['C', 'c'], () => {
<div class="flex flex-col gap-3">
<div
v-for="[element, value] of Object.entries({
A: (question as AdvancedQuestion)?.answer_a,
B: (question as AdvancedQuestion)?.answer_b,
C: (question as AdvancedQuestion)?.answer_c,
A: question?.answer_a,
B: question?.answer_b,
C: question?.answer_c,
})"
:key="`btn_answer_${element}_${value}`"
>
<input
:id="`odp_${element}`"
:ref="`${element}-button`"
v-model="answer"
type="radio"
name="abc"
@ -60,13 +32,7 @@ onKeyStroke(['C', 'c'], () => {
/>
<label :for="`odp_${element}`">
<div
:class="
phase == 'exam'
? answer === element
? ' !btn-secondary'
: ''
: `${answer === element ? 'outline-set-solid outline-2' : ''} ${element == question?.correct_answer ? 'btn-success' : 'btn-error'}`
"
:class="answer === element ? ' !btn-secondary' : ''"
class="btn btn-primary btn-lg"
>
{{ element }}

View file

@ -1,28 +1,9 @@
<script lang="ts" setup>
import { onKeyStroke } from '@vueuse/core';
const props = defineProps<{
question: Question;
phase: string;
defineProps<{
question: BasicQuestion | undefined;
}>();
const answer = defineModel<string | null | undefined>();
const yesButton = useTemplateRef('true-button');
onKeyStroke(['T', 't', 'Y', 'y'], () => {
if (props.phase != 'result') {
yesButton.value[0].click();
}
});
const noButton = useTemplateRef('false-button');
onKeyStroke(['N', 'n'], () => {
if (props.phase != 'result') {
noButton.value[0].click();
}
});
</script>
<template>
@ -37,7 +18,6 @@ onKeyStroke(['N', 'n'], () => {
<input
v-for="[element, value] of Object.entries({ TAK: true, NIE: false })"
:id="`odp_${element}`"
:ref="`${value}-button`"
:key="`btn_answer_${element}`"
v-model="answer"
type="radio"
@ -46,21 +26,11 @@ onKeyStroke(['N', 'n'], () => {
class="btn btn-primary btn-xl"
:aria-label="element"
:class="
phase == 'exam'
? answer == null
answer == null
? false
: answer === value.toString()
? '!btn-secondary'
: ''
: `${
answer === value.toString()
? 'outline-set-solid outline-2'
: ''
} ${
question?.correct_answer?.toString() == value.toString()
? ' btn-success'
: ' btn-error'
}`
"
:checked="answer == null ? false : answer === value.toString()"
/>

Binary file not shown.

View file

@ -1,36 +0,0 @@
import { sqliteTable, text, int } from 'drizzle-orm/sqlite-core';
export const tasks = sqliteTable('tasks', {
id: int().notNull(),
correct_answer: text(),
media_url: text(),
weight: int(),
});
export const questions = sqliteTable('questions', {
task_id: int(),
lang: text(),
text: text(),
});
export const tasks_advanced = sqliteTable('tasks_advanced', {
id: int().notNull(),
correct_answer: text(),
media_url: text(),
weight: int(),
});
export const questions_advanced = sqliteTable('questions_advanced', {
task_id: int(),
lang: text(),
text: text(),
answer_a: text(),
answer_b: text(),
answer_c: text(),
});
export const categories_db = sqliteTable('categories', {
name: text(),
task_id: int(),
});

View file

@ -2,9 +2,9 @@ import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'postgresql',
schema: './src/db/schema.ts',
out: './drizzle',
schema: './db/schema.ts',
dialect: 'sqlite',
dbCredentials: {
url: process.env.DATABASE_URL!,
},

View file

@ -1,9 +1,9 @@
export default defineNuxtRouteMiddleware(async () => {
const examStore = useExamStore();
if (examStore.end) {
return '/result';
}
if (examStore.category === '') {
return '/anomaly';
examStore.resetExam();
return navigateTo('/');
}
examStore.mildReset();
});

View file

@ -1,6 +1,8 @@
export default defineNuxtRouteMiddleware(async () => {
export default defineNuxtRouteMiddleware(() => {
const examStore = useExamStore();
if (!examStore.end || examStore.category === '') {
return '/anomaly';
if (!examStore.end) {
examStore.resetExam();
return navigateTo('/');
}
});

View file

@ -6,11 +6,10 @@ export default defineNuxtConfig({
'@nuxtjs/tailwindcss',
'@nuxt/fonts',
'@pinia/nuxt',
'pinia-plugin-persistedstate/nuxt',
'@nuxt/eslint',
'@nuxt/image',
],
ssr: false,
ssr: true,
imports: {
dirs: ['types/*.ts', 'store/*.ts', 'types/**/*.ts'],
},
@ -47,7 +46,4 @@ export default defineNuxtConfig({
},
provider: 'selfhost',
},
pinia: {
storesDirs: ['./store/**'],
},
});

View file

@ -14,12 +14,10 @@
"tsc": "nuxi typecheck"
},
"dependencies": {
"@libsql/client": "^0.15.15",
"@nuxt/fonts": "0.11.1",
"@nuxt/image": "1.10.0",
"@nuxtjs/tailwindcss": "6.13.2",
"@pinia/nuxt": "0.11.0",
"@vueuse/core": "^14.1.0",
"array-shuffle": "^3.0.0",
"daisyui": "^5.0.27",
"date-fns": "^4.1.0",
@ -29,8 +27,8 @@
"eslint": "^9.24.0",
"lodash": "^4.17.21",
"nuxt": "~3.16.2",
"pg": "^8.14.1",
"pinia": "^3.0.2",
"pinia-plugin-persistedstate": "^4.7.1",
"ufo": "^1.6.1",
"vue": "latest",
"vue-router": "latest"
@ -39,6 +37,7 @@
"devDependencies": {
"@nuxt/eslint": "1.3.0",
"@types/lodash": "^4.17.16",
"@types/pg": "^8.11.13",
"eslint-config-prettier": "^10.1.2",
"prettier": "^3.5.3",
"tsx": "^4.19.3",

View file

@ -1,22 +0,0 @@
<script setup lang="ts">
const examStore = useExamStore();
const basicStore = useBasicStore();
const advancedStore = useAdvancedStore();
</script>
<template>
<div class="flex flex-col gap-2 items-start m-2">
<h1 class="text-2xl">Nastąpiła anomalia</h1>
<br />
Przekierowanie z: {{ useRoute().redirectedFrom ?? '""' }} <br />
Kategoria:
{{ examStore.category != '' ? examStore.category : '""' }}
<br />
Koniec: {{ examStore.end }} <br />
Pytania podstawowe: <code class="text-xs">{{ basicStore.basic }}</code>
<br />
Pytania specjalistyczne:
<code class="text-xs">{{ advancedStore.advanced }}</code> <br />
<NuxtLink to="/" class="btn btn-primary"> Wróć na stronę główną </NuxtLink>
</div>
</template>

View file

@ -1,92 +1,32 @@
<script lang="ts" setup>
import { intervalToDuration, addMinutes, addSeconds, isAfter } from 'date-fns';
import { useNow } from '@vueuse/core';
import { intervalToDuration, addMinutes, addSeconds, isEqual } from 'date-fns';
import { useExamStore } from '~/store/examStore';
definePageMeta({ middleware: 'exam' });
definePageMeta({ middleware: ['exam'] });
const examStore = useExamStore();
const basicStore = useBasicStore();
const advancedStore = useAdvancedStore();
await callOnce(() => examStore.mildReset(), { mode: 'navigation' });
useHead({
title: 'Pytanie 1/20',
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function preventRefresh(e: any) {
e.preventDefault();
e.returnValue = ''; // polyfill for older chrome
}
const now = ref('basic');
const nowTime = useNow();
const nowTime = ref(new Date());
const timeEnd = addMinutes(new Date(), 25);
const questionEnd = ref(addSeconds(new Date(), 20));
const mediaLoaded = ref(false);
const time = ref({
total: computed(() =>
const timeRemainingTotal = computed(() =>
intervalToDuration({
start: nowTime.value,
end: timeEnd,
}),
),
question: computed(() => {
const interval = intervalToDuration({
start: nowTime.value,
end: questionEnd.value,
});
if (Object.hasOwn(interval, 'seconds') && interval.seconds != undefined) {
return interval.seconds;
} else {
return 0;
}
}),
phase: 'set-basic',
});
function changeQuestionTimeAfterNext() {
if (time.value.phase == 'set-basic') {
time.value.phase = 'start-basic';
questionEnd.value = addSeconds(new Date(), 15);
} else if (time.value.phase == 'start-basic') {
if (now.value == 'basic') {
time.value.phase = 'set-basic';
mediaLoaded.value = false;
questionEnd.value = addSeconds(new Date(), 20);
} else {
time.value.phase = 'set-advanced';
questionEnd.value = addSeconds(new Date(), 50);
}
} else if (time.value.phase == 'set-advanced') {
questionEnd.value = addSeconds(new Date(), 50);
}
}
function onMediaLoad() {
mediaLoaded.value = true;
}
function clickNext() {
if (ending.value) {
endExam();
}
if (time.value.phase != 'set-basic') {
next();
}
changeQuestionTimeAfterNext();
}
);
// const timeRemainingQuestion - to implement
onMounted(() => {
useHead({
title: 'Pytanie 1/20',
});
window.addEventListener('beforeunload', preventRefresh);
watchEffect(() => {
if (isAfter(nowTime.value, timeEnd)) endExam();
});
const endInterval = setInterval(() => {
nowTime.value = addSeconds(nowTime.value, 1);
if (isEqual(nowTime.value, timeEnd)) {
clearInterval(endInterval);
endExam();
}
}, 1000);
watchEffect(() => {
if (now.value === 'basic')
@ -94,20 +34,9 @@ onMounted(() => {
if (now.value === 'advanced')
useHead({ title: `Pytanie ${countAdvanced.value + 1}/12` });
});
watchEffect(() => {
if (mediaLoaded.value == false && time.value.phase == 'start-basic') {
questionEnd.value = addSeconds(new Date(), 15);
}
if (time.value.question < 0 && ending.value == false) {
clickNext();
}
});
});
onBeforeUnmount(() => {
window.removeEventListener('beforeunload', preventRefresh);
});
const examStore = useExamStore();
const {
data: dataBasic,
@ -132,6 +61,7 @@ const {
const countBasic = ref(0);
const countAdvanced = ref(-1);
const now = ref('basic');
const answer = ref<string>('');
const ending = ref(false);
@ -154,30 +84,28 @@ const result: Ref<ResultEndType> = ref({
advanced: [],
});
function next() {
if (examStore.end == false) {
result.value[now.value as keyof ResultEndType].push({
async function next() {
if (now.value === 'basic' || now.value === 'advanced') {
result.value[now.value].push({
question: question.value,
chosen_answer: answer.value,
correct_answer: question.value?.correct_answer ?? null,
chosen_is_correct: answer.value == question.value?.correct_answer,
chosen_is_correct: answer.value === question.value?.correct_answer,
});
answer.value = '';
} else {
console.error('next() incorrectly executed - exam has already ended.');
}
answer.value = '';
if (now.value === 'basic') {
if (countBasic.value + 1 <= 19) {
if (countBasic.value < 19) {
countBasic.value++;
} else {
now.value = 'advanced';
countAdvanced.value++;
}
} else if (now.value === 'advanced') {
if (countAdvanced.value + 1 <= 11) {
if (countAdvanced.value < 11) {
countAdvanced.value++;
}
if (countAdvanced.value + 1 >= 12) {
if (countAdvanced.value >= 11) {
ending.value = true;
}
}
@ -185,30 +113,20 @@ function next() {
function endExam() {
loading.value = true;
while (ending.value == false) {
if (time.value.phase != 'set-basic') {
while (!ending.value) {
next();
}
changeQuestionTimeAfterNext();
}
next();
basicStore.set(result.value.basic);
advancedStore.set(result.value.advanced);
examStore.setResult(result.value);
examStore.setEnd(true);
if (
basicStore.basic == result.value.basic &&
advancedStore.advanced == result.value.advanced &&
examStore.end
) {
return navigateTo(`/result`, { replace: true });
} else {
return navigateTo(`/anomaly`);
while (true) {
if (examStore.result == result.value && examStore.end) {
return navigateTo('/result', { replace: true });
}
}
}
const loading = ref(false);
const showEndModal = ref(false);
</script>
<template>
@ -221,38 +139,28 @@ const showEndModal = ref(false);
<BarTop
:points="question?.weight"
:category="examStore.category"
:time-remaining="time.total"
/>
<MediaBox
:media-path="question?.media_url"
:phase="time.phase"
@mediaload="onMediaLoad()"
:time-remaining="timeRemainingTotal"
/>
<MediaBox :media-path="question?.media_url" />
<QuestionBasic
v-if="now === 'basic'"
v-model="answer"
phase="exam"
:question="questionBasic"
/>
<QuestionAdvanced
v-else-if="now === 'advanced'"
v-model="answer"
phase="exam"
:question="questionAdvanced"
/>
</div>
<BarRightExam
:count-basic="countBasic"
:count-advanced="countAdvanced"
:now="now"
:ending="ending"
:set-basic="time.phase == 'set-basic'"
:time="time.question"
:phase="time.phase"
@show-end-modal="showEndModal = true"
@next-question="clickNext()"
@next-question="next()"
@end-exam="endExam()"
@next-time="changeQuestionTimeAfterNext()"
/>
</div>
</div>
@ -260,10 +168,5 @@ const showEndModal = ref(false);
An API error occurred: {{ errorBasic }} {{ errorAdvanced }}
</div>
<LoadingScreen v-else />
<EndModal
:show-modal="showEndModal"
@hide-end-modal="showEndModal = false"
@end-exam="endExam()"
/>
</div>
</template>

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import categories, { opis, wiek } from '~/categories';
import categories from '~/categories';
onMounted(() => {
useHead({
@ -10,7 +10,6 @@ onMounted(() => {
const loading = ref(false);
const examStore = useExamStore();
await callOnce(() => examStore.resetExam(), { mode: 'navigation' });
function setAndGo(category: string) {
loading.value = true;
@ -25,24 +24,22 @@ function setAndGo(category: string) {
<template>
<div>
<div v-if="!loading" class="text-3xl m-2 flex flex-col gap-2">
<div v-if="!loading" class="text-3xl">
<span>Test na prawo jazdy</span>
<div
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-slate-100 border-1 border-slate-500 rounded-xl w-fit"
>
<div
v-for="[index, category] of categories.entries()"
<p>
Witaj w teście na prawo jazdy, aby rozpocząć, naciśnij jeden z
poniższych przycisków:
<br />
</p>
<div class="flex flex-row flex-wrap gap-2">
<button
v-for="category in categories"
:key="`btn-${category}`"
class="flex flex-row gap-3 items-center"
class="btn btn-xl btn-secondary"
@click="setAndGo(category)"
>
<button class="btn btn-xl btn-secondary" @click="setAndGo(category)">
{{ category }}
</button>
<div class="flex flex-col text-sm">
<div>{{ opis[index] }}</div>
<div>{{ wiek[index] }}</div>
</div>
</div>
</div>
</div>
<LoadingScreen v-else />

View file

@ -5,19 +5,14 @@ definePageMeta({ middleware: ['result'] });
const examStore = useExamStore();
const basicStore = useBasicStore();
const advancedStore = useAdvancedStore();
const loading = ref(false);
const points = ref<number>(0);
basicStore.basic.forEach((answer) => {
examStore.result.basic.forEach((answer) => {
if (answer.chosen_is_correct) {
points.value += answer.question?.weight ?? 0;
}
});
advancedStore.advanced.forEach((answer) => {
examStore.result.advanced.forEach((answer) => {
if (answer.chosen_is_correct) {
points.value += answer.question?.weight ?? 0;
}
@ -37,17 +32,17 @@ onMounted(() => {
const countBasic = ref(0);
const countAdvanced = ref(0);
const resultQuestionBasic = computed<ResultType | undefined>(() =>
basicStore.basic.at(countBasic.value),
);
const resultQuestionAdvanced = computed<ResultType | undefined>(() =>
advancedStore.advanced.at(countAdvanced.value),
const resultQuestionBasic = computed<ResultType<BasicQuestion> | undefined>(
() => examStore.result.basic.at(countBasic.value),
);
const resultQuestionAdvanced = computed<
ResultType<AdvancedQuestion> | undefined
>(() => examStore.result.advanced.at(countAdvanced.value));
const questionBasic = computed<Question>(
const questionBasic = computed<BasicQuestion | undefined>(
() => resultQuestionBasic.value?.question,
);
const questionAdvanced = computed<Question>(
const questionAdvanced = computed<AdvancedQuestion | undefined>(
() => resultQuestionAdvanced.value?.question,
);
@ -86,62 +81,42 @@ function changeCount(num: number) {
countAdvanced.value = num;
}
}
async function again() {
loading.value = true;
await examStore.mildReset();
return await navigateTo('/exam');
}
async function home() {
loading.value = true;
await examStore.resetExam();
return await navigateTo('/');
}
</script>
<template>
<div>
<LoadingScreen v-if="loading" />
<div v-else>
<ResultModal @again="again" @home="home">
<ResultModal>
<template #title>Egzamin teorytyczny</template>
<template #category>{{ examStore.category }}</template>
<template #points>{{ points }}</template>
<template #resultTrueFalse>{{ resultTrueFalse }} </template>
<template #resultTrueFalse>{{ resultTrueFalse }}</template>
</ResultModal>
<div>
<div class="grid grid-cols-4 min-h-dvh">
<div class="col-span-3 flex flex-col">
<BarTop :points="question?.weight" :category="examStore.category" />
<MediaBox :media-path="question?.media_url" phase="" />
<MediaBox :media-path="question?.media_url" />
<QuestionBasic
v-if="now === 'basic'"
v-model="answer"
:question="questionBasic"
phase="result"
class="select-none z-[-1]"
/>
<QuestionAdvanced
v-else-if="now === 'advanced'"
v-model="answer"
:question="questionAdvanced"
phase="result"
class="select-none z-[-1]"
/>
</div>
<BarRightResult
:result="{
basic: basicStore.basic,
advanced: advancedStore.advanced,
}"
:result="examStore.result"
:count-basic="countBasic"
:count-advanced="countAdvanced"
:now="now"
@change-now="changeNow"
@change-count="changeCount"
@home="home"
@again="again"
>
<template #points>{{ points }}</template>
<template #resultTrueFalse>{{ resultTrueFalse }}</template>
@ -149,5 +124,4 @@ async function home() {
</div>
</div>
</div>
</div>
</template>

388
pnpm-lock.yaml generated
View file

@ -8,30 +8,24 @@ importers:
.:
dependencies:
'@libsql/client':
specifier: ^0.15.15
version: 0.15.15
'@nuxt/fonts':
specifier: 0.11.1
version: 0.11.1(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
version: 0.11.1(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
'@nuxt/image':
specifier: 1.10.0
version: 1.10.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)
version: 1.10.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)
'@nuxtjs/tailwindcss':
specifier: 6.13.2
version: 6.13.2(magicast@0.3.5)
'@pinia/nuxt':
specifier: 0.11.0
version: 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
'@vueuse/core':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.13(typescript@5.8.3))
array-shuffle:
specifier: ^3.0.0
version: 3.0.0
daisyui:
specifier: ^5.0.27
version: 5.2.3
specifier: ^5.0.23
version: 5.0.23
date-fns:
specifier: ^4.1.0
version: 4.1.0
@ -43,7 +37,7 @@ importers:
version: 0.31.0
drizzle-orm:
specifier: ^0.42.0
version: 0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)
version: 0.42.0(@types/pg@8.11.13)(pg@8.14.1)
eslint:
specifier: ^9.24.0
version: 9.24.0(jiti@2.4.2)
@ -52,13 +46,13 @@ importers:
version: 4.17.21
nuxt:
specifier: ~3.16.2
version: 3.16.2(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@22.14.1)(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1))(eslint@9.24.0(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(yaml@2.7.1)
version: 3.16.2(@parcel/watcher@2.5.1)(@types/node@22.14.1)(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1))(eslint@9.24.0(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(yaml@2.7.1)
pg:
specifier: ^8.14.1
version: 8.14.1
pinia:
specifier: ^3.0.2
version: 3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))
pinia-plugin-persistedstate:
specifier: ^4.7.1
version: 4.7.1(@nuxt/kit@3.16.2(magicast@0.3.5))(@pinia/nuxt@0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))))(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
ufo:
specifier: ^1.6.1
version: 1.6.1
@ -75,6 +69,9 @@ importers:
'@types/lodash':
specifier: ^4.17.16
version: 4.17.16
'@types/pg':
specifier: ^8.11.13
version: 8.11.13
eslint-config-prettier:
specifier: ^10.1.2
version: 10.1.2(eslint@9.24.0(jiti@2.4.2))
@ -681,67 +678,6 @@ packages:
'@kwsites/promise-deferred@1.1.1':
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
'@libsql/client@0.15.15':
resolution: {integrity: sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==}
'@libsql/core@0.15.15':
resolution: {integrity: sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==}
'@libsql/darwin-arm64@0.5.22':
resolution: {integrity: sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==}
cpu: [arm64]
os: [darwin]
'@libsql/darwin-x64@0.5.22':
resolution: {integrity: sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==}
cpu: [x64]
os: [darwin]
'@libsql/hrana-client@0.7.0':
resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==}
'@libsql/isomorphic-fetch@0.3.1':
resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==}
engines: {node: '>=18.0.0'}
'@libsql/isomorphic-ws@0.1.5':
resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
'@libsql/linux-arm-gnueabihf@0.5.22':
resolution: {integrity: sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==}
cpu: [arm]
os: [linux]
'@libsql/linux-arm-musleabihf@0.5.22':
resolution: {integrity: sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==}
cpu: [arm]
os: [linux]
'@libsql/linux-arm64-gnu@0.5.22':
resolution: {integrity: sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==}
cpu: [arm64]
os: [linux]
'@libsql/linux-arm64-musl@0.5.22':
resolution: {integrity: sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==}
cpu: [arm64]
os: [linux]
'@libsql/linux-x64-gnu@0.5.22':
resolution: {integrity: sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==}
cpu: [x64]
os: [linux]
'@libsql/linux-x64-musl@0.5.22':
resolution: {integrity: sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==}
cpu: [x64]
os: [linux]
'@libsql/win32-x64-msvc@0.5.22':
resolution: {integrity: sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==}
cpu: [x64]
os: [win32]
'@mapbox/node-pre-gyp@2.0.0':
resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==}
engines: {node: '>=18'}
@ -750,9 +686,6 @@ packages:
'@napi-rs/wasm-runtime@0.2.8':
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
'@neon-rs/load@0.0.4':
resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
'@netlify/functions@3.0.4':
resolution: {integrity: sha512-Ox8+ABI+nsLK+c4/oC5dpquXuEIjzfTlJrdQKgQijCsDQoje7inXFAtKDLvvaGvuvE+PVpMLwQcIUL6P9Ob1hQ==}
engines: {node: '>=18.0.0'}
@ -1276,12 +1209,6 @@ packages:
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@typescript-eslint/eslint-plugin@8.30.1':
resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1504,19 +1431,6 @@ packages:
'@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
'@vueuse/core@14.1.0':
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
peerDependencies:
vue: ^3.5.0
'@vueuse/metadata@14.1.0':
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
'@vueuse/shared@14.1.0':
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
peerDependencies:
vue: ^3.5.0
abbrev@3.0.1:
resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==}
engines: {node: ^18.17.0 || >=20.5.0}
@ -1997,12 +1911,8 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
daisyui@5.2.3:
resolution: {integrity: sha512-sldBQUIFCsSPoF4LvoHhIi9GnvBX/3aZD9NoTOvpTSX8sDjO484wQx7yEvRyREMpn4rZMvQSKKskHAHdM8+B4Q==}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
daisyui@5.0.23:
resolution: {integrity: sha512-SIR8yAneeNxvrpaR5kREG1DrPK8XFyfmyvqyZEUTJ2e6tv4Pp56/w+52Vdv34hmSmtAyDldTCEPWx+GvPyp0Yg==}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
@ -2111,10 +2021,6 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
detect-libc@2.0.2:
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
engines: {node: '>=8'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
@ -2517,10 +2423,6 @@ packages:
picomatch:
optional: true
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@ -2561,10 +2463,6 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@ -2921,9 +2819,6 @@ packages:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-base64@3.7.8:
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -3020,10 +2915,6 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
libsql@0.5.22:
resolution: {integrity: sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==}
os: [darwin, linux, win32]
lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
@ -3318,11 +3209,6 @@ packages:
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-fetch-native@1.6.6:
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
@ -3335,10 +3221,6 @@ packages:
encoding:
optional: true
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
@ -3608,20 +3490,6 @@ packages:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
pinia-plugin-persistedstate@4.7.1:
resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==}
peerDependencies:
'@nuxt/kit': '>=3.0.0'
'@pinia/nuxt': '>=0.10.0'
pinia: '>=3.0.0'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
'@pinia/nuxt':
optional: true
pinia:
optional: true
pinia@3.0.2:
resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==}
peerDependencies:
@ -3922,9 +3790,6 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
promise-limit@2.7.0:
resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@ -4760,10 +4625,6 @@ packages:
typescript:
optional: true
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@ -5412,68 +5273,6 @@ snapshots:
'@kwsites/promise-deferred@1.1.1': {}
'@libsql/client@0.15.15':
dependencies:
'@libsql/core': 0.15.15
'@libsql/hrana-client': 0.7.0
js-base64: 3.7.8
libsql: 0.5.22
promise-limit: 2.7.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@libsql/core@0.15.15':
dependencies:
js-base64: 3.7.8
'@libsql/darwin-arm64@0.5.22':
optional: true
'@libsql/darwin-x64@0.5.22':
optional: true
'@libsql/hrana-client@0.7.0':
dependencies:
'@libsql/isomorphic-fetch': 0.3.1
'@libsql/isomorphic-ws': 0.1.5
js-base64: 3.7.8
node-fetch: 3.3.2
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@libsql/isomorphic-fetch@0.3.1': {}
'@libsql/isomorphic-ws@0.1.5':
dependencies:
'@types/ws': 8.18.1
ws: 8.18.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@libsql/linux-arm-gnueabihf@0.5.22':
optional: true
'@libsql/linux-arm-musleabihf@0.5.22':
optional: true
'@libsql/linux-arm64-gnu@0.5.22':
optional: true
'@libsql/linux-arm64-musl@0.5.22':
optional: true
'@libsql/linux-x64-gnu@0.5.22':
optional: true
'@libsql/linux-x64-musl@0.5.22':
optional: true
'@libsql/win32-x64-msvc@0.5.22':
optional: true
'@mapbox/node-pre-gyp@2.0.0':
dependencies:
consola: 3.4.2
@ -5494,8 +5293,6 @@ snapshots:
'@tybys/wasm-util': 0.9.0
optional: true
'@neon-rs/load@0.0.4': {}
'@netlify/functions@3.0.4':
dependencies:
'@netlify/serverless-functions-api': 1.36.0
@ -5683,7 +5480,7 @@ snapshots:
- utf-8-validate
- vite
'@nuxt/fonts@0.11.1(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
'@nuxt/fonts@0.11.1(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
dependencies:
'@nuxt/devtools-kit': 2.4.0(magicast@0.3.5)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
'@nuxt/kit': 3.16.2(magicast@0.3.5)
@ -5704,7 +5501,7 @@ snapshots:
ufo: 1.6.1
unifont: 0.1.7
unplugin: 2.3.2
unstorage: 1.15.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
unstorage: 1.15.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@ -5728,7 +5525,7 @@ snapshots:
- uploadthing
- vite
'@nuxt/image@1.10.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)':
'@nuxt/image@1.10.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)(magicast@0.3.5)':
dependencies:
'@nuxt/kit': 3.16.2(magicast@0.3.5)
consola: 3.4.2
@ -5741,7 +5538,7 @@ snapshots:
std-env: 3.9.0
ufo: 1.6.1
optionalDependencies:
ipx: 2.1.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
ipx: 2.1.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@ -6202,16 +5999,9 @@ snapshots:
'@types/node': 22.14.1
pg-protocol: 1.8.0
pg-types: 4.0.2
optional: true
'@types/resolve@1.20.2': {}
'@types/web-bluetooth@0.0.21': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 22.14.1
'@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@ -6505,19 +6295,6 @@ snapshots:
'@vue/shared@3.5.13': {}
'@vueuse/core@14.1.0(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.1.0
'@vueuse/shared': 14.1.0(vue@3.5.13(typescript@5.8.3))
vue: 3.5.13(typescript@5.8.3)
'@vueuse/metadata@14.1.0': {}
'@vueuse/shared@14.1.0(vue@3.5.13(typescript@5.8.3))':
dependencies:
vue: 3.5.13(typescript@5.8.3)
abbrev@3.0.1: {}
abort-controller@3.0.0:
@ -7014,16 +6791,13 @@ snapshots:
csstype@3.1.3: {}
daisyui@5.2.3: {}
data-uri-to-buffer@4.0.1: {}
daisyui@5.0.23: {}
date-fns@4.1.0: {}
db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)):
db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)):
optionalDependencies:
'@libsql/client': 0.15.15
drizzle-orm: 0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)
drizzle-orm: 0.42.0(@types/pg@8.11.13)(pg@8.14.1)
debug@3.2.7:
dependencies:
@ -7074,8 +6848,6 @@ snapshots:
detect-libc@1.0.3: {}
detect-libc@2.0.2: {}
detect-libc@2.0.3: {}
devalue@5.1.1: {}
@ -7125,9 +6897,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1):
drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1):
optionalDependencies:
'@libsql/client': 0.15.15
'@types/pg': 8.11.13
pg: 8.14.1
@ -7496,11 +7267,6 @@ snapshots:
optionalDependencies:
picomatch: 4.0.2
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@ -7560,10 +7326,6 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
fraction.js@4.3.7: {}
fresh@0.5.2: {}
@ -7823,7 +7585,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
ipx@2.1.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1):
ipx@2.1.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1):
dependencies:
'@fastify/accept-negotiator': 1.1.0
citty: 0.1.6
@ -7839,7 +7601,7 @@ snapshots:
sharp: 0.32.6
svgo: 3.3.2
ufo: 1.6.1
unstorage: 1.15.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
unstorage: 1.15.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
xss: 1.0.15
transitivePeerDependencies:
- '@azure/app-configuration'
@ -7963,8 +7725,6 @@ snapshots:
jiti@2.4.2: {}
js-base64@3.7.8: {}
js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
@ -8078,21 +7838,6 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
libsql@0.5.22:
dependencies:
'@neon-rs/load': 0.0.4
detect-libc: 2.0.2
optionalDependencies:
'@libsql/darwin-arm64': 0.5.22
'@libsql/darwin-x64': 0.5.22
'@libsql/linux-arm-gnueabihf': 0.5.22
'@libsql/linux-arm-musleabihf': 0.5.22
'@libsql/linux-arm64-gnu': 0.5.22
'@libsql/linux-arm64-musl': 0.5.22
'@libsql/linux-x64-gnu': 0.5.22
'@libsql/linux-x64-musl': 0.5.22
'@libsql/win32-x64-msvc': 0.5.22
lightningcss-darwin-arm64@1.29.2:
optional: true
@ -8330,7 +8075,7 @@ snapshots:
negotiator@0.6.3: {}
nitropack@2.11.9(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)):
nitropack@2.11.9(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
'@netlify/functions': 3.0.4
@ -8352,7 +8097,7 @@ snapshots:
cookie-es: 2.0.0
croner: 9.0.0
crossws: 0.3.4
db0: 0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1))
db0: 0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1))
defu: 6.1.4
destr: 2.0.5
dot-prop: 9.0.0
@ -8398,7 +8143,7 @@ snapshots:
unenv: 2.0.0-rc.15
unimport: 5.0.0
unplugin-utils: 0.2.4
unstorage: 1.15.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
unstorage: 1.15.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
untyped: 2.0.0
unwasm: 0.3.9
youch: 4.1.0-beta.7
@ -8440,20 +8185,12 @@ snapshots:
node-addon-api@7.1.1: {}
node-domexception@1.0.0: {}
node-fetch-native@1.6.6: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-forge@1.3.1: {}
node-gyp-build@4.8.4: {}
@ -8489,7 +8226,7 @@ snapshots:
dependencies:
boolbase: 1.0.0
nuxt@3.16.2(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@22.14.1)(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1))(eslint@9.24.0(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(yaml@2.7.1):
nuxt@3.16.2(@parcel/watcher@2.5.1)(@types/node@22.14.1)(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1))(eslint@9.24.0(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(yaml@2.7.1):
dependencies:
'@nuxt/cli': 3.24.1(magicast@0.3.5)
'@nuxt/devalue': 2.0.2
@ -8526,7 +8263,7 @@ snapshots:
mlly: 1.7.4
mocked-exports: 0.1.1
nanotar: 0.2.0
nitropack: 2.11.9(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1))
nitropack: 2.11.9(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1))
nypm: 0.6.0
ofetch: 1.4.1
ohash: 2.0.11
@ -8548,7 +8285,7 @@ snapshots:
unimport: 4.2.0
unplugin: 2.3.2
unplugin-vue-router: 0.12.0(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
unstorage: 1.15.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
unstorage: 1.15.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1)
untyped: 2.0.0
vue: 3.5.13(typescript@5.8.3)
vue-bundle-renderer: 2.1.1
@ -8622,8 +8359,7 @@ snapshots:
object-hash@3.0.0: {}
obuf@1.1.2:
optional: true
obuf@1.1.2: {}
ofetch@1.4.1:
dependencies:
@ -8775,22 +8511,17 @@ snapshots:
pg-cloudflare@1.1.1:
optional: true
pg-connection-string@2.7.0:
optional: true
pg-connection-string@2.7.0: {}
pg-int8@1.0.1:
optional: true
pg-int8@1.0.1: {}
pg-numeric@1.0.2:
optional: true
pg-numeric@1.0.2: {}
pg-pool@3.8.0(pg@8.14.1):
dependencies:
pg: 8.14.1
optional: true
pg-protocol@1.8.0:
optional: true
pg-protocol@1.8.0: {}
pg-types@2.2.0:
dependencies:
@ -8799,7 +8530,6 @@ snapshots:
postgres-bytea: 1.0.0
postgres-date: 1.0.7
postgres-interval: 1.2.0
optional: true
pg-types@4.0.2:
dependencies:
@ -8810,7 +8540,6 @@ snapshots:
postgres-date: 2.1.0
postgres-interval: 3.0.0
postgres-range: 1.1.4
optional: true
pg@8.14.1:
dependencies:
@ -8821,12 +8550,10 @@ snapshots:
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
optional: true
pgpass@1.0.5:
dependencies:
split2: 4.2.0
optional: true
picocolors@1.1.1: {}
@ -8836,14 +8563,6 @@ snapshots:
pify@2.3.0: {}
pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.16.2(magicast@0.3.5))(@pinia/nuxt@0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))))(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))):
dependencies:
defu: 6.1.4
optionalDependencies:
'@nuxt/kit': 3.16.2(magicast@0.3.5)
'@pinia/nuxt': 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
pinia: 3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))
pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)):
dependencies:
'@vue/devtools-api': 7.7.2
@ -9072,36 +8791,27 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
postgres-array@2.0.0:
optional: true
postgres-array@2.0.0: {}
postgres-array@3.0.4:
optional: true
postgres-array@3.0.4: {}
postgres-bytea@1.0.0:
optional: true
postgres-bytea@1.0.0: {}
postgres-bytea@3.0.0:
dependencies:
obuf: 1.1.2
optional: true
postgres-date@1.0.7:
optional: true
postgres-date@1.0.7: {}
postgres-date@2.1.0:
optional: true
postgres-date@2.1.0: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
optional: true
postgres-interval@3.0.0:
optional: true
postgres-interval@3.0.0: {}
postgres-range@1.1.4:
optional: true
postgres-range@1.1.4: {}
prebuild-install@7.1.3:
dependencies:
@ -9129,8 +8839,6 @@ snapshots:
process@0.11.10: {}
promise-limit@2.7.0: {}
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@ -9469,8 +9177,7 @@ snapshots:
speakingurl@14.0.1: {}
split2@4.2.0:
optional: true
split2@4.2.0: {}
stable-hash@0.0.5: {}
@ -9883,7 +9590,7 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.5.0
'@unrs/resolver-binding-win32-x64-msvc': 1.5.0
unstorage@1.15.0(db0@0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1):
unstorage@1.15.0(db0@0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1)))(ioredis@5.6.1):
dependencies:
anymatch: 3.1.3
chokidar: 4.0.3
@ -9894,7 +9601,7 @@ snapshots:
ofetch: 1.4.1
ufo: 1.6.1
optionalDependencies:
db0: 0.3.1(@libsql/client@0.15.15)(drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.11.13)(pg@8.14.1))
db0: 0.3.1(drizzle-orm@0.42.0(@types/pg@8.11.13)(pg@8.14.1))
ioredis: 5.6.1
untun@0.1.3:
@ -10081,8 +9788,6 @@ snapshots:
optionalDependencies:
typescript: 5.8.3
web-streams-polyfill@3.3.3: {}
webidl-conversions@3.0.1: {}
webpack-virtual-modules@0.6.2: {}
@ -10126,8 +9831,7 @@ snapshots:
cssfilter: 0.0.10
optional: true
xtend@4.0.2:
optional: true
xtend@4.0.2: {}
y18n@5.0.8: {}

View file

@ -1,8 +1,12 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/libsql';
import { drizzle } from 'drizzle-orm/node-postgres';
import { sql, eq, and } from 'drizzle-orm';
import arrayShuffle from 'array-shuffle';
import { tasks_advanced, questions_advanced, categories_db } from '~/db/schema';
import {
tasks_advanced,
questions_advanced,
categories_db,
} from '@/src/db/schema';
import type { AdvancedQuestion } from '~/types';
import categories from '~/categories';

View file

@ -1,8 +1,8 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/libsql';
import { drizzle } from 'drizzle-orm/node-postgres';
import { sql, eq, and } from 'drizzle-orm';
import arrayShuffle from 'array-shuffle';
import { tasks, questions, categories_db } from '~/db/schema';
import { tasks, questions, categories_db } from '@/src/db/schema';
import type { BasicQuestion } from '~/types';
import categories from '~/categories';

35
src/db/schema.ts Normal file
View file

@ -0,0 +1,35 @@
import { integer, pgTable, text, smallint, char } from 'drizzle-orm/pg-core';
export const tasks = pgTable('tasks', {
id: integer().notNull(),
correct_answer: text(),
media_url: text(),
weight: smallint(),
});
export const questions = pgTable('questions', {
task_id: integer(),
lang: char({ length: 2 }),
text: text(),
});
export const tasks_advanced = pgTable('tasks_advanced', {
id: integer().notNull(),
correct_answer: char({ length: 1 }),
media_url: text(),
weight: smallint(),
});
export const questions_advanced = pgTable('questions_advanced', {
task_id: integer(),
lang: char({ length: 2 }),
text: text(),
answer_a: text(),
answer_b: text(),
answer_c: text(),
});
export const categories_db = pgTable('categories', {
name: text(),
task_id: integer(),
});

View file

@ -1,54 +1,43 @@
export const useBasicStore = defineStore('basicStore', {
state: () => ({
basic: [] as ResultType[],
}),
actions: {
async set(basic: ResultType[]) {
this.basic = basic;
},
},
persist: {
storage: piniaPluginPersistedstate.localStorage(),
},
});
import { defineStore } from 'pinia';
export const useAdvancedStore = defineStore('advancedStore', {
state: () => ({
advanced: [] as ResultType[],
}),
actions: {
async set(advanced: ResultType[]) {
this.advanced = advanced;
},
},
persist: {
storage: piniaPluginPersistedstate.localStorage(),
},
});
export const useExamStore = defineStore('examStore', {
state: () => ({
category: '',
end: false,
}),
actions: {
async setCategory(category: string) {
this.category = category;
},
async setEnd(end: boolean) {
this.end = end;
},
async mildReset() {
this.end = false;
useBasicStore().set([]);
useAdvancedStore().set([]);
},
async resetExam() {
this.category = '';
this.mildReset();
},
},
persist: {
storage: piniaPluginPersistedstate.cookies(),
},
export const useExamStore = defineStore('exam-store', () => {
const category = ref('');
const end = ref(false);
const result: Ref<ResultEndType> = ref({
basic: [],
advanced: [],
});
function resetExam() {
category.value = '';
mildReset();
}
function mildReset() {
end.value = false;
result.value = {
basic: [],
advanced: [],
};
}
function setEnd(value: boolean) {
end.value = value;
return end.value;
}
function setCategory(value: string) {
category.value = value;
return category.value;
}
function setResult(value: ResultEndType) {
result.value = value;
return result.value;
}
return {
category,
end,
result,
resetExam,
mildReset,
setEnd,
setCategory,
setResult,
};
});

View file

@ -12,16 +12,13 @@ export interface AdvancedQuestion extends BasicQuestion {
answer_c: string | null;
}
export type Question = BasicQuestion | AdvancedQuestion | null | undefined;
export interface ResultType {
question: Question;
export interface ResultType<T> {
question: T | undefined;
chosen_answer: string;
correct_answer: string | null;
chosen_is_correct: boolean | undefined;
}
export interface ResultEndType {
basic: ResultType[];
advanced: ResultType[];
basic: ResultType<BasicQuestion>[];
advanced: ResultType<AdvancedQuestion>[];
}