diff --git a/README.md b/README.md index 67e10f6..c7bfcb2 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@

The open-source quiz-platform!
- Visit the website » + Visit the website »

- Docs + Docs · - Register + Register · - Self-Hosting + Self-Hosting

@@ -33,21 +33,21 @@ quiz, so students can compete with their knowledge against each other. ## Try it -There is a hosted version at [ClassQuiz.Mawoka.eu](https://classquiz.mawoka.eu?utm_medium=Github&utm_source=Readme). The +There is a hosted version at [classquiz.de](https://classquiz.de?utm_medium=Github&utm_source=Readme). The server is located in Karlsruhe, Germany and hosted by [netcup](https://mawoka.eu/redir?token=2), so expect some latency depending on your location. ## Self-Host -Please see https://classquiz.mawoka.eu/docs/self-host +Please see https://classquiz.de/docs/self-host ## Development -See https://classquiz.mawoka.eu/docs/develop +See https://classquiz.de/docs/develop ## Docs -The docs are online at https://classquiz.mawoka.eu/docs +The docs are online at https://classquiz.de/docs ### Things to know about the structure diff --git a/classquiz/__init__.py b/classquiz/__init__.py index 8749237..9ff9eea 100644 --- a/classquiz/__init__.py +++ b/classquiz/__init__.py @@ -3,7 +3,7 @@ from fastapi import FastAPI, Request from sentry_sdk.integrations.redis import RedisIntegration from socketio import ASGIApp -from classquiz.config import settings +from classquiz.config import settings, meilisearch from classquiz.db import database from classquiz.routers import users, quiz, utils, stats, storage, search from classquiz.socket_server import sio @@ -32,6 +32,7 @@ async def startup() -> None: database_ = app.state.database if not database_.is_connected: await database_.connect() + meilisearch.index(settings.meilisearch_index).update_settings({"sortableAttributes": ["created_at"]}) @app.on_event("shutdown") diff --git a/classquiz/kahoot_importer/import_quiz.py b/classquiz/kahoot_importer/import_quiz.py index fa4b5ae..d49a6db 100644 --- a/classquiz/kahoot_importer/import_quiz.py +++ b/classquiz/kahoot_importer/import_quiz.py @@ -30,6 +30,8 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str: return "quiz not found" quiz_questions: list[dict] = [] quiz_id = uuid.uuid4() + meilisearch.delete_index(settings.meilisearch_index) + meilisearch.create_index(settings.meilisearch_index) for q in quiz.kahoot.questions: answers: list[QuizAnswer] = [] diff --git a/classquiz/routers/quiz.py b/classquiz/routers/quiz.py index b16d290..7056f1b 100644 --- a/classquiz/routers/quiz.py +++ b/classquiz/routers/quiz.py @@ -37,7 +37,8 @@ async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_curren raise HTTPException(status_code=400, detail="image url is not valid") quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4()) await redis.delete("global_quiz_count") - meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)]) + if quiz_input.public: + meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)]) return await quiz.save() diff --git a/classquiz/routers/users.py b/classquiz/routers/users.py index 8c64916..064fe8d 100644 --- a/classquiz/routers/users.py +++ b/classquiz/routers/users.py @@ -16,11 +16,11 @@ from classquiz.auth import ( get_current_user, ) from classquiz.cache import clear_cache_for_account -from classquiz.config import redis, settings +from classquiz.config import redis, settings, meilisearch import uuid import bleach from pydantic import BaseModel -from classquiz.db.models import User, UserSession, UpdatePassword, Token +from classquiz.db.models import User, UserSession, UpdatePassword, Token, Quiz from classquiz.emails import send_register_email, send_forgotten_password_email settings = settings() @@ -259,3 +259,24 @@ async def delete_session(session_id: str, user: User = Depends(get_current_user) async def get_session(user: User = Depends(get_current_user)): session = await UserSession.objects.filter(user=user).first() return session + + +class DeleteUserInput(BaseModel): + password: str + + +@router.delete("/me") +async def delete_user_account(input_data: DeleteUserInput, user: User = Depends(get_current_user)): + if not verify_password(input_data.password, user.password): + raise HTTPException(status_code=400, detail="Incorrect password") + user = await User.objects.filter(id=user.id).get_or_none() + await UserSession.objects.filter(user=user).delete() + quizzes = await Quiz.objects.filter(user_id=user).all() + quizzes_to_delete = [] + for quiz in quizzes: + if quiz.is_public: + quizzes_to_delete.append(quiz.id) + if len(quizzes_to_delete) > 0: + meilisearch.index(settings.meilisearch_index).delete_documents(quizzes_to_delete) + await User.objects.filter(id=user.id).delete() + await user.delete() diff --git a/classquiz/tests/test_kahoot_get.py b/classquiz/tests/test_kahoot_get.py index 9d1f068..cbd4d41 100644 --- a/classquiz/tests/test_kahoot_get.py +++ b/classquiz/tests/test_kahoot_get.py @@ -3,7 +3,7 @@ import pytest from classquiz.kahoot_importer.get import get -@pytest.mark.order("last") +@pytest.mark.order(-2) @pytest.mark.asyncio async def test_get(): await get("1f95eb0b-fcf4-4db2-879b-5418ef75116b") diff --git a/classquiz/tests/test_kahoot_search.py b/classquiz/tests/test_kahoot_search.py index 7104d46..dc045bb 100644 --- a/classquiz/tests/test_kahoot_search.py +++ b/classquiz/tests/test_kahoot_search.py @@ -4,7 +4,7 @@ from classquiz.kahoot_importer.search import search @pytest.mark.asyncio -@pytest.mark.order("last") +@pytest.mark.order(-2) async def test_search(): res = await search(query="Python", limit=100) assert len(res.entities) == 100 diff --git a/classquiz/tests/test_server.py b/classquiz/tests/test_server.py index cc4b69d..351e0c8 100644 --- a/classquiz/tests/test_server.py +++ b/classquiz/tests/test_server.py @@ -359,3 +359,16 @@ class TestQuiz: ValueStorage.imported_quizzes.append(resp.json()["id"]) resp = test_client.post("/api/v1/quiz/import/1f95eb0bdassdadasdas", cookies={"access_token": token}) assert resp.text == '"quiz not found"' + + +class TestDeleteUser: + @pytest.mark.asyncio + async def test_delete_user(self, test_client): + resp = test_client.post( + "/api/v1/users/token/cookie", data={"username": test_user_email, "password": test_user_password} + ) + token = resp.cookies["access_token"] + resp = test_client.delete( + "/api/v1/users/me", cookies={"access_token": token}, json={"password": test_user_password} + ) + assert resp.status_code == 200 diff --git a/frontend/src/routes/docs/privacy-policy.svelte b/frontend/src/routes/docs/privacy-policy.svelte index a14a975..96df45f 100644 --- a/frontend/src/routes/docs/privacy-policy.svelte +++ b/frontend/src/routes/docs/privacy-policy.svelte @@ -31,29 +31,26 @@ - Your IP-Address + The IP-Address of the user - For a very long time - On my server, by Netcup, in Germany. + For a very long time + On the developers server, by Netcup, in Germany. - The data you enter (quizzes, email, username) - Self-explaining - Untill you delete your account - On my server, by Netcup, in Germany. + The data the user enters (quizzes, email, username) + Self-explaining + Maximum of 30 days after you've deleted your account + On the developers server, by Netcup, in Germany. - Your hashed password + The hashed password of the user Self-explaining - Untill you delete your account + Maximum of 30 days after you've deleted your account On my server, by Netcup, in Germany. diff --git a/import_to_meili.py b/import_to_meili.py index 44e17fe..63fcb9f 100644 --- a/import_to_meili.py +++ b/import_to_meili.py @@ -15,7 +15,7 @@ async def __main__(): quizzes = await Quiz.objects.filter(public=True).all() for quiz in quizzes: meili_data.append(await get_meili_data(quiz)) - print(meili_data) + print(len(meili_data)) client = meilisearch.Client(settings.meilisearch_url) client.index(settings.meilisearch_index).add_documents(meili_data) client.index(settings.meilisearch_index).update_settings({"sortableAttributes": ["created_at"]})