-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### Description Please explain the changes you made here. ### Checklist - [x] Created tests which fail without the change (if possible) - [x] All tests passing - [ ] Extended the documentation, if necessary --------- Co-authored-by: shaihh <guillaume.tavernier@ec22.ec-lyon.fr> Co-authored-by: Brzuszek Maël <mael.brzuszek@ecl21.ec-lyon.fr> Co-authored-by: Maxime Roucher <maximeroucher@gmail.com> Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com>
- Loading branch information
1 parent
2fd8740
commit 44c0634
Showing
7 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import logging | ||
|
||
from sqlalchemy import and_, func, select | ||
from sqlalchemy.exc import IntegrityError | ||
from sqlalchemy.ext.asyncio import AsyncSession | ||
from sqlalchemy.orm import selectinload | ||
|
||
from app.modules.flappybird import models_flappybird | ||
|
||
hyperion_logger = logging.getLogger("hyperion.error") | ||
|
||
|
||
async def get_flappybird_score_leaderboard( | ||
db: AsyncSession, | ||
skip: int, | ||
limit: int, | ||
) -> list[models_flappybird.FlappyBirdScore]: | ||
"""Return the flappybird leaderboard scores from postion skip to skip+limit""" | ||
subquery = ( | ||
select( | ||
func.max(models_flappybird.FlappyBirdScore.value).label("max_score"), | ||
models_flappybird.FlappyBirdScore.user_id, | ||
) | ||
.group_by(models_flappybird.FlappyBirdScore.user_id) | ||
.alias("subquery") | ||
) | ||
|
||
result = await db.execute( | ||
select(models_flappybird.FlappyBirdScore) | ||
.join( | ||
subquery, | ||
and_( | ||
models_flappybird.FlappyBirdScore.user_id == subquery.c.user_id, | ||
models_flappybird.FlappyBirdScore.value == subquery.c.max_score, | ||
), | ||
) | ||
.options(selectinload(models_flappybird.FlappyBirdScore.user)) | ||
.order_by(models_flappybird.FlappyBirdScore.value.desc()) | ||
.offset(skip) | ||
.limit(limit), | ||
) | ||
return list(result.scalars().all()) | ||
|
||
|
||
async def get_flappybird_personal_best_by_user_id( | ||
db: AsyncSession, | ||
user_id: str, | ||
) -> models_flappybird.FlappyBirdScore | None: | ||
"""Return the flappybird PB in the leaderboard by user_id""" | ||
|
||
personal_best_result = await db.execute( | ||
select(models_flappybird.FlappyBirdScore) | ||
.where(models_flappybird.FlappyBirdScore.user_id == user_id) | ||
.order_by(models_flappybird.FlappyBirdScore.value.desc()) | ||
.limit(1), | ||
) | ||
return personal_best_result.scalar() | ||
|
||
|
||
async def get_flappybird_score_position( | ||
db: AsyncSession, | ||
score_value: int, | ||
) -> int | None: | ||
"""Return the position in the leaderboard of a given score value""" | ||
subquery = ( | ||
select( | ||
func.max(models_flappybird.FlappyBirdScore.value).label("max_score"), | ||
models_flappybird.FlappyBirdScore.user_id, | ||
) | ||
.group_by(models_flappybird.FlappyBirdScore.user_id) | ||
.alias("subquery") | ||
) | ||
|
||
result = await db.execute( | ||
select(func.count()) | ||
.select_from(subquery) | ||
.where(subquery.c.max_score >= score_value), | ||
) | ||
|
||
return result.scalar() | ||
|
||
|
||
async def create_flappybird_score( | ||
db: AsyncSession, | ||
flappybird_score: models_flappybird.FlappyBirdScore, | ||
) -> models_flappybird.FlappyBirdScore: | ||
"""Add a FlappyBirdScore in database""" | ||
db.add(flappybird_score) | ||
try: | ||
await db.commit() | ||
return flappybird_score | ||
except IntegrityError as error: | ||
await db.rollback() | ||
raise ValueError(error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import uuid | ||
from datetime import UTC, datetime | ||
|
||
from fastapi import Depends, HTTPException | ||
from sqlalchemy.ext.asyncio import AsyncSession | ||
|
||
from app.core import models_core | ||
from app.core.groups.groups_type import GroupType | ||
from app.core.module import Module | ||
from app.dependencies import get_db, is_user_a_member | ||
from app.modules.flappybird import ( | ||
cruds_flappybird, | ||
models_flappybird, | ||
schemas_flappybird, | ||
) | ||
|
||
module = Module( | ||
root="flappybird", | ||
tag="Flappy Bird", | ||
default_allowed_groups_ids=[GroupType.student], | ||
) | ||
|
||
|
||
@module.router.get( | ||
"/flappybird/scores", | ||
response_model=list[schemas_flappybird.FlappyBirdScoreInDB], | ||
status_code=200, | ||
) | ||
async def get_flappybird_score( | ||
skip: int = 0, | ||
limit: int = 10, | ||
db: AsyncSession = Depends(get_db), | ||
): | ||
"""Return the leaderboard score of the skip...limit""" | ||
leaderboard = await cruds_flappybird.get_flappybird_score_leaderboard( | ||
db=db, | ||
skip=skip, | ||
limit=limit, | ||
) | ||
return leaderboard | ||
|
||
|
||
@module.router.get( | ||
"/flappybird/scores/me", | ||
status_code=200, | ||
response_model=schemas_flappybird.FlappyBirdScoreCompleteFeedBack, | ||
) | ||
async def get_current_user_flappybird_personal_best( | ||
db: AsyncSession = Depends(get_db), | ||
user: models_core.CoreUser = Depends(is_user_a_member), | ||
): | ||
user_personal_best_table = ( | ||
await cruds_flappybird.get_flappybird_personal_best_by_user_id( | ||
db=db, | ||
user_id=user.id, | ||
) | ||
) | ||
|
||
if user_personal_best_table is None: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Not found", | ||
) | ||
|
||
position = await cruds_flappybird.get_flappybird_score_position( | ||
db=db, | ||
score_value=user_personal_best_table.value, | ||
) | ||
if position is None: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Not found", | ||
) | ||
user_personal_best = schemas_flappybird.FlappyBirdScoreCompleteFeedBack( | ||
value=user_personal_best_table.value, | ||
user=user_personal_best_table.user, | ||
creation_time=user_personal_best_table.creation_time, | ||
position=position, | ||
) | ||
|
||
return user_personal_best | ||
|
||
|
||
@module.router.post( | ||
"/flappybird/scores", | ||
response_model=schemas_flappybird.FlappyBirdScoreBase, | ||
status_code=201, | ||
) | ||
async def create_flappybird_score( | ||
flappybird_score: schemas_flappybird.FlappyBirdScoreBase, | ||
user: models_core.CoreUser = Depends(is_user_a_member), | ||
db: AsyncSession = Depends(get_db), | ||
): | ||
# Currently, flappybird_score is a schema instance | ||
# To add it to the database, we need to create a model | ||
|
||
# We need to generate a new UUID for the score | ||
score_id = uuid.uuid4() | ||
# And get the current date and time | ||
creation_time = datetime.now(UTC) | ||
|
||
db_flappybird_score = models_flappybird.FlappyBirdScore( | ||
id=score_id, | ||
user_id=user.id, | ||
value=flappybird_score.value, | ||
creation_time=creation_time, | ||
# We add all informations contained in the schema | ||
) | ||
try: | ||
return await cruds_flappybird.create_flappybird_score( | ||
flappybird_score=db_flappybird_score, | ||
db=db, | ||
) | ||
except ValueError as error: | ||
raise HTTPException(status_code=400, detail=str(error)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from datetime import datetime | ||
|
||
from sqlalchemy import ForeignKey, String | ||
from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
|
||
from app.core.models_core import CoreUser | ||
from app.types.sqlalchemy import Base, PrimaryKey | ||
|
||
|
||
class FlappyBirdScore(Base): | ||
__tablename__ = "flappy-bird_score" | ||
|
||
# id: Mapped[str] = mapped_column(String, primary_key=True, index=True) | ||
id: Mapped[PrimaryKey] | ||
user_id: Mapped[str] = mapped_column( | ||
String, | ||
ForeignKey("core_user.id"), | ||
nullable=False, | ||
) | ||
user: Mapped[CoreUser] = relationship("CoreUser") | ||
value: Mapped[int] | ||
creation_time: Mapped[datetime] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import uuid | ||
from datetime import datetime | ||
|
||
from pydantic import BaseModel | ||
|
||
from app.core.schemas_core import CoreUserSimple | ||
|
||
|
||
class FlappyBirdScoreBase(BaseModel): | ||
value: int | ||
|
||
|
||
class FlappyBirdScore(FlappyBirdScoreBase): | ||
user: CoreUserSimple | ||
creation_time: datetime | ||
|
||
|
||
class FlappyBirdScoreInDB(FlappyBirdScore): | ||
id: uuid.UUID | ||
user_id: str | ||
|
||
|
||
class FlappyBirdScoreCompleteFeedBack(FlappyBirdScore): | ||
""" | ||
A score, with it's position in the best players leaderboard | ||
""" | ||
|
||
position: int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""flappybird | ||
Create Date: 2024-05-14 12:08:30.761420 | ||
""" | ||
|
||
from collections.abc import Sequence | ||
from typing import TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from pytest_alembic import MigrationContext | ||
|
||
import sqlalchemy as sa | ||
from alembic import op | ||
|
||
from app.types.sqlalchemy import TZDateTime | ||
|
||
# revision identifiers, used by Alembic. | ||
revision: str = "e98026d51884" | ||
down_revision: str | None = "e22bfe152f72" | ||
branch_labels: str | Sequence[str] | None = None | ||
depends_on: str | Sequence[str] | None = None | ||
|
||
|
||
def upgrade() -> None: | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table( | ||
"flappy-bird_score", | ||
sa.Column("id", sa.Uuid(), nullable=False), | ||
sa.Column("user_id", sa.String(), nullable=False), | ||
sa.Column("value", sa.Integer(), nullable=False), | ||
sa.Column("creation_time", TZDateTime(), nullable=False), | ||
sa.ForeignKeyConstraint(["user_id"], ["core_user.id"]), | ||
sa.PrimaryKeyConstraint("id"), | ||
) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade() -> None: | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_table("flappy-bird_score") | ||
# ### end Alembic commands ### | ||
|
||
|
||
def pre_test_upgrade( | ||
alembic_runner: "MigrationContext", | ||
alembic_connection: sa.Connection, | ||
) -> None: | ||
pass | ||
|
||
|
||
def test_upgrade( | ||
alembic_runner: "MigrationContext", | ||
alembic_connection: sa.Connection, | ||
) -> None: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import uuid | ||
from datetime import UTC, datetime | ||
|
||
import pytest_asyncio | ||
|
||
from app.core import models_core | ||
from app.core.groups.groups_type import GroupType | ||
from app.modules.flappybird import models_flappybird | ||
from tests.commons import ( | ||
add_object_to_db, | ||
client, | ||
create_api_access_token, | ||
create_user_with_groups, | ||
event_loop, # noqa | ||
) | ||
|
||
flappybird_score: models_flappybird.FlappyBirdScore | None = None | ||
user: models_core.CoreUser | None = None | ||
token: str = "" | ||
|
||
|
||
@pytest_asyncio.fixture(scope="module", autouse=True) | ||
async def init_objects(): | ||
global user | ||
user = await create_user_with_groups([GroupType.student]) | ||
|
||
global token | ||
token = create_api_access_token(user=user) | ||
|
||
global flappybird_score | ||
flappybird_score = models_flappybird.FlappyBirdScore( | ||
id=uuid.uuid4(), | ||
user_id=user.id, | ||
user=user, | ||
value=25, | ||
creation_time=datetime.now(UTC), | ||
) | ||
|
||
await add_object_to_db(flappybird_score) | ||
|
||
|
||
def test_get_flappybird_score(): | ||
response = client.get( | ||
"/flappybird/scores/", | ||
headers={"Authorization": f"Bearer {token}"}, | ||
) | ||
assert response.status_code == 200 | ||
|
||
|
||
def test_get_current_user_flappybird_personal_best(): | ||
response = client.get( | ||
"/flappybird/scores/me/", | ||
headers={"Authorization": f"Bearer {token}"}, | ||
) | ||
assert response.status_code == 200 | ||
|
||
|
||
def test_create_flappybird_score(): | ||
response = client.post( | ||
"/flappybird/scores", | ||
json={ | ||
"value": "26", | ||
}, | ||
headers={"Authorization": f"Bearer {token}"}, | ||
) | ||
assert response.status_code == 201 |