diff --git a/Pipfile b/Pipfile index 6334632..875dc00 100644 --- a/Pipfile +++ b/Pipfile @@ -17,6 +17,7 @@ pydantic = "*" redis = "==4.2.0rc1" aiohttp = "*" gunicorn = { version = "*"} +qrcode = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 0f9538a..b307300 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6667340defc4a3152bca65e1359586067a229573e833d055c86a6ef800e64c15" + "sha256": "dc7b78e9bce0cdb40c43f8d8bcdc80a2d4dc8d56c430808ea702ae8b0d6740fa" }, "pipfile-spec": 6, "requires": { @@ -226,7 +226,7 @@ "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44", "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6" ], - "markers": "python_version >= '3.6' and python_full_version < '4.0.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==2.2.0" }, "ecdsa": { @@ -783,6 +783,13 @@ ], "version": "==6.0" }, + "qrcode": { + "hashes": [ + "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578" + ], + "index": "pypi", + "version": "==7.3.1" + }, "redis": { "hashes": [ "sha256:95ff6381bc15acbb330d24896601fb8c50468003c0f30c9503afda2907225d08", @@ -796,7 +803,7 @@ "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17", "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb" ], - "markers": "python_version >= '3.6' and python_full_version < '4.0.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.8" }, "setuptools": { diff --git a/classquiz/__init__.py b/classquiz/__init__.py index abc15d6..2285731 100644 --- a/classquiz/__init__.py +++ b/classquiz/__init__.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from classquiz.socket_server import sio from socketio import ASGIApp -from classquiz.routers import users, quiz +from classquiz.routers import users, quiz, utils from classquiz.db import database app = FastAPI() @@ -25,4 +25,5 @@ async def shutdown() -> None: app.include_router(users.router, tags=["users"], prefix="/api/v1/users") app.include_router(quiz.router, tags=["quiz"], prefix="/api/v1/quiz") +app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils") app.mount("/", ASGIApp(sio)) diff --git a/classquiz/routers/utils.py b/classquiz/routers/utils.py new file mode 100644 index 0000000..201fa76 --- /dev/null +++ b/classquiz/routers/utils.py @@ -0,0 +1,24 @@ +import io + +import qrcode +import qrcode.image.svg +from fastapi import APIRouter +from classquiz.config import settings +from fastapi.responses import Response + +router = APIRouter() + + +@router.get("/qr/{quiz_pin}") +async def get_qr(quiz_pin: str): + """ + Get QR code for quiz + """ + factory = qrcode.image.svg.SvgImage + qr = qrcode.QRCode(image_factory=qrcode.image.svg.SvgPathImage, version=1 + , box_size=20, border=0) + qr.add_data(f"{settings.root_address}/play?pin={quiz_pin}") + buf = io.BytesIO() + qr.make(fit=True) + qr.make_image(fill_color="black", back_color="white").save(buf, "SVG") + return Response(content=buf.getvalue(), media_type="image/svg+xml") diff --git a/frontend/package.json b/frontend/package.json index e8a39c8..8f1d78d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "svelte": "^3.44.0", "svelte-check": "^2.2.6", "svelte-preprocess": "^4.10.1", + "sveltejs-tippy": "^3.0.0", "tailwindcss": "^3.0.12", "tslib": "^2.3.1", "typescript": "~4.5.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 7b59078..683992b 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -29,6 +29,7 @@ specifiers: svelte: ^3.44.0 svelte-check: ^2.2.6 svelte-preprocess: ^4.10.1 + sveltejs-tippy: ^3.0.0 tailwindcss: ^3.0.12 tslib: ^2.3.1 typescript: ~4.5.4 @@ -63,6 +64,7 @@ devDependencies: svelte: 3.46.4 svelte-check: 2.4.5_bb21b93d557fc716a6ae071c4dce10e1 svelte-preprocess: 4.10.4_c54fd0a3479054b5002c3dc2e70c9c9a + sveltejs-tippy: 3.0.0 tailwindcss: 3.0.23_autoprefixer@10.4.2 tslib: 2.3.1 typescript: 4.5.5 @@ -180,6 +182,10 @@ packages: fastq: 1.13.0 dev: true + /@popperjs/core/2.11.2: + resolution: {integrity: sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==} + dev: true + /@rollup/pluginutils/4.1.2: resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==} engines: {node: '>= 8.0.0'} @@ -2174,6 +2180,12 @@ packages: engines: {node: '>= 8'} dev: true + /sveltejs-tippy/3.0.0: + resolution: {integrity: sha512-LAfQikm61AeqZW1hCFWUsmGEMmhMHDvrPV0JhbT38Ell9SqBjmFaY//+YKUxaJvx6nLMxGQdlcHA1rDg2Ml8tQ==} + dependencies: + tippy.js: 6.0.3 + dev: true + /table/6.8.0: resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} engines: {node: '>=10.0.0'} @@ -2229,6 +2241,12 @@ packages: globrex: 0.1.2 dev: true + /tippy.js/6.0.3: + resolution: {integrity: sha512-buQF6HugTA4YeSA/s9xBhu0ferEAx7sRSn45G+Juh3p+Dz3vEKfxtqdg6JUnHIWDCV/r0u8Lrobs9AB4dtYcUQ==} + dependencies: + '@popperjs/core': 2.11.2 + dev: true + /to-regex-range/5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} diff --git a/frontend/src/lib/play/join.svelte b/frontend/src/lib/play/join.svelte index bc8d2b8..8d9fac4 100644 --- a/frontend/src/lib/play/join.svelte +++ b/frontend/src/lib/play/join.svelte @@ -1,7 +1,8 @@ - - + {#if game_pin === '' || game_pin.length <= 7} -
+
{}} - class="flex-col flex justify-center align-center mx-auto" + class='flex-col flex justify-center align-center mx-auto' > -

Game Pin

+

Game Pin

+ +
{:else} -
+
-

Username

- +

Username

+
{/if}
diff --git a/frontend/src/routes/account/login.svelte b/frontend/src/routes/account/login.svelte index 402893b..57d50d3 100644 --- a/frontend/src/routes/account/login.svelte +++ b/frontend/src/routes/account/login.svelte @@ -3,7 +3,7 @@ if (session.authenticated) { return { status: 302, - redirect: '/' + redirect: '/overview' }; } return {}; diff --git a/frontend/src/routes/account/register.svelte b/frontend/src/routes/account/register.svelte index 260e9d2..ddbd42d 100644 --- a/frontend/src/routes/account/register.svelte +++ b/frontend/src/routes/account/register.svelte @@ -3,7 +3,7 @@ if (session.authenticated) { return { status: 302, - redirect: '/' + redirect: '/overview' }; } return {}; diff --git a/frontend/src/routes/admin.svelte b/frontend/src/routes/admin.svelte index de6cdd4..98f924e 100644 --- a/frontend/src/routes/admin.svelte +++ b/frontend/src/routes/admin.svelte @@ -8,10 +8,15 @@ } const token = url.searchParams.get('token'); const pin = url.searchParams.get('pin'); + let auto_connect = url.searchParams.get('connect') !== null; + if (token === null || pin === null) { + auto_connect = false; + } return { props: { game_pin: pin === null ? '' : pin, - game_token: token === null ? '' : token + game_token: token === null ? '' : token, + auto_connect: auto_connect } }; } @@ -28,6 +33,7 @@ // game_pin: '66190765' // }; export let game_pin: string; + export let auto_connect: boolean; export let game_token: string; console.log(game_pin, game_token); let success = false; @@ -45,6 +51,9 @@ game_id: game_token }); }; + if (auto_connect) { + connect(); + } socket.on('registered_as_admin', (data) => { console.log('registered_as_admin', data['game']); quiz_data = JSON.parse(data['game']); @@ -104,8 +113,11 @@

{errorMessage}

{/if} {:else if !game_started} + +

Pin: {quiz_data.game_pin}

    {#if players.length > 0} + {#each players as player}
  • {player.username} diff --git a/frontend/src/routes/overview.svelte b/frontend/src/routes/overview.svelte index 6ea7b83..3a94ead 100644 --- a/frontend/src/routes/overview.svelte +++ b/frontend/src/routes/overview.svelte @@ -1,4 +1,4 @@ - + + +{#await getData()} +

    Loading...

    + +{:then quizzes} + +
    +
    +
    +
    + + + + + + + + + + + + + {#each quizzes as quiz} + + + + + + + + + {/each} + +
    + Title + + Created at + + Question count + + Play + + Edit +
    + {quiz.title} + + {formatDate(quiz.created_at)} + + {quiz.questions.length} + + + + Edit +
    +
    +
    +
    +
    +{:catch err} +

    {err}

    +{/await}