Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added app/modules/rPlace/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions app/modules/rPlace/coredata_rplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from app.types.core_data import BaseCoreData


class gridInformation(BaseCoreData):
nbLigne: int = 100
nbColonne: int = 100
pixelSize: float = 10
cooldown: int = 10000000
100 changes: 100 additions & 0 deletions app/modules/rPlace/cruds_rplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from datetime import UTC, datetime

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.rPlace import models_rplace


async def get_pixels(db: AsyncSession) -> list[models_rplace.Pixel]:
subquery = (
select(
func.max(models_rplace.Pixel.date).label("max_date"),
models_rplace.Pixel.x,
models_rplace.Pixel.y,
)
.group_by(models_rplace.Pixel.x, models_rplace.Pixel.y)
.alias("subquery")
)

result = await db.execute(
select(models_rplace.Pixel)
.join(
subquery,
and_(
models_rplace.Pixel.x == subquery.c.x,
models_rplace.Pixel.y == subquery.c.y,
models_rplace.Pixel.date == subquery.c.max_date,
),
)
.order_by(models_rplace.Pixel.date.desc()),
)

return list(result.scalars().all())


async def create_pixel(
db: AsyncSession,
rplace_pixel: models_rplace.Pixel,
) -> models_rplace.Pixel:
"""Add a pixel in database"""
db.add(rplace_pixel)
try:
await db.commit()
except IntegrityError as error:
await db.rollback()
raise ValueError(error)
return rplace_pixel


async def get_pixel_info(
db: AsyncSession,
x: int,
y: int,
) -> models_rplace.Pixel | None:
result = await db.execute(
select(models_rplace.Pixel)
.where(models_rplace.Pixel.x == x, models_rplace.Pixel.y == y)
.order_by(models_rplace.Pixel.date.desc())
.options(
selectinload(models_rplace.Pixel.user),
),
)

return result.scalars().first()


async def get_last_pixel_date(
db: AsyncSession,
user_id: str,
) -> datetime:
result = await db.execute(
select(models_rplace.Pixel.date)
.where(
models_rplace.Pixel.user_id == user_id,
)
.order_by(
models_rplace.Pixel.date.desc(),
)
.limit(1),
)
return result.scalars().first() or datetime(2003, 12, 18, 7, 46, 0, 0, UTC)
# return {"last_pixel_date": date_result.isoformat()}


async def get_last_pixel(
db: AsyncSession,
user_id: str,
) -> models_rplace.Pixel | None:
result = await db.execute(
select(models_rplace.Pixel)
.where(
models_rplace.Pixel.user_id == user_id,
)
.order_by(
models_rplace.Pixel.date.desc(),
),
)
return result.scalars().first()
183 changes: 183 additions & 0 deletions app/modules/rPlace/endpoints_rplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import logging
import uuid
from datetime import UTC, datetime, timedelta

from fastapi import Depends, HTTPException, WebSocket
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.groups.groups_type import AccountType
from app.core.users import models_users
from app.core.utils.config import Settings
from app.dependencies import (
get_db,
get_settings,
get_unsafe_db,
get_websocket_connection_manager,
is_user,
is_user_a_member,
)
from app.modules.rPlace import (
coredata_rplace,
cruds_rplace,
models_rplace,
schemas_rplace,
)
from app.types.module import Module
from app.types.websocket import HyperionWebsocketsRoom, WebsocketConnectionManager
from app.utils.tools import get_core_data

module = Module(
root="rplace",
tag="rplace",
default_allowed_account_types=[AccountType.student],
factory=None,
)

hyperion_error_logger = logging.getLogger("hyperion_error_logger")


@module.router.get(
"/rplace/pixels",
response_model=list[schemas_rplace.Pixel],
status_code=200,
)
async def get_pixels(
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_member),
):
return await cruds_rplace.get_pixels(db=db)


@module.router.post(
"/rplace/pixels",
response_model=schemas_rplace.Pixel,
status_code=201,
)
async def create_pixel(
pixel: schemas_rplace.Pixel,
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_member),
ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager),
):
pixel_id = uuid.uuid4()

db_item = models_rplace.Pixel(
id=pixel_id,
date=datetime.now(tz=UTC),
user_id=user.id,
x=pixel.x,
y=pixel.y,
color=pixel.color,
)

last_pixel_placed = await cruds_rplace.get_last_pixel(
db=db,
user_id=user.id,
)

grid_information = await get_core_data(coredata_rplace.gridInformation, db)

if not last_pixel_placed or datetime.now(UTC) - last_pixel_placed.date.replace(
tzinfo=UTC,
) >= timedelta(
microseconds=grid_information.cooldown,
):
try:
res = await cruds_rplace.create_pixel(
rplace_pixel=db_item,
db=db,
)
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error))

try:
await ws_manager.send_message_to_room(
message=schemas_rplace.NewPixelWSMessageModel(
data=schemas_rplace.Pixel(
x=pixel.x,
y=pixel.y,
color=pixel.color,
),
),
room_id=HyperionWebsocketsRoom.RPLACE,
)
except Exception:
hyperion_error_logger.exception(
f"Error while sending a message to the room {HyperionWebsocketsRoom.CDR}",
)
return res
else:

Check failure on line 109 in app/modules/rPlace/endpoints_rplace.py

View workflow job for this annotation

GitHub Actions / lintandformat

Ruff (RET505)

app/modules/rPlace/endpoints_rplace.py:109:5: RET505 Unnecessary `else` after `return` statement
raise HTTPException(
status_code=401,
detail="Vous devez attendre avant de placer un autre pixel",
)


@module.router.get(
"/rplace/information",
response_model=coredata_rplace.gridInformation,
status_code=200,
)
async def get_grid_information(
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user()),
):
"""
Get grid information
"""
return await get_core_data(coredata_rplace.gridInformation, db)


@module.router.websocket("/rplace/ws")
async def websocket_endpoint(
websocket: WebSocket,
ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager),
db: AsyncSession = Depends(get_unsafe_db),
settings: Settings = Depends(get_settings),
):
await ws_manager.manage_websocket(
websocket=websocket,
settings=settings,
room=HyperionWebsocketsRoom.RPLACE,
db=db,
)


@module.router.get(
"/rplace/pixel_info/{x}/{y}",
response_model=schemas_rplace.PixelComplete,
status_code=200,
)
async def get_pixel_info(
x: int,
y: int,
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_member),
):
info = await cruds_rplace.get_pixel_info(
db=db,
x=x,
y=y,
)
if info is None:
raise HTTPException(
status_code=404,
detail="pas de pixel place",
)
return info


@module.router.get(
"/rplace/last_pixel_date",
response_model=schemas_rplace.UserInfo,
status_code=200,
)
async def get_last_pixel_date(
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_member),
):
date = await cruds_rplace.get_last_pixel_date(
db=db,
user_id=user.id,
)
return schemas_rplace.UserInfo(date=date)
21 changes: 21 additions & 0 deletions app/modules/rPlace/models_rplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import datetime

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.core.users.models_users import CoreUser
from app.types.sqlalchemy import Base, PrimaryKey


class Pixel(Base):
__tablename__ = "pixels"

id: Mapped[PrimaryKey]
user_id: Mapped[str] = mapped_column(
ForeignKey("core_user.id"),
)
user: Mapped[CoreUser] = relationship("CoreUser", init=False)
date: Mapped[datetime]
x: Mapped[int]
y: Mapped[int]
color: Mapped[str]
27 changes: 27 additions & 0 deletions app/modules/rPlace/schemas_rplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from datetime import datetime
from typing import Literal

from pydantic import BaseModel

from app.core.users.schemas_users import CoreUserSimple
from app.types.websocket import WSMessageModel


class Pixel(BaseModel):
x: int
y: int
color: str


class PixelComplete(BaseModel):
user: CoreUserSimple
date: datetime


class NewPixelWSMessageModel(WSMessageModel):
command: Literal["NEW_PIXEL"] = "NEW_PIXEL"
data: Pixel


class UserInfo(BaseModel):
date: datetime
1 change: 1 addition & 0 deletions app/types/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

class HyperionWebsocketsRoom(str, Enum):
CDR = "5a816d32-8b5d-4c44-8a8d-18fd830ec5a8"
RPLACE = "4c9e2273-3b4a-4f5d-a879-9c3b2d07376f"


hyperion_error_logger = logging.getLogger("hyperion.error")
Expand Down
Loading
Loading