diff --git a/mini-program/app/dao/binding.py b/mini-program/app/dao/binding.py index 87bfa00..8d8a39d 100644 --- a/mini-program/app/dao/binding.py +++ b/mini-program/app/dao/binding.py @@ -252,6 +252,40 @@ class BindingDAO(BaseDAO): .first() ) + def list_by_user(self, user_id: int, limit: int = 20, cursor: int = None) -> list[Mapping]: + params = {"user_id": user_id, "limit": limit + 1} + where = "db.owner_user_id = :user_id AND db.status = 1" + if cursor is not None: + where += " AND db.id < :cursor" + params["cursor"] = cursor + + rows = ( + self.db.execute( + text( + f""" + SELECT + db.id, + db.device_id, + db.child_id, + c.child_name, + db.status, + db.bound_at + FROM device_bindings AS db + LEFT JOIN children AS c + ON c.child_id = db.child_id + AND c.status = 1 + WHERE {where} + ORDER BY db.id DESC + LIMIT :limit + """ + ), + params, + ) + .mappings() + .all() + ) + return rows + def get_by_device(self, device_id: str, user_id: int) -> Optional[Mapping]: return ( self.db.execute( diff --git a/mini-program/app/routers/bindings.py b/mini-program/app/routers/bindings.py index 8fffd5a..7bf2115 100644 --- a/mini-program/app/routers/bindings.py +++ b/mini-program/app/routers/bindings.py @@ -1,7 +1,7 @@ import logging from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException, Request, status +from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from pydantic import BaseModel try: @@ -45,6 +45,20 @@ class BindingGetResponse(BaseModel): bound_at: datetime +class BindingListItem(BaseModel): + device_id: str + child_id: int | None + child_name: str | None = None + status: int + bound_at: datetime + + +class BindingListResponse(BaseModel): + items: list[BindingListItem] + total: int + next_cursor: int | None = None + + class BindHistoryItem(BaseModel): device_id: str child_id: int | None @@ -140,6 +154,30 @@ def get_current_binding( return binding +@router.get("", response_model=BindingListResponse) +def list_bindings( + request: Request, + cursor: int | None = Query(default=None, ge=1), + limit: int = Query(default=20, ge=1, le=100), + current_user_id: int = Depends(get_current_user_id), + db=Depends(get_db_session), +) -> BindingListResponse: + service = BindingService(db) + rows, has_more = service.list_bindings(current_user_id, limit, cursor) + next_cursor = int(rows[-1]["id"]) if has_more and rows else None + items = [ + BindingListItem( + device_id=row["device_id"], + child_id=row["child_id"], + child_name=row["child_name"], + status=row["status"], + bound_at=row["bound_at"], + ) + for row in rows + ] + return BindingListResponse(items=items, total=len(items), next_cursor=next_cursor) + + @router.get("/{device_id}", response_model=BindingGetResponse) def get_binding( device_id: str, diff --git a/mini-program/app/service/binding.py b/mini-program/app/service/binding.py index abe1611..da01612 100644 --- a/mini-program/app/service/binding.py +++ b/mini-program/app/service/binding.py @@ -31,6 +31,12 @@ class BindingService: 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, child_id: int | None, user_id: int) -> Mapping: self.dao.direct_bind(device_id, child_id, user_id) return {"device_id": device_id, "child_id": child_id}