🎨 black

This commit is contained in:
Mawoka
2022-04-09 11:06:36 +02:00
parent fd0b51d7e3
commit 6d5ba5ce74
18 changed files with 403 additions and 159 deletions
+3
View File
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 120
extend-ignore = E203
+1
View File
@@ -28,6 +28,7 @@ coverage = "*"
pytest = "*"
pytest-asyncio = "*"
flake8 = "*"
black = "*"
[requires]
python_version = "3.10"
Generated
+64 -5
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "5764345626b30779222e0a592f2ab91ce0938823d1baa12a4168ceed03a95170"
"sha256": "075e07880b28878645b61e807c6091ae0ecdef50b3486e047aa853023fc2ae33"
},
"pipfile-spec": 6,
"requires": {
@@ -331,7 +331,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
],
"markers": "python_version >= '3.6' and python_full_version < '4.0.0'",
"markers": "python_version >= '3.6' and python_version < '4'",
"version": "==2.2.1"
},
"ecdsa": {
@@ -933,7 +933,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"
},
"sentry-sdk": {
@@ -1031,7 +1031,7 @@
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.9"
},
"uvicorn": {
@@ -1284,6 +1284,43 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.4.0"
},
"black": {
"hashes": [
"sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b",
"sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176",
"sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09",
"sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a",
"sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015",
"sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79",
"sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb",
"sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20",
"sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464",
"sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968",
"sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82",
"sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21",
"sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0",
"sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265",
"sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b",
"sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a",
"sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72",
"sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce",
"sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0",
"sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a",
"sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163",
"sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad",
"sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
],
"index": "pypi",
"version": "==22.3.0"
},
"click": {
"hashes": [
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"coverage": {
"hashes": [
"sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9",
@@ -1353,6 +1390,13 @@
],
"version": "==0.6.1"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
@@ -1361,6 +1405,21 @@
"markers": "python_version >= '3.6'",
"version": "==21.3"
},
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"platformdirs": {
"hashes": [
"sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d",
"sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"
],
"markers": "python_version >= '3.7'",
"version": "==2.5.1"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
@@ -1423,7 +1482,7 @@
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version >= '3.7'",
"markers": "python_version < '3.11'",
"version": "==2.0.1"
}
}
+5 -5
View File
@@ -28,11 +28,11 @@ ACCESS_TOKEN_EXPIRE_MINUTES = settings.access_token_expire_minutes
class OAuth2PasswordBearerWithCookie(OAuth2):
def __init__(
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
+8 -3
View File
@@ -10,6 +10,7 @@ class Settings(BaseSettings):
"""
Settings class for the shop app.
"""
root_address: str = "http://127.0.0.1:8000"
redis: RedisDsn = "redis://localhost:6379/0?decode_responses=True"
skip_email_verification: bool = False
@@ -36,7 +37,7 @@ class Settings(BaseSettings):
class Config:
env_file = ".env"
env_file_encoding = 'utf-8'
env_file_encoding = "utf-8"
@lru_cache()
@@ -45,5 +46,9 @@ def settings() -> Settings:
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
storage: Storage = Storage(backend=settings().storage_backend, deta_key=settings().deta_project_key,
deta_id=settings().deta_project_id, storage_path=settings().storage_path)
storage: Storage = Storage(
backend=settings().storage_backend,
deta_key=settings().deta_project_key,
deta_id=settings().deta_project_id,
storage_path=settings().storage_path,
)
+8 -3
View File
@@ -11,6 +11,7 @@ class User(ormar.Model):
"""
The user model in the database
"""
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
email: str = ormar.String(unique=True, max_length=100)
username: str = ormar.String(unique=True, max_length=100)
@@ -20,7 +21,7 @@ class User(ormar.Model):
created_at: datetime = ormar.DateTime(default=datetime.now())
class Meta:
tablename = 'users'
tablename = "users"
metadata = metadata
database = database
@@ -29,6 +30,7 @@ class UserSession(ormar.Model):
"""
The user session model for user-sessions
"""
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
user: uuid.UUID = ormar.ForeignKey(User)
session_key: str = ormar.String(unique=True, max_length=64)
@@ -38,7 +40,7 @@ class UserSession(ormar.Model):
last_seen: datetime = ormar.DateTime(default=datetime.now())
class Meta:
tablename = 'user_sessions'
tablename = "user_sessions"
metadata = metadata
database = database
@@ -73,7 +75,7 @@ class Quiz(ormar.Model):
questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
class Meta:
tablename = 'quiz'
tablename = "quiz"
metadata = metadata
database = database
@@ -82,6 +84,7 @@ class Token(BaseModel):
"""
For JWT
"""
access_token: str
token_type: str
@@ -90,6 +93,7 @@ class TokenData(BaseModel):
"""
For JWT
"""
email: str | None = None
@@ -97,6 +101,7 @@ class PlayGame(BaseModel):
"""
For JWT
"""
quiz_id: uuid.UUID | str
description: str
title: str
+17 -22
View File
@@ -12,17 +12,18 @@ from classquiz.db.models import User
settings = settings()
jinja = Environment(
loader=PackageLoader('classquiz.emails', 'templates'),
autoescape=select_autoescape(['html', 'xml']),
enable_async=True)
loader=PackageLoader("classquiz.emails", "templates"),
autoescape=select_autoescape(["html", "xml"]),
enable_async=True,
)
def _sendMail(template: str, to: str, subject: str):
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = settings.mail_address
msg['To'] = to
msg.attach(MIMEText(template, 'html'))
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = settings.mail_address
msg["To"] = to
msg.attach(MIMEText(template, "html"))
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
server = smtplib.SMTP(host=settings.mail_server, port=settings.mail_port)
server.ehlo()
@@ -35,25 +36,19 @@ def _sendMail(template: str, to: str, subject: str):
async def send_register_email(email: str):
user = await User.objects.get_or_none(email=email, verified=False)
if user is None:
raise ValueError('User not found')
template = jinja.get_template('register.jinja2')
template = await template.render_async(
base_url=settings.root_address,
token=user.verify_key
)
_sendMail(template=template, to=email, subject='Verify your email')
raise ValueError("User not found")
template = jinja.get_template("register.jinja2")
template = await template.render_async(base_url=settings.root_address, token=user.verify_key)
_sendMail(template=template, to=email, subject="Verify your email")
async def send_forgotten_password_email(email: str):
user = await User.objects.get_or_none(email=email)
if user is None:
raise ValueError('User not found')
template = jinja.get_template('forgotten_password.jinja2')
raise ValueError("User not found")
template = jinja.get_template("forgotten_password.jinja2")
token = os.urandom(32).hex()
template = await template.render_async(
base_url=settings.root_address,
token=token
)
template = await template.render_async(base_url=settings.root_address, token=token)
await redis.set(f"reset_passwd:{token}", str(user.id), ex=3600)
_sendMail(template=template, to=email, subject='Reset your password')
_sendMail(template=template, to=email, subject="Reset your password")
pass
+18 -4
View File
@@ -53,6 +53,7 @@ async def _download_image(url: str) -> bytes:
# data = await resp.json()
# return data["data"]["id"]
async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
"""
Imports a quiz from Kahoot.
@@ -78,8 +79,21 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
for a in q.choices:
answers.append((QuizAnswer(right=a.correct, answer=html.unescape(a.answer))))
quiz_questions.append(
QuizQuestion(question=q.question, answers=answers, time=str(q.time / 1000), image=image).dict())
quiz_data = Quiz(id=quiz_id, public=False, title=quiz.kahoot.title, description=quiz.kahoot.description,
created_at=datetime.now(), updated_at=datetime.now(), user_id=user.id,
questions=json.dumps(quiz_questions))
QuizQuestion(
question=q.question,
answers=answers,
time=str(q.time / 1000),
image=image,
).dict()
)
quiz_data = Quiz(
id=quiz_id,
public=False,
title=quiz.kahoot.title,
description=quiz.kahoot.description,
created_at=datetime.now(),
updated_at=datetime.now(),
user_id=user.id,
questions=json.dumps(quiz_questions),
)
return await quiz_data.save()
+8 -4
View File
@@ -14,8 +14,13 @@ class _Response(BaseModel):
pageTimestamp: int
async def search(query: str | None, limit: int | None = 9, cursor: int | None = 1,
search_cluster: int | None = 1, inventory_item_id: str | None = "ANY") -> _Response:
async def search(
query: str | None,
limit: int | None = 9,
cursor: int | None = 1,
search_cluster: int | None = 1,
inventory_item_id: str | None = "ANY",
) -> _Response:
"""
:param inventory_item_id: I dkon't know
@@ -27,8 +32,7 @@ async def search(query: str | None, limit: int | None = 9, cursor: int | None =
"""
async with ClientSession() as session:
async with session.get(
f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}" # noqa : E501
f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}" # noqa : E501
) as response:
# print(
# f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}")
+8 -2
View File
@@ -62,8 +62,14 @@ async def start_quiz(quiz_id: str, user: User = Depends(get_current_user)):
return JSONResponse(status_code=404, content={"detail": "quiz not found"})
else:
game_pin = randint(10000000, 99999999)
game = PlayGame(quiz_id=quiz_id, game_pin=str(game_pin), questions=quiz.questions, game_id=uuid.uuid4(),
title=quiz.title, description=quiz.description)
game = PlayGame(
quiz_id=quiz_id,
game_pin=str(game_pin),
questions=quiz.questions,
game_id=uuid.uuid4(),
title=quiz.title,
description=quiz.description,
)
await redis.set(f"game:{str(game.game_pin)}", (game.json()), ex=18000)
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
+6 -3
View File
@@ -12,7 +12,7 @@ router = APIRouter()
file_regex = r"^[a-z0-9]{8}-[a-z0-9-]{27}--[a-z0-9-]{36}$"
@router.get('/download/{file_name}')
@router.get("/download/{file_name}")
async def download_file(file_name: str):
if not re.match(file_regex, file_name):
raise HTTPException(status_code=400, detail="Invalid file name")
@@ -24,5 +24,8 @@ async def download_file(file_name: str):
def iter_file():
yield from download
return StreamingResponse(iter_file(), media_type='image/*',
headers={"Cache-Control": "public, immutable, max-age=31536000"})
return StreamingResponse(
iter_file(),
media_type="image/*",
headers={"Cache-Control": "public, immutable, max-age=31536000"},
)
+57 -21
View File
@@ -7,18 +7,32 @@ from fastapi.background import BackgroundTasks
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import OAuth2PasswordRequestForm
from classquiz.auth import get_password_hash, verify_password, authenticate_user, create_access_token, get_current_user
from classquiz.auth import (
get_password_hash,
verify_password,
authenticate_user,
create_access_token,
get_current_user,
)
from classquiz.cache import clear_cache_for_account
from classquiz.config import redis, settings
import uuid
from pydantic import BaseModel
from classquiz.db.models import User, UserSession, UpdatePassword, Token
from classquiz.emails import send_register_email, send_forgotten_password_email
settings = settings()
router = APIRouter()
route_user = User.get_pydantic(
exclude={"id": ..., "verified": ..., "verify_key": ..., "created_at": ..., "usersessions": ...})
exclude={
"id": ...,
"verified": ...,
"verify_key": ...,
"created_at": ...,
"usersessions": ...,
}
)
async def _sign_out_everywhere(user: User) -> None:
@@ -26,8 +40,11 @@ async def _sign_out_everywhere(user: User) -> None:
await clear_cache_for_account(user)
@router.post("/create", response_model=User,
response_model_include={"id": ..., "verified": ..., "email": ...})
@router.post(
"/create",
response_model=User,
response_model_include={"id": ..., "verified": ..., "email": ...},
)
async def create_user(user: route_user, background_task: BackgroundTasks) -> User | JSONResponse:
user = User(**user.dict(), id=uuid.uuid4())
try:
@@ -50,8 +67,11 @@ async def create_user(user: route_user, background_task: BackgroundTasks) -> Use
@router.post("/token/cookie", response_model=Token)
async def login_for_cookie_access_token(request: Request, response: Response,
form_data: OAuth2PasswordRequestForm = Depends()):
async def login_for_cookie_access_token(
request: Request,
response: Response,
form_data: OAuth2PasswordRequestForm = Depends(),
):
user = await authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
@@ -59,21 +79,29 @@ async def login_for_cookie_access_token(request: Request, response: Response,
detail="Incorrect username or password",
)
session_key = os.urandom(32).hex()
user_session = UserSession(user=user, session_key=session_key, ip_address=request.client.host,
user_agent=request.headers.get("User-Agent"), id=uuid.uuid4())
user_session = UserSession(
user=user,
session_key=session_key,
ip_address=request.client.host,
user_agent=request.headers.get("User-Agent"),
id=uuid.uuid4(),
)
await user_session.save()
# await user_session.save()
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
access_token = create_access_token(
data={"sub": user.email}, expires_delta=access_token_expires
)
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
await redis.set(access_token, user.email, ex=settings.access_token_expire_minutes * 60)
response.set_cookie(key="access_token", value=f"Bearer {access_token}",
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(
key="access_token",
value=f"Bearer {access_token}",
httponly=True,
samesite="strict",
max_age=settings.access_token_expire_minutes * 60,
)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="rememberme", value="")
response.set_cookie(key="rememberme_token", value=session_key, httponly=True, samesite='strict')
response.set_cookie(key="rememberme_token", value=session_key, httponly=True, samesite="strict")
return {"access_token": access_token, "token_type": "bearer"}
@@ -82,16 +110,20 @@ async def rememberme_token(request: Request, response: Response):
rememberme_cookie = request.cookies.get("rememberme_token")
if rememberme_cookie is None:
raise HTTPException(status_code=400, detail="No rememberme cookie")
user_session: UserSession | None = await UserSession.objects.filter(session_key=rememberme_cookie).select_related(
UserSession.user).get_or_none()
user_session: UserSession | None = (
await UserSession.objects.filter(session_key=rememberme_cookie).select_related(UserSession.user).get_or_none()
)
if (user_session is None) or (user_session.user is None):
raise HTTPException(status_code=401, detail="No user session")
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes * 60)
access_token = create_access_token(
data={"sub": user_session.user.email}, expires_delta=access_token_expires
access_token = create_access_token(data={"sub": user_session.user.email}, expires_delta=access_token_expires)
response.set_cookie(
key="access_token",
value=f"Bearer {access_token}",
httponly=True,
samesite="strict",
max_age=settings.access_token_expire_minutes * 60,
)
response.set_cookie(key="access_token", value=f"Bearer {access_token}",
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
await user_session.update(last_seen=datetime.now())
@@ -127,7 +159,11 @@ async def verify_user(verify_key: str):
@router.put("/password/update")
async def change_password(password_data: UpdatePassword, response: Response, user: User = Depends(get_current_user)):
async def change_password(
password_data: UpdatePassword,
response: Response,
user: User = Depends(get_current_user),
):
if not verify_password(password_data.old_password, user.password):
raise HTTPException(status_code=400, detail="Incorrect password")
user.password = get_password_hash(password_data.new_password)
+66 -23
View File
@@ -14,8 +14,10 @@ settings = settings()
async def join_game(sid, data):
async with aiohttp.ClientSession() as session:
try:
async with session.post("https://hcaptcha.com/siteverify",
data={"response": data["captcha"], "secret": settings.hcaptcha_key}) as resp:
async with session.post(
"https://hcaptcha.com/siteverify",
data={"response": data["captcha"], "secret": settings.hcaptcha_key},
) as resp:
resp_data = await resp.json()
if not resp_data["success"]:
print("CAPTCHA FAILED")
@@ -27,17 +29,34 @@ async def join_game(sid, data):
if redis_res is None:
await sio.emit("game_not_found", room=sid)
else:
session = {'game_pin': data["game_pin"], "username": data["username"], "admin": False}
session = {
"game_pin": data["game_pin"],
"username": data["username"],
"admin": False,
}
await sio.save_session(sid, session)
await sio.emit("joined_game", redis_res, room=sid)
redis_res = (await redis.get(f"game_session:{data['game_pin']}"))
redis_res = await redis.get(f"game_session:{data['game_pin']}")
redis_res = json.loads(redis_res)
# print(redis_res)
redis_res["players"].append({"username": data["username"], "sid": sid})
await redis.set(f"game_session:{data['game_pin']}",
json.dumps({"admin": redis_res["admin"], "game_id": redis_res["game_id"],
"players": redis_res["players"], "answers": []}), ex=18000)
await sio.emit("player_joined", {"username": data["username"], "sid": sid}, room=redis_res["admin"])
await redis.set(
f"game_session:{data['game_pin']}",
json.dumps(
{
"admin": redis_res["admin"],
"game_id": redis_res["game_id"],
"players": redis_res["players"],
"answers": [],
}
),
ex=18000,
)
await sio.emit(
"player_joined",
{"username": data["username"], "sid": sid},
room=redis_res["admin"],
)
sio.enter_room(sid, data["game_pin"]) # TODO: make more secure
@@ -55,14 +74,19 @@ async def register_as_admin(sid, data):
game_pin = data["game_pin"]
game_id = data["game_id"]
if (await redis.get(f"game_session:{game_pin}")) is None:
await redis.set(f"game_session:{game_pin}", json.dumps({"admin": sid, "game_id": game_id, "players": []}),
ex=18000)
await redis.set(
f"game_session:{game_pin}",
json.dumps({"admin": sid, "game_id": game_id, "players": []}),
ex=18000,
)
await sio.emit("registered_as_admin", {"game_id": game_id, "game": await redis.get(
f"game:{data['game_pin']}")},
room=sid)
await sio.emit(
"registered_as_admin",
{"game_id": game_id, "game": await redis.get(f"game:{data['game_pin']}")},
room=sid,
)
async with sio.session(sid) as session:
session['game_pin'] = data["game_pin"]
session["game_pin"] = data["game_pin"]
session["admin"] = True
sio.enter_room(sid, data["game_pin"])
else:
@@ -74,7 +98,7 @@ async def get_question_results(sid, data):
session = await sio.get_session(sid)
if session["admin"]:
redis_res = await redis.get(f"game_session:{session['game_pin']}:{data['question_number']}")
game_pin = session['game_pin']
game_pin = session["game_pin"]
await sio.emit("question_results", redis_res, room=game_pin)
@@ -82,7 +106,7 @@ async def get_question_results(sid, data):
async def set_question_number(sid, data):
session = await sio.get_session(sid)
if session["admin"]:
game_pin = session['game_pin']
game_pin = session["game_pin"]
await sio.emit("set_question_number", data, room=game_pin)
@@ -100,21 +124,40 @@ async def submit_answer(sid, data):
break
answers = await redis.get(f"game_session:{session['game_pin']}:{data['question_index']}")
if answers is None:
await redis.set(f"game_session:{session['game_pin']}:{data['question_index']}",
json.dumps(
[{"username": session["username"], "answer": data["answer"], "right": answer_right}]),
ex=18000)
await redis.set(
f"game_session:{session['game_pin']}:{data['question_index']}",
json.dumps(
[
{
"username": session["username"],
"answer": data["answer"],
"right": answer_right,
}
]
),
ex=18000,
)
else:
answers = json.loads(answers)
answers.append({"username": session["username"], "answer": data["answer"], "right": answer_right})
await redis.set(f"game_session:{session['game_pin']}:{data['question_index']}", json.dumps(answers), ex=18000)
answers.append(
{
"username": session["username"],
"answer": data["answer"],
"right": answer_right,
}
)
await redis.set(
f"game_session:{session['game_pin']}:{data['question_index']}",
json.dumps(answers),
ex=18000,
)
# await redis.set(f"game_data:{session['game_pin']}", json.dumps(data))
@sio.event
async def get_game_data(sid, _data):
game_pin = (await sio.get_session(sid))['game_pin']
game_pin = (await sio.get_session(sid))["game_pin"]
game_data = await redis.get(f"game:{game_pin}")
if game_data is not None:
await sio.emit("game_data", json.loads(game_data), room=game_pin)
+13 -5
View File
@@ -6,7 +6,13 @@ from .local_storage import LocalStorage
class Storage:
def __init__(self, backend: str, deta_key: Optional[str], deta_id: Optional[str], storage_path: Optional[str]):
def __init__(
self,
backend: str,
deta_key: Optional[str],
deta_id: Optional[str],
storage_path: Optional[str],
):
self.backend = backend
self.deta_key: str | None = deta_key
self.deta_id: str | None = deta_id
@@ -15,8 +21,11 @@ class Storage:
if deta_key is None or deta_id is None:
raise ValueError("deta_key and deta_id must be provided")
else:
self.deta_instance = DetaStorage(deta_base_url=self.deta_base_url, deta_key=self.deta_key,
deta_id=self.deta_id)
self.deta_instance = DetaStorage(
deta_base_url=self.deta_base_url,
deta_key=self.deta_key,
deta_id=self.deta_id,
)
elif backend == "local":
if storage_path is None:
@@ -29,8 +38,7 @@ class Storage:
async def download(self, file_name: str) -> BytesIO | None:
if self.backend == "deta":
return await self.deta_instance.download(
file_name)
return await self.deta_instance.download(file_name)
elif self.backend == "local":
return await self.local_instance.get_file(file_name)
+2 -2
View File
@@ -11,13 +11,13 @@ class LocalStorage:
async def get_file(self, file_name: str) -> io.BytesIO | None:
try:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='rb') as f:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="rb") as f:
return io.BytesIO(await f.read())
except FileNotFoundError:
return None
async def write_file(self, file_name: str, data: bytes) -> None:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='wb') as f:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="wb") as f:
await f.write(data)
async def delete_file(self, file_names: [str]) -> None:
+103 -51
View File
@@ -5,60 +5,112 @@ from classquiz.kahoot_importer.get import get
@pytest.mark.asyncio
async def test_get():
await get('1f95eb0b-fcf4-4db2-879b-5418ef75116b')
await get("1f95eb0b-fcf4-4db2-879b-5418ef75116b")
await get("c63d4d39-767e-4754-8c16-66c3c2d9633c")
await get("c5d31de6-4598-4574-8e67-ca9a2aba49f8")
await get("3d545fc3-b830-44b6-94ac-c8e4a3c9fb88")
quiz_list = ['c63d4d39-767e-4754-8c16-66c3c2d9633c', 'a73689d2-1108-4524-88e1-10c54282346c',
'e8e2d869-7c75-4ad6-8872-031bc8e37fa9', '387b4139-9e77-4b47-93b7-dbb6d75c54d1',
'a1fff657-b99d-4547-93cf-4e9629994844', '0880d792-a77e-4cba-8a8b-359de40ffe76',
'e2d12f01-a3dd-4d0e-a6c4-29f38046117f', 'e0af9b8e-8840-4a7d-8d51-3d02bb6a07e7',
'e655c086-558d-4602-99fd-5fc429ffe0ce', '1642f29d-b5cb-4213-82e6-7c1fae401aca',
'fabdd572-da3e-4621-b912-41b9bc5a18fb', 'ded3cbbf-5af3-4761-a6da-b3b6ca040e1a',
'370b3e33-ed97-4d36-9362-7f76aa9d12b9', 'cfa32a28-1a01-47f1-9bca-c85640bceeb8',
'993f615d-6890-4dd3-ac08-f462de9ac5a3', '7f08a377-e371-425e-95f9-7196401ae7ff',
'19656fed-5891-48e7-ae7f-1358f623bfd0', '5e766d32-930a-4640-82a1-87c58612f579',
'ab2450e8-ecbb-4aed-bb3c-87e47b2b81f5', 'dd3c9175-addd-48df-8297-7598d4f12006',
'b8de862c-fc4b-4df6-9fa5-81b91868fc3b', '3b082f4e-9c50-45ea-98c4-3845b14e400c',
'10179f67-a344-4fd0-80a1-d1e1b6ca7953', '8288c1dd-9675-411c-9175-4180004da3c3',
'af838c22-dcbb-497d-8c7b-f6499835b83d', '152c5fdb-3b0a-4cdf-a0d9-1984de0643ad',
'846fd2bb-6b95-4333-b5d4-ef0a363e73a9', 'dd6b446f-817b-469b-9e6b-e1d34c466a7f',
'2720d636-8918-4c6a-86a9-928c489382f4', 'a6985641-df97-4e56-97fc-e0901e32b464',
'1d2705b4-b79e-4b51-8d61-cfb60fc7d1b9', '677fa485-4231-4479-beb2-fe757053bf41',
'2b6379f8-450e-46c0-a854-5e368a03d5a6', 'f3ba42fe-11e8-47fc-8978-8d67bc0ce0b1',
'51d2e839-a9d7-4f26-b1fd-fb0743c3a236', 'f2212243-4553-456d-87e7-7ddfd6ee7563',
'2d6982d3-7b46-4022-bbb6-cb5451c581ba', '1cccae22-5140-46b5-ba6a-aa17cec14dc3',
'b01dd2c2-ffcf-4c02-a274-e12296dbe4e1', '2669d193-a81f-4752-a003-dd2433beef92',
'06203ee6-3615-4e26-bb08-4fab249698a4', '933c1426-c759-43a3-9770-c8e3948db998',
'02046384-fb05-4f1d-855a-f0e52a0761e3', 'b5266e9b-7372-4df0-94a9-ec8a6c0aabc6',
'fb8e3a3d-505a-4157-bd9a-b73b00591f10', '42092675-9171-400f-ab4d-c898ec9f70bd',
'1d6307e2-98c2-42f2-b765-d02a68e0da80', '8f431ed0-31df-4435-bcba-aff46929fe82',
'd573d76f-d715-4a54-bdde-7ac7b6235fb2', 'feb8985b-84fb-4d41-bc41-70c322f508fa',
'f9f945cc-c166-4f7d-a95f-f0be154c43cf', '009b130a-1709-41a6-943d-85eee4889792',
'a3f765f7-2329-47e0-83eb-0837b4b54b43', 'fea9e558-206f-444a-8d7f-a3f0301fda8d',
'9b419098-ccfc-467d-9de0-db18b60d6c28', 'e6c2174e-3a3f-4193-9782-1bf3b5cfae63',
'd42c962e-1691-4fa4-8926-c33157423bd2', '8d27aa83-a103-4f05-8230-69e857bc3ffd',
'60cd19b2-6858-4704-b07b-5196e8e24325', '31624ea4-1e4f-4f90-ae4f-759a0eb93b61',
'fcd038f4-6b05-485b-afe3-ce67e05b1695', '1207bbe5-2df5-4c86-8f54-1b2675a86859',
'eea3b0b0-deae-41de-b010-d6d50855d016', 'ca8b0278-744c-4a3f-a2b8-6ab29d76fa7f',
'68d37bc8-28b9-49fd-9e02-48a3c9195fdf', '8bf13f61-c5cc-4332-aab9-37405d1fdd14',
'8ceef7de-9408-48d1-8bb9-b8ac3ef73596', '7e053a85-b04c-484b-8815-d26ce646c748',
'dfc09237-85b7-4596-b4e8-0bfbfd1aaeb7', 'd8d291ab-9dfa-45c0-bfc2-0a81167e3c63',
'd0834157-d464-4632-b620-147e211e0614', '30758f35-fabc-48bc-b410-47450de64c82',
'7232bc80-a9cb-4668-9449-16b9282a2331', '29a194bd-2ecc-44bc-a6a9-6b8a25c3d860',
'52b43400-117a-4e16-81b8-b510fe987750', '52ef0c45-9ba1-4d5e-ba0c-82703196ec6c',
'9f0fa778-e6ae-4f7b-b4fd-2ccb1bd37eb7', '54b57afd-c91c-4d5f-8177-c3fe940bc723',
'23620661-6eb6-41df-9de4-18ff99e6f35f', 'c983fe89-6ac3-4395-882e-4b11aa11a99d',
'd4d01494-e7d0-4a38-aa93-523a77855371', 'e5a77df6-fd29-48c7-8df6-f625de0179e7',
'bca45eef-fc64-465f-9106-403814f180a0', '3b5c305d-be5e-4209-9ba2-c2d1620a5a4f',
'14c533a5-c15c-493b-8c7d-7c20a0bc5ea1', '7b3b1e40-c75d-4c2c-843b-1599c10123c2',
'54604482-72aa-4d64-a3bb-9d8dc4a38b41', 'baf6f609-2dce-4978-840b-b74261c5ba6d',
'250c536f-6cd9-4707-8706-b682961be032', 'a9b08140-ae44-409a-9941-22794c3fd097',
'e516bd18-c46f-46b5-88dc-c7eac5a0f5a9', '6a220133-c71f-44df-8912-16f5b72568cb',
'b63c2adf-b8f3-4e43-8651-44574475621f', '01d88287-387b-4a34-b6d3-0931aba8eb1a',
'eb0cef3d-4e2b-45b3-88df-fc547a763d01', 'a79dee0f-8579-4e0a-b77f-0a468a3ca71f',
'62e50edb-03e6-4f95-a6f4-8ff3ef28a32b', '337fffe8-cc68-44b7-9636-2433f86ed5a9',
'bd68f7b7-f8bb-489e-ac29-f8cbfe1978b3', '93d2a7d6-06a6-41bf-9632-c24e1f1c917a']
quiz_list = [
"c63d4d39-767e-4754-8c16-66c3c2d9633c",
"a73689d2-1108-4524-88e1-10c54282346c",
"e8e2d869-7c75-4ad6-8872-031bc8e37fa9",
"387b4139-9e77-4b47-93b7-dbb6d75c54d1",
"a1fff657-b99d-4547-93cf-4e9629994844",
"0880d792-a77e-4cba-8a8b-359de40ffe76",
"e2d12f01-a3dd-4d0e-a6c4-29f38046117f",
"e0af9b8e-8840-4a7d-8d51-3d02bb6a07e7",
"e655c086-558d-4602-99fd-5fc429ffe0ce",
"1642f29d-b5cb-4213-82e6-7c1fae401aca",
"fabdd572-da3e-4621-b912-41b9bc5a18fb",
"ded3cbbf-5af3-4761-a6da-b3b6ca040e1a",
"370b3e33-ed97-4d36-9362-7f76aa9d12b9",
"cfa32a28-1a01-47f1-9bca-c85640bceeb8",
"993f615d-6890-4dd3-ac08-f462de9ac5a3",
"7f08a377-e371-425e-95f9-7196401ae7ff",
"19656fed-5891-48e7-ae7f-1358f623bfd0",
"5e766d32-930a-4640-82a1-87c58612f579",
"ab2450e8-ecbb-4aed-bb3c-87e47b2b81f5",
"dd3c9175-addd-48df-8297-7598d4f12006",
"b8de862c-fc4b-4df6-9fa5-81b91868fc3b",
"3b082f4e-9c50-45ea-98c4-3845b14e400c",
"10179f67-a344-4fd0-80a1-d1e1b6ca7953",
"8288c1dd-9675-411c-9175-4180004da3c3",
"af838c22-dcbb-497d-8c7b-f6499835b83d",
"152c5fdb-3b0a-4cdf-a0d9-1984de0643ad",
"846fd2bb-6b95-4333-b5d4-ef0a363e73a9",
"dd6b446f-817b-469b-9e6b-e1d34c466a7f",
"2720d636-8918-4c6a-86a9-928c489382f4",
"a6985641-df97-4e56-97fc-e0901e32b464",
"1d2705b4-b79e-4b51-8d61-cfb60fc7d1b9",
"677fa485-4231-4479-beb2-fe757053bf41",
"2b6379f8-450e-46c0-a854-5e368a03d5a6",
"f3ba42fe-11e8-47fc-8978-8d67bc0ce0b1",
"51d2e839-a9d7-4f26-b1fd-fb0743c3a236",
"f2212243-4553-456d-87e7-7ddfd6ee7563",
"2d6982d3-7b46-4022-bbb6-cb5451c581ba",
"1cccae22-5140-46b5-ba6a-aa17cec14dc3",
"b01dd2c2-ffcf-4c02-a274-e12296dbe4e1",
"2669d193-a81f-4752-a003-dd2433beef92",
"06203ee6-3615-4e26-bb08-4fab249698a4",
"933c1426-c759-43a3-9770-c8e3948db998",
"02046384-fb05-4f1d-855a-f0e52a0761e3",
"b5266e9b-7372-4df0-94a9-ec8a6c0aabc6",
"fb8e3a3d-505a-4157-bd9a-b73b00591f10",
"42092675-9171-400f-ab4d-c898ec9f70bd",
"1d6307e2-98c2-42f2-b765-d02a68e0da80",
"8f431ed0-31df-4435-bcba-aff46929fe82",
"d573d76f-d715-4a54-bdde-7ac7b6235fb2",
"feb8985b-84fb-4d41-bc41-70c322f508fa",
"f9f945cc-c166-4f7d-a95f-f0be154c43cf",
"009b130a-1709-41a6-943d-85eee4889792",
"a3f765f7-2329-47e0-83eb-0837b4b54b43",
"fea9e558-206f-444a-8d7f-a3f0301fda8d",
"9b419098-ccfc-467d-9de0-db18b60d6c28",
"e6c2174e-3a3f-4193-9782-1bf3b5cfae63",
"d42c962e-1691-4fa4-8926-c33157423bd2",
"8d27aa83-a103-4f05-8230-69e857bc3ffd",
"60cd19b2-6858-4704-b07b-5196e8e24325",
"31624ea4-1e4f-4f90-ae4f-759a0eb93b61",
"fcd038f4-6b05-485b-afe3-ce67e05b1695",
"1207bbe5-2df5-4c86-8f54-1b2675a86859",
"eea3b0b0-deae-41de-b010-d6d50855d016",
"ca8b0278-744c-4a3f-a2b8-6ab29d76fa7f",
"68d37bc8-28b9-49fd-9e02-48a3c9195fdf",
"8bf13f61-c5cc-4332-aab9-37405d1fdd14",
"8ceef7de-9408-48d1-8bb9-b8ac3ef73596",
"7e053a85-b04c-484b-8815-d26ce646c748",
"dfc09237-85b7-4596-b4e8-0bfbfd1aaeb7",
"d8d291ab-9dfa-45c0-bfc2-0a81167e3c63",
"d0834157-d464-4632-b620-147e211e0614",
"30758f35-fabc-48bc-b410-47450de64c82",
"7232bc80-a9cb-4668-9449-16b9282a2331",
"29a194bd-2ecc-44bc-a6a9-6b8a25c3d860",
"52b43400-117a-4e16-81b8-b510fe987750",
"52ef0c45-9ba1-4d5e-ba0c-82703196ec6c",
"9f0fa778-e6ae-4f7b-b4fd-2ccb1bd37eb7",
"54b57afd-c91c-4d5f-8177-c3fe940bc723",
"23620661-6eb6-41df-9de4-18ff99e6f35f",
"c983fe89-6ac3-4395-882e-4b11aa11a99d",
"d4d01494-e7d0-4a38-aa93-523a77855371",
"e5a77df6-fd29-48c7-8df6-f625de0179e7",
"bca45eef-fc64-465f-9106-403814f180a0",
"3b5c305d-be5e-4209-9ba2-c2d1620a5a4f",
"14c533a5-c15c-493b-8c7d-7c20a0bc5ea1",
"7b3b1e40-c75d-4c2c-843b-1599c10123c2",
"54604482-72aa-4d64-a3bb-9d8dc4a38b41",
"baf6f609-2dce-4978-840b-b74261c5ba6d",
"250c536f-6cd9-4707-8706-b682961be032",
"a9b08140-ae44-409a-9941-22794c3fd097",
"e516bd18-c46f-46b5-88dc-c7eac5a0f5a9",
"6a220133-c71f-44df-8912-16f5b72568cb",
"b63c2adf-b8f3-4e43-8651-44574475621f",
"01d88287-387b-4a34-b6d3-0931aba8eb1a",
"eb0cef3d-4e2b-45b3-88df-fc547a763d01",
"a79dee0f-8579-4e0a-b77f-0a468a3ca71f",
"62e50edb-03e6-4f95-a6f4-8ff3ef28a32b",
"337fffe8-cc68-44b7-9636-2433f86ed5a9",
"bd68f7b7-f8bb-489e-ac29-f8cbfe1978b3",
"93d2a7d6-06a6-41bf-9632-c24e1f1c917a",
]
rounds = 0
for i in quiz_list:
rounds = rounds + 1
+13 -6
View File
@@ -10,11 +10,14 @@ file_contents = b"hello world"
def test_storage_init():
with pytest.raises(NotImplementedError):
Storage(backend="asdsad", deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id, storage_path=settings.storage_path)
Storage(
backend="asdsad",
deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id,
storage_path=settings.storage_path,
)
with pytest.raises(ValueError):
Storage(backend="deta", deta_key=None,
deta_id=None, storage_path=None)
Storage(backend="deta", deta_key=None, deta_id=None, storage_path=None)
with pytest.raises(ValueError):
Storage(backend="local", storage_path=None, deta_key=None, deta_id=None)
@@ -34,8 +37,12 @@ async def storage_tester(storage: Storage):
@pytest.mark.asyncio
async def test_deta():
storage: Storage = Storage(backend="deta", deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id, storage_path=settings.storage_path)
storage: Storage = Storage(
backend="deta",
deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id,
storage_path=settings.storage_path,
)
await storage_tester(storage)
+3
View File
@@ -0,0 +1,3 @@
[tool.black]
line-length = 120
target-version = ["py310"]