244 lines
7.0 KiB
Python
244 lines
7.0 KiB
Python
import logging
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
|
from pydantic import BaseModel
|
|
|
|
try:
|
|
from app.security import get_current_user_id
|
|
from app.service.binding import BindingError, BindingService
|
|
from app.service import get_db_session
|
|
except ModuleNotFoundError:
|
|
from security import get_current_user_id
|
|
from service.binding import BindingError, BindingService
|
|
from service import get_db_session
|
|
|
|
|
|
router = APIRouter(prefix="/bindings", tags=["bindings"])
|
|
logger = logging.getLogger("app.bindings")
|
|
|
|
|
|
class BindStartRequest(BaseModel):
|
|
device_id: str
|
|
serial_number: str
|
|
child_id: int | None = None
|
|
|
|
|
|
class BindStartResponse(BaseModel):
|
|
bind_token: str
|
|
expires_at: str
|
|
|
|
|
|
class BindConfirmRequest(BaseModel):
|
|
bind_token: str
|
|
challenge_code: str
|
|
|
|
|
|
class BindConfirmResponse(BaseModel):
|
|
device_id: str
|
|
child_id: int | None
|
|
|
|
|
|
class BindingGetResponse(BaseModel):
|
|
device_id: str
|
|
child_id: int | None
|
|
status: int
|
|
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
|
|
bound_at: datetime
|
|
unbound_at: datetime | None
|
|
|
|
|
|
class BindHistoryResponse(BaseModel):
|
|
items: list[BindHistoryItem]
|
|
total: int
|
|
next_cursor: str | None
|
|
|
|
|
|
@router.post("/start", response_model=BindStartResponse)
|
|
def start_bind(
|
|
payload: BindStartRequest,
|
|
request: Request,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> BindStartResponse:
|
|
service = BindingService(db)
|
|
try:
|
|
bind_token, expires_at = service.start_bind(
|
|
current_user_id,
|
|
payload.device_id,
|
|
payload.serial_number,
|
|
payload.child_id,
|
|
)
|
|
except BindingError as e:
|
|
raise HTTPException(status_code=e.status_code, detail=str(e))
|
|
return BindStartResponse(bind_token=bind_token, expires_at=expires_at.isoformat())
|
|
|
|
|
|
@router.post("/confirm", response_model=BindConfirmResponse)
|
|
def confirm_bind(
|
|
payload: BindConfirmRequest,
|
|
request: Request,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> BindConfirmResponse:
|
|
service = BindingService(db)
|
|
try:
|
|
result = service.confirm_bind(payload.bind_token, current_user_id)
|
|
except BindingError as e:
|
|
raise HTTPException(status_code=e.status_code, detail=str(e))
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
return BindConfirmResponse(**result)
|
|
|
|
|
|
class DirectBindRequest(BaseModel):
|
|
device_id: str
|
|
serial_number: str
|
|
child_id: int | None = None
|
|
|
|
|
|
class DirectBindResponse(BaseModel):
|
|
device_id: str
|
|
child_id: int | None
|
|
|
|
|
|
class BindSetChildRequest(BaseModel):
|
|
child_id: int
|
|
|
|
|
|
@router.post("/direct", response_model=DirectBindResponse)
|
|
def direct_bind(
|
|
payload: DirectBindRequest,
|
|
request: Request,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> DirectBindResponse:
|
|
service = BindingService(db)
|
|
try:
|
|
result = service.direct_bind(
|
|
payload.device_id,
|
|
payload.serial_number,
|
|
payload.child_id,
|
|
current_user_id,
|
|
)
|
|
except BindingError as e:
|
|
raise HTTPException(status_code=e.status_code, detail=str(e))
|
|
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 BindingError as e:
|
|
raise HTTPException(status_code=e.status_code, detail=str(e))
|
|
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,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
):
|
|
service = BindingService(db)
|
|
binding = service.get_current_binding(current_user_id)
|
|
if not binding:
|
|
raise HTTPException(status_code=404, detail="no binding found")
|
|
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,
|
|
request: Request,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> BindingGetResponse:
|
|
service = BindingService(db)
|
|
binding = service.get_binding(device_id, current_user_id)
|
|
if not binding:
|
|
raise HTTPException(status_code=404, detail="binding not found")
|
|
return BindingGetResponse(**binding)
|
|
|
|
|
|
@router.delete("/{device_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def unbind_device(
|
|
device_id: str,
|
|
request: Request,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> None:
|
|
service = BindingService(db)
|
|
if not service.unbind(device_id, current_user_id):
|
|
raise HTTPException(status_code=404, detail="binding not found")
|
|
|
|
|
|
@router.get("/history/{device_id}", response_model=BindHistoryResponse)
|
|
def get_bind_history(
|
|
device_id: str,
|
|
request: Request,
|
|
cursor: str | None = None,
|
|
limit: int = 20,
|
|
current_user_id: int = Depends(get_current_user_id),
|
|
db=Depends(get_db_session),
|
|
) -> BindHistoryResponse:
|
|
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)
|