Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -14,14 +14,14 @@
|
|||||||
<p align='center'>
|
<p align='center'>
|
||||||
The open-source quiz-platform!
|
The open-source quiz-platform!
|
||||||
<br/>
|
<br/>
|
||||||
<a href='https://classquiz.mawoka.eu/'><strong>Visit the website »</strong></a>
|
<a href='https://classquiz.de/'><strong>Visit the website »</strong></a>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<a href='https://classquiz.mawoka.eu/docs'>Docs</a>
|
<a href='https://classquiz.de/docs'>Docs</a>
|
||||||
·
|
·
|
||||||
<a href='https://classquiz.mawoka.eu/account/register'>Register</a>
|
<a href='https://classquiz.de/account/register'>Register</a>
|
||||||
·
|
·
|
||||||
<a href='https://classquiz.mawoka.eu/docs/self-host'>Self-Hosting</a>
|
<a href='https://classquiz.de/docs/self-host'>Self-Hosting</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,21 +33,21 @@ quiz, so students can compete with their knowledge against each other.
|
|||||||
|
|
||||||
## Try it
|
## 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
|
server is located in Karlsruhe, Germany and hosted by [netcup](https://mawoka.eu/redir?token=2), so expect some latency
|
||||||
depending on your location.
|
depending on your location.
|
||||||
|
|
||||||
## Self-Host
|
## Self-Host
|
||||||
|
|
||||||
Please see https://classquiz.mawoka.eu/docs/self-host
|
Please see https://classquiz.de/docs/self-host
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See https://classquiz.mawoka.eu/docs/develop
|
See https://classquiz.de/docs/develop
|
||||||
|
|
||||||
## Docs
|
## 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
|
### Things to know about the structure
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from fastapi import FastAPI, Request
|
|||||||
from sentry_sdk.integrations.redis import RedisIntegration
|
from sentry_sdk.integrations.redis import RedisIntegration
|
||||||
from socketio import ASGIApp
|
from socketio import ASGIApp
|
||||||
|
|
||||||
from classquiz.config import settings
|
from classquiz.config import settings, meilisearch
|
||||||
from classquiz.db import database
|
from classquiz.db import database
|
||||||
from classquiz.routers import users, quiz, utils, stats, storage, search
|
from classquiz.routers import users, quiz, utils, stats, storage, search
|
||||||
from classquiz.socket_server import sio
|
from classquiz.socket_server import sio
|
||||||
@@ -32,6 +32,7 @@ async def startup() -> None:
|
|||||||
database_ = app.state.database
|
database_ = app.state.database
|
||||||
if not database_.is_connected:
|
if not database_.is_connected:
|
||||||
await database_.connect()
|
await database_.connect()
|
||||||
|
meilisearch.index(settings.meilisearch_index).update_settings({"sortableAttributes": ["created_at"]})
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
|||||||
return "quiz not found"
|
return "quiz not found"
|
||||||
quiz_questions: list[dict] = []
|
quiz_questions: list[dict] = []
|
||||||
quiz_id = uuid.uuid4()
|
quiz_id = uuid.uuid4()
|
||||||
|
meilisearch.delete_index(settings.meilisearch_index)
|
||||||
|
meilisearch.create_index(settings.meilisearch_index)
|
||||||
|
|
||||||
for q in quiz.kahoot.questions:
|
for q in quiz.kahoot.questions:
|
||||||
answers: list[QuizAnswer] = []
|
answers: list[QuizAnswer] = []
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_curren
|
|||||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
raise HTTPException(status_code=400, detail="image url is not valid")
|
||||||
quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4())
|
quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4())
|
||||||
await redis.delete("global_quiz_count")
|
await redis.delete("global_quiz_count")
|
||||||
|
if quiz_input.public:
|
||||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
||||||
return await quiz.save()
|
return await quiz.save()
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ from classquiz.auth import (
|
|||||||
get_current_user,
|
get_current_user,
|
||||||
)
|
)
|
||||||
from classquiz.cache import clear_cache_for_account
|
from classquiz.cache import clear_cache_for_account
|
||||||
from classquiz.config import redis, settings
|
from classquiz.config import redis, settings, meilisearch
|
||||||
import uuid
|
import uuid
|
||||||
import bleach
|
import bleach
|
||||||
from pydantic import BaseModel
|
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
|
from classquiz.emails import send_register_email, send_forgotten_password_email
|
||||||
|
|
||||||
settings = settings()
|
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)):
|
async def get_session(user: User = Depends(get_current_user)):
|
||||||
session = await UserSession.objects.filter(user=user).first()
|
session = await UserSession.objects.filter(user=user).first()
|
||||||
return session
|
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()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import pytest
|
|||||||
from classquiz.kahoot_importer.get import get
|
from classquiz.kahoot_importer.get import get
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.order("last")
|
@pytest.mark.order(-2)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get():
|
async def test_get():
|
||||||
await get("1f95eb0b-fcf4-4db2-879b-5418ef75116b")
|
await get("1f95eb0b-fcf4-4db2-879b-5418ef75116b")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from classquiz.kahoot_importer.search import search
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.order("last")
|
@pytest.mark.order(-2)
|
||||||
async def test_search():
|
async def test_search():
|
||||||
res = await search(query="Python", limit=100)
|
res = await search(query="Python", limit=100)
|
||||||
assert len(res.entities) == 100
|
assert len(res.entities) == 100
|
||||||
|
|||||||
@@ -359,3 +359,16 @@ class TestQuiz:
|
|||||||
ValueStorage.imported_quizzes.append(resp.json()["id"])
|
ValueStorage.imported_quizzes.append(resp.json()["id"])
|
||||||
resp = test_client.post("/api/v1/quiz/import/1f95eb0bdassdadasdas", cookies={"access_token": token})
|
resp = test_client.post("/api/v1/quiz/import/1f95eb0bdassdadasdas", cookies={"access_token": token})
|
||||||
assert resp.text == '"quiz not found"'
|
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
|
||||||
|
|||||||
@@ -31,29 +31,26 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td> Your IP-Address </td>
|
<td> The IP-Address of the user</td>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
I store them because I am too lazy to configure them to be not stored.
|
|
||||||
</li>
|
|
||||||
<li>To prevent abuse</li>
|
<li>To prevent abuse</li>
|
||||||
<li>To provide user-sessions</li>
|
<li>To provide user-sessions</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td> For a very long time </td>
|
<td> For a very long time</td>
|
||||||
<td> On my server, by Netcup, in Germany. </td>
|
<td> On the developers server, by Netcup, in Germany.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> The data you enter (quizzes, email, username) </td>
|
<td> The data the user enters (quizzes, email, username)</td>
|
||||||
<td> Self-explaining </td>
|
<td> Self-explaining</td>
|
||||||
<td> Untill you delete your account </td>
|
<td> Maximum of 30 days after you've deleted your account</td>
|
||||||
<td> On my server, by Netcup, in Germany. </td>
|
<td> On the developers server, by Netcup, in Germany.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Your hashed password</td>
|
<td>The <b>hashed</b> password of the user</td>
|
||||||
<td>Self-explaining</td>
|
<td>Self-explaining</td>
|
||||||
<td>Untill you delete your account</td>
|
<td>Maximum of 30 days after you've deleted your account</td>
|
||||||
<td>On my server, by Netcup, in Germany.</td>
|
<td>On my server, by Netcup, in Germany.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ async def __main__():
|
|||||||
quizzes = await Quiz.objects.filter(public=True).all()
|
quizzes = await Quiz.objects.filter(public=True).all()
|
||||||
for quiz in quizzes:
|
for quiz in quizzes:
|
||||||
meili_data.append(await get_meili_data(quiz))
|
meili_data.append(await get_meili_data(quiz))
|
||||||
print(meili_data)
|
print(len(meili_data))
|
||||||
client = meilisearch.Client(settings.meilisearch_url)
|
client = meilisearch.Client(settings.meilisearch_url)
|
||||||
client.index(settings.meilisearch_index).add_documents(meili_data)
|
client.index(settings.meilisearch_index).add_documents(meili_data)
|
||||||
client.index(settings.meilisearch_index).update_settings({"sortableAttributes": ["created_at"]})
|
client.index(settings.meilisearch_index).update_settings({"sortableAttributes": ["created_at"]})
|
||||||
|
|||||||
Reference in New Issue
Block a user