mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Add fingerprint-based detection for duplicate device registration with configurable behavior via new body params: - allow_existing: return existing device if fingerprint matches - allow_duplicate: skip fingerprint check, always create new device - reset_syncs: clear tracked saves when reclaiming existing device Fingerprint matching uses mac_address (primary) or hostname+platform (fallback). Returns 409 Conflict with device_id when duplicate detected without flags, 200 OK for existing device, 201 Created for new.
130 lines
3.8 KiB
Python
130 lines
3.8 KiB
Python
from collections.abc import Sequence
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import delete, select, update
|
|
from sqlalchemy.orm import Session
|
|
|
|
from decorators.database import begin_session
|
|
from models.device_save_sync import DeviceSaveSync
|
|
|
|
from .base_handler import DBBaseHandler
|
|
|
|
|
|
class DBDeviceSaveSyncHandler(DBBaseHandler):
|
|
@begin_session
|
|
def get_sync(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync | None:
|
|
return session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
|
|
@begin_session
|
|
def get_syncs_for_device_and_saves(
|
|
self,
|
|
device_id: str,
|
|
save_ids: list[int],
|
|
session: Session = None, # type: ignore
|
|
) -> Sequence[DeviceSaveSync]:
|
|
if not save_ids:
|
|
return []
|
|
return session.scalars(
|
|
select(DeviceSaveSync).filter(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id.in_(save_ids),
|
|
)
|
|
).all()
|
|
|
|
@begin_session
|
|
def upsert_sync(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
synced_at: datetime | None = None,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync:
|
|
now = synced_at or datetime.now(timezone.utc)
|
|
existing = session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
if existing:
|
|
session.execute(
|
|
update(DeviceSaveSync)
|
|
.where(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id == save_id,
|
|
)
|
|
.values(last_synced_at=now, is_untracked=False)
|
|
.execution_options(synchronize_session="evaluate")
|
|
)
|
|
existing.last_synced_at = now
|
|
existing.is_untracked = False
|
|
return existing
|
|
else:
|
|
sync = DeviceSaveSync(
|
|
device_id=device_id,
|
|
save_id=save_id,
|
|
last_synced_at=now,
|
|
is_untracked=False,
|
|
)
|
|
session.add(sync)
|
|
session.flush()
|
|
return sync
|
|
|
|
@begin_session
|
|
def set_untracked(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
untracked: bool,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync | None:
|
|
existing = session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
if existing:
|
|
session.execute(
|
|
update(DeviceSaveSync)
|
|
.where(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id == save_id,
|
|
)
|
|
.values(is_untracked=untracked)
|
|
.execution_options(synchronize_session="evaluate")
|
|
)
|
|
existing.is_untracked = untracked
|
|
return existing
|
|
elif untracked:
|
|
now = datetime.now(timezone.utc)
|
|
sync = DeviceSaveSync(
|
|
device_id=device_id,
|
|
save_id=save_id,
|
|
last_synced_at=now,
|
|
is_untracked=True,
|
|
)
|
|
session.add(sync)
|
|
session.flush()
|
|
return sync
|
|
return None
|
|
|
|
@begin_session
|
|
def delete_syncs_for_device(
|
|
self,
|
|
device_id: str,
|
|
session: Session = None, # type: ignore
|
|
) -> None:
|
|
session.execute(
|
|
delete(DeviceSaveSync)
|
|
.where(DeviceSaveSync.device_id == device_id)
|
|
.execution_options(synchronize_session="evaluate")
|
|
)
|