mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 06:46:00 +00:00
Fix race condition in collection and favorite rom membership updates
Replace full rom_ids list replacement with atomic POST/DELETE endpoints that add or remove individual ROMs from a collection. This prevents concurrent rapid clicks from overwriting each other (last-write-wins). Also fix missing session.flush() in add_rom_user() and add collection endpoint tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import delete, insert, literal, or_, select, update
|
||||
@@ -146,6 +146,66 @@ class DBCollectionsHandler(DBBaseHandler):
|
||||
|
||||
return session.scalar(query.filter_by(id=id).limit(1))
|
||||
|
||||
@begin_session
|
||||
@with_roms
|
||||
def add_roms_to_collection(
|
||||
self,
|
||||
id: int,
|
||||
rom_ids: list[int],
|
||||
query: Query = None, # type: ignore
|
||||
session: Session = None, # type: ignore
|
||||
) -> Collection:
|
||||
if rom_ids:
|
||||
valid_rom_ids = set(
|
||||
session.scalars(select(Rom.id).where(Rom.id.in_(rom_ids))).all()
|
||||
)
|
||||
existing_ids = set(
|
||||
session.scalars(
|
||||
select(CollectionRom.rom_id).where(
|
||||
CollectionRom.collection_id == id
|
||||
)
|
||||
).all()
|
||||
)
|
||||
new_ids = valid_rom_ids - existing_ids
|
||||
if new_ids:
|
||||
session.execute(
|
||||
insert(CollectionRom),
|
||||
[{"collection_id": id, "rom_id": rom_id} for rom_id in new_ids],
|
||||
)
|
||||
session.execute(
|
||||
update(Collection)
|
||||
.where(Collection.id == id)
|
||||
.values(updated_at=datetime.now(timezone.utc))
|
||||
.execution_options(synchronize_session="evaluate")
|
||||
)
|
||||
|
||||
return session.scalar(query.filter_by(id=id).limit(1))
|
||||
|
||||
@begin_session
|
||||
@with_roms
|
||||
def remove_roms_from_collection(
|
||||
self,
|
||||
id: int,
|
||||
rom_ids: list[int],
|
||||
query: Query = None, # type: ignore
|
||||
session: Session = None, # type: ignore
|
||||
) -> Collection:
|
||||
if rom_ids:
|
||||
session.execute(
|
||||
delete(CollectionRom).where(
|
||||
CollectionRom.collection_id == id,
|
||||
CollectionRom.rom_id.in_(rom_ids),
|
||||
)
|
||||
)
|
||||
session.execute(
|
||||
update(Collection)
|
||||
.where(Collection.id == id)
|
||||
.values(updated_at=datetime.now(timezone.utc))
|
||||
.execution_options(synchronize_session="evaluate")
|
||||
)
|
||||
|
||||
return session.scalar(query.filter_by(id=id).limit(1))
|
||||
|
||||
@begin_session
|
||||
def delete_collection(
|
||||
self,
|
||||
|
||||
@@ -1066,7 +1066,9 @@ class DBRomsHandler(DBBaseHandler):
|
||||
user_id: int,
|
||||
session: Session = None, # type: ignore
|
||||
) -> RomUser:
|
||||
return session.merge(RomUser(rom_id=rom_id, user_id=user_id))
|
||||
rom_user = session.merge(RomUser(rom_id=rom_id, user_id=user_id))
|
||||
session.flush()
|
||||
return rom_user
|
||||
|
||||
@begin_session
|
||||
def get_rom_user(
|
||||
|
||||
Reference in New Issue
Block a user