feat: camera batch endpoint implementation and tests
Self-contained server stub and pytest tests for the /api/v1/camera/events/batch endpoint, mirroring the BLE batch pattern with idempotent INSERT on (device_id, period_start). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
69
server/camera_endpoint.py
Normal file
69
server/camera_endpoint.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# server/camera_endpoint.py
|
||||
# Add these models and endpoint to the server's main.py alongside the existing BLE endpoints.
|
||||
# Requires: camera_records table (see migrations/004_camera_records.sql)
|
||||
#
|
||||
# IMPORTANT: Before deploying, verify the HMAC message format in verify_device_hmac
|
||||
# matches what the firmware computes:
|
||||
# HMAC-SHA256(secret, f"{device_id}:{timestamp}:{sha256_hex(body)}")
|
||||
# Headers expected: X-Device-Id, X-Timestamp, X-HMAC-Signature
|
||||
|
||||
import sqlite3
|
||||
from typing import List
|
||||
|
||||
from fastapi import Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CameraRecord(BaseModel):
|
||||
period_start: int
|
||||
period_end: int
|
||||
entries: int
|
||||
exits: int
|
||||
|
||||
|
||||
class CameraEventsRequest(BaseModel):
|
||||
location_id: str
|
||||
records: List[CameraRecord]
|
||||
|
||||
|
||||
class CameraEventsResponse(BaseModel):
|
||||
status: str
|
||||
accepted: int
|
||||
|
||||
|
||||
# Add this endpoint to your FastAPI app (alongside receive_batch_events):
|
||||
#
|
||||
# @app.post("/api/v1/camera/events/batch", response_model=CameraEventsResponse)
|
||||
# async def receive_camera_events(
|
||||
# batch: CameraEventsRequest,
|
||||
# device_id: str = Depends(verify_device_hmac),
|
||||
# db: sqlite3.Connection = Depends(get_db),
|
||||
# ):
|
||||
def receive_camera_events_impl(
|
||||
batch: CameraEventsRequest,
|
||||
device_id: str,
|
||||
db: sqlite3.Connection,
|
||||
) -> CameraEventsResponse:
|
||||
"""Receive hourly camera entry/exit records; idempotent on (device_id, period_start)."""
|
||||
cursor = db.cursor()
|
||||
accepted = 0
|
||||
for record in batch.records:
|
||||
try:
|
||||
cursor.execute(
|
||||
"""INSERT INTO camera_records
|
||||
(device_id, location_id, period_start, period_end, entries, exits)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""",
|
||||
(
|
||||
device_id,
|
||||
batch.location_id,
|
||||
record.period_start,
|
||||
record.period_end,
|
||||
record.entries,
|
||||
record.exits,
|
||||
),
|
||||
)
|
||||
accepted += 1
|
||||
except sqlite3.IntegrityError:
|
||||
pass # duplicate (device_id, period_start) — idempotent
|
||||
db.commit()
|
||||
return CameraEventsResponse(status="ok", accepted=accepted)
|
||||
Reference in New Issue
Block a user