87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
from collections.abc import Mapping
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from app.dao.binding import BindingDAO
|
|
|
|
|
|
class BindingError(ValueError):
|
|
def __init__(self, message: str, status_code: int = 400) -> None:
|
|
super().__init__(message)
|
|
self.status_code = status_code
|
|
|
|
|
|
class BindingService:
|
|
def __init__(self, db):
|
|
self.dao = BindingDAO(db)
|
|
|
|
def _ensure_bindable_device(self, device_id: str, serial_number: str) -> None:
|
|
row = self.dao.get_device_auth(device_id)
|
|
if row is None:
|
|
raise BindingError("device not found in device_auth", status_code=404)
|
|
if str(row["serial_number"]) != serial_number:
|
|
raise BindingError("serial_number does not match device_id", status_code=400)
|
|
if int(row["is_active"]) != 1:
|
|
raise BindingError("device is inactive", status_code=400)
|
|
|
|
def start_bind(
|
|
self,
|
|
user_id: int,
|
|
device_id: str,
|
|
serial_number: str,
|
|
child_id: int | None = None,
|
|
) -> tuple[str, datetime]:
|
|
self._ensure_bindable_device(device_id, serial_number)
|
|
bind_token = self.dao.start_bind(user_id, device_id, child_id)
|
|
return bind_token, datetime.utcnow()
|
|
|
|
def confirm_bind(self, bind_token: str, user_id: int) -> Mapping:
|
|
session = self.dao.get_session(bind_token, user_id)
|
|
if not session:
|
|
raise ValueError("Bind session not found")
|
|
if datetime.utcnow() > session["expires_at"]:
|
|
raise ValueError("Bind session expired")
|
|
if session["status"] != 1:
|
|
raise ValueError("Bind session already processed")
|
|
|
|
self.dao.confirm_bind(session["id"], session["device_id"], session["target_child_id"], user_id)
|
|
return {"device_id": session["device_id"], "child_id": session["target_child_id"]}
|
|
|
|
def get_binding(self, device_id: str, user_id: int) -> Optional[Mapping]:
|
|
return self.dao.get_by_device(device_id, user_id)
|
|
|
|
def get_current_binding(self, user_id: int) -> Optional[Mapping]:
|
|
return self.dao.get_current_by_user(user_id)
|
|
|
|
def list_bindings(self, user_id: int, limit: int = 20, cursor: int = None) -> tuple[list, bool]:
|
|
rows = self.dao.list_by_user(user_id, limit, cursor)
|
|
has_more = len(rows) > limit
|
|
rows = rows[:limit]
|
|
return rows, has_more
|
|
|
|
def direct_bind(
|
|
self,
|
|
device_id: str,
|
|
serial_number: str,
|
|
child_id: int | None,
|
|
user_id: int,
|
|
) -> Mapping:
|
|
self._ensure_bindable_device(device_id, serial_number)
|
|
self.dao.direct_bind(device_id, child_id, user_id)
|
|
return {"device_id": device_id, "child_id": child_id}
|
|
|
|
def set_binding_child(self, device_id: str, child_id: int, user_id: int) -> Mapping:
|
|
ok = self.dao.set_binding_child(device_id=device_id, child_id=child_id, user_id=user_id)
|
|
if not ok:
|
|
raise ValueError("binding not found")
|
|
return {"device_id": device_id, "child_id": child_id}
|
|
|
|
def unbind(self, device_id: str, user_id: int) -> bool:
|
|
return self.dao.unbind(device_id, user_id)
|
|
|
|
def list_history(self, device_id: str, limit: int = 20, cursor: datetime = None) -> tuple[list, bool]:
|
|
rows = self.dao.list_history(device_id, limit, cursor)
|
|
has_more = len(rows) > limit
|
|
rows = rows[:limit]
|
|
return rows, has_more
|