Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ROMM-829] Status fields on rom user #1137

Merged
merged 14 commits into from
Sep 9, 2024
12 changes: 11 additions & 1 deletion backend/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from models.base import BaseModel
from models.firmware import Firmware # noqa
from models.platform import Platform # noqa
from models.rom import Rom # noqa
from models.rom import Rom, SiblingRom # noqa
from models.user import User # noqa
from sqlalchemy import create_engine

Expand All @@ -33,6 +33,14 @@
# ... etc.


# Ignore specific models when running migrations
def include_object(object, name, type_, reflected, compare_to):
if type_ == "table" and name in [SiblingRom.__tablename__]: # Virtual table
return False

return True


def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.

Expand All @@ -53,6 +61,7 @@ def run_migrations_offline() -> None:
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
include_object=include_object,
)

with context.begin_transaction():
Expand All @@ -75,6 +84,7 @@ def run_migrations_online() -> None:
target_metadata=target_metadata,
render_as_batch=True,
compare_type=True,
include_object=include_object,
)

with context.begin_transaction():
Expand Down
84 changes: 84 additions & 0 deletions backend/alembic/versions/0026_romuser_status_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""empty message

Revision ID: 0026_romuser_status_fields
Revises: 0025_roms_hashes
Create Date: 2024-08-29 15:52:56.031850

"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = "0026_romuser_status_fields"
down_revision = "0025_roms_hashes"
branch_labels = None
depends_on = None


def upgrade() -> None:
with op.batch_alter_table("collections", schema=None) as batch_op:
batch_op.alter_column(
"path_cover_l",
existing_type=mysql.VARCHAR(length=1000),
type_=sa.Text(),
existing_nullable=True,
)
batch_op.alter_column(
"path_cover_s",
existing_type=mysql.VARCHAR(length=1000),
type_=sa.Text(),
existing_nullable=True,
)

with op.batch_alter_table("rom_user", schema=None) as batch_op:
batch_op.add_column(
sa.Column("last_played", sa.DateTime(timezone=True), nullable=True)
)
batch_op.add_column(sa.Column("backlogged", sa.Boolean(), nullable=False))
batch_op.add_column(sa.Column("now_playing", sa.Boolean(), nullable=False))
batch_op.add_column(sa.Column("hidden", sa.Boolean(), nullable=False))
batch_op.add_column(sa.Column("rating", sa.Integer(), nullable=False))
batch_op.add_column(sa.Column("difficulty", sa.Integer(), nullable=False))
batch_op.add_column(sa.Column("completion", sa.Integer(), nullable=False))
batch_op.add_column(
sa.Column(
"status",
sa.Enum(
"INCOMPLETE",
"FINISHED",
"COMPLETED_100",
"RETIRED",
"NEVER_PLAYING",
name="romuserstatus",
),
nullable=True,
)
)


def downgrade() -> None:
with op.batch_alter_table("rom_user", schema=None) as batch_op:
batch_op.drop_column("status")
batch_op.drop_column("completion")
batch_op.drop_column("difficulty")
batch_op.drop_column("rating")
batch_op.drop_column("hidden")
batch_op.drop_column("now_playing")
batch_op.drop_column("backlogged")
batch_op.drop_column("last_played")

with op.batch_alter_table("collections", schema=None) as batch_op:
batch_op.alter_column(
"path_cover_s",
existing_type=sa.Text(),
type_=mysql.VARCHAR(length=1000),
existing_nullable=True,
)
batch_op.alter_column(
"path_cover_l",
existing_type=sa.Text(),
type_=mysql.VARCHAR(length=1000),
existing_nullable=True,
)
1 change: 1 addition & 0 deletions backend/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def str_to_bool(value: str) -> bool:
DEV_HOST: Final = os.environ.get("DEV_HOST", "127.0.0.1")
DEV_PORT: Final = int(os.environ.get("DEV_PORT", "5000"))
GUNICORN_WORKERS: Final = int(os.environ.get("GUNICORN_WORKERS", 2))
TIMEZONE: Final = os.environ.get("TZ", "Etc/UTC")

# PATHS
ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm")
Expand Down
7 changes: 3 additions & 4 deletions backend/endpoints/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Annotated, Final

from endpoints.forms.identity import OAuth2RequestForm
Expand Down Expand Up @@ -161,9 +161,8 @@ def login(
request.session.update({"iss": "romm:auth", "sub": user.username})

# Update last login and active times
db_user_handler.update_user(
user.id, {"last_login": datetime.now(), "last_active": datetime.now()}
)
now = datetime.now(timezone.utc)
db_user_handler.update_user(user.id, {"last_login": now, "last_active": now})

return {"msg": "Successfully logged in"}

Expand Down
7 changes: 4 additions & 3 deletions backend/endpoints/platform.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone

from decorators.auth import protected_route
from endpoints.responses import MessageResponse
Expand Down Expand Up @@ -69,6 +69,7 @@ def get_supported_platforms(request: Request) -> list[PlatformSchema]:
db_platforms_map = {p.name: p.id for p in db_platforms}

for platform in IGDB_PLATFORM_LIST:
now = datetime.now(timezone.utc)
sup_plat = {
"id": -1,
"name": platform["name"],
Expand All @@ -77,8 +78,8 @@ def get_supported_platforms(request: Request) -> list[PlatformSchema]:
"logo_path": "",
"roms": [],
"rom_count": 0,
"created_at": datetime.now(),
"updated_at": datetime.now(),
"created_at": now,
"updated_at": now,
}

if platform["name"] in db_platforms_map:
Expand Down
82 changes: 56 additions & 26 deletions backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from __future__ import annotations

import re
from datetime import datetime
from datetime import datetime, timezone
from typing import NotRequired, TypedDict, get_type_hints

from endpoints.responses.assets import SaveSchema, ScreenshotSchema, StateSchema
from endpoints.responses.collection import CollectionSchema
from fastapi import Request
from handler.metadata.igdb_handler import IGDBMetadata
from handler.metadata.moby_handler import MobyMetadata
from models.rom import Rom, RomFile
from pydantic import BaseModel, Field, computed_field
from models.rom import Rom, RomFile, RomUserStatus
from pydantic import BaseModel, computed_field

SORT_COMPARE_REGEX = re.compile(r"^([Tt]he|[Aa]|[Aa]nd)\s")

Expand All @@ -26,6 +26,28 @@
)


def rom_user_schema_factory() -> RomUserSchema:
now = datetime.now(timezone.utc)
return RomUserSchema(
id=-1,
user_id=-1,
rom_id=-1,
created_at=now,
updated_at=now,
note_raw_markdown="",
note_is_public=False,
is_main_sibling=False,
backlogged=False,
now_playing=False,
hidden=False,
rating=0,
difficulty=0,
completion=0,
status=None,
user__username="",
)


class RomUserSchema(BaseModel):
id: int
user_id: int
Expand All @@ -35,18 +57,26 @@ class RomUserSchema(BaseModel):
note_raw_markdown: str
note_is_public: bool
is_main_sibling: bool
backlogged: bool
now_playing: bool
hidden: bool
rating: int
difficulty: int
completion: int
status: RomUserStatus | None
user__username: str

class Config:
from_attributes = True

@classmethod
def for_user(cls, user_id: int, db_rom: Rom) -> RomUserSchema | None:
def for_user(cls, user_id: int, db_rom: Rom) -> RomUserSchema:
for n in db_rom.rom_users:
if n.user_id == user_id:
return cls.model_validate(n)

return None
# Return a dummy RomUserSchema if the user + rom combination doesn't exist
return rom_user_schema_factory()

@classmethod
def notes_for_user(cls, user_id: int, db_rom: Rom) -> list[UserNotesSchema]:
Expand Down Expand Up @@ -130,52 +160,52 @@ def sort_comparator(self) -> str:


class SimpleRomSchema(RomSchema):
sibling_roms: list[RomSchema] = Field(default_factory=list)
rom_user: RomUserSchema | None = Field(default=None)
sibling_roms: list[RomSchema]
rom_user: RomUserSchema

@classmethod
def from_orm_with_request(cls, db_rom: Rom, request: Request) -> SimpleRomSchema:
rom = cls.model_validate(db_rom)
def from_orm_with_request(
cls, db_rom: Rom, request: Request
) -> SimpleRomSchema | None:
user_id = request.user.id

rom.rom_user = RomUserSchema.for_user(user_id, db_rom)
db_rom.rom_user = RomUserSchema.for_user(user_id, db_rom)

return rom
return cls.model_validate(db_rom)


class DetailedRomSchema(RomSchema):
merged_screenshots: list[str]
sibling_roms: list[RomSchema] = Field(default_factory=list)
rom_user: RomUserSchema | None = Field(default=None)
user_saves: list[SaveSchema] = Field(default_factory=list)
user_states: list[StateSchema] = Field(default_factory=list)
user_screenshots: list[ScreenshotSchema] = Field(default_factory=list)
user_notes: list[UserNotesSchema] = Field(default_factory=list)
user_collections: list[CollectionSchema] = Field(default_factory=list)
sibling_roms: list[RomSchema]
rom_user: RomUserSchema
user_saves: list[SaveSchema]
user_states: list[StateSchema]
user_screenshots: list[ScreenshotSchema]
user_notes: list[UserNotesSchema]
user_collections: list[CollectionSchema]

@classmethod
def from_orm_with_request(cls, db_rom: Rom, request: Request) -> DetailedRomSchema:
rom = cls.model_validate(db_rom)
user_id = request.user.id

rom.rom_user = RomUserSchema.for_user(user_id, db_rom)
rom.user_notes = RomUserSchema.notes_for_user(user_id, db_rom)
rom.user_saves = [
db_rom.rom_user = RomUserSchema.for_user(user_id, db_rom)
db_rom.user_notes = RomUserSchema.notes_for_user(user_id, db_rom)
db_rom.user_saves = [
SaveSchema.model_validate(s) for s in db_rom.saves if s.user_id == user_id
]
rom.user_states = [
db_rom.user_states = [
StateSchema.model_validate(s) for s in db_rom.states if s.user_id == user_id
]
rom.user_screenshots = [
db_rom.user_screenshots = [
ScreenshotSchema.model_validate(s)
for s in db_rom.screenshots
if s.user_id == user_id
]
rom.user_collections = CollectionSchema.for_user(
db_rom.user_collections = CollectionSchema.for_user(
user_id, db_rom.get_collections()
)

return rom
return cls.model_validate(db_rom)


class UserNotesSchema(TypedDict):
Expand Down
41 changes: 33 additions & 8 deletions backend/endpoints/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ def get_roms(
limit=limit,
)

return [SimpleRomSchema.from_orm_with_request(rom, request) for rom in roms]
roms = [SimpleRomSchema.from_orm_with_request(rom, request) for rom in roms]
return [rom for rom in roms if rom]


@protected_route(
Expand Down Expand Up @@ -539,12 +540,36 @@ async def update_rom_user(request: Request, id: int) -> RomUserSchema:
id, request.user.id
) or db_rom_handler.add_rom_user(id, request.user.id)

cleaned_data = {
"note_raw_markdown": data.get(
"note_raw_markdown", db_rom_user.note_raw_markdown
),
"note_is_public": data.get("note_is_public", db_rom_user.note_is_public),
"is_main_sibling": data.get("is_main_sibling", db_rom_user.is_main_sibling),
}
cleaned_data = {}

if "note_raw_markdown" in data:
cleaned_data.update({"note_raw_markdown": data.get("note_raw_markdown")})

if "note_is_public" in data:
cleaned_data.update({"note_is_public": data.get("note_is_public")})

if "is_main_sibling" in data:
cleaned_data.update({"is_main_sibling": data.get("is_main_sibling")})

if "backlogged" in data:
cleaned_data.update({"backlogged": data.get("backlogged")})

if "now_playing" in data:
cleaned_data.update({"now_playing": data.get("now_playing")})

if "hidden" in data:
cleaned_data.update({"hidden": data.get("hidden")})

if "rating" in data:
cleaned_data.update({"rating": data.get("rating")})

if "difficulty" in data:
cleaned_data.update({"difficulty": data.get("difficulty")})

if "completion" in data:
cleaned_data.update({"completion": data.get("completion")})

if "status" in data:
cleaned_data.update({"status": data.get("status")})

return db_rom_handler.update_rom_user(db_rom_user.id, cleaned_data)
Loading
Loading