Phase 5: Camera Discovery (T049-T055)
Implemented complete camera discovery system with Redis caching:
**Tests:**
- Contract tests for GET /api/v1/cameras (list cameras)
- Contract tests for GET /api/v1/cameras/{id} (camera detail)
- Integration tests for camera data consistency
- Tests for caching behavior and all authentication roles
**Schemas:**
- CameraInfo: Camera data model (id, name, description, has_ptz, has_video_sensor, status)
- CameraListResponse: List endpoint response
- CameraDetailResponse: Detail endpoint response with extended fields
- CameraStatusEnum: Status constants (online, offline, unknown, error, maintenance)
**Services:**
- CameraService: list_cameras(), get_camera(), invalidate_cache()
- Additional methods: search_cameras(), get_online_cameras(), get_ptz_cameras()
- Integrated Redis caching with 60s TTL
- Automatic cache invalidation and refresh
**Router Endpoints:**
- GET /api/v1/cameras - List all cameras (cached, 60s TTL)
- GET /api/v1/cameras/{id} - Get camera details
- POST /api/v1/cameras/refresh - Force refresh (bypass cache)
- GET /api/v1/cameras/search/{query} - Search cameras by name/description
- GET /api/v1/cameras/filter/online - Get online cameras only
- GET /api/v1/cameras/filter/ptz - Get PTZ cameras only
**Authorization:**
- All camera endpoints require at least Viewer role
- All authenticated users can read camera data
**Integration:**
- Registered camera router in main.py
- Camera service communicates with SDK Bridge via gRPC
- Redis caching for performance optimization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
117
src/api/schemas/camera.py
Normal file
117
src/api/schemas/camera.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Camera schemas for request/response validation
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class CameraInfo(BaseModel):
|
||||
"""Camera information schema"""
|
||||
id: int = Field(..., description="Camera ID (channel number in GeViScope)")
|
||||
name: str = Field(..., description="Camera name")
|
||||
description: Optional[str] = Field(None, description="Camera description")
|
||||
has_ptz: bool = Field(default=False, description="Whether camera has PTZ capabilities")
|
||||
has_video_sensor: bool = Field(default=False, description="Whether camera has video sensor (motion detection)")
|
||||
status: str = Field(..., description="Camera status (online, offline, unknown)")
|
||||
last_seen: Optional[datetime] = Field(None, description="Last time camera was seen online")
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True,
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Entrance Camera",
|
||||
"description": "Main entrance monitoring",
|
||||
"has_ptz": True,
|
||||
"has_video_sensor": True,
|
||||
"status": "online",
|
||||
"last_seen": "2025-12-09T10:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CameraListResponse(BaseModel):
|
||||
"""Response schema for camera list endpoint"""
|
||||
cameras: list[CameraInfo] = Field(..., description="List of cameras")
|
||||
total: int = Field(..., description="Total number of cameras")
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"cameras": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Entrance Camera",
|
||||
"description": "Main entrance",
|
||||
"has_ptz": True,
|
||||
"has_video_sensor": True,
|
||||
"status": "online",
|
||||
"last_seen": "2025-12-09T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Parking Lot",
|
||||
"description": "Parking area monitoring",
|
||||
"has_ptz": False,
|
||||
"has_video_sensor": True,
|
||||
"status": "online",
|
||||
"last_seen": "2025-12-09T10:30:00Z"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CameraDetailResponse(BaseModel):
|
||||
"""Response schema for single camera detail"""
|
||||
id: int = Field(..., description="Camera ID")
|
||||
name: str = Field(..., description="Camera name")
|
||||
description: Optional[str] = Field(None, description="Camera description")
|
||||
has_ptz: bool = Field(default=False, description="PTZ capability")
|
||||
has_video_sensor: bool = Field(default=False, description="Video sensor capability")
|
||||
status: str = Field(..., description="Camera status")
|
||||
last_seen: Optional[datetime] = Field(None, description="Last seen timestamp")
|
||||
|
||||
# Additional details that might be available
|
||||
channel_id: Optional[int] = Field(None, description="Physical channel ID")
|
||||
ip_address: Optional[str] = Field(None, description="Camera IP address")
|
||||
model: Optional[str] = Field(None, description="Camera model")
|
||||
firmware_version: Optional[str] = Field(None, description="Firmware version")
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True,
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Entrance Camera",
|
||||
"description": "Main entrance monitoring",
|
||||
"has_ptz": True,
|
||||
"has_video_sensor": True,
|
||||
"status": "online",
|
||||
"last_seen": "2025-12-09T10:30:00Z",
|
||||
"channel_id": 1,
|
||||
"ip_address": "192.168.1.100",
|
||||
"model": "Geutebruck G-Cam/E2510",
|
||||
"firmware_version": "7.9.975.68"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CameraStatusEnum:
|
||||
"""Camera status constants"""
|
||||
ONLINE = "online"
|
||||
OFFLINE = "offline"
|
||||
UNKNOWN = "unknown"
|
||||
ERROR = "error"
|
||||
MAINTENANCE = "maintenance"
|
||||
Reference in New Issue
Block a user