Merge pull request #3124 from rommapp/copilot/feature-allow-partial-rom-update

Allow partial updates to ROM user props via typed payload schema
This commit is contained in:
Georges-Antoine Assi
2026-03-11 22:55:40 -04:00
committed by GitHub
2 changed files with 68 additions and 22 deletions

View File

@@ -61,7 +61,7 @@ from handler.metadata.ss_handler import get_preferred_media_types
from logger.formatter import BLUE
from logger.formatter import highlight as hl
from logger.logger import log
from models.rom import Rom
from models.rom import Rom, RomUserStatus
from utils.database import safe_int, safe_str_to_bool
from utils.filesystem import sanitize_filename
from utils.hashing import crc32_to_hex
@@ -133,10 +133,48 @@ class RomUpdateForm(BaseModel):
url_manual: str | None = None
class RomUserData(BaseModel):
is_main_sibling: bool | None = Field(
default=None, description="Whether this rom is the main sibling."
)
backlogged: bool | None = Field(
default=None, description="Whether this rom is in the backlog."
)
now_playing: bool | None = Field(
default=None, description="Whether this rom is currently being played."
)
hidden: bool | None = Field(default=None, description="Whether this rom is hidden.")
rating: int | None = Field(
default=None, description="User rating for this rom (0-10).", ge=0, le=10
)
difficulty: int | None = Field(
default=None,
description="User difficulty rating for this rom (0-10).",
ge=0,
le=10,
)
completion: int | None = Field(
default=None,
description="User completion percentage for this rom (0-100).",
ge=0,
le=100,
)
status: RomUserStatus | None = Field(
default=None, description="User play status for this rom."
)
class RomUserUpdatePayload(BaseModel):
data: dict[str, Any] = Field(default_factory=dict)
update_last_played: bool = False
remove_last_played: bool = False
data: RomUserData = Field(
default_factory=RomUserData,
description="Partial rom user data to update. Only provided fields will be updated.",
)
update_last_played: bool = Field(
default=False, description="Set last played timestamp to now."
)
remove_last_played: bool = Field(
default=False, description="Clear the last played timestamp."
)
async def parse_rom_update_form(
@@ -1482,8 +1520,6 @@ async def update_rom_user(
payload: Annotated[RomUserUpdatePayload, Body()],
) -> RomUserSchema:
"""Update rom data associated to the current user."""
rom_user_data = payload.data
rom = db_rom_handler.get_rom(id)
if not rom:
@@ -1493,22 +1529,7 @@ async def update_rom_user(
id, request.user.id
) or db_rom_handler.add_rom_user(id, request.user.id)
fields_to_update = [
"is_main_sibling",
"backlogged",
"now_playing",
"hidden",
"rating",
"difficulty",
"completion",
"status",
]
cleaned_data = {
field: rom_user_data[field]
for field in fields_to_update
if field in rom_user_data
}
cleaned_data = payload.data.model_dump(exclude_unset=True)
if payload.update_last_played:
cleaned_data.update({"last_played": datetime.now(timezone.utc)})

View File

@@ -139,6 +139,31 @@ def test_update_rom_user_props_with_data_envelope(
assert body["rating"] == 7
def test_update_rom_user_props_partial_update(
client: TestClient, access_token: str, rom: Rom
):
# Set initial values
setup_response = client.put(
f"/api/roms/{rom.id}/props",
headers={"Authorization": f"Bearer {access_token}"},
json={"data": {"backlogged": True, "rating": 5, "hidden": True}},
)
assert setup_response.status_code == status.HTTP_200_OK
# Partial update: only update rating, backlogged and hidden should remain unchanged
response = client.put(
f"/api/roms/{rom.id}/props",
headers={"Authorization": f"Bearer {access_token}"},
json={"data": {"rating": 9}},
)
assert response.status_code == status.HTTP_200_OK
body = response.json()
assert body["rating"] == 9
assert body["backlogged"] is True
assert body["hidden"] is True
def test_update_rom_user_props_last_played_flags(
client: TestClient, access_token: str, rom: Rom
):