diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..93117f8 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build & Push + +on: + push: + branches: [main] + +jobs: + build: + runs-on: [build, docker, rust] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login Gitea Registry + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.enuxia.fr \ + -u ${{ gitea.actor }} --password-stdin + + - name: Setup Docker Buildx + run: | + docker buildx create --use --name multiarch || true + + - name: Build & Push ARM64 + run: | + docker buildx build \ + --platform linux/arm64 \ + --tag git.enuxia.fr/enuxia-public/enuxia-quiz:latest \ + --tag git.enuxia.fr/enuxia-public/enuxia-quiz:${{ gitea.sha }} \ + --push \ + . + + - name: Logout + if: always() + run: docker logout git.enuxia.fr \ No newline at end of file diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..6f5d276 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Deploy Pi + +on: + workflow_run: + workflows: ["Build & Push"] + types: [completed] + +jobs: + deploy: + runs-on: [deploy-app] + if: ${{ gitea.event.workflow_run.conclusion == 'success' }} + steps: + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H ${{ secrets.PI_HOST }} >> ~/.ssh/known_hosts + + - name: Deploy sur le Pi + run: | + ssh -i ~/.ssh/deploy_key ${{ secrets.PI_USER }}@${{ secrets.PI_HOST }} \ + "docker login git.enuxia.fr -u luuna -p ${{ secrets.REGISTRY_TOKEN }} && \ + docker pull git.enuxia.fr/enuxia-public/enuxia-quiz:latest && \ + docker compose -f /opt/enuxia-quiz/docker-compose.yml --env-file /opt/enuxia-quiz/.env up -d --force-recreate && \ + docker logout git.enuxia.fr" + + - name: Cleanup SSH + if: always() + run: rm -f ~/.ssh/deploy_key \ No newline at end of file diff --git a/.gitea/workflows/traefik.yml b/.gitea/workflows/traefik.yml new file mode 100644 index 0000000..b457325 --- /dev/null +++ b/.gitea/workflows/traefik.yml @@ -0,0 +1,19 @@ +name: Traefik Config + +on: + push: + branches: [main] + paths: + - 'traefik/**' + +jobs: + traefik: + runs-on: [deploy-traefik] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy config Traefik + run: | + cp traefik/enuxia-quiz.yml /opt/gateway/traefik/dynamic/enuxia-quiz.yml + echo "✓ Config Traefik déployée" \ No newline at end of file diff --git a/.sqlx/query-062cb95763326b6381b2d63523c5256a246a127d68829b94954782ac635ca256.json b/.sqlx/query-062cb95763326b6381b2d63523c5256a246a127d68829b94954782ac635ca256.json new file mode 100644 index 0000000..bf61dc4 --- /dev/null +++ b/.sqlx/query-062cb95763326b6381b2d63523c5256a246a127d68829b94954782ac635ca256.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n q.id as question_id,\n q.text as question_text,\n q.section,\n COUNT(sa.id) as \"total_count: i64\",\n SUM(CASE WHEN sa.correct = 1 THEN 1 ELSE 0 END) as \"correct_count: i64\"\n FROM questions q\n LEFT JOIN student_answers sa ON sa.question_id = q.id\n LEFT JOIN submissions s ON s.id = sa.submission_id AND s.quiz_id = ?\n WHERE q.quiz_id = ?\n GROUP BY q.id\n ORDER BY q.position", + "describe": { + "columns": [ + { + "name": "question_id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "question_text", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "section", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "total_count: i64", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "correct_count: i64", + "ordinal": 4, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "062cb95763326b6381b2d63523c5256a246a127d68829b94954782ac635ca256" +} diff --git a/.sqlx/query-117fa7176ad7e0f41f277783739c417272eaaabd5a855e67f0a7113a3fee5963.json b/.sqlx/query-117fa7176ad7e0f41f277783739c417272eaaabd5a855e67f0a7113a3fee5963.json new file mode 100644 index 0000000..7b92f10 --- /dev/null +++ b/.sqlx/query-117fa7176ad7e0f41f277783739c417272eaaabd5a855e67f0a7113a3fee5963.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM quizzes WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "117fa7176ad7e0f41f277783739c417272eaaabd5a855e67f0a7113a3fee5963" +} diff --git a/.sqlx/query-1a8e18219acf70f6719777de288c63d7f7798a24652c219c53abbe7b1678bc42.json b/.sqlx/query-1a8e18219acf70f6719777de288c63d7f7798a24652c219c53abbe7b1678bc42.json new file mode 100644 index 0000000..f035aa2 --- /dev/null +++ b/.sqlx/query-1a8e18219acf70f6719777de288c63d7f7798a24652c219c53abbe7b1678bc42.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, first_name, last_name, score, total,\n submitted_at as \"submitted_at: chrono::DateTime\"\n FROM submissions WHERE quiz_id = ?\n ORDER BY last_name, first_name", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "score", + "ordinal": 4, + "type_info": "Integer" + }, + { + "name": "total", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "submitted_at: chrono::DateTime", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "1a8e18219acf70f6719777de288c63d7f7798a24652c219c53abbe7b1678bc42" +} diff --git a/.sqlx/query-1af19b597fe1a5246338a2ad060389cf2d8224c7267c20e4ef20d8e6cd475aea.json b/.sqlx/query-1af19b597fe1a5246338a2ad060389cf2d8224c7267c20e4ef20d8e6cd475aea.json new file mode 100644 index 0000000..5fb80cb --- /dev/null +++ b/.sqlx/query-1af19b597fe1a5246338a2ad060389cf2d8224c7267c20e4ef20d8e6cd475aea.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT title FROM quizzes WHERE id = ?", + "describe": { + "columns": [ + { + "name": "title", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "1af19b597fe1a5246338a2ad060389cf2d8224c7267c20e4ef20d8e6cd475aea" +} diff --git a/.sqlx/query-30364604e9c6881a3f3db4637e6a302e3c270f9612175bf8b494fbeb3f56b0ae.json b/.sqlx/query-30364604e9c6881a3f3db4637e6a302e3c270f9612175bf8b494fbeb3f56b0ae.json new file mode 100644 index 0000000..e4ffa60 --- /dev/null +++ b/.sqlx/query-30364604e9c6881a3f3db4637e6a302e3c270f9612175bf8b494fbeb3f56b0ae.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n EXISTS(\n SELECT 1 FROM submissions\n WHERE quiz_id = ?\n AND first_name = ?\n AND last_name = ?\n ) as existing,\n EXISTS(\n SELECT 1 FROM resets\n WHERE quiz_id = ?\n AND first_name = ?\n AND last_name = ?\n AND used = 0\n ) as has_reset", + "describe": { + "columns": [ + { + "name": "existing", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "has_reset", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 6 + }, + "nullable": [ + false, + false + ] + }, + "hash": "30364604e9c6881a3f3db4637e6a302e3c270f9612175bf8b494fbeb3f56b0ae" +} diff --git a/.sqlx/query-3de07cf64a7f17fc50ff39eb140daa0238124c94d81a8e295785909cecb1039a.json b/.sqlx/query-3de07cf64a7f17fc50ff39eb140daa0238124c94d81a8e295785909cecb1039a.json new file mode 100644 index 0000000..82269f9 --- /dev/null +++ b/.sqlx/query-3de07cf64a7f17fc50ff39eb140daa0238124c94d81a8e295785909cecb1039a.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO resets (quiz_id, first_name, last_name) VALUES (?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "3de07cf64a7f17fc50ff39eb140daa0238124c94d81a8e295785909cecb1039a" +} diff --git a/.sqlx/query-4d4aa56417a1b25dfe70ebad01085b46d2c2c0a1d6cc24de9fced1725dde4697.json b/.sqlx/query-4d4aa56417a1b25dfe70ebad01085b46d2c2c0a1d6cc24de9fced1725dde4697.json new file mode 100644 index 0000000..473994e --- /dev/null +++ b/.sqlx/query-4d4aa56417a1b25dfe70ebad01085b46d2c2c0a1d6cc24de9fced1725dde4697.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE questions SET text = ?, section = ?, position = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "4d4aa56417a1b25dfe70ebad01085b46d2c2c0a1d6cc24de9fced1725dde4697" +} diff --git a/.sqlx/query-50fadd296fda903f62d3adaa9e42db120aa267fde24595994f5f9136ec164a92.json b/.sqlx/query-50fadd296fda903f62d3adaa9e42db120aa267fde24595994f5f9136ec164a92.json new file mode 100644 index 0000000..e81b295 --- /dev/null +++ b/.sqlx/query-50fadd296fda903f62d3adaa9e42db120aa267fde24595994f5f9136ec164a92.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO questions (quiz_id, text, section, position) VALUES (?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "50fadd296fda903f62d3adaa9e42db120aa267fde24595994f5f9136ec164a92" +} diff --git a/.sqlx/query-56c3a62763c635c84ecbf468b074994103758c0903a25ed9e69b0573617d4264.json b/.sqlx/query-56c3a62763c635c84ecbf468b074994103758c0903a25ed9e69b0573617d4264.json new file mode 100644 index 0000000..3493f56 --- /dev/null +++ b/.sqlx/query-56c3a62763c635c84ecbf468b074994103758c0903a25ed9e69b0573617d4264.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, first_name, last_name, used as \"used: bool\"\n FROM resets WHERE quiz_id = ? ORDER BY created_at DESC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "used: bool", + "ordinal": 4, + "type_info": "Bool" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "56c3a62763c635c84ecbf468b074994103758c0903a25ed9e69b0573617d4264" +} diff --git a/.sqlx/query-583c153124bf780b5670eaca062a9758621502767612ffbcf761c78c6a04e3da.json b/.sqlx/query-583c153124bf780b5670eaca062a9758621502767612ffbcf761c78c6a04e3da.json new file mode 100644 index 0000000..6a50dcf --- /dev/null +++ b/.sqlx/query-583c153124bf780b5670eaca062a9758621502767612ffbcf761c78c6a04e3da.json @@ -0,0 +1,62 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, title, description,\n active as \"active: bool\",\n shuffle_questions as \"shuffle_questions: bool\",\n shuffle_answers as \"shuffle_answers: bool\",\n time_limit_seconds,\n created_at as \"created_at: chrono::DateTime\"\n FROM quizzes WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "title", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "active: bool", + "ordinal": 3, + "type_info": "Bool" + }, + { + "name": "shuffle_questions: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "shuffle_answers: bool", + "ordinal": 5, + "type_info": "Bool" + }, + { + "name": "time_limit_seconds", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "created_at: chrono::DateTime", + "ordinal": 7, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "583c153124bf780b5670eaca062a9758621502767612ffbcf761c78c6a04e3da" +} diff --git a/.sqlx/query-59ad0d1eb3ef477661c3a78cae712174c11dbd1df7684c0ddf8e1c24dcc0c4cc.json b/.sqlx/query-59ad0d1eb3ef477661c3a78cae712174c11dbd1df7684c0ddf8e1c24dcc0c4cc.json new file mode 100644 index 0000000..e9747ee --- /dev/null +++ b/.sqlx/query-59ad0d1eb3ef477661c3a78cae712174c11dbd1df7684c0ddf8e1c24dcc0c4cc.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, first_name, last_name, score, total,\n submitted_at as \"submitted_at: chrono::DateTime\"\n FROM submissions WHERE quiz_id = ? ORDER BY score DESC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "score", + "ordinal": 4, + "type_info": "Integer" + }, + { + "name": "total", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "submitted_at: chrono::DateTime", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "59ad0d1eb3ef477661c3a78cae712174c11dbd1df7684c0ddf8e1c24dcc0c4cc" +} diff --git a/.sqlx/query-6390d90322dd8e117398f7716133354c63fa08e9ae917dc976589ea6c7c4afcd.json b/.sqlx/query-6390d90322dd8e117398f7716133354c63fa08e9ae917dc976589ea6c7c4afcd.json new file mode 100644 index 0000000..e214193 --- /dev/null +++ b/.sqlx/query-6390d90322dd8e117398f7716133354c63fa08e9ae917dc976589ea6c7c4afcd.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE resets SET used = 1\n WHERE quiz_id = ?\n AND first_name = ?\n AND last_name = ?\n AND used = 0", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "6390d90322dd8e117398f7716133354c63fa08e9ae917dc976589ea6c7c4afcd" +} diff --git a/.sqlx/query-66e52beba899bcc08d4cd1586aa5785e1f1e74832a9242d32a09e40cd052072d.json b/.sqlx/query-66e52beba899bcc08d4cd1586aa5785e1f1e74832a9242d32a09e40cd052072d.json new file mode 100644 index 0000000..5cf395c --- /dev/null +++ b/.sqlx/query-66e52beba899bcc08d4cd1586aa5785e1f1e74832a9242d32a09e40cd052072d.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT correct as \"correct: bool\" FROM answers WHERE id = ?", + "describe": { + "columns": [ + { + "name": "correct: bool", + "ordinal": 0, + "type_info": "Bool" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "66e52beba899bcc08d4cd1586aa5785e1f1e74832a9242d32a09e40cd052072d" +} diff --git a/.sqlx/query-7291781f8aa6a64540c52f31dd1f7140fc847963f33098dc6f7bf6a9662aec48.json b/.sqlx/query-7291781f8aa6a64540c52f31dd1f7140fc847963f33098dc6f7bf6a9662aec48.json new file mode 100644 index 0000000..b51edd5 --- /dev/null +++ b/.sqlx/query-7291781f8aa6a64540c52f31dd1f7140fc847963f33098dc6f7bf6a9662aec48.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n q.text as q_text,\n q.section,\n a.label as chosen_label,\n a.text as chosen_text,\n sa.correct as \"is_correct: bool\",\n correct_a.label as correct_label,\n correct_a.text as correct_text\n FROM student_answers sa\n JOIN questions q ON q.id = sa.question_id\n JOIN answers a ON a.id = sa.answer_id\n JOIN answers correct_a ON correct_a.question_id = sa.question_id\n AND correct_a.correct = 1\n WHERE sa.submission_id = ?\n ORDER BY q.position", + "describe": { + "columns": [ + { + "name": "q_text", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "section", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "chosen_label", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "chosen_text", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "is_correct: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "correct_label", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "correct_text", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + true + ] + }, + "hash": "7291781f8aa6a64540c52f31dd1f7140fc847963f33098dc6f7bf6a9662aec48" +} diff --git a/.sqlx/query-73c411eceb3040317ef4ba3db04fdd089452faca291d0780b59eef19a7910d98.json b/.sqlx/query-73c411eceb3040317ef4ba3db04fdd089452faca291d0780b59eef19a7910d98.json new file mode 100644 index 0000000..900afeb --- /dev/null +++ b/.sqlx/query-73c411eceb3040317ef4ba3db04fdd089452faca291d0780b59eef19a7910d98.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM resets\n WHERE quiz_id = ?\n AND LOWER(TRIM(first_name)) = ?\n AND LOWER(TRIM(last_name)) = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "73c411eceb3040317ef4ba3db04fdd089452faca291d0780b59eef19a7910d98" +} diff --git a/.sqlx/query-81980500da33b7bfa75cc6e356f0064a45930edba049576eb5c82992784ebacc.json b/.sqlx/query-81980500da33b7bfa75cc6e356f0064a45930edba049576eb5c82992784ebacc.json new file mode 100644 index 0000000..1e5b97d --- /dev/null +++ b/.sqlx/query-81980500da33b7bfa75cc6e356f0064a45930edba049576eb5c82992784ebacc.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO student_answers (submission_id, question_id, answer_id, correct) VALUES (?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "81980500da33b7bfa75cc6e356f0064a45930edba049576eb5c82992784ebacc" +} diff --git a/.sqlx/query-974ce29db7c6d8f4084614c9027fdbce151dbe01a1f99f5f62b230fc93599790.json b/.sqlx/query-974ce29db7c6d8f4084614c9027fdbce151dbe01a1f99f5f62b230fc93599790.json new file mode 100644 index 0000000..7e3a18d --- /dev/null +++ b/.sqlx/query-974ce29db7c6d8f4084614c9027fdbce151dbe01a1f99f5f62b230fc93599790.json @@ -0,0 +1,62 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, title, description,\n active as \"active: bool\",\n shuffle_questions as \"shuffle_questions: bool\",\n shuffle_answers as \"shuffle_answers: bool\",\n time_limit_seconds,\n created_at as \"created_at: chrono::DateTime\"\n FROM quizzes WHERE active = 1 ORDER BY created_at DESC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "title", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "active: bool", + "ordinal": 3, + "type_info": "Bool" + }, + { + "name": "shuffle_questions: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "shuffle_answers: bool", + "ordinal": 5, + "type_info": "Bool" + }, + { + "name": "time_limit_seconds", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "created_at: chrono::DateTime", + "ordinal": 7, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "974ce29db7c6d8f4084614c9027fdbce151dbe01a1f99f5f62b230fc93599790" +} diff --git a/.sqlx/query-9c7521481bf857c51d7c395753e8fef96e74e419440b43ef5109d2914f309fca.json b/.sqlx/query-9c7521481bf857c51d7c395753e8fef96e74e419440b43ef5109d2914f309fca.json new file mode 100644 index 0000000..0a7d692 --- /dev/null +++ b/.sqlx/query-9c7521481bf857c51d7c395753e8fef96e74e419440b43ef5109d2914f309fca.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "9c7521481bf857c51d7c395753e8fef96e74e419440b43ef5109d2914f309fca" +} diff --git a/.sqlx/query-9dd35b887ec6a0a00cd85a0acbb420a45449e6982170970bb66aeef233a7d225.json b/.sqlx/query-9dd35b887ec6a0a00cd85a0acbb420a45449e6982170970bb66aeef233a7d225.json new file mode 100644 index 0000000..7f479a7 --- /dev/null +++ b/.sqlx/query-9dd35b887ec6a0a00cd85a0acbb420a45449e6982170970bb66aeef233a7d225.json @@ -0,0 +1,86 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n sa.id as sa_id,\n sa.submission_id,\n sa.question_id,\n sa.answer_id,\n sa.correct as \"sa_correct: bool\",\n q.quiz_id,\n q.text as q_text,\n q.section,\n q.position,\n a.label,\n a.text as a_text,\n a.correct as \"a_correct: bool\"\n FROM student_answers sa\n JOIN questions q ON q.id = sa.question_id\n JOIN answers a ON a.id = sa.answer_id\n WHERE sa.submission_id = ?\n ORDER BY q.position", + "describe": { + "columns": [ + { + "name": "sa_id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "submission_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "question_id", + "ordinal": 2, + "type_info": "Integer" + }, + { + "name": "answer_id", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "sa_correct: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "quiz_id", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "q_text", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "section", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "position", + "ordinal": 8, + "type_info": "Integer" + }, + { + "name": "label", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "a_text", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "a_correct: bool", + "ordinal": 11, + "type_info": "Bool" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "9dd35b887ec6a0a00cd85a0acbb420a45449e6982170970bb66aeef233a7d225" +} diff --git a/.sqlx/query-9f446fea4be730b92077288cf55a73b6efc65c44156c0bb7eb22b382aacb890b.json b/.sqlx/query-9f446fea4be730b92077288cf55a73b6efc65c44156c0bb7eb22b382aacb890b.json new file mode 100644 index 0000000..f8d9b86 --- /dev/null +++ b/.sqlx/query-9f446fea4be730b92077288cf55a73b6efc65c44156c0bb7eb22b382aacb890b.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT value FROM config WHERE key = ?", + "describe": { + "columns": [ + { + "name": "value", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "9f446fea4be730b92077288cf55a73b6efc65c44156c0bb7eb22b382aacb890b" +} diff --git a/.sqlx/query-a23c3648497b6093974a2488be819ff7a99fc9d59c109594896ddecb19204192.json b/.sqlx/query-a23c3648497b6093974a2488be819ff7a99fc9d59c109594896ddecb19204192.json new file mode 100644 index 0000000..3f74798 --- /dev/null +++ b/.sqlx/query-a23c3648497b6093974a2488be819ff7a99fc9d59c109594896ddecb19204192.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM questions WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "a23c3648497b6093974a2488be819ff7a99fc9d59c109594896ddecb19204192" +} diff --git a/.sqlx/query-a52776015ba245b114124874a0e250a41396250ef5e73f987cafb88318700e35.json b/.sqlx/query-a52776015ba245b114124874a0e250a41396250ef5e73f987cafb88318700e35.json new file mode 100644 index 0000000..e30e8ef --- /dev/null +++ b/.sqlx/query-a52776015ba245b114124874a0e250a41396250ef5e73f987cafb88318700e35.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO submissions (quiz_id, first_name, last_name, score, total) VALUES (?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 5 + }, + "nullable": [] + }, + "hash": "a52776015ba245b114124874a0e250a41396250ef5e73f987cafb88318700e35" +} diff --git a/.sqlx/query-a682287c0f20f270a0aa183f69c094fef56728ff6ea246d015e48942eeead7f4.json b/.sqlx/query-a682287c0f20f270a0aa183f69c094fef56728ff6ea246d015e48942eeead7f4.json new file mode 100644 index 0000000..e501d11 --- /dev/null +++ b/.sqlx/query-a682287c0f20f270a0aa183f69c094fef56728ff6ea246d015e48942eeead7f4.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO quizzes (title, description) VALUES (?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "a682287c0f20f270a0aa183f69c094fef56728ff6ea246d015e48942eeead7f4" +} diff --git a/.sqlx/query-b5ad1b275532a5fee969cd4dec66e1f7e5d6c6c2bafeb1bbfc6e84f86ee43e9e.json b/.sqlx/query-b5ad1b275532a5fee969cd4dec66e1f7e5d6c6c2bafeb1bbfc6e84f86ee43e9e.json new file mode 100644 index 0000000..3e6e0c4 --- /dev/null +++ b/.sqlx/query-b5ad1b275532a5fee969cd4dec66e1f7e5d6c6c2bafeb1bbfc6e84f86ee43e9e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE quizzes\n SET title = ?, description = ?, active = ?,\n shuffle_questions = ?, shuffle_answers = ?,\n time_limit_seconds = ?\n WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 7 + }, + "nullable": [] + }, + "hash": "b5ad1b275532a5fee969cd4dec66e1f7e5d6c6c2bafeb1bbfc6e84f86ee43e9e" +} diff --git a/.sqlx/query-c47c6e7f9402abc300085c720014ba9e10e1a1203a3451cd74e9866c4c9489bc.json b/.sqlx/query-c47c6e7f9402abc300085c720014ba9e10e1a1203a3451cd74e9866c4c9489bc.json new file mode 100644 index 0000000..784421c --- /dev/null +++ b/.sqlx/query-c47c6e7f9402abc300085c720014ba9e10e1a1203a3451cd74e9866c4c9489bc.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, text, section, position FROM questions WHERE quiz_id = ? ORDER BY position", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "text", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "section", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "position", + "ordinal": 4, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "c47c6e7f9402abc300085c720014ba9e10e1a1203a3451cd74e9866c4c9489bc" +} diff --git a/.sqlx/query-c554b8748e1cf2d03da9ba6137ac2726775d94165b38e7a953b469acf3fa23c5.json b/.sqlx/query-c554b8748e1cf2d03da9ba6137ac2726775d94165b38e7a953b469acf3fa23c5.json new file mode 100644 index 0000000..406e44c --- /dev/null +++ b/.sqlx/query-c554b8748e1cf2d03da9ba6137ac2726775d94165b38e7a953b469acf3fa23c5.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM submissions\n WHERE quiz_id = ?\n AND LOWER(TRIM(first_name)) = ?\n AND LOWER(TRIM(last_name)) = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "c554b8748e1cf2d03da9ba6137ac2726775d94165b38e7a953b469acf3fa23c5" +} diff --git a/.sqlx/query-c6596680247ad2ea057ebf95bffb9732d62f26782adc09c223f0fef8844eb577.json b/.sqlx/query-c6596680247ad2ea057ebf95bffb9732d62f26782adc09c223f0fef8844eb577.json new file mode 100644 index 0000000..ee20f2d --- /dev/null +++ b/.sqlx/query-c6596680247ad2ea057ebf95bffb9732d62f26782adc09c223f0fef8844eb577.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, question_id, label, text, correct as \"correct: bool\"\n FROM answers WHERE question_id = ? ORDER BY label", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "question_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "label", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "text", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "correct: bool", + "ordinal": 4, + "type_info": "Bool" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "c6596680247ad2ea057ebf95bffb9732d62f26782adc09c223f0fef8844eb577" +} diff --git a/.sqlx/query-cbf7717c5022a51f41241ed2aef99a916e4ff8c6c70e56b6f22633a0ed4e6bec.json b/.sqlx/query-cbf7717c5022a51f41241ed2aef99a916e4ff8c6c70e56b6f22633a0ed4e6bec.json new file mode 100644 index 0000000..6456b36 --- /dev/null +++ b/.sqlx/query-cbf7717c5022a51f41241ed2aef99a916e4ff8c6c70e56b6f22633a0ed4e6bec.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM answers WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "cbf7717c5022a51f41241ed2aef99a916e4ff8c6c70e56b6f22633a0ed4e6bec" +} diff --git a/.sqlx/query-cec25b15cafd60cab31cb9a6c8907e0a4fbb657186adf02eae9ca31033225340.json b/.sqlx/query-cec25b15cafd60cab31cb9a6c8907e0a4fbb657186adf02eae9ca31033225340.json new file mode 100644 index 0000000..df0520e --- /dev/null +++ b/.sqlx/query-cec25b15cafd60cab31cb9a6c8907e0a4fbb657186adf02eae9ca31033225340.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO answers (question_id, label, text, correct) VALUES (?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "cec25b15cafd60cab31cb9a6c8907e0a4fbb657186adf02eae9ca31033225340" +} diff --git a/.sqlx/query-cf3972c10a6ec56a774b76575abdab5eb9df8887fd5b214abf069d319882b79f.json b/.sqlx/query-cf3972c10a6ec56a774b76575abdab5eb9df8887fd5b214abf069d319882b79f.json new file mode 100644 index 0000000..9af7471 --- /dev/null +++ b/.sqlx/query-cf3972c10a6ec56a774b76575abdab5eb9df8887fd5b214abf069d319882b79f.json @@ -0,0 +1,80 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n sa.id as sa_id,\n sa.submission_id,\n sa.question_id,\n sa.answer_id,\n sa.correct as \"correct: bool\",\n q.text as question_text,\n q.section,\n q.position,\n q.quiz_id,\n a.label,\n a.text as answer_text\n FROM student_answers sa\n JOIN questions q ON q.id = sa.question_id\n JOIN answers a ON a.id = sa.answer_id\n WHERE sa.submission_id = ?\n ORDER BY q.position", + "describe": { + "columns": [ + { + "name": "sa_id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "submission_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "question_id", + "ordinal": 2, + "type_info": "Integer" + }, + { + "name": "answer_id", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "correct: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "question_text", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "section", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "position", + "ordinal": 7, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 8, + "type_info": "Integer" + }, + { + "name": "label", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "answer_text", + "ordinal": 10, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "cf3972c10a6ec56a774b76575abdab5eb9df8887fd5b214abf069d319882b79f" +} diff --git a/.sqlx/query-d399cb531bf1e4e0e4fd5eef1ed341974401adddda33d27ed81870a20d391597.json b/.sqlx/query-d399cb531bf1e4e0e4fd5eef1ed341974401adddda33d27ed81870a20d391597.json new file mode 100644 index 0000000..797ab19 --- /dev/null +++ b/.sqlx/query-d399cb531bf1e4e0e4fd5eef1ed341974401adddda33d27ed81870a20d391597.json @@ -0,0 +1,62 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, title, description,\n active as \"active: bool\",\n shuffle_questions as \"shuffle_questions: bool\",\n shuffle_answers as \"shuffle_answers: bool\",\n time_limit_seconds,\n created_at as \"created_at: chrono::DateTime\"\n FROM quizzes ORDER BY created_at DESC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "title", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "active: bool", + "ordinal": 3, + "type_info": "Bool" + }, + { + "name": "shuffle_questions: bool", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "shuffle_answers: bool", + "ordinal": 5, + "type_info": "Bool" + }, + { + "name": "time_limit_seconds", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "created_at: chrono::DateTime", + "ordinal": 7, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "d399cb531bf1e4e0e4fd5eef1ed341974401adddda33d27ed81870a20d391597" +} diff --git a/.sqlx/query-dd1ada605f9406c7aab473cb9f8cc2e655968450f985882b9382d3bce738a99b.json b/.sqlx/query-dd1ada605f9406c7aab473cb9f8cc2e655968450f985882b9382d3bce738a99b.json new file mode 100644 index 0000000..8c7f068 --- /dev/null +++ b/.sqlx/query-dd1ada605f9406c7aab473cb9f8cc2e655968450f985882b9382d3bce738a99b.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE answers SET label = ?, text = ?, correct = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "dd1ada605f9406c7aab473cb9f8cc2e655968450f985882b9382d3bce738a99b" +} diff --git a/.sqlx/query-ea18f88dbfe1f03d56e14710420b2d817070c6508047902b3e2c7aef0d051134.json b/.sqlx/query-ea18f88dbfe1f03d56e14710420b2d817070c6508047902b3e2c7aef0d051134.json new file mode 100644 index 0000000..b5e59d3 --- /dev/null +++ b/.sqlx/query-ea18f88dbfe1f03d56e14710420b2d817070c6508047902b3e2c7aef0d051134.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n EXISTS(\n SELECT 1 FROM submissions\n WHERE quiz_id = ?\n AND LOWER(TRIM(first_name)) = ?\n AND LOWER(TRIM(last_name)) = ?\n ) as existing,\n EXISTS(\n SELECT 1 FROM resets\n WHERE quiz_id = ?\n AND first_name = ?\n AND last_name = ?\n AND used = 0\n ) as has_reset", + "describe": { + "columns": [ + { + "name": "existing", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "has_reset", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 6 + }, + "nullable": [ + false, + false + ] + }, + "hash": "ea18f88dbfe1f03d56e14710420b2d817070c6508047902b3e2c7aef0d051134" +} diff --git a/.sqlx/query-eec63079f7405e41a004aa8d23554a719e78c2df5e5bfd62f8f5d5a42d201243.json b/.sqlx/query-eec63079f7405e41a004aa8d23554a719e78c2df5e5bfd62f8f5d5a42d201243.json new file mode 100644 index 0000000..73fecfb --- /dev/null +++ b/.sqlx/query-eec63079f7405e41a004aa8d23554a719e78c2df5e5bfd62f8f5d5a42d201243.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, first_name, last_name, score, total,\n submitted_at as \"submitted_at: chrono::DateTime\"\n FROM submissions WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "score", + "ordinal": 4, + "type_info": "Integer" + }, + { + "name": "total", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "submitted_at: chrono::DateTime", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "eec63079f7405e41a004aa8d23554a719e78c2df5e5bfd62f8f5d5a42d201243" +} diff --git a/.sqlx/query-fe46285200e0d3a53ab65d413cf8e44fa4833b58431b6e385f9bc3f95c1b2cdd.json b/.sqlx/query-fe46285200e0d3a53ab65d413cf8e44fa4833b58431b6e385f9bc3f95c1b2cdd.json new file mode 100644 index 0000000..048850f --- /dev/null +++ b/.sqlx/query-fe46285200e0d3a53ab65d413cf8e44fa4833b58431b6e385f9bc3f95c1b2cdd.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, quiz_id, first_name, last_name, score, total,\n submitted_at as \"submitted_at: chrono::DateTime\"\n FROM submissions WHERE quiz_id = ?\n ORDER BY submitted_at DESC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "quiz_id", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "first_name", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "last_name", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "score", + "ordinal": 4, + "type_info": "Integer" + }, + { + "name": "total", + "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "submitted_at: chrono::DateTime", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "fe46285200e0d3a53ab65d413cf8e44fa4833b58431b6e385f9bc3f95c1b2cdd" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2d9862d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +# ── Stage 1 : Builder ───────────────────────────────────────────────────────── +FROM --platform=linux/amd64 rust:1.85-slim AS builder + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + curl \ + git \ + clang \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +RUN rustup target add wasm32-unknown-unknown +RUN rustup target add aarch64-unknown-linux-gnu +RUN cargo install cargo-leptos --locked + +ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc +ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc + +WORKDIR /app + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY style ./style +COPY public ./public +COPY package*.json ./ +COPY .sqlx ./.sqlx + +RUN npm install + +ENV LEPTOS_TAILWIND_VERSION=v4.1.13 +ENV SQLX_OFFLINE=true + +RUN cargo leptos build --release \ + --bin-target-triple aarch64-unknown-linux-gnu + +# ── Stage 2 : Runtime ───────────────────────────────────────────────────────── +FROM --platform=linux/arm64 debian:bookworm-slim AS runtime + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /app/target/aarch64-unknown-linux-gnu/release/enuxia-quiz ./ +COPY --from=builder /app/target/site ./site + +RUN mkdir -p /data + +ENV LEPTOS_OUTPUT_NAME=enuxia-quiz +ENV LEPTOS_SITE_ROOT=/app/site +ENV LEPTOS_SITE_ADDR=0.0.0.0:3000 +ENV DATABASE_URL=sqlite:///data/quiz.db + +EXPOSE 3000 + +CMD ["./enuxia-quiz"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..36a90d2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + enuxia-quiz: + image: git.enuxia.fr/enuxia-public/enuxia-quiz:latest + container_name: enuxia-quiz + restart: unless-stopped + ports: + - "3010:3000" + volumes: + - /opt/enuxia-quiz/data:/data + environment: + - DATABASE_URL=sqlite:///data/quiz.db + - SESSION_PASSWORD=${SESSION_PASSWORD} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} \ No newline at end of file diff --git a/traefik/enuxia-quiz.yml b/traefik/enuxia-quiz.yml new file mode 100644 index 0000000..5ca6b2e --- /dev/null +++ b/traefik/enuxia-quiz.yml @@ -0,0 +1,30 @@ +http: + routers: + # ── Étudiants (public) ────────────────────────────────────────────────── + enuxia-quiz-public: + rule: "Host(`quiz.enuxia.fr`)" + entryPoints: + - websecure + middlewares: + - public-chain@file + service: enuxia-quiz-svc + tls: + certResolver: lehttp + + # ── Admin (VPN uniquement) ────────────────────────────────────────────── + enuxia-quiz-admin: + rule: "Host(`admin-quiz.enuxia.fr`)" + entryPoints: + - websecure + middlewares: + - vpn-only@file + - sensitive-chain@file + service: enuxia-quiz-svc + tls: + certResolver: lehttp + + services: + enuxia-quiz-svc: + loadBalancer: + servers: + - url: "http://100.91.166.9:3010" \ No newline at end of file