diff --git a/src/crud/highscores.py b/src/crud/highscores.py index 649610e..5acd8d3 100644 --- a/src/crud/highscores.py +++ b/src/crud/highscores.py @@ -6,8 +6,7 @@ from sqlalchemy.orm import Session from src.enums import MinigameEnum from src.models import HighScore, User -from src.schemas.highscores import HighScoreBase -from src.schemas.saved_data import Score +from src.schemas.highscores import HighScoreBase, Score def get_most_recent_high_scores(db: Session, minigame: MinigameEnum, amount: int): @@ -27,7 +26,7 @@ def get_most_recent_high_scores(db: Session, minigame: MinigameEnum, amount: int for high_score in high_scores_query: high_scores.append( - Score(score_value=high_score.score_value, time=high_score.time) + Score(score_value=high_score.score_value, time=str(high_score.time)) ) return high_scores @@ -54,10 +53,11 @@ def get_highest_high_scores( .first() ) if high_score: + print(str(high_score.time)) return [ Score( score_value=high_score.score_value, - time=high_score.time, + time=str(high_score.time), ) ] else: @@ -76,7 +76,7 @@ def get_highest_high_scores( for high_score in high_scores_query: high_scores.append( - Score(score_value=high_score.score_value, time=high_score.time) + Score(score_value=high_score.score_value, time=str(high_score.time)) ) return high_scores @@ -97,7 +97,7 @@ def create_high_score( db.add(db_high_score) db.commit() db.refresh(db_high_score) - return db_high_score + return Score(score_value=db_high_score.score_value, time=str(db_high_score.time)) old_high_score = ( db.query(HighScore) @@ -112,6 +112,6 @@ def create_high_score( db.delete(old_high_score) return add_to_db() else: - return old_high_score + return Score(score_value=old_high_score.score_value, time=str(old_high_score.time)) else: return add_to_db() diff --git a/src/crud/saved_data.py b/src/crud/saved_data.py deleted file mode 100644 index fee3c80..0000000 --- a/src/crud/saved_data.py +++ /dev/null @@ -1,81 +0,0 @@ -from sqlalchemy.orm import Session - -from src.crud.highscores import (get_highest_high_scores, - get_most_recent_high_scores) -from src.crud.users import get_user_by_username -from src.enums import CourseEnum, MinigameEnum -from src.models import CourseProgress, LearnableProgress -from src.schemas.saved_data import * - - -def get_saved_data(db: Session, username: str): - """Fetches all saved progress for the current user from the database""" - user = get_user_by_username(db, username) - minigames = [] - courses = [] - - for minigame in MinigameEnum: - minigames.append( - SavedMinigameProgress( - minigame_index=minigame, - latest_scores=[ - score for score in get_most_recent_high_scores(db, minigame, 10) - ], - highest_scores=[ - score - for score in get_highest_high_scores(db, minigame, user, 10, False) - ], - ) - ) - - course_progress_query = ( - db.query(CourseProgress).filter(CourseProgress.owner_id == user.user_id).all() - ) - - for course_progress in course_progress_query: - learnable_progress_query = ( - db.query(LearnableProgress) - .filter( - LearnableProgress.course_progress_id - == course_progress.course_progress_id - ) - .all() - ) - - learnables = [ - SavedLearnableProgress( - index=lp.index, in_use=lp.in_use, name=lp.name, progress=lp.progress - ) - for lp in learnable_progress_query - ] - - completed_learnables = sum( - [1 if learnable.progress == 5.0 else 0 for learnable in learnables] - ) - - in_use_learnables = sum( - [1 if learnable.in_use else 0 for learnable in learnables] - ) - - total_learnables = len(learnables) - - courses.append( - SavedCourseProgress( - course_index=course_progress.course, - progress=course_progress.progress, - completed_learnables=completed_learnables, - in_use_learnables=in_use_learnables, - total_learnables=total_learnables, - learnables=learnables, - ) - ) - - user_progress = SavedUser( - username=user.username, - avatar_index=user.avatar_index, - playtime=user.playtime, - minigames=minigames, - courses=courses, - ) - - return user_progress diff --git a/src/crud/users.py b/src/crud/users.py index 5e92efd..bd467b6 100644 --- a/src/crud/users.py +++ b/src/crud/users.py @@ -3,7 +3,15 @@ from passlib.context import CryptContext from sqlalchemy.orm import Session from src.models import User +from src.crud.highscores import (get_highest_high_scores, + get_most_recent_high_scores) from src.schemas.users import UserCreate +from src.enums import CourseEnum, MinigameEnum +from src.models import CourseProgress, LearnableProgress +from src.schemas.highscores import SavedMinigameProgress +from src.schemas.courseprogress import SavedCourseProgress +from src.schemas.learnableprogress import SavedLearnableProgress +from src.schemas.users import SavedUser pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") @@ -20,15 +28,26 @@ def check_empty_fields(username: str, password: str, avatar_index: int): def patch_user(db: Session, username: str, user: UserCreate): """Changes the username and/or the password of a User""" - check_empty_fields(user.username, user.password, user.avatar_index) + #check_empty_fields(user.username, user.password, user.avatar_index) db_user = get_user_by_username(db, username) potential_duplicate = get_user_by_username(db, user.username) if potential_duplicate: if potential_duplicate.user_id != db_user.user_id: raise HTTPException(status_code=400, detail="Username already registered") - db_user.username = user.username - db_user.hashed_password = pwd_context.hash(user.password) - db_user.avatar_index = user.avatar_index + + if user.playtime < 0: + raise HTTPException(status_code=400, detail="Negative playtime is invalid") + + if len(user.username) > 0: + db_user.username = user.username + + if len(user.password) > 0: + db_user.hashed_password = pwd_context.hash(user.password) + + if user.avatar_index > -1: + db_user.avatar_index = user.avatar_index + + db_user.playtime += user.playtime db.commit() @@ -40,3 +59,76 @@ def get_user_by_username(db: Session, username: str): def get_users(db: Session): """Fetch a list of all users""" return db.query(User).all() + + +def get_saved_data(db: Session, username: str): + """Fetches all saved progress for the current user from the database""" + user = get_user_by_username(db, username) + minigames = [] + courses = [] + + for minigame in MinigameEnum: + minigames.append( + SavedMinigameProgress( + minigame_index=minigame, + latest_scores=[ + score for score in get_most_recent_high_scores(db, minigame, 10) + ], + highest_scores=[ + score + for score in get_highest_high_scores(db, minigame, user, 10, False) + ], + ) + ) + + course_progress_query = ( + db.query(CourseProgress).filter(CourseProgress.owner_id == user.user_id).all() + ) + + for course_progress in course_progress_query: + learnable_progress_query = ( + db.query(LearnableProgress) + .filter( + LearnableProgress.course_progress_id + == course_progress.course_progress_id + ) + .all() + ) + + learnables = [ + SavedLearnableProgress( + index=lp.index, in_use=lp.in_use, name=lp.name, progress=lp.progress + ) + for lp in learnable_progress_query + ] + + completed_learnables = sum( + [1 if learnable.progress == 5.0 else 0 for learnable in learnables] + ) + + in_use_learnables = sum( + [1 if learnable.in_use else 0 for learnable in learnables] + ) + + total_learnables = len(learnables) + + courses.append( + SavedCourseProgress( + course_index=course_progress.course, + progress=course_progress.progress, + completed_learnables=completed_learnables, + in_use_learnables=in_use_learnables, + total_learnables=total_learnables, + learnables=learnables, + ) + ) + + user_progress = SavedUser( + username=user.username, + avatar_index=user.avatar_index, + playtime=user.playtime, + minigames=minigames, + courses=courses, + ) + + return user_progress diff --git a/src/main.py b/src/main.py index b2422cd..b2b2d16 100644 --- a/src/main.py +++ b/src/main.py @@ -9,7 +9,6 @@ sys.path.append("..") from src.crud import authentication as crud_authentication from src.crud import courseprogress as crud_courseprogress from src.crud import highscores as crud_highscores -from src.crud import saved_data as crud_saved_data from src.crud import users as crud_users from src.database import Base, engine, get_db from src.enums import CourseEnum, MinigameEnum @@ -53,7 +52,7 @@ async def read_saved_data( current_user_name: str = Depends(crud_authentication.get_current_user_name), db: Session = Depends(get_db), ): - return crud_saved_data.get_saved_data(db, current_user_name) + return crud_users.get_saved_data(db, current_user_name) @app.post("/register") @@ -88,7 +87,7 @@ async def get_high_scores( ) -@app.put("/highscores/{minigame}", response_model=highscores.HighScore) +@app.put("/highscores/{minigame}", response_model=highscores.Score) async def create_high_score( minigame: MinigameEnum, high_score: highscores.HighScoreBase, diff --git a/src/schemas/courseprogress.py b/src/schemas/courseprogress.py index 7e563a3..1fef0a1 100644 --- a/src/schemas/courseprogress.py +++ b/src/schemas/courseprogress.py @@ -27,4 +27,7 @@ class SavedCourseProgress(BaseModel): completed_learnables: int in_use_learnables: int total_learnables: int - learnables: List[SavedLearnableProgress] \ No newline at end of file + learnables: List[SavedLearnableProgress] + + class Config: + orm_mode = True \ No newline at end of file diff --git a/src/schemas/highscores.py b/src/schemas/highscores.py index 75680c2..75eed72 100644 --- a/src/schemas/highscores.py +++ b/src/schemas/highscores.py @@ -9,28 +9,17 @@ class HighScoreBase(BaseModel): score_value: float -class HighScore(HighScoreBase): - high_score_id: int - owner_id: int - minigame: MinigameEnum - - class Config: - orm_mode = True - - -class Score(BaseModel): - score_id: int - score_value: float +class Score(HighScoreBase): time: str class Config: orm_mode = True -class Score(BaseModel): - score_value: int - time: str class SavedMinigameProgress(BaseModel): minigame_index: MinigameEnum latest_scores: List[Score] highest_scores: List[Score] + + class Config: + orm_mode = True diff --git a/src/schemas/saved_data.py b/src/schemas/saved_data.py deleted file mode 100644 index 39fc7ee..0000000 --- a/src/schemas/saved_data.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List - -from pydantic import BaseModel - -from src.enums import CourseEnum, MinigameEnum - - -class Score(BaseModel): - score_value: int - time: str - - -class SavedLearnableProgress(BaseModel): - index: int - in_use: bool - name: str - progress: float - - - - diff --git a/src/schemas/users.py b/src/schemas/users.py index 9ba0b9a..24a22f4 100644 --- a/src/schemas/users.py +++ b/src/schemas/users.py @@ -5,17 +5,16 @@ from src.schemas.courseprogress import SavedCourseProgress class UserBase(BaseModel): - username: str + username: str = "" avatar_index: int = -1 class UserCreate(UserBase): - password: str + password: str = "" + playtime: float = 0.0 -class SavedUser(BaseModel): - username: str - avatar_index: int = -1 +class SavedUser(UserBase): playtime: float minigames: List[SavedMinigameProgress] courses: List[SavedCourseProgress] diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 5da1c6e..a5cf574 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -46,7 +46,7 @@ async def test_register_without_username_should_fail(): json={"password": password, "avatar_index": avatar_index}, ) - assert response.status_code == 422 + assert response.status_code == 400 assert "access_token" not in response.json() @@ -61,7 +61,7 @@ async def test_register_without_password_should_fail(): json={"username": username, "avatar_index": avatar_index}, ) - assert response.status_code == 422 + assert response.status_code == 400 assert "access_token" not in response.json() @@ -76,7 +76,6 @@ async def test_register_without_avatar_should_fail(): json={"username": username, "password": password}, ) - # Not ideal that this is 400 instead of 422, but had no other choice than to give this field a default value assert response.status_code == 400 assert "access_token" not in response.json() @@ -115,7 +114,7 @@ async def test_login_wrong_password_should_fail(): @pytest.mark.asyncio async def test_login_without_username_should_fail(): - """Test whether logging in without passing a username fails""" + """Test whether logging in without passing a username fails, since the default is an empty string""" clear_db() await register_user() @@ -125,13 +124,13 @@ async def test_login_without_username_should_fail(): json={"password": password}, ) - assert response.status_code == 422 + assert response.status_code == 401 assert "access_token" not in response.json() @pytest.mark.asyncio async def test_login_without_password_should_fail(): - """Test whether logging in without passing a password fails""" + """Test whether logging in without passing a password fails, since the default is an empty string""" clear_db() await register_user() @@ -141,5 +140,5 @@ async def test_login_without_password_should_fail(): json={"username": username}, ) - assert response.status_code == 422 + assert response.status_code == 401 assert "access_token" not in response.json() diff --git a/tests/test_highscores.py b/tests/test_highscores.py index f06a42e..c9f31cc 100644 --- a/tests/test_highscores.py +++ b/tests/test_highscores.py @@ -3,7 +3,7 @@ import random import pytest from src.enums import MinigameEnum -from tests.base import client, password, register_user +from tests.base import client, password, avatar_index, register_user from tests.config.database import clear_db @@ -27,7 +27,6 @@ async def test_put_highscore(): response = response.json() - assert response["minigame"] == minigame assert response["score_value"] == score_value @@ -51,7 +50,6 @@ async def test_put_lower_highscore_does_not_change_old_value(): response = response.json() - assert response["minigame"] == minigame assert response["score_value"] == score_value lower_score_value = score_value - 100 @@ -65,7 +63,6 @@ async def test_put_lower_highscore_does_not_change_old_value(): response = response.json() - assert response["minigame"] == minigame assert response["score_value"] == score_value @@ -121,7 +118,7 @@ async def test_get_highscores_without_auth_should_fail(): assert response.status_code == 403 response = client.get( - f"/highscores/{minigame}?mine_only=false&nr_highest={random.randint(1, 50)}", + f"/highscores/{minigame}?mine_only=false&amount={random.randint(1, 50)}", headers=headers, ) @@ -146,7 +143,7 @@ async def test_get_highscore_for_nonexisting_minigame_should_fail(): assert response.status_code == 422 response = client.get( - f"/highscores/{fake_minigame}?mine_only=false&nr_highest={random.randint(1, 50)}", + f"/highscores/{fake_minigame}?mine_only=false&amount={random.randint(1, 50)}", headers=headers, ) @@ -163,7 +160,7 @@ async def test_get_invalid_number_of_highscores_should_fail(): for minigame in MinigameEnum: response = client.get( - f"/highscores/{minigame}?nr_highest={random.randint(-100, 0)}", + f"/highscores/{minigame}?amount={random.randint(-100, 0)}", headers=headers, ) @@ -231,7 +228,7 @@ async def test_get_highscores_returns_sorted_list_with_correct_length(): assert response.status_code == 200 response = client.get( - f"/highscores/{minigame}?mine_only=false&nr_highest={int(nr_entries)}", + f"/highscores/{minigame}?mine_only=false&amount={int(nr_entries)}", headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", @@ -301,7 +298,7 @@ async def test_get_multiple_own_high_scores_of_same_game_should_fail(): for minigame in MinigameEnum: response = client.get( - f"/highscores/{minigame}?nr_highest={random.randint(2, 20)}", + f"/highscores/{minigame}?amount={random.randint(2, 20)}", headers=headers, ) diff --git a/tests/test_users.py b/tests/test_users.py index 135b907..0710496 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -8,30 +8,6 @@ patched_password = "New password" patched_avatar_index = 2 -@pytest.mark.asyncio -async def test_get_current_user(): - """Test the GET /users endpoint to get info about the current user""" - clear_db() - - token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} - - response = client.get("/users", headers=headers) - assert response.status_code == 200 - response = response.json() - assert response["username"] == username - assert response["avatar_index"] == avatar_index - - -@pytest.mark.asyncio -async def test_get_current_user_without_auth(): - """Getting the current user without a token should fail""" - clear_db() - - response = client.get("/users", headers={"Content-Type": "application/json"}) - - assert response.status_code == 403 - @pytest.mark.asyncio async def test_patch_user(): @@ -64,7 +40,7 @@ async def test_patch_user(): headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} - response = client.get("/users", headers=headers) + response = client.get("/saveddata", headers=headers) assert response.status_code == 200 # Correctness of password and username is already asserted by the login @@ -80,35 +56,38 @@ async def test_patch_user_with_empty_fields(): headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} - response = client.patch( - "/users", - json={ - "username": patched_username, - "password": patched_password, - "avatar_index": "", - }, - headers=headers, - ) - assert response.status_code == 422 - - response = client.patch( - "/users", - json={ - "username": patched_username, - "password": "", - "avatar_index": patched_avatar_index, - }, - headers=headers, - ) - assert response.status_code == 400 - response = client.patch( "/users", json={ "username": "", "password": patched_password, "avatar_index": patched_avatar_index, + "playtime": 0.0 }, headers=headers, ) - assert response.status_code == 400 + assert response.status_code == 200 + + response = client.patch( + "/users", + json={ + "username": username, + "password": patched_password, + "avatar_index": -1, + "playtime": 0.0 + }, + headers=headers, + ) + assert response.status_code == 200 + + response = client.patch( + "/users", + json={ + "username": username, + "password": "", + "avatar_index": patched_avatar_index, + "playtime": 0.0 + }, + headers=headers, + ) + assert response.status_code == 200