├── .deepsource.toml ├── .dockerignore ├── .flake8 ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Feature_request.md │ ├── Repo_architecture_changes.md │ └── bug_report.yml ├── PULL_REQUEST_TEMPLATE.md ├── codecov.yml └── workflows │ ├── backend_lint.yml │ ├── build_backend.yml │ ├── build_frontend.yml │ ├── frontend_lint.yml │ └── pytest.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .reuse └── dep5 ├── .tokeignore ├── .vscode └── settings.json ├── CONTACT.md ├── CONTRIBUTING.md ├── Caddyfile ├── Caddyfile-docker ├── Dockerfile ├── LICENSE ├── LICENSES ├── MIT.txt └── MPL-2.0.txt ├── Pipfile ├── Pipfile.lock ├── Pipfile.lock.license ├── README.md ├── SocketIo.md ├── alembic.ini ├── classquiz ├── __init__.py ├── auth.py ├── cache.py ├── config.py ├── db │ ├── __init__.py │ ├── models.py │ └── quiztivity.py ├── emails │ ├── __init__.py │ └── templates │ │ ├── footer.jinja2 │ │ ├── forgotten_password.jinja2 │ │ └── register.jinja2 ├── helpers │ ├── __init__.py │ ├── avatar.py │ ├── box_controller.py │ ├── hashcash.py │ └── pixabay.py ├── kahoot_importer │ ├── README.md │ ├── __init__.py │ ├── get.py │ ├── import_quiz.py │ └── search.py ├── oauth │ ├── __init__.py │ ├── authenticate_user.py │ ├── custom.py │ ├── github.py │ ├── google.py │ └── init_oauth.py ├── routers │ ├── __init__.py │ ├── admin.py │ ├── avatar.py │ ├── box_controller │ │ ├── __init__.py │ │ ├── embedded │ │ │ ├── __init__.py │ │ │ └── socket.py │ │ ├── embedded_ws.md │ │ └── web.py │ ├── community.py │ ├── cqa-file-format.md │ ├── editor.py │ ├── eximport.py │ ├── live.py │ ├── login.py │ ├── moderation.py │ ├── pixabay.py │ ├── quiz.py │ ├── quiztivity │ │ ├── __init__.py │ │ └── shares.py │ ├── remote.py │ ├── results.py │ ├── search.py │ ├── sitemap.py │ ├── stats.py │ ├── storage.py │ ├── testing_routes.py │ ├── users │ │ ├── __init__.py │ │ ├── twofa.py │ │ └── webauthn.py │ └── utils.py ├── socket_server │ ├── __init__.py │ ├── export_helpers.py │ └── session.py ├── storage │ ├── __init__.py │ ├── errors.py │ ├── local_storage.py │ └── s3_storage.py ├── tests │ ├── __init__.py │ ├── test_auth.py │ ├── test_kahoot_get.py │ ├── test_kahoot_import.py │ ├── test_kahoot_search.py │ ├── test_server.py │ └── test_storage.py └── worker │ ├── __init__.py │ └── storage.py ├── docker-compose.dev.yml ├── docker-compose.yml ├── frontend ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierrc ├── .prettierrc.license ├── Dockerfile ├── README.md ├── i18next-scanner.config.engine.cjs ├── package.json ├── package.json.license ├── pnpm-lock.yaml ├── pnpm-lock.yaml.license ├── postcss.config.cjs ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── env.d.ts │ ├── hooks.server.ts │ ├── lib │ │ ├── Map.svelte │ │ ├── Spinner.svelte │ │ ├── admin.svelte │ │ ├── admin.ts │ │ ├── assets │ │ │ ├── landing │ │ │ │ ├── .gitignore │ │ │ │ ├── admin_overview.webp │ │ │ │ ├── admin_overview.webp.license │ │ │ │ ├── import-select-mockup.webp │ │ │ │ ├── import-select-mockup.webp.license │ │ │ │ ├── opengraph-home.jpg │ │ │ │ ├── opengraph-home.jpg.license │ │ │ │ ├── opengraph-home.webp │ │ │ │ ├── opengraph-home.webp.license │ │ │ │ ├── overview-mockup.webp │ │ │ │ ├── overview-mockup.webp.license │ │ │ │ ├── results-mockup.webp │ │ │ │ ├── results-mockup.webp.license │ │ │ │ ├── results.webp │ │ │ │ ├── results.webp.license │ │ │ │ ├── select-mockup.webp │ │ │ │ ├── select-mockup.webp.license │ │ │ │ ├── select.webp │ │ │ │ ├── select.webp.license │ │ │ │ ├── solution-mockup.webp │ │ │ │ ├── solution-mockup.webp.license │ │ │ │ ├── solution.webp │ │ │ │ └── solution.webp.license │ │ │ ├── landing_new │ │ │ │ ├── edit.webp │ │ │ │ ├── edit.webp.license │ │ │ │ ├── edit_org.webp │ │ │ │ ├── edit_org.webp.license │ │ │ │ ├── find.webp │ │ │ │ ├── find.webp.license │ │ │ │ ├── find_org.webp │ │ │ │ ├── find_org.webp.license │ │ │ │ ├── import.webp │ │ │ │ ├── import.webp.license │ │ │ │ ├── import_org.webp │ │ │ │ ├── import_org.webp.license │ │ │ │ ├── result-admin.webp │ │ │ │ ├── result-admin.webp.license │ │ │ │ ├── result-admin_org.webp │ │ │ │ ├── result-admin_org.webp.license │ │ │ │ ├── result.webp │ │ │ │ ├── result.webp.license │ │ │ │ ├── result_org.webp │ │ │ │ ├── result_org.webp.license │ │ │ │ ├── select.webp │ │ │ │ ├── select.webp.license │ │ │ │ ├── select_org.webp │ │ │ │ ├── select_org.webp.license │ │ │ │ ├── solution.webp │ │ │ │ ├── solution.webp.license │ │ │ │ ├── solution_org.webp │ │ │ │ ├── solution_org.webp.license │ │ │ │ ├── winners.webp │ │ │ │ ├── winners.webp.license │ │ │ │ ├── winners_org.webp │ │ │ │ └── winners_org.webp.license │ │ │ └── music │ │ │ │ ├── 1-128.mp3 │ │ │ │ ├── 1-128.mp3.license │ │ │ │ ├── 1_original.mp3 │ │ │ │ └── 1_original.mp3.license │ │ ├── clickOutside.js │ │ ├── collapsible.svelte │ │ ├── components │ │ │ ├── DownloadQuiz.svelte │ │ │ ├── buttons │ │ │ │ ├── brown.svelte │ │ │ │ └── gray.svelte │ │ │ ├── commandpalette.svelte │ │ │ ├── controller │ │ │ │ └── code.svelte │ │ │ └── popover │ │ │ │ ├── commandpalettenotice.svelte │ │ │ │ ├── smalltop.svelte │ │ │ │ └── smalltop.ts │ │ ├── config.ts │ │ ├── dashboard │ │ │ ├── main_slider.svelte │ │ │ ├── start_game.svelte │ │ │ ├── start_game_background.svg │ │ │ └── useViewportAction.js │ │ ├── editor.svelte │ │ ├── editor │ │ │ ├── ABCDEditorPart.svelte │ │ │ ├── AddNewQuestionPopup.svelte │ │ │ ├── MediaComponent.svelte │ │ │ ├── OrderEditorPart.svelte │ │ │ ├── RangeSelectorEditorPart.svelte │ │ │ ├── TextEditorPart.svelte │ │ │ ├── VotingEditorPart.svelte │ │ │ ├── card.svelte │ │ │ ├── checker-bg.svg │ │ │ ├── settings-card.svelte │ │ │ ├── sidebar.svelte │ │ │ ├── slide.svelte │ │ │ ├── slides │ │ │ │ ├── edit_menu.svelte │ │ │ │ ├── element_selection.svelte │ │ │ │ ├── icons │ │ │ │ │ └── x-circle.svg │ │ │ │ ├── settings_menu.svelte │ │ │ │ └── types │ │ │ │ │ ├── circle.svelte │ │ │ │ │ ├── headline.svelte │ │ │ │ │ ├── rectangle.svelte │ │ │ │ │ └── text.svelte │ │ │ ├── uploader.svelte │ │ │ └── uploader │ │ │ │ ├── Library.svelte │ │ │ │ └── Pixabay.svelte │ │ ├── files │ │ │ └── dashboard.svelte │ │ ├── footer.svelte │ │ ├── hashcash.ts │ │ ├── helpers.ts │ │ ├── hljs.css │ │ ├── i18n │ │ │ ├── i18n-service.ts │ │ │ ├── index.ts │ │ │ ├── locales │ │ │ │ ├── ar.json │ │ │ │ ├── ar.json.license │ │ │ │ ├── ca.json │ │ │ │ ├── ca.json.license │ │ │ │ ├── cs.json │ │ │ │ ├── cs.json.license │ │ │ │ ├── de.json │ │ │ │ ├── de.json.license │ │ │ │ ├── en.json │ │ │ │ ├── en.json.license │ │ │ │ ├── es.json │ │ │ │ ├── es.json.license │ │ │ │ ├── et.json │ │ │ │ ├── eu.json │ │ │ │ ├── fr.json │ │ │ │ ├── fr.json.license │ │ │ │ ├── hu.json │ │ │ │ ├── hu.json.license │ │ │ │ ├── id.json │ │ │ │ ├── id.json.license │ │ │ │ ├── it.json │ │ │ │ ├── it.json.license │ │ │ │ ├── ja.json │ │ │ │ ├── ja.json.license │ │ │ │ ├── nb_NO.json │ │ │ │ ├── nb_NO.json.license │ │ │ │ ├── nl.json │ │ │ │ ├── nl.json.license │ │ │ │ ├── nn.json │ │ │ │ ├── nn.json.license │ │ │ │ ├── pl.json │ │ │ │ ├── pl.json.license │ │ │ │ ├── pt.json │ │ │ │ ├── pt.json.license │ │ │ │ ├── ru.json │ │ │ │ ├── ta.json │ │ │ │ ├── tr.json │ │ │ │ ├── tr.json.license │ │ │ │ ├── uk.json │ │ │ │ ├── uk.json.license │ │ │ │ ├── uz.json │ │ │ │ ├── uz.json.license │ │ │ │ ├── vi.json │ │ │ │ ├── vi.json.license │ │ │ │ ├── zh_Hans.json │ │ │ │ ├── zh_Hans.json.license │ │ │ │ ├── zh_Hant.json │ │ │ │ └── zh_Hant.json.license │ │ │ ├── translation-service.ts │ │ │ └── translations.ts │ │ ├── inline-editor.svelte │ │ ├── inline-editor_new_trash.svelte │ │ ├── landing │ │ │ ├── landing-promo.svelte │ │ │ ├── newsletter.svelte │ │ │ └── testimonials.svelte │ │ ├── language-toggle.svelte │ │ ├── modals │ │ │ └── alert.svelte │ │ ├── navbar.svelte │ │ ├── play │ │ │ ├── admin │ │ │ │ ├── controls.svelte │ │ │ │ ├── final_results.svelte │ │ │ │ ├── game_not_started.svelte │ │ │ │ ├── question.svelte │ │ │ │ ├── results.svelte │ │ │ │ ├── slide.svelte │ │ │ │ └── voting_results.svelte │ │ │ ├── audio_player.svelte │ │ │ ├── circular_progress.svelte │ │ │ ├── end.svelte │ │ │ ├── join.svelte │ │ │ ├── kahoot_mode_assets │ │ │ │ ├── 0.svg │ │ │ │ ├── 1.svg │ │ │ │ ├── 2.svg │ │ │ │ ├── 3.svg │ │ │ │ └── kahoot_icons.ts │ │ │ ├── question.svelte │ │ │ ├── questions │ │ │ │ └── check.svelte │ │ │ ├── results_kahoot.svelte │ │ │ ├── show_results.svelte │ │ │ └── title.svelte │ │ ├── practice │ │ │ ├── question.svelte │ │ │ └── title_screen.svelte │ │ ├── quiz_image_uncover.svelte │ │ ├── quiz_types.ts │ │ ├── quiztivity │ │ │ ├── add_new_slide.svelte │ │ │ ├── components │ │ │ │ ├── abcd │ │ │ │ │ ├── edit.svelte │ │ │ │ │ └── play.svelte │ │ │ │ ├── markdown │ │ │ │ │ ├── edit.svelte │ │ │ │ │ └── play.svelte │ │ │ │ ├── memory │ │ │ │ │ ├── edit.svelte │ │ │ │ │ └── play.svelte │ │ │ │ └── pdf │ │ │ │ │ └── edit.svelte │ │ │ ├── editor.svelte │ │ │ ├── shares_popover.svelte │ │ │ └── types.ts │ │ ├── search-card.svelte │ │ ├── socket.ts │ │ ├── stores.ts │ │ ├── tinykeys.ts │ │ ├── view_quiz │ │ │ ├── Hoverable.svelte │ │ │ ├── RatingComponent.svelte │ │ │ └── imported_or_not.svelte │ │ └── yupSchemas.ts │ └── routes │ │ ├── +error.svelte │ │ ├── +layout.server.ts │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ ├── account │ │ ├── +layout.svelte │ │ ├── controllers │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── [controller_id] │ │ │ │ ├── +page.svelte │ │ │ │ ├── +page.ts │ │ │ │ ├── SaveIndicator.svelte │ │ │ │ └── commons.ts │ │ │ └── add │ │ │ │ ├── +page.svelte │ │ │ │ ├── +page.ts │ │ │ │ └── wait │ │ │ │ ├── +page.server.ts │ │ │ │ └── +page.svelte │ │ ├── login │ │ │ ├── +page.server.ts │ │ │ ├── +page.svelte │ │ │ ├── backup_component.svelte │ │ │ ├── oauth_block.svelte │ │ │ ├── password_component.svelte │ │ │ ├── select_method.svelte │ │ │ ├── start_window.svelte │ │ │ ├── totp_component.svelte │ │ │ ├── verified_badge.svelte │ │ │ └── webauthn_component.svelte │ │ ├── oauth-error │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ ├── password-reset │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ ├── register │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ ├── reset-password │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ └── settings │ │ │ ├── +page.server.ts │ │ │ ├── +page.svelte │ │ │ ├── avatar │ │ │ └── +page.svelte │ │ │ └── security │ │ │ ├── +page.svelte │ │ │ ├── backup_codes.svelte │ │ │ └── totp_setup.svelte │ │ ├── admin │ │ ├── +page.server.ts │ │ └── +page.svelte │ │ ├── controller │ │ └── +page.svelte │ │ ├── create │ │ ├── +page.server.ts │ │ └── +page.svelte │ │ ├── dashboard │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ ├── +page.ts │ │ ├── Analytics.svelte │ │ └── files │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ ├── docs │ │ ├── (markdown) │ │ │ ├── +layout.svelte │ │ │ ├── classquizcontroller │ │ │ │ └── +page.svx │ │ │ ├── features │ │ │ │ ├── +page.svx │ │ │ │ ├── custom-field │ │ │ │ │ └── +page.svx │ │ │ │ └── remote-control │ │ │ │ │ └── +page.svx │ │ │ └── quiz │ │ │ │ └── question-types │ │ │ │ ├── +page.svx │ │ │ │ ├── check-choice │ │ │ │ └── +page.svx │ │ │ │ ├── multiple-choice │ │ │ │ └── +page.svx │ │ │ │ ├── order │ │ │ │ └── +page.svx │ │ │ │ ├── range │ │ │ │ └── +page.svx │ │ │ │ ├── text │ │ │ │ └── +page.svx │ │ │ │ └── voting │ │ │ │ └── +page.svx │ │ ├── +layout.svelte │ │ ├── +page.js │ │ ├── +page.svelte │ │ ├── attribution │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── develop │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── import-from-kahoot │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── pow │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── privacy-policy │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── roadmap │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── self-host │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ └── tos │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── edit │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ ├── files │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── uploader.svelte │ │ └── videos │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ ├── explore │ │ ├── +page.svelte │ │ └── +page.ts │ │ ├── import │ │ ├── +page.server.ts │ │ └── +page.svelte │ │ ├── moderation │ │ ├── +page.svelte │ │ └── +page.ts │ │ ├── overview │ │ └── +server.ts │ │ ├── play │ │ ├── +page.server.ts │ │ └── +page.svelte │ │ ├── practice │ │ └── +page.svelte │ │ ├── quiztivity │ │ ├── create │ │ │ └── +page.svelte │ │ ├── edit │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ ├── play │ │ │ ├── +error.svelte │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── navigation_bar.svelte │ │ └── share │ │ │ └── [share_id] │ │ │ └── +page.server.ts │ │ ├── remote │ │ └── +page.svelte │ │ ├── results │ │ ├── +page.svelte │ │ ├── +page.ts │ │ └── [result_id] │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── general_overview.svelte │ │ │ ├── player_overview.svelte │ │ │ ├── question_overview.svelte │ │ │ └── question_tab_thing.svelte │ │ ├── search │ │ └── +page.svelte │ │ ├── user │ │ └── [user_id] │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ └── view │ │ ├── +error.svelte │ │ └── [quiz_id] │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ └── ModComponent.svelte ├── static │ ├── android-chrome-192x192.png │ ├── android-chrome-192x192.png.license │ ├── android-chrome-512x512.png │ ├── android-chrome-512x512.png.license │ ├── apple-touch-icon.png │ ├── apple-touch-icon.png.license │ ├── favicon-16x16.png │ ├── favicon-16x16.png.license │ ├── favicon-32x32.png │ ├── favicon-32x32.png.license │ ├── favicon.ico │ ├── favicon.ico.license │ ├── site.webmanifest │ └── site.webmanifest.license ├── svelte.config.js ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.json.license └── vite.config.js ├── gunicorn_conf.py ├── image_cleanup.py ├── import_to_meili.py ├── live_redis_data.md ├── logo.png ├── logo.png.license ├── migration_to_rust.md ├── migrations ├── README ├── env.py ├── script.py.mako └── versions │ ├── 0c081a52ab8a_added_google_oauth.py │ ├── 17ea75679da8_init.py │ ├── 230ac26db527_added_ratings_plays_and_views_to_quiz.py │ ├── 25f2c34a69c8_added_webauthn.py │ ├── 2ed6823c69b2_added_proper_delete_actions.py │ ├── 32649a1ffcf2_added_controllers_table.py │ ├── 3f63c0130bce_added_instance_data.py │ ├── 400f8ed06c48_added_background_color.py │ ├── 438516c09cf3_added_game_results.py │ ├── 4bbe1850b61a_added_1.py │ ├── 694cb11c6886_added_totp.py │ ├── 6dc09ad6f6ef_imported_from_kahoot_field.py │ ├── 7ad8502af419_added_kahoot_id.py │ ├── 7afe98d04169_added_custom_openid.py │ ├── 820e06ef2c2a_added_background_image.py │ ├── 89c4b5d547aa_new_storage.py │ ├── 8ac2bed1718e_added_quiztivityshares.py │ ├── 901dfcdf8d38_added_backup_code.py │ ├── 97144a8cf6b6_added_github_user_id.py │ ├── 9d7fa2e6b24c_added_mod_rating.py │ ├── b2acaede5c2f_made_title_and_description_be_text_not_.py │ ├── cda6903dfc0c_added_cover_image.py │ ├── da778d551bf4_added_github_oauth.py │ ├── ec6cf07ff68a_added_apikey.py │ └── ff573859eb32_user_avatar.py ├── prestart.sh ├── pyproject.toml ├── pytest.ini ├── run_tests.sh ├── simulate_players.py └── start.sh /.deepsource.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | version = 1 5 | test_patterns = ["classquiz/tests/**", "test_*.py"] 6 | 7 | 8 | [[analyzers]] 9 | name = "python" 10 | enabled = true 11 | 12 | [analyzers.meta] 13 | runtime_version = "3.x.x" 14 | 15 | [[analyzers]] 16 | name = "javascript" 17 | enabled = true 18 | 19 | #[[analyzers]] 20 | #name = "test-coverage" 21 | #enabled = false 22 | 23 | [[transformers]] 24 | name = "black" 25 | enabled = true 26 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | .env* 5 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | [flake8] 5 | max-line-length = 120 6 | extend-ignore = E203 7 | exclude = ./migrations/versions/, 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | ko_fi: mawoka 5 | liberapay: Mawoka 6 | github: mawoka-myblock 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | 6 | --- 7 | name: Feature request 8 | about: Make a clear demand for improving ClassQuiz 💪 9 | 10 | --- 11 | 12 | **Describe the bug** 13 | A clear and concise description of the new feature you propose. 14 | 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your feature. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | 22 | Thanks for contributing to ClassQuiz! 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Repo_architecture_changes.md: -------------------------------------------------------------------------------- 1 | 6 | --- 7 | name: Project refactoring 8 | about: All issues concerning CI/CD, improvements of documentation, development setup etc. 🔧 9 | 10 | --- 11 | 12 | **Describe the change and why it is needed** 13 | A clear and concise description of the improvement you're asking for. 14 | 15 | 16 | **Additional context** 17 | Add any other context about the problem here. 18 | 19 | Thanks for contributing to ClassQuiz! 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | name: Bug Report 5 | description: File a bug report 6 | labels: ["bug"] 7 | body: 8 | - type: input 9 | attributes: 10 | label: Which component is affected? 11 | description: Which component/part of ClassQuiz is affected by the bug? The editor? The admin-screen if a game is running? 12 | validations: 13 | required: true 14 | - type: dropdown 15 | attributes: 16 | label: Did the issue occur at ClassQuiz.de, or on a self-hosted instance? 17 | options: 18 | - On ClassQuiz.de 19 | - On a self-hosted instance 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: How can the issue be reproduced? 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Describe the bug (with screenshots if possible) 30 | validations: 31 | required: true 32 | - type: dropdown 33 | attributes: 34 | label: Device 35 | options: 36 | - Desktop 37 | - Laptop/Notebook 38 | - Smartphone 39 | - Tablet 40 | - Smartwatch 41 | - Fridge/Toaster 42 | validations: 43 | required: true 44 | - type: input 45 | attributes: 46 | label: Operating System 47 | placeholder: Windows, Linux, iOS, etc 48 | validations: 49 | required: true 50 | - type: input 51 | attributes: 52 | label: Browser 53 | placeholder: Chrome, Safari, Firefox, etc 54 | validations: 55 | required: true 56 | - type: markdown 57 | attributes: 58 | value: Additional 59 | validations: 60 | required: true 61 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | #### :tophat: What? Why? 8 | *Please describe your pull request.* 9 | 10 | #### :pushpin: Related Issues 11 | *Link your PR to an issue* 12 | - Related to #? 13 | - Fixes #? 14 | 15 | #### Testing 16 | *Describe the best way to test or validate your PR.* 17 | 18 | #### :clipboard: Checklist 19 | ⚠️ No tests suites for now ⚠️ 20 | :rotating_light: Please review the [guidelines for contributing](https://github.com/mawoka-myblock/ClassQuiz/blob/master/CONTRIBUTING.md) to this repository. 21 | 22 | - [ ] :question: ~~**CONSIDER** adding a unit test if your PR resolves an issue.~~ 23 | - [ ] :heavy_check_mark: **DO** check open PR's to avoid duplicates. 24 | - [ ] :heavy_check_mark: **DO** keep pull requests small so they can be easily reviewed. 25 | - [ ] :heavy_check_mark: **DO** build locally before pushing. 26 | - [ ] :heavy_check_mark: ~~**DO** make sure tests pass.~~ 27 | - [ ] :heavy_check_mark: ~~**DO** add CHANGELOG upgrade notes if required.~~ 28 | - [ ] :x:~~**AVOID** breaking the continuous integration build.~~ 29 | - [ ] :x:**AVOID** making significant changes to the overall architecture. 30 | 31 | ### :camera: Screenshots 32 | *Please add screenshots of the changes you're proposing* 33 | 34 | :hearts: Thank you! 35 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | codecov: 5 | require_ci_to_pass: false 6 | 7 | coverage: 8 | precision: 2 9 | round: down 10 | range: "50...100" 11 | 12 | parsers: 13 | gcov: 14 | branch_detection: 15 | conditional: yes 16 | loop: yes 17 | method: no 18 | macro: no 19 | 20 | comment: 21 | layout: "reach,diff,flags,files,footer" 22 | behavior: default 23 | require_changes: false 24 | -------------------------------------------------------------------------------- /.github/workflows/backend_lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 7 | 8 | name: "[CI] Backend / Lint" 9 | 10 | on: 11 | workflow_dispatch: 12 | # push: 13 | # branches: [ master ] 14 | # pull_request: 15 | 16 | 17 | jobs: 18 | backend_lint: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v3.3.0 23 | with: 24 | fetch-depth: 1 25 | - name: Set up Python 26 | uses: actions/setup-python@v2 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install flake8 32 | python -m pip install pipenv 33 | # if [ -f Pipfile ]; then pipenv install; fi 34 | - name: Lint with flake8 35 | run: | 36 | # stop the build if there are Python syntax errors or undefined names 37 | flake8 . --count --show-source --statistics 38 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 39 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 40 | -------------------------------------------------------------------------------- /.github/workflows/frontend_lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 7 | 8 | name: "[CI] Frontend / Lint" 9 | 10 | on: 11 | push: 12 | branches: [ master ] 13 | paths: 14 | - "frontend/**" 15 | workflow_dispatch: 16 | pull_request: 17 | 18 | jobs: 19 | frontend_lint: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3.3.0 24 | with: 25 | fetch-depth: 1 26 | 27 | - uses: pnpm/action-setup@v2.2.4 28 | with: 29 | version: 8.14.0 30 | working-directory: ./frontend 31 | 32 | - name: Install dependencies 33 | working-directory: ./frontend 34 | run: | 35 | pnpm install 36 | 37 | - name: Lint 38 | working-directory: ./frontend 39 | run: | 40 | pnpm run lint-without-format-checking 41 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # .github/workflows/app.yaml 6 | name: PyTest 7 | on: 8 | push: 9 | paths: 10 | - "classquiz/**" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 10 17 | 18 | steps: 19 | - name: Check out repository code 20 | uses: actions/checkout@v3 21 | - name: Install pipenv 22 | run: pipx install pipenv 23 | 24 | # Setup Python (faster than using Python container) 25 | - name: Setup Python 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: "3.10" 29 | cache: 'pipenv' 30 | - run: pipenv install --dev 31 | - name: Prepare tests 32 | run: | 33 | echo ${{ secrets.DOTENV }} | base64 --decode > .env 34 | set -o allexport; source .env; set +o allexport 35 | chmod +x run_tests.sh 36 | - name: Run tests 37 | run: | 38 | ./run_tests.sh a 39 | pipenv run coverage xml 40 | # - name: Report results to DeepSource 41 | # run: | 42 | # # Install deepsource CLI 43 | # curl https://deepsource.io/cli | sh 44 | # 45 | # # From the root directory, run the report coverage command 46 | # ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml 47 | # 48 | # env: 49 | # DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} 50 | 51 | - name: Upload Coverage to Codecov 52 | uses: codecov/codecov-action@v2 53 | with: 54 | files: ./coverage.xml 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | __pycache__/ 6 | .idea/ 7 | node_modules/ 8 | .svelte-kit/ 9 | .env* 10 | *.db 11 | *.rdb 12 | survey.json 13 | .coverage 14 | export_deta.py 15 | target/ 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # See https://pre-commit.com for more information 6 | # See https://pre-commit.com/hooks.html for more hooks 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.5.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: end-of-file-fixer 13 | - id: check-yaml 14 | args: 15 | - --allow-multiple-documents 16 | # - id: check-added-large-files 17 | 18 | - repo: https://github.com/psf/black 19 | rev: 24.3.0 20 | hooks: 21 | - id: black 22 | - repo: https://github.com/pre-commit/mirrors-prettier 23 | rev: 'v4.0.0-alpha.8' # Use the sha / tag you want to point at 24 | hooks: 25 | - id: prettier 26 | files: "^frontend/" 27 | exclude: "^frontend/src/lib/i18n/locales/" 28 | additional_dependencies: 29 | - "prettier-plugin-svelte@latest" 30 | - "prettier@latest" 31 | - repo: https://github.com/pycqa/flake8 32 | rev: 7.0.0 33 | hooks: 34 | - id: flake8 35 | # flake8 is passed in all tracked python files 36 | # so --exclude in .flake8 does not work 37 | exclude: ^migrations/versions/ 38 | - repo: https://github.com/fsfe/reuse-tool 39 | rev: v3.0.1 40 | hooks: 41 | - id: reuse 42 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ClassQuiz 3 | Upstream-Contact: Mawoka 4 | Source: https://classquiz.de 5 | 6 | # Sample paragraph, commented out: 7 | # 8 | # Files: src/* 9 | # Copyright: $YEAR $NAME <$CONTACT> 10 | # License: ... 11 | -------------------------------------------------------------------------------- /.tokeignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | *.y*ml 6 | *.json 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | { 6 | "sqltools.connections": [ 7 | { 8 | "previewLimit": 50, 9 | "server": "localhost", 10 | "driver": "PostgreSQL", 11 | "connectString": "postgresql://postgres:mysecretpassword@localhost:5432/classquiz", 12 | "name": "ClassQuiz" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /CONTACT.md: -------------------------------------------------------------------------------- 1 | 6 | Welcome to the ClassQuiz community 7 | 8 | Grant and indulge critique constructively, within desired privacy. 9 | Settle disputes within these confines. 10 | Finding yourselves unable, e-mail hi@mawoka.eu answered by Marlon (Mawoka), the project maintainer. 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 6 | # Contribute to ClassQuiz 7 | 8 | For the development-setup, please check out the 9 | docs: [classquiz.mawoka.eu/docs/develop](https://classquiz.mawoka.eu/docs/develop) 10 | 11 | ## Formatting git-commits 12 | 13 | Please use [Gitmoji](https://gitmoji.dev/) to format your commits. 14 | 15 | ## Opening PRs 16 | 17 | Just do so. 18 | 19 | ## Found a bug 20 | 21 | If it is a security-related bug, please contact me at [mawoka.eu/contact](https://mawoka.eu/contact). If not, just open 22 | an issue here on GitHub. 23 | 24 | **Please try to reproduce the bug with the adblocker disabled**, so I will be able to see the bug in Sentry. 25 | 26 | ## Want to translate? 27 | 28 | Go to [Weblate](https://translate.mawoka.eu/projects/classquiz/frontend/). 29 | If the language isn't available, please open 30 | an issue here, so I'll be able to add it. 31 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | #* { 6 | :8080 { 7 | # tls /home/mawoka/certs/cert.pem /home/mawoka/certs/key.pem 8 | reverse_proxy /* localhost:3000 9 | reverse_proxy /api* localhost:8000 10 | reverse_proxy /rapidoc* localhost:8000 11 | reverse_proxy /openapi.json localhost:8000 12 | reverse_proxy /socket.io* localhost:8000 13 | } 14 | -------------------------------------------------------------------------------- /Caddyfile-docker: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | :8080 { 6 | reverse_proxy * http://frontend:3000 7 | reverse_proxy /api/* http://api:80 8 | reverse_proxy /openapi.json http://api:80 # Only use if you need to serve the OpenAPI spec 9 | reverse_proxy /socket.io/* api:80 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | FROM python:3.11-slim 6 | 7 | COPY Pipfile* /app/ 8 | WORKDIR /app/ 9 | RUN apt update && \ 10 | apt install -y jq gcc libpq5 libpq-dev libmagic1 && \ 11 | jq -r '.default | to_entries[] | .key + .value.version' Pipfile.lock > requirements.txt && \ 12 | sed -i "s/psycopg2-binary/psycopg2/g" requirements.txt 13 | 14 | RUN pip install -r requirements.txt && \ 15 | apt remove -y jq gcc 16 | 17 | COPY classquiz/ /app/classquiz/ 18 | COPY image_cleanup.py /app/image_cleanup.py 19 | COPY alembic.ini /app/ 20 | COPY migrations/ /app/migrations/ 21 | COPY *start.sh /app/ 22 | COPY gunicorn_conf.py /app/ 23 | 24 | 25 | EXPOSE 80 26 | ENV PYTHONPATH=/app 27 | RUN chmod +x start.sh 28 | ENV APP_MODULE=classquiz:app 29 | CMD ["./start.sh"] 30 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | [[source]] 6 | url = "https://pypi.org/simple" 7 | verify_ssl = true 8 | name = "pypi" 9 | 10 | [packages] 11 | fastapi = "*" 12 | uvicorn = "*" 13 | python-socketio = "*" 14 | ormar = { version = "*", extras = ["postgresql"] } 15 | passlib = "*" 16 | python-jose = "*" 17 | alembic = "*" 18 | email-validator = "*" 19 | python-multipart = "*" 20 | pydantic = "*" 21 | redis = "*" 22 | aiohttp = "*" 23 | gunicorn = "*" 24 | qrcode = "*" 25 | jinja2 = "*" 26 | argon2-cffi = "*" 27 | sentry-sdk = "*" 28 | aiofiles = "*" 29 | meilisearch = "*" 30 | bleach = "*" 31 | bidict = "*" 32 | xlsxwriter = "*" 33 | authlib = "*" 34 | httpx = "*" 35 | itsdangerous = "*" 36 | py-avataaars-no-png = "*" 37 | cryptography = "*" 38 | pyotp = "*" 39 | minio = "*" 40 | xxhash = "*" 41 | arq = "*" 42 | thumbhash-python = "*" 43 | python-magic = "*" 44 | openpyxl = "*" 45 | starlette = "*" 46 | pyopenssl = "*" 47 | python-dotenv = "*" 48 | webauthn = "==1.*" 49 | pydantic-settings = "==2.2.1" 50 | 51 | [dev-packages] 52 | coverage = "*" 53 | pytest = "*" 54 | pytest-asyncio = "*" 55 | flake8 = "*" 56 | black = "*" 57 | pytest-dependency = "*" 58 | pytest-order = "*" 59 | pre-commit = "*" 60 | python-socketio = {extras = ["client"], version = "*"} 61 | 62 | [requires] 63 | python_version = "3.11" 64 | 65 | [scripts] 66 | format = "black ." 67 | lint = "flake8 classquiz" 68 | test = "coverage run -m pytest --lf -v --asyncio-mode=strict classquiz/tests" 69 | worker = "arq classquiz.worker.WorkerSettings" 70 | -------------------------------------------------------------------------------- /Pipfile.lock.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /classquiz/db/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import databases 7 | import sqlalchemy 8 | 9 | from classquiz.config import settings 10 | 11 | settings = settings() 12 | 13 | database = databases.Database(settings.db_url) 14 | metadata = sqlalchemy.MetaData() 15 | -------------------------------------------------------------------------------- /classquiz/db/quiztivity.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from pydantic import BaseModel 7 | import enum 8 | 9 | 10 | class Pdf(BaseModel): 11 | url: str 12 | 13 | 14 | class _MemoryCard(BaseModel): 15 | image: str | None = None 16 | text: str | None = None 17 | id: str 18 | 19 | 20 | class Memory(BaseModel): 21 | cards: list[list[_MemoryCard]] 22 | 23 | 24 | class Markdown(BaseModel): 25 | # skipcq: PTC-W0052 26 | markdown: str 27 | 28 | 29 | class _AbcdAnswer(BaseModel): 30 | answer: str 31 | correct: bool 32 | 33 | 34 | class Abcd(BaseModel): 35 | question: str 36 | answers: list[_AbcdAnswer] 37 | 38 | 39 | class QuizTivityTypes(str, enum.Enum): 40 | SLIDE = "SLIDE" 41 | PDF = "PDF" 42 | MEMORY = "MEMORY" 43 | MARKDOWN = "MARKDOWN" 44 | ABCD = "ABCD" 45 | 46 | 47 | TYPE_CLASS_LIST = { 48 | QuizTivityTypes.PDF: type(Pdf), 49 | QuizTivityTypes.MEMORY: type(Memory), 50 | QuizTivityTypes.MARKDOWN: type(Markdown), 51 | QuizTivityTypes.ABCD: type(Abcd), 52 | } 53 | 54 | 55 | class QuizTivityPage(BaseModel): 56 | title: str | None = None 57 | type: QuizTivityTypes 58 | data: Pdf | Memory | Markdown | Abcd 59 | -------------------------------------------------------------------------------- /classquiz/emails/templates/footer.jinja2: -------------------------------------------------------------------------------- 1 | 6 | 14 |
15 | 18 | 19 | 20 | 26 | 27 | 28 |
22 |

23 | 💌 Send with love by Mawoka 25 |

29 |
30 | -------------------------------------------------------------------------------- /classquiz/helpers/avatar.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import py_avataaars_no_png as pa 7 | from random import choice 8 | import gzip 9 | 10 | 11 | def _gen_avatar() -> str: 12 | mouth_type = pa.MouthType 13 | avatar = pa.PyAvataaar( 14 | style=pa.AvatarStyle.TRANSPARENT, 15 | skin_color=choice(list(pa.SkinColor)), 16 | hair_color=choice(list(pa.HairColor)), 17 | facial_hair_type=choice(list(pa.FacialHairType)), 18 | facial_hair_color=choice(list(pa.HairColor)), 19 | top_type=choice(list(pa.TopType)), 20 | hat_color=choice(list(pa.Color)), 21 | mouth_type=choice([mouth_type.DEFAULT, mouth_type.SMILE, mouth_type.TONGUE, mouth_type.TWINKLE]), 22 | eye_type=pa.EyesType.DEFAULT, # choice(list(pa.EyesType)) 23 | eyebrow_type=choice(list(pa.EyebrowType)), 24 | nose_type=choice(list(pa.NoseType)), 25 | accessories_type=choice(list(pa.AccessoriesType)), 26 | clothe_type=choice(list(pa.ClotheType)), 27 | clothe_color=choice(list(pa.Color)), 28 | clothe_graphic_type=choice(list(pa.ClotheGraphicType)), 29 | ) 30 | return avatar.render_svg() 31 | 32 | 33 | def gzipped_user_avatar() -> bytes: 34 | return gzip.compress(str.encode(_gen_avatar())) 35 | 36 | 37 | def str_user_avatar() -> str: 38 | return _gen_avatar() 39 | -------------------------------------------------------------------------------- /classquiz/helpers/box_controller.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import random 7 | 8 | 9 | def generate_code(specified_length: int) -> str: 10 | buttons = [ 11 | "B", 12 | "b", 13 | "G", 14 | "g", 15 | "Y", 16 | "y", 17 | "R", 18 | "r", 19 | ] # Capital stands for long press, lowercase letter for short press 20 | resulting_code = "" 21 | for _ in range(specified_length): 22 | resulting_code += random.choice(buttons) 23 | return resulting_code 24 | -------------------------------------------------------------------------------- /classquiz/kahoot_importer/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Kahoot-Importer 8 | 9 | With this package you can search kahoot-quizzes and get their data. 10 | 11 | This library is also tested. 12 | 13 | ## Get 14 | 15 | ```python 16 | from classquiz.kahoot_importer.get import get, _Response 17 | from asyncio import run 18 | # _Response ia a pydantic-object, so you have access to 19 | # .dict() or .json(exclude={"kahoot"}) 20 | 21 | async def main(): 22 | kahoot_quiz: _Response = await get("GAME_ID") 23 | print(kahoot_quiz.json(exclude={"kahoot"})) 24 | run(main()) 25 | ``` 26 | 27 | 28 | ## Search 29 | 30 | ```python 31 | from classquiz.kahoot_importer.search import search, _Response 32 | from asyncio import run 33 | # _Response ia a pydantic-object, so you have access to 34 | # .dict() or .json(exclude={"kahoot"}) 35 | 36 | async def main(): 37 | kahoot_quizzes: _Response = await search("QUERY") 38 | print(kahoot_quizzes.json()) 39 | run(main()) 40 | ``` 41 | 42 | 43 | ## Import-Quiz 44 | This script is meant just to be used with classquiz, not alone. 45 | --- 46 | *Kahoot! and the K! logo are trademarks of Kahoot! AS* 47 | -------------------------------------------------------------------------------- /classquiz/kahoot_importer/get.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from aiohttp import ClientSession 7 | from pydantic import BaseModel 8 | 9 | from classquiz.kahoot_importer import _Card, _Kahoot 10 | 11 | 12 | class _Response(BaseModel): 13 | card: _Card 14 | kahoot: _Kahoot 15 | 16 | 17 | async def get(game_id: str) -> _Response | int: 18 | async with ( 19 | ClientSession() as session, 20 | session.get(f"https://create.kahoot.it/rest/kahoots/{game_id}/card/?includeKahoot=true") as response, 21 | ): 22 | if response.status == 200: 23 | return _Response(**await response.json()) 24 | else: 25 | return response.status 26 | -------------------------------------------------------------------------------- /classquiz/kahoot_importer/search.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from typing import List 7 | 8 | from aiohttp import ClientSession 9 | from pydantic import BaseModel 10 | 11 | from classquiz.kahoot_importer import _Entity 12 | 13 | 14 | # noqa : E501 15 | class _Response(BaseModel): 16 | entities: List[_Entity] 17 | totalHits: int 18 | cursor: int | None = None 19 | pageTimestamp: int 20 | 21 | 22 | async def search( 23 | query: str | None = None, 24 | limit: int | None = 9, 25 | cursor: int | None = 1, 26 | search_cluster: int | None = 1, 27 | inventory_item_id: str | None = "ANY", 28 | ) -> _Response: 29 | """ 30 | 31 | :param inventory_item_id: I dkon't know 32 | :param search_cluster: Doesn't seeem to matter 33 | :param cursor: The position in the result-list (page) 34 | :param query: The search query 35 | :param limit: Less or equals 100 36 | :return: 37 | """ 38 | async with ( 39 | ClientSession() as session, 40 | session.get( 41 | f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}" # noqa : E501 42 | ) as response, 43 | ): 44 | return _Response(**await response.json()) 45 | -------------------------------------------------------------------------------- /classquiz/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /classquiz/routers/admin.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from uuid import UUID 7 | 8 | from fastapi import APIRouter, Depends 9 | 10 | from classquiz.auth import get_admin_user 11 | from classquiz.db.models import User 12 | 13 | router = APIRouter() 14 | 15 | 16 | @router.delete("/user/id") 17 | async def delete_user_by_id(user_id: UUID, user: User = Depends(get_admin_user)): 18 | return {"deleted": await User.objects.delete(id=user_id)} 19 | 20 | 21 | @router.delete("/user/username") 22 | async def delete_user_by_username(username: str, user: User = Depends(get_admin_user)): 23 | return {"deleted": await User.objects.delete(username=username)} 24 | 25 | 26 | @router.delete("/user/email") 27 | async def delete_user_by_email(email: str, user: User = Depends(get_admin_user)): 28 | return {"deleted": await User.objects.delete(email=email)} 29 | -------------------------------------------------------------------------------- /classquiz/routers/box_controller/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from fastapi import APIRouter 7 | from classquiz.routers.box_controller import web, embedded 8 | 9 | router = APIRouter() 10 | 11 | router.include_router(web.router, prefix="/web") 12 | router.include_router(embedded.router, prefix="/embedded") 13 | -------------------------------------------------------------------------------- /classquiz/routers/box_controller/embedded_ws.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Basic Request 8 | 9 | Every request is a json-object: `{"type": "SOME_TYPE", "data": "ANY_DATA"}` 10 | 11 | ### Types 12 | 13 | #### e (Error) 14 | 15 | - [ ] Client 16 | - [x] Server 17 | 18 | Pretty self-explanatory. 19 | 20 | Value is a CamelCase errorcode. 21 | Codes: 22 | 23 | - `ValidationError` 24 | - `BadId` 25 | 26 | #### bp (ButtonPress) 27 | 28 | - [x] Client 29 | - [ ] Server 30 | 31 | Sends a button-press to the server, where `data` is either `b`, `g`, `y` or `r`. Capital letters indicate a long-press. 32 | -------------------------------------------------------------------------------- /classquiz/routers/cqa-file-format.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # .cqa - ClassQuizArchive 8 | 9 | A file-format for ClassQuiz-quizzes which stores images and the quiz in one compact file 10 | 11 | ## How the file format works 12 | 13 | ``` 14 | [QUIZ_DATA] {C7 C7 C7 00} 15 | {C6 C6 C6 00} [UTF-8 encoded image-index] {C5 C5 00} [IMAGE as it is] 16 | {C6 C6 C6 00} [UTF-8 encoded image-index] {C5 C5 00} [IMAGE as it is] 17 | ``` 18 | ## Understanding the schema 19 | 20 | ### in {} 21 | - `{C7 C7 C7 00}`: separates the quiz-data from the images 22 | - `{C6 C6 C6 00}`: Indicates that a new image-block starts 23 | - `{C5 C5 00}`: separates image-index and image 24 | 25 | ### in [] 26 | - `[QUIZ_DATA]`: The **gzipped JSON** of the quiz with some modifications: 27 | - Removed `id`-field and `user_id`-field 28 | - Dates (`created_at` and `updated_at`) formatted as ISO-Dates 29 | - Images replaced with their question-index 30 | - Cover-image with index `-1` 31 | - `[UTF-8 encoded image-index]`: The index of the question which the image belongs to. 32 | - `[IMAGE as it is]`: The unmodified uploaded image (jpg, png, webp, etc.) 33 | -------------------------------------------------------------------------------- /classquiz/routers/remote.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from fastapi import APIRouter, Depends, HTTPException 7 | 8 | from classquiz.auth import get_current_user 9 | from classquiz.db.models import User, GameInLobby 10 | from classquiz.config import redis 11 | 12 | router = APIRouter() 13 | 14 | 15 | @router.get("/game_waiting") 16 | async def get_game_in_lobby(user: User = Depends(get_current_user)): 17 | game_in_lobby_raw = await redis.get(f"game_in_lobby:{user.id.hex}") 18 | if game_in_lobby_raw is None: 19 | raise HTTPException(status_code=404, detail="No game waiting") 20 | game_in_lobby = GameInLobby.parse_raw(game_in_lobby_raw) 21 | return game_in_lobby 22 | -------------------------------------------------------------------------------- /classquiz/routers/testing_routes.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from fastapi import APIRouter 7 | from classquiz.db.models import User 8 | from classquiz.config import settings 9 | 10 | settings = settings() 11 | 12 | router = APIRouter() 13 | 14 | 15 | @router.get("/user/{email}") 16 | async def get_user_by_email(email: str, secret_key: str) -> User: 17 | if secret_key == settings.secret_key: 18 | return await User.objects.filter(email=email).get() 19 | -------------------------------------------------------------------------------- /classquiz/socket_server/export_helpers.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import json 7 | from datetime import datetime 8 | 9 | from pydantic import ValidationError 10 | 11 | from classquiz.config import redis 12 | from classquiz.db.models import PlayGame, GameResults 13 | 14 | 15 | async def save_quiz_to_storage(game_pin: str): 16 | game = PlayGame.parse_raw(await redis.get(f"game:{game_pin}")) 17 | player_count = await redis.scard(f"game_session:{game_pin}:players") 18 | answers = [] 19 | for i in range(len(game.questions)): 20 | redis_res = await redis.get(f"game_session:{game_pin}:{i}") 21 | try: 22 | answers.append(json.loads(redis_res)) 23 | except (ValidationError, TypeError): 24 | answers.append([]) 25 | player_scores = await redis.hgetall(f"game_session:{game_pin}:player_scores") 26 | custom_field_data = await redis.hgetall(f"game:{game_pin}:players:custom_fields") 27 | q_return = [] 28 | for q in game.questions: 29 | q_return.append(q.dict()) 30 | data = GameResults( 31 | id=game.game_id, 32 | quiz=game.quiz_id, 33 | user=game.user_id, 34 | timestamp=datetime.now(), 35 | player_count=player_count, 36 | answers=json.dumps(answers), 37 | player_scores=json.dumps(player_scores), 38 | custom_field_data=json.dumps(custom_field_data), 39 | title=game.title, 40 | description=game.description, 41 | questions=json.dumps(q_return), 42 | ) 43 | await data.save() 44 | -------------------------------------------------------------------------------- /classquiz/socket_server/session.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | from classquiz.config import redis 6 | import json 7 | from typing import Any 8 | from socketio import AsyncServer 9 | from socketio.exceptions import ConnectionRefusedError 10 | 11 | 12 | async def get_session(sid: str, sio: AsyncServer, disconnect_on_error: bool = True) -> dict: 13 | session_id = (await sio.get_session(sid)).get("session_id") 14 | if session_id is None: 15 | raise ConnectionRefusedError("Session not configured") 16 | val = await redis.get(f"socket_io_session:{session_id}") 17 | if disconnect_on_error and val is None: 18 | raise ConnectionRefusedError("session not available") 19 | return json.loads(val) 20 | 21 | 22 | async def save_session(sid: str, sio: AsyncServer, data: Any, disconnect_on_error: bool = True) -> None: 23 | session_id = (await sio.get_session(sid)).get("session_id") 24 | if session_id is None: 25 | raise ConnectionRefusedError("Session not configured") 26 | await redis.set(f"socket_io_session:{session_id}", json.dumps(data), ex=3600) 27 | -------------------------------------------------------------------------------- /classquiz/storage/errors.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | class DeletionFailedError(Exception): 7 | pass 8 | 9 | 10 | class SavingFailedError(Exception): 11 | pass 12 | 13 | 14 | class DownloadingFailedError(Exception): 15 | pass 16 | -------------------------------------------------------------------------------- /classquiz/storage/local_storage.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import os 7 | from shutil import copyfileobj 8 | from typing import BinaryIO, Generator 9 | 10 | import aiofiles 11 | import aiofiles.os 12 | 13 | _DEFAULT_CHUNK_SIZE = 32768 # bytes; arbitrary 14 | 15 | 16 | class LocalStorage: 17 | def __init__(self, base_path: str): 18 | self.base_path = base_path 19 | 20 | async def download(self, file_name: str) -> Generator | None: 21 | try: 22 | async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="rb") as f: 23 | while True: 24 | chunk = await f.read(8192) 25 | if not chunk: 26 | break 27 | yield chunk 28 | except FileNotFoundError: 29 | yield None 30 | 31 | # skipcq: PYL-W0613 32 | async def upload( 33 | self, 34 | file_name: str, 35 | file: BinaryIO, 36 | size: int | None, 37 | mime_type: str | None = None, 38 | ) -> None: 39 | with open(file=os.path.join(self.base_path, file_name), mode="wb") as f: 40 | copyfileobj(file, f) 41 | 42 | async def delete(self, file_names: [str]) -> None: 43 | for i in file_names: 44 | try: 45 | await aiofiles.os.remove(os.path.join(self.base_path, i)) 46 | except FileNotFoundError: 47 | pass 48 | return None 49 | 50 | def size(self, file_name: str) -> int | None: 51 | try: 52 | return os.stat(os.path.join(self.base_path, file_name)).st_size 53 | except FileNotFoundError: 54 | return None 55 | -------------------------------------------------------------------------------- /classquiz/tests/test_auth.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import pytest 7 | from classquiz.auth import get_password_hash, verify_password, settings, ALGORITHM, create_access_token 8 | from jose import JWTError, jwt 9 | 10 | test_passwords = ["password", "password123", "12345678", "saddsaasdsad", "dsadasasdasddasasdsdasad"] 11 | 12 | 13 | @pytest.mark.asyncio 14 | @pytest.mark.parametrize("password", test_passwords) 15 | async def test_password_hashes(password): 16 | passwd_hash = get_password_hash(password) 17 | assert verify_password(password, passwd_hash) 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_jwt_engine(): 22 | access_token = create_access_token({"sub": "test@test.com"}) 23 | assert access_token is not None 24 | payload = jwt.decode(access_token, settings.secret_key, algorithms=[ALGORITHM]) 25 | email: str = payload.get("sub") 26 | assert email == "test@test.com" 27 | with pytest.raises(JWTError): 28 | jwt.decode(access_token, "wrong_secret", algorithms=[ALGORITHM]) 29 | -------------------------------------------------------------------------------- /classquiz/tests/test_kahoot_import.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import pytest 7 | 8 | from classquiz.kahoot_importer.import_quiz import _download_image 9 | 10 | ddg_robots_txt = b"""a""" 11 | test_url = ( 12 | "https://gist.githubusercontent.com/mawoka-myblock/b43f0d888a9e6a25806b3c73e63b658f/raw" 13 | "/134f135f99f8f385695304f739667c70b636386a/test-gist" 14 | ) 15 | 16 | 17 | @pytest.mark.asyncio 18 | async def test_download_image(): 19 | image = await _download_image(test_url) 20 | assert image == ddg_robots_txt 21 | -------------------------------------------------------------------------------- /classquiz/tests/test_kahoot_search.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | import pytest 7 | 8 | from classquiz.kahoot_importer.search import search 9 | 10 | 11 | @pytest.mark.asyncio 12 | @pytest.mark.order(-2) 13 | async def test_search(): 14 | res = await search(query="Python", limit=100) 15 | assert len(res.entities) == 100 16 | await search(query="Test Quiz", limit=100) 17 | await search(query="Biologie", limit=100) 18 | await search(query="Chemie", limit=100) 19 | await search(query="Deutsch", limit=100) 20 | await search(query="Mathe", limit=100) 21 | await search(query="Englisch", limit=100) 22 | await search(query="Barbie", limit=100) 23 | await search(query="Internet", limit=100) 24 | await search(query="Windows", limit=100) 25 | await search(query="Python", limit=100) 26 | -------------------------------------------------------------------------------- /classquiz/worker/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | from arq import cron 7 | from arq.connections import RedisSettings 8 | 9 | from classquiz import settings 10 | from classquiz.db import database 11 | from classquiz.worker.storage import clean_editor_images_up, calculate_hash, quiz_update 12 | 13 | 14 | async def startup(ctx): 15 | ctx["db"] = database 16 | if not ctx["db"].is_connected: 17 | await ctx["db"].connect() 18 | 19 | 20 | async def shutdown(ctx): 21 | if ctx["db"].is_connected: 22 | await ctx["db"].disconnect() 23 | 24 | 25 | class WorkerSettings: 26 | functions = [calculate_hash, quiz_update] 27 | cron_jobs = [cron(clean_editor_images_up, hour={0, 6, 12, 18}, minute=0)] 28 | on_startup = startup 29 | on_shutdown = shutdown 30 | redis_settings = RedisSettings.from_dsn(str(settings.redis)) 31 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | services: 6 | db: 7 | # build: 8 | # context: pg_uuidv7 9 | # args: 10 | # PG_MAJOR_VERSION: 16 11 | image: postgres 12 | environment: 13 | POSTGRES_PASSWORD: mysecretpassword 14 | POSTGRES_DB: classquiz 15 | volumes: 16 | - db:/var/lib/postgresql/data 17 | ports: 18 | - 5432:5432 19 | restart: unless-stopped 20 | meilisearch: 21 | image: getmeili/meilisearch:v1.7 22 | volumes: 23 | - search:/meili_data 24 | ports: 25 | - 7700:7700 26 | environment: 27 | MEILI_NO_ANALYTICS: "true" 28 | restart: unless-stopped 29 | caddy: 30 | image: caddy:alpine 31 | volumes: 32 | - ./Caddyfile:/etc/caddy/Caddyfile 33 | network_mode: "host" 34 | privileged: true 35 | restart: unless-stopped 36 | minio: 37 | image: bitnami/minio 38 | environment: 39 | MINIO_ROOT_USER: fediprint 40 | MINIO_ROOT_PASSWORD: fediprint 41 | volumes: 42 | - minio:/bitnami/minio/data 43 | ports: 44 | - 9000:9000 45 | - 9001:9001 46 | restart: unless-stopped 47 | redis: 48 | image: redis 49 | ports: 50 | - 6379:6379 51 | volumes: 52 | db: 53 | search: 54 | minio: 55 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | module.exports = { 6 | root: true, 7 | parser: '@typescript-eslint/parser', 8 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 9 | plugins: ['svelte3', '@typescript-eslint'], 10 | ignorePatterns: ['*.cjs'], 11 | overrides: [ 12 | { files: ['*.svelte'], processor: 'svelte3/svelte3' }, 13 | { 14 | files: ['*.*'], 15 | rules: { 16 | 'a11y-click-events-have-key-events': 'off' 17 | } 18 | } 19 | ], 20 | rules: { 21 | 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 22 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] 23 | }, 24 | settings: { 25 | 'svelte3/typescript': () => require('typescript') 26 | }, 27 | parserOptions: { 28 | sourceType: 'module', 29 | ecmaVersion: 2020 30 | }, 31 | env: { 32 | browser: true, 33 | es2017: true, 34 | node: true 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | .DS_Store 6 | node_modules 7 | /build 8 | /.svelte-kit 9 | /package 10 | .env 11 | .env.* 12 | !.env.example 13 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | engine-strict=true 6 | strict-peer-dependencies=false 7 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.prettierrc.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # create-svelte 8 | 9 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 10 | 11 | ## Creating a project 12 | 13 | If you're seeing this, you've probably already done this step. Congrats! 14 | 15 | ```bash 16 | # create a new project in the current directory 17 | npm init svelte@next 18 | 19 | # create a new project in my-app 20 | npm init svelte@next my-app 21 | ``` 22 | 23 | > Note: the `@next` is temporary 24 | 25 | ## Developing 26 | 27 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 28 | 29 | ```bash 30 | npm run dev 31 | 32 | # or start the server and open the app in a new browser tab 33 | npm run dev -- --open 34 | ``` 35 | 36 | ## Building 37 | 38 | To create a production version of your app: 39 | 40 | ```bash 41 | npm run build 42 | ``` 43 | 44 | You can preview the production build with `npm run preview`. 45 | 46 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 47 | -------------------------------------------------------------------------------- /frontend/i18next-scanner.config.engine.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 3 | 4 | SPDX-License-Identifier: MPL-2.0 5 | */ 6 | 7 | module.exports = { 8 | options: { 9 | debug: true, 10 | // read strings from functions: IllegalMoveError('KEY') or t('KEY') 11 | func: { 12 | list: ['IllegalMoveError', 't'], 13 | extensions: ['.js', '.svelte'] 14 | }, 15 | 16 | trans: false, 17 | 18 | // Create and update files `en.json`, `fr.json`, `es.json` 19 | lngs: ['en'], 20 | 21 | ns: [ 22 | // The namespace I use 23 | 'translation' 24 | ], 25 | 26 | defaultLng: 'en', 27 | defaultNs: 'translation', 28 | 29 | // Put a blank string as initial translation 30 | // (useful for Weblate be marked as 'not yet translated', see later) 31 | defaultValue: () => '', 32 | 33 | // Location of translation files 34 | resource: { 35 | loadPath: 'src/lib/i18n/locales/{{lng}}.json', 36 | savePath: 'src/lib/i18n/locales/{{lng}}.json', 37 | jsonIndent: 4 38 | }, 39 | 40 | nsSeparator: ':', 41 | keySeparator: '.' 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/package.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/pnpm-lock.yaml.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 3 | 4 | SPDX-License-Identifier: MPL-2.0 5 | */ 6 | 7 | const tailwindcss = require('tailwindcss'); 8 | const autoprefixer = require('autoprefixer'); 9 | const cssnano = require('cssnano'); 10 | const postcss_import = require('postcss-import'); 11 | 12 | const config = { 13 | plugins: [ 14 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 15 | tailwindcss(), 16 | postcss_import(), 17 | //But others, like autoprefixer, need to run after, 18 | autoprefixer, 19 | cssnano({ preset: 'default' }) 20 | ] 21 | }; 22 | 23 | module.exports = config; 24 | -------------------------------------------------------------------------------- /frontend/src/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 3 | 4 | SPDX-License-Identifier: MPL-2.0 5 | */ 6 | 7 | @import 'tippy.js/animations/perspective-subtle.css'; 8 | @import 'tippy.js/dist/tippy.css'; 9 | /* Write your global styles here, in PostCSS syntax */ 10 | @tailwind base; 11 | @tailwind components; 12 | @tailwind utilities; 13 | 14 | .marck-script { 15 | font-family: 'Marck Script'; 16 | } 17 | 18 | .link-hover { 19 | transition: all 0.3s; 20 | } 21 | 22 | .link-hover:hover { 23 | color: #4e6e58; 24 | } 25 | 26 | @layer utilities { 27 | .normal-background { 28 | @apply bg-gradient-to-r from-[#009444] via-[#39b54a] to-[#8dc63f] dark:bg-[#0f2702] dark:from-[#0f2702] dark:via-[#0f2702] dark:to[#0f2702]; 29 | } 30 | 31 | .admin-button { 32 | @apply px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50; 33 | } 34 | 35 | .action-button { 36 | @apply px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | /// 6 | 7 | // See https://kit.svelte.dev/docs/types#app 8 | // for information about these interfaces 9 | // and what to do when importing types 10 | declare namespace App { 11 | interface Locals { 12 | email: string | null; 13 | } 14 | 15 | // interface Platform {} 16 | 17 | // interface Stuff {} 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/app.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | %sveltekit.head% 45 | 46 | 47 |
%sveltekit.body%
48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | /// 6 | 7 | interface ImportMetaEnv { 8 | readonly VITE_APP_TITLE: string; 9 | // more env variables... 10 | } 11 | 12 | interface ImportMeta { 13 | readonly env: ImportMetaEnv; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | import type { Handle } from '@sveltejs/kit'; 6 | import jws from 'jws'; 7 | 8 | /** @type {import('@sveltejs/kit').Handle} */ 9 | export const handle: Handle = async ({ event, resolve }) => { 10 | const access_token = event.cookies.get('access_token'); 11 | if (!access_token) { 12 | event.locals.email = null; 13 | return resolve(event); 14 | } 15 | const jwt = jws.decode(access_token.replace('Bearer ', '')); 16 | // if token expires, do a request to get a new one and set the response-cookies on the response 17 | if (Date.now() >= jwt.payload.exp * 1000) { 18 | const res = await fetch(`${process.env.API_URL}/api/v1/users/check`, { 19 | method: 'GET', 20 | headers: { 21 | 'Content-Type': 'application/json', 22 | Cookie: event.request.headers.get('cookie') || '' 23 | } 24 | }); 25 | if (res.ok) { 26 | event.locals.email = await res.text(); 27 | const resp = await resolve(event); 28 | try { 29 | resp.headers.set('Set-Cookie', res.headers.get('set-cookie')); 30 | } catch { 31 | /* empty */ 32 | } 33 | return resp; 34 | } 35 | } 36 | event.locals.email = jwt.payload.sub; 37 | return resolve(event); 38 | }; 39 | 40 | /*export const getSession: GetSession = async (event) => { 41 | return { 42 | email: event.locals.email, 43 | authenticated: Boolean(event.locals.email) 44 | }; 45 | };*/ 46 | -------------------------------------------------------------------------------- /frontend/src/lib/Map.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | {#if browser} 20 | 21 | 22 | 23 | {/if} 24 | -------------------------------------------------------------------------------- /frontend/src/lib/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/admin.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | import type { QuizData } from '$lib/quiz_types'; 6 | 7 | export const get_question_title = (q_number: number, quiz_data: QuizData): string => { 8 | if (q_number - 1 === quiz_data.questions.length) { 9 | return; 10 | } 11 | try { 12 | return quiz_data.questions[q_number].question; 13 | } catch (e) { 14 | return ''; 15 | } 16 | }; 17 | 18 | export const getWinnersSorted = ( 19 | quiz_data: QuizData, 20 | final_results: Array | Array> 21 | ) => { 22 | const winners = {}; 23 | const q_count = quiz_data.questions.length; 24 | 25 | function sortObjectbyValue(obj) { 26 | const asc = false; 27 | const ret = {}; 28 | Object.keys(obj) 29 | .sort((a, b) => obj[asc ? a : b] - obj[asc ? b : a]) 30 | .forEach((s) => (ret[s] = obj[s])); 31 | return ret; 32 | } 33 | 34 | try { 35 | for (let i = 0; i < q_count; i++) { 36 | const q_res = final_results[i]; 37 | if (q_res === null) { 38 | continue; 39 | } 40 | for (const res of q_res) { 41 | if (res['right']) { 42 | if (winners[res['username']] === undefined) { 43 | winners[res['username']] = 0; 44 | } 45 | winners[res['username']] += 1; 46 | } 47 | } 48 | } 49 | 50 | return sortObjectbyValue(winners); 51 | } catch { 52 | return undefined; 53 | } 54 | }; 55 | 56 | export interface Player { 57 | username: string; 58 | } 59 | 60 | export interface PlayerAnswer { 61 | username: string; 62 | answer: string; 63 | right: string; 64 | } 65 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | *.png 6 | *.xcf 7 | admin_overview.webp 8 | results.webp 9 | select.webp 10 | solution.webp 11 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/admin_overview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/admin_overview.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/admin_overview.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/import-select-mockup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/import-select-mockup.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/import-select-mockup.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/opengraph-home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/opengraph-home.jpg -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/opengraph-home.jpg.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/opengraph-home.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/opengraph-home.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/opengraph-home.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/overview-mockup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/overview-mockup.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/overview-mockup.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/results-mockup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/results-mockup.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/results-mockup.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/results.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/results.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/results.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/select-mockup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/select-mockup.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/select-mockup.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/select.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/select.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/select.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/solution-mockup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/solution-mockup.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/solution-mockup.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/solution.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing/solution.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing/solution.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/edit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/edit.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/edit.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/edit_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/edit_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/edit_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/find.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/find.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/find.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/find_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/find_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/find_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/import.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/import.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/import.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/import_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/import_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/import_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result-admin.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/result-admin.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result-admin.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result-admin_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/result-admin_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result-admin_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/result.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/result_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/result_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/select.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/select.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/select.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/select_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/select_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/select_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/solution.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/solution.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/solution.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/solution_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/solution_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/solution_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/winners.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/winners.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/winners.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/winners_org.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/landing_new/winners_org.webp -------------------------------------------------------------------------------- /frontend/src/lib/assets/landing_new/winners_org.webp.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/music/1-128.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/music/1-128.mp3 -------------------------------------------------------------------------------- /frontend/src/lib/assets/music/1-128.mp3.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/music/1_original.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawoka-myblock/ClassQuiz/f4d69a5e286eca55877cc8625f0842b2a869be34/frontend/src/lib/assets/music/1_original.mp3 -------------------------------------------------------------------------------- /frontend/src/lib/assets/music/1_original.mp3.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /frontend/src/lib/clickOutside.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | /** Dispatch event on click outside of node */ 6 | export function clickOutside(node) { 7 | const handleClick = (event) => { 8 | if (node && !node.contains(event.target) && !event.defaultPrevented) { 9 | node.dispatchEvent(new CustomEvent('click_outside', node)); 10 | } 11 | }; 12 | 13 | document.addEventListener('click', handleClick, true); 14 | 15 | return { 16 | destroy() { 17 | document.removeEventListener('click', handleClick, true); 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/collapsible.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 |
14 |

15 | 31 |

32 | 33 |
34 | 35 |
36 |
37 | 38 | 73 | -------------------------------------------------------------------------------- /frontend/src/lib/components/DownloadQuiz.svelte: -------------------------------------------------------------------------------- 1 | 6 | 26 | 27 | {#if quiz_id} 28 |
33 |
34 |

{$t('downloader.select_download_type')}

35 |
36 |
37 | {$t('downloader.own_format')} 39 | 40 |
41 |
42 | {$t('downloader.excel_format')} 44 | 45 |
46 |
47 |

{$t('downloader.help')}

48 |
49 |
50 | {/if} 51 | -------------------------------------------------------------------------------- /frontend/src/lib/components/buttons/brown.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | {#if href} 19 | 31 | 32 | 33 | {:else} 34 | 44 | {/if} 45 | -------------------------------------------------------------------------------- /frontend/src/lib/components/buttons/gray.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | {#if href} 16 | 25 | 26 | 27 | {:else} 28 | 37 | {/if} 38 | -------------------------------------------------------------------------------- /frontend/src/lib/components/controller/code.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 |
18 | {#each code as c} 19 |
20 |

{c}

21 | 26 |
27 | {/each} 28 |
29 | -------------------------------------------------------------------------------- /frontend/src/lib/components/popover/commandpalettenotice.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /frontend/src/lib/components/popover/smalltop.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | /* eslint-disable no-unused-vars */ 6 | export enum PopoverTypes { 7 | Copy, 8 | GameInLobby, 9 | Generic 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/lib/config.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | export const google_auth_enabled = import.meta.env.VITE_GOOGLE_AUTH_ENABLED === 'true'; 6 | export const github_auth_enabled = import.meta.env.VITE_GITHUB_AUTH_ENABLED === 'true'; 7 | export const captcha_enabled = import.meta.env.VITE_CAPTCHA_ENABLED === 'true'; 8 | export const custom_oauth_name = import.meta.env.VITE_CUSTOM_OAUTH_NAME; 9 | -------------------------------------------------------------------------------- /frontend/src/lib/dashboard/useViewportAction.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) 2 | // 3 | // SPDX-License-Identifier: MPL-2.0 4 | 5 | // Stolen from https://svelte.dev/repl/c6a402704224403f96a3db56c2f48dfc?version=3.55.0 6 | // skipcq: JS-0119 7 | let intersectionObserver; 8 | 9 | function ensureIntersectionObserver() { 10 | if (intersectionObserver) return; 11 | 12 | intersectionObserver = new IntersectionObserver((entries) => { 13 | entries.forEach((entry) => { 14 | const eventName = entry.isIntersecting ? 'enterViewport' : 'exitViewport'; 15 | entry.target.dispatchEvent(new CustomEvent(eventName)); 16 | }); 17 | }); 18 | } 19 | 20 | export default function viewport(element) { 21 | ensureIntersectionObserver(); 22 | 23 | intersectionObserver.observe(element); 24 | 25 | return { 26 | destroy() { 27 | intersectionObserver.unobserve(element); 28 | } 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/checker-bg.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/icons/x-circle.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/settings_menu.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 |
24 | 32 | 40 |
41 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/types/circle.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 |
17 | {#if editor} 18 | 19 | {/if} 20 |
21 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/types/headline.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | {#if editor} 13 | 18 | {:else} 19 |

20 | {data} 21 |

22 | {/if} 23 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/types/rectangle.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 |
17 | {#if editor} 18 | 19 | {/if} 20 |
21 | -------------------------------------------------------------------------------- /frontend/src/lib/editor/slides/types/text.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | {#if editor} 13 |