Refactor: enhance API response schemas for pagination; update environment variables for Supabase and Vision API; improve Vite configuration for proxy routing

This commit is contained in:
salirezav
2025-08-12 13:48:17 -04:00
parent 7a939920fa
commit 62dd0d162b
9 changed files with 57 additions and 35 deletions

View File

@@ -71,13 +71,21 @@ class VideoInfoResponse(BaseModel):
class VideoListResponse(BaseModel): class VideoListResponse(BaseModel):
"""Video list response""" """Video list response"""
videos: List[VideoInfoResponse] = Field(..., description="List of videos") videos: List[VideoInfoResponse] = Field(..., description="List of videos")
total_count: int = Field(..., description="Total number of videos") total_count: int = Field(..., description="Total number of matching videos (ignores pagination)")
page: Optional[int] = Field(None, description="Current page number (if using page/limit)")
total_pages: Optional[int] = Field(None, description="Total pages (if using page/limit)")
has_next: Optional[bool] = Field(None, description="Is there a next page?")
has_previous: Optional[bool] = Field(None, description="Is there a previous page?")
class Config: class Config:
schema_extra = { schema_extra = {
"example": { "example": {
"videos": [], "videos": [],
"total_count": 0 "total_count": 0,
"page": 1,
"total_pages": 1,
"has_next": False,
"has_previous": False
} }
} }
@@ -108,8 +116,9 @@ class VideoListRequest(BaseModel):
start_date: Optional[datetime] = Field(None, description="Filter by start date") start_date: Optional[datetime] = Field(None, description="Filter by start date")
end_date: Optional[datetime] = Field(None, description="Filter by end date") end_date: Optional[datetime] = Field(None, description="Filter by end date")
limit: Optional[int] = Field(50, description="Maximum number of results") limit: Optional[int] = Field(50, description="Maximum number of results")
include_metadata: bool = Field(False, description="Include video metadata") offset: Optional[int] = Field(0, description="Number of items to skip (for pagination)")
include_metadata: bool = Field(False, description="Include video metadata for returned items only")
class Config: class Config:
schema_extra = { schema_extra = {
"example": { "example": {
@@ -117,6 +126,7 @@ class VideoListRequest(BaseModel):
"start_date": "2025-08-04T00:00:00", "start_date": "2025-08-04T00:00:00",
"end_date": "2025-08-04T23:59:59", "end_date": "2025-08-04T23:59:59",
"limit": 50, "limit": 50,
"offset": 0,
"include_metadata": True "include_metadata": True
} }
} }

View File

@@ -1,5 +1,3 @@
version: "3.9"
services: services:
api: api:
build: build:
@@ -8,11 +6,14 @@ services:
working_dir: /app working_dir: /app
volumes: volumes:
- ./camera-management-api:/app - ./camera-management-api:/app
- ./camera-management-api/storage:/storage - /storage:/storage
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib - LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib
- PYTHONPATH=/app:/app/camera_sdk - PYTHONPATH=/app:/app/camera_sdk
- TZ=America/New_York
command: > command: >
sh -lc " sh -lc "
apt-get update && apt-get install -y libusb-1.0-0-dev; apt-get update && apt-get install -y libusb-1.0-0-dev;
@@ -39,8 +40,7 @@ services:
# Start the application # Start the application
python main.py --config config.compose.json python main.py --config config.compose.json
" "
ports: network_mode: host
- "8000:8000"
web: web:
image: node:20-alpine image: node:20-alpine
@@ -49,13 +49,15 @@ services:
- ./management-dashboard-web-app:/app - ./management-dashboard-web-app:/app
environment: environment:
- CHOKIDAR_USEPOLLING=true - CHOKIDAR_USEPOLLING=true
- VITE_SUPABASE_URL=${VITE_SUPABASE_URL} - TZ=America/New_York
- VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
command: > command: >
sh -lc " sh -lc "
npm ci; npm ci;
npm run dev -- --host 0.0.0.0 --port 8080 npm run dev -- --host 0.0.0.0 --port 8080
" "
# Ensure the web container can resolve host.docker.internal on Linux
extra_hosts:
- "host.docker.internal:host-gateway"
ports: ports:
- "8080:8080" - "8080:8080"

View File

@@ -1,10 +1,10 @@
# Environment Configuration for Pecan Experiments Application # Environment Configuration for Pecan Experiments Application
# USDA Vision Camera System API Configuration # USDA Vision Camera System API Configuration
# Default: http://localhost:8000 (current working setup) # Recommended default: use a relative path so the dev server proxy routes to the API container
# For localhost setup, use: http://localhost:8000 # Leave unset to default to "/api" (see vite.config.ts proxy)
# For remote systems, use: http://192.168.1.100:8000 (replace with actual IP) # To override and point directly, set e.g.:
VITE_VISION_API_URL=http://localhost:8000 # VITE_VISION_API_URL=http://vm-host-or-ip:8000
# Supabase Configuration (if needed for production) # Supabase Configuration (if needed for production)
# VITE_SUPABASE_URL=your_supabase_url # VITE_SUPABASE_URL=your_supabase_url

View File

@@ -15,9 +15,8 @@ import {
} from '../types'; } from '../types';
import { performanceMonitor } from '../utils/performanceMonitor'; import { performanceMonitor } from '../utils/performanceMonitor';
// Configuration - Use environment variable or default to vision container // Configuration - Prefer env var; default to relative "/api" so Vite proxy can route to the API container
// The API is accessible at localhost:8000 in the current setup const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api';
const API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000';
/** /**
* Custom error class for video API errors * Custom error class for video API errors

View File

@@ -1,8 +1,12 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
// Local development configuration // Supabase configuration from environment (Vite)
const supabaseUrl = 'http://127.0.0.1:54321' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = '[REDACTED]' const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error('Supabase is not configured. Please set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in your environment (.env)')
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey) export const supabase = createClient(supabaseUrl, supabaseAnonKey)

View File

@@ -1,7 +1,6 @@
// Vision System API Client // Vision System API Client
// Base URL for the vision system API - Use environment variable or default to vision container // Base URL for the vision system API - Prefer env var; default to relative "/api" so Vite proxy can route to the API container
// The API is accessible at localhost:8000 in the current setup const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || '/api'
const VISION_API_BASE_URL = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000'
// Types based on the API documentation // Types based on the API documentation
export interface SystemStatus { export interface SystemStatus {
@@ -290,7 +289,7 @@ class VisionApiClient {
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> { private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseUrl}${endpoint}` const url = `${this.baseUrl}${endpoint}`
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -424,8 +423,8 @@ class VisionApiClient {
} catch (error: any) { } catch (error: any) {
// If the error is related to missing auto-recording fields, try to handle it gracefully // If the error is related to missing auto-recording fields, try to handle it gracefully
if (error.message?.includes('auto_start_recording_enabled') || if (error.message?.includes('auto_start_recording_enabled') ||
error.message?.includes('auto_recording_max_retries') || error.message?.includes('auto_recording_max_retries') ||
error.message?.includes('auto_recording_retry_delay_seconds')) { error.message?.includes('auto_recording_retry_delay_seconds')) {
// Try to get the raw camera data and add default auto-recording fields // Try to get the raw camera data and add default auto-recording fields
try { try {
@@ -540,7 +539,7 @@ export const formatDuration = (seconds: number): string => {
const hours = Math.floor(seconds / 3600) const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60) const minutes = Math.floor((seconds % 3600) / 60)
const secs = Math.floor(seconds % 60) const secs = Math.floor(seconds % 60)
if (hours > 0) { if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s` return `${hours}h ${minutes}m ${secs}s`
} else if (minutes > 0) { } else if (minutes > 0) {
@@ -554,7 +553,7 @@ export const formatUptime = (seconds: number): string => {
const days = Math.floor(seconds / 86400) const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600) const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60) const minutes = Math.floor((seconds % 3600) / 60)
if (days > 0) { if (days > 0) {
return `${days}d ${hours}h ${minutes}m` return `${days}d ${hours}h ${minutes}m`
} else if (hours > 0) { } else if (hours > 0) {

View File

@@ -3,6 +3,7 @@
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_SUPABASE_URL: string; readonly VITE_SUPABASE_URL: string;
readonly VITE_SUPABASE_ANON_KEY: string; readonly VITE_SUPABASE_ANON_KEY: string;
readonly VITE_VISION_API_URL?: string; // optional; defaults to "/api" via vite proxy
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -1 +1 @@
v2.31.8 v2.33.9

View File

@@ -9,9 +9,16 @@ export default defineConfig({
tailwindcss(), tailwindcss(),
], ],
server: { server: {
// Allow connecting via this VM's hostname // Allow connections from the VM hostname and any other host/IP
allowedHosts: ['exp-dash'], allowedHosts: ['exp-dash', 'localhost'],
// host is provided via CLI in docker-compose, but keeping this commented for local use: // Proxy API calls from the browser to the API container via the compose service name
// host: true, proxy: {
'/api': {
// Route to API via the host so this works whether API is on bridge (via port mapping) or host network
target: 'http://host.docker.internal:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}, },
}) })