Skip to content

Commit

Permalink
Flappy Bird Module (#109)
Browse files Browse the repository at this point in the history
### 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
5 people committed May 14, 2024
1 parent 2fd8740 commit 44c0634
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 0 deletions.
Empty file.
94 changes: 94 additions & 0 deletions app/modules/flappybird/cruds_flappybird.py
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)
115 changes: 115 additions & 0 deletions app/modules/flappybird/endpoints_flappybird.py
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))
22 changes: 22 additions & 0 deletions app/modules/flappybird/models_flappybird.py
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]
28 changes: 28 additions & 0 deletions app/modules/flappybird/schemas_flappybird.py
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
55 changes: 55 additions & 0 deletions migrations/versions/12-Flappybird.py
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
66 changes: 66 additions & 0 deletions tests/test_flappybird.py
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

0 comments on commit 44c0634

Please sign in to comment.