feat: 支持先绑定设备后关联儿童档案

- 绑定模型新增 owner_user_id,支持 child_id 为空\n- 新增绑定后设置儿童接口 /bindings/{device_id}/child\n- 前端绑定页改为先绑设备后补充儿童\n- 补充数据库迁移脚本与 DAO 测试用例
This commit is contained in:
ChengCan
2026-04-14 12:47:52 +08:00
parent a22fcafea9
commit 1b4c47f77a
11 changed files with 699 additions and 246 deletions

View File

@@ -1,4 +1,5 @@
import logging
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
@@ -19,7 +20,7 @@ logger = logging.getLogger("app.bindings")
class BindStartRequest(BaseModel):
device_id: str
child_id: int
child_id: int | None = None
class BindStartResponse(BaseModel):
@@ -34,21 +35,21 @@ class BindConfirmRequest(BaseModel):
class BindConfirmResponse(BaseModel):
device_id: str
child_id: int
child_id: int | None
class BindingGetResponse(BaseModel):
device_id: str
child_id: int
child_id: int | None
status: int
bound_at: str
bound_at: datetime
class BindHistoryItem(BaseModel):
device_id: str
child_id: int
bound_at: str
unbound_at: str | None
child_id: int | None
bound_at: datetime
unbound_at: datetime | None
class BindHistoryResponse(BaseModel):
@@ -86,11 +87,15 @@ def confirm_bind(
class DirectBindRequest(BaseModel):
device_id: str
child_id: int
child_id: int | None = None
class DirectBindResponse(BaseModel):
device_id: str
child_id: int | None
class BindSetChildRequest(BaseModel):
child_id: int
@@ -106,6 +111,22 @@ def direct_bind(
return DirectBindResponse(**result)
@router.patch("/{device_id}/child", response_model=DirectBindResponse)
def set_binding_child(
device_id: str,
payload: BindSetChildRequest,
request: Request,
current_user_id: int = Depends(get_current_user_id),
db=Depends(get_db_session),
) -> DirectBindResponse:
service = BindingService(db)
try:
result = service.set_binding_child(device_id=device_id, child_id=payload.child_id, user_id=current_user_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return DirectBindResponse(**result)
@router.get("/current", response_model=BindingGetResponse)
def get_current_binding(
request: Request,
@@ -154,11 +175,9 @@ def get_bind_history(
current_user_id: int = Depends(get_current_user_id),
db=Depends(get_db_session),
) -> BindHistoryResponse:
from datetime import datetime
cursor_dt = datetime.fromisoformat(cursor) if cursor else None
service = BindingService(db)
rows, has_more = service.list_history(device_id, limit, cursor_dt)
next_cursor = rows[-1]["bound_at"].isoformat() if has_more and rows else None
items = [BindHistoryItem(**row) for row in rows]
return BindHistoryResponse(items=items, total=len(items), next_cursor=next_cursor)
return BindHistoryResponse(items=items, total=len(items), next_cursor=next_cursor)