Skip to content

Commit

Permalink
Merge branch 'master' into romm-1114
Browse files Browse the repository at this point in the history
  • Loading branch information
gantoine committed Sep 21, 2024
2 parents 6cb332e + fc6455e commit 6880d11
Show file tree
Hide file tree
Showing 58 changed files with 1,293 additions and 630 deletions.
Binary file added .github/resources/romm_complete.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- trunk-ignore(markdownlint/MD041) -->
<div align="center">

<img src=".github/resources/romm_complete.svg" height="220px" width="auto" alt="romm logo">
<img src=".github/resources/romm_complete.png" height="220px" width="auto" alt="romm logo">

<h3 style="font-size: 25px;">
A beautiful, powerful, self-hosted rom manager.
Expand Down
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,
)
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
83 changes: 57 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 @@ -85,6 +115,7 @@ class RomSchema(BaseModel):

# Metadata fields
first_release_date: int | None
youtube_video_id: str | None
alternative_names: list[str]
genres: list[str]
franchises: list[str]
Expand Down Expand Up @@ -131,52 +162,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

0 comments on commit 6880d11

Please sign in to comment.