Phase 1 Complete: Project Setup & Configuration
Completed Tasks (T001-T010): - ✅ Project structure created (src/, tests/, docs/, scripts/) - ✅ Python dependencies defined (requirements.txt) - ✅ C# SDK Bridge project initialized (.csproj) - ✅ Configuration template (.env.example) - ✅ Database migration config (alembic.ini) - ✅ Code quality tools (pyproject.toml with ruff, black, mypy) - ✅ Development setup script (setup_dev_environment.ps1) - ✅ Service startup script (start_services.ps1) - ✅ Architecture documentation (docs/architecture.md) - ✅ Revised MVP tasks (tasks-revised-mvp.md - 84 tasks focused on cross-switching) MVP Scope Refined: - Focus: Cross-switching control for GSCView viewers - NO recordings, NO analytics, NO LPR in MVP - REST API only, no UI needed - Phase 2: GeViSet configuration management Ready for Phase 2: SDK Bridge Foundation 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
49
.env.example
Normal file
49
.env.example
Normal file
@@ -0,0 +1,49 @@
|
||||
# API Configuration
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
API_TITLE=Geutebruck Cross-Switching API
|
||||
API_VERSION=1.0.0
|
||||
ENVIRONMENT=development
|
||||
|
||||
# GeViScope SDK Bridge
|
||||
SDK_BRIDGE_HOST=localhost
|
||||
SDK_BRIDGE_PORT=50051
|
||||
|
||||
# GeViServer Connection
|
||||
GEVISERVER_HOST=localhost
|
||||
GEVISERVER_USERNAME=sysadmin
|
||||
GEVISERVER_PASSWORD=masterkey
|
||||
|
||||
# Database (PostgreSQL)
|
||||
DATABASE_URL=postgresql+asyncpg://geutebruck:geutebruck@localhost:5432/geutebruck_api
|
||||
DATABASE_POOL_SIZE=20
|
||||
DATABASE_MAX_OVERFLOW=10
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
REDIS_PASSWORD=
|
||||
REDIS_MAX_CONNECTIONS=50
|
||||
|
||||
# JWT Authentication
|
||||
JWT_SECRET_KEY=change-this-to-a-secure-random-key-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FORMAT=json
|
||||
|
||||
# Security
|
||||
ALLOWED_HOSTS=*
|
||||
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
|
||||
|
||||
# Cache Settings
|
||||
CACHE_CAMERA_LIST_TTL=60
|
||||
CACHE_MONITOR_LIST_TTL=60
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_ENABLED=true
|
||||
RATE_LIMIT_PER_MINUTE=60
|
||||
102
alembic.ini
Normal file
102
alembic.ini
Normal file
@@ -0,0 +1,102 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = src/api/migrations
|
||||
|
||||
# template used to generate migration files
|
||||
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to src/api/migrations/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:src/api/migrations/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# Database URL (override from environment variable)
|
||||
sqlalchemy.url = postgresql+asyncpg://geutebruck:geutebruck@localhost:5432/geutebruck_api
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
497
docs/architecture.md
Normal file
497
docs/architecture.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# Geutebruck Cross-Switching API - Architecture
|
||||
|
||||
**Version**: 1.0.0 (MVP)
|
||||
**Last Updated**: 2025-12-08
|
||||
**Status**: In Development
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Geutebruck Cross-Switching API provides a modern REST API for controlling video routing between cameras (video inputs) and monitors/viewers (video outputs) in Geutebruck surveillance systems. The system acts as a bridge between the native GeViScope/GeViSoft SDK and modern web/mobile applications.
|
||||
|
||||
**Core Functionality**:
|
||||
- 🔐 User authentication with JWT tokens
|
||||
- 📹 Camera discovery and management
|
||||
- 🖥️ Monitor/viewer discovery and status
|
||||
- 🔀 Cross-switching operations (route camera to monitor)
|
||||
- 📊 Routing state tracking and audit logging
|
||||
|
||||
---
|
||||
|
||||
## System Architecture
|
||||
|
||||
### High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Client Apps │ (Postman, curl, custom apps)
|
||||
└────────┬────────┘
|
||||
│ HTTP/REST
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ FastAPI Server │ (Python 3.11)
|
||||
│ Port: 8000 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┬───────────┬──────────┐
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌────────┐ ┌─────────┐
|
||||
│ PostgreSQL│ │ Redis │ │SDK Bridge│ │Auth/JWT │
|
||||
│ Port:5432│ │Port:6379│ │Port:50051│ │ Service │
|
||||
└─────────┘ └────────┘ └────┬───┘ └─────────┘
|
||||
│ gRPC
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ GeViScope │
|
||||
│ SDK (.NET) │
|
||||
└──────┬───────┘
|
||||
│ TCP/IP
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ GeViServer │
|
||||
│ Port: 7700+ │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌────────┴────────┐
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ Cameras │ │ GSCView │
|
||||
│ (Inputs) │ │ Viewers │
|
||||
└──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. FastAPI Server (Python)
|
||||
|
||||
**Purpose**: REST API layer handling HTTP requests, authentication, and business logic
|
||||
|
||||
**Technology Stack**:
|
||||
- Python 3.11+
|
||||
- FastAPI (web framework)
|
||||
- SQLAlchemy (ORM)
|
||||
- Pydantic (validation)
|
||||
- PyJWT (authentication)
|
||||
|
||||
**Key Responsibilities**:
|
||||
- Accept HTTP REST requests from clients
|
||||
- Authenticate users and generate JWT tokens
|
||||
- Validate request data
|
||||
- Communicate with SDK Bridge via gRPC
|
||||
- Store routing state in PostgreSQL
|
||||
- Cache camera/monitor lists in Redis
|
||||
- Audit log all operations
|
||||
- Return HTTP responses to clients
|
||||
|
||||
**Port**: 8000
|
||||
|
||||
---
|
||||
|
||||
### 2. SDK Bridge (C# .NET 8.0)
|
||||
|
||||
**Purpose**: gRPC service that wraps the GeViScope SDK, translating between modern gRPC and legacy SDK
|
||||
|
||||
**Technology Stack**:
|
||||
- C# .NET 8.0
|
||||
- Grpc.AspNetCore
|
||||
- GeViScope SDK (.NET Framework 4.8 DLL)
|
||||
- Serilog (logging)
|
||||
|
||||
**Key Responsibilities**:
|
||||
- Connect to GeViServer using GeViScope SDK
|
||||
- Enumerate cameras (GetFirstVideoInput / GetNextVideoInput)
|
||||
- Enumerate monitors (GetFirstVideoOutput / GetNextVideoOutput)
|
||||
- Execute cross-switching (CrossSwitch action)
|
||||
- Clear monitors (ClearVideoOutput action)
|
||||
- Translate SDK errors to gRPC status codes
|
||||
- Maintain connection health with retry logic
|
||||
|
||||
**Why Separate Service?**:
|
||||
- ✅ Isolates SDK crashes from Python API
|
||||
- ✅ Enables independent scaling
|
||||
- ✅ Clear separation of concerns (SDK complexity vs API logic)
|
||||
- ✅ Type-safe gRPC communication
|
||||
- ✅ Can run on different machines if needed
|
||||
|
||||
**Port**: 50051 (gRPC)
|
||||
|
||||
---
|
||||
|
||||
### 3. PostgreSQL Database
|
||||
|
||||
**Purpose**: Persistent storage for users, routing state, and audit logs
|
||||
|
||||
**Schema**:
|
||||
```sql
|
||||
users:
|
||||
- id (UUID, primary key)
|
||||
- username (unique)
|
||||
- password_hash (bcrypt)
|
||||
- role (viewer, operator, administrator)
|
||||
- created_at, updated_at
|
||||
|
||||
crossswitch_routes:
|
||||
- id (UUID, primary key)
|
||||
- camera_id (int)
|
||||
- monitor_id (int)
|
||||
- switched_at (timestamp)
|
||||
- switched_by_user_id (UUID, FK to users)
|
||||
|
||||
audit_logs:
|
||||
- id (UUID, primary key)
|
||||
- user_id (UUID, FK to users)
|
||||
- action (string)
|
||||
- target (string)
|
||||
- timestamp (timestamp)
|
||||
- details (JSON)
|
||||
```
|
||||
|
||||
**Port**: 5432
|
||||
|
||||
---
|
||||
|
||||
### 4. Redis
|
||||
|
||||
**Purpose**: Session storage, caching, and future pub/sub for events
|
||||
|
||||
**Usage**:
|
||||
- **Session Storage**: JWT tokens and user sessions
|
||||
- **Caching**: Camera list (60s TTL), monitor list (60s TTL)
|
||||
- **Future**: Pub/sub for real-time routing updates
|
||||
|
||||
**Port**: 6379
|
||||
|
||||
---
|
||||
|
||||
### 5. GeViScope SDK & GeViServer
|
||||
|
||||
**GeViServer**:
|
||||
- Backend service managing surveillance system
|
||||
- Handles actual video routing
|
||||
- Controls GSCView viewers
|
||||
- Manages camera inputs and outputs
|
||||
|
||||
**GeViScope SDK**:
|
||||
- .NET Framework 4.8 DLL (GeViProcAPINET_4_0.dll)
|
||||
- Provides C# wrapper for GeViServer communication
|
||||
- Uses action-based message passing
|
||||
- State query pattern for enumeration
|
||||
|
||||
**Ports**: 7700, 7701, 7703
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
### 1. Authentication Flow
|
||||
|
||||
```
|
||||
Client → POST /api/v1/auth/login
|
||||
{ username: "admin", password: "secret" }
|
||||
↓
|
||||
FastAPI validates credentials
|
||||
↓
|
||||
Hash password with bcrypt
|
||||
↓
|
||||
Query PostgreSQL for user
|
||||
↓
|
||||
Generate JWT token (1hr expiry)
|
||||
↓
|
||||
Store session in Redis
|
||||
↓
|
||||
Client ← { access_token: "eyJ...", token_type: "bearer" }
|
||||
```
|
||||
|
||||
### 2. Camera Discovery Flow
|
||||
|
||||
```
|
||||
Client → GET /api/v1/cameras
|
||||
Header: Authorization: Bearer eyJ...
|
||||
↓
|
||||
FastAPI validates JWT
|
||||
↓
|
||||
Check Redis cache for camera list
|
||||
↓ (cache miss)
|
||||
gRPC call to SDK Bridge: ListCameras()
|
||||
↓
|
||||
SDK Bridge → GeViScope SDK
|
||||
→ CSQGetFirstVideoInput()
|
||||
→ CSQGetNextVideoInput() (loop)
|
||||
↓
|
||||
SDK Bridge ← Camera list
|
||||
↓
|
||||
FastAPI ← gRPC response
|
||||
↓
|
||||
Store in Redis (60s TTL)
|
||||
↓
|
||||
Client ← { cameras: [
|
||||
{ id: 1, name: "Camera 1", has_ptz: false },
|
||||
{ id: 2, name: "Front Gate", has_ptz: true }
|
||||
]}
|
||||
```
|
||||
|
||||
### 3. Cross-Switching Flow
|
||||
|
||||
```
|
||||
Client → POST /api/v1/crossswitch
|
||||
{ camera_id: 7, monitor_id: 3, mode: 0 }
|
||||
↓
|
||||
FastAPI validates JWT (requires operator role)
|
||||
↓
|
||||
Validate camera_id and monitor_id exist
|
||||
↓
|
||||
gRPC call to SDK Bridge: ExecuteCrossSwitch(7, 3, 0)
|
||||
↓
|
||||
SDK Bridge → GeViScope SDK
|
||||
→ SendMessage("CrossSwitch(7, 3, 0)")
|
||||
↓
|
||||
GeViServer executes cross-switch
|
||||
↓
|
||||
SDK Bridge ← Success confirmation
|
||||
↓
|
||||
FastAPI stores route in PostgreSQL
|
||||
↓
|
||||
FastAPI logs to audit_logs table
|
||||
↓
|
||||
Client ← { success: true, message: "Camera 7 routed to monitor 3" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
**Authentication**: JWT (JSON Web Tokens)
|
||||
- Access tokens: 1 hour lifetime
|
||||
- Refresh tokens: 7 days lifetime (future)
|
||||
- Tokens stored in Redis for quick invalidation
|
||||
- Bcrypt password hashing (cost factor: 12)
|
||||
|
||||
**Authorization**: Role-Based Access Control (RBAC)
|
||||
|
||||
| Role | Permissions |
|
||||
|------|------------|
|
||||
| **Viewer** | Read cameras, Read monitors, Read routing state |
|
||||
| **Operator** | Viewer + Execute cross-switch, Clear monitors |
|
||||
| **Administrator** | Operator + User management, Configuration |
|
||||
|
||||
### API Security
|
||||
|
||||
- ✅ HTTPS enforced in production (TLS 1.2+)
|
||||
- ✅ CORS configured for allowed origins
|
||||
- ✅ Rate limiting (60 requests/minute per IP)
|
||||
- ✅ JWT secret key from environment (not hardcoded)
|
||||
- ✅ Database credentials in environment variables
|
||||
- ✅ No stack traces exposed to clients
|
||||
- ✅ Audit logging for all operations
|
||||
|
||||
---
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Current Architecture (MVP)
|
||||
- Single FastAPI instance
|
||||
- Single SDK Bridge instance
|
||||
- Single GeViServer connection
|
||||
|
||||
### Future Horizontal Scaling
|
||||
|
||||
**FastAPI Layer**:
|
||||
- ✅ Stateless design enables multiple instances
|
||||
- ✅ Load balancer in front (nginx/HAProxy)
|
||||
- ✅ Shared PostgreSQL and Redis
|
||||
|
||||
**SDK Bridge Layer**:
|
||||
- ⚠️ Limited by GeViServer connection capacity
|
||||
- Consider: Connection pooling pattern
|
||||
- Consider: Multiple SDK Bridge instances if needed
|
||||
|
||||
**Database Layer**:
|
||||
- PostgreSQL read replicas for camera/monitor queries
|
||||
- Redis Cluster for high availability
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### SDK Bridge Error Translation
|
||||
|
||||
| SDK Error | gRPC Status | HTTP Status |
|
||||
|-----------|-------------|-------------|
|
||||
| Connection Failed | UNAVAILABLE | 503 Service Unavailable |
|
||||
| Invalid Channel | INVALID_ARGUMENT | 400 Bad Request |
|
||||
| Permission Denied | PERMISSION_DENIED | 403 Forbidden |
|
||||
| Timeout | DEADLINE_EXCEEDED | 504 Gateway Timeout |
|
||||
| Unknown | INTERNAL | 500 Internal Server Error |
|
||||
|
||||
### Retry Logic
|
||||
- SDK Bridge connection: 3 attempts with exponential backoff
|
||||
- gRPC calls from FastAPI: 2 attempts with 1s delay
|
||||
- Transient errors logged but not exposed to client
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Logging
|
||||
|
||||
**FastAPI**:
|
||||
- Structured JSON logs (Structlog)
|
||||
- Log levels: DEBUG, INFO, WARNING, ERROR
|
||||
- Correlation IDs for request tracing
|
||||
|
||||
**SDK Bridge**:
|
||||
- Serilog with file and console sinks
|
||||
- Separate logs for SDK communication
|
||||
|
||||
### Metrics (Future)
|
||||
|
||||
**Prometheus Endpoint**: `/metrics`
|
||||
- Request count by endpoint
|
||||
- Request latency (p50, p95, p99)
|
||||
- Active cross-switch operations
|
||||
- gRPC call success/failure rates
|
||||
- Cache hit/miss rates
|
||||
|
||||
### Health Checks
|
||||
|
||||
**Endpoint**: `GET /api/v1/health`
|
||||
|
||||
Returns:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"components": {
|
||||
"database": "up",
|
||||
"redis": "up",
|
||||
"sdk_bridge": "up"
|
||||
},
|
||||
"timestamp": "2025-12-08T15:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### Development Environment
|
||||
```
|
||||
Localhost:
|
||||
- PostgreSQL (Docker or native)
|
||||
- Redis (Docker or native)
|
||||
- SDK Bridge (.NET)
|
||||
- FastAPI (uvicorn --reload)
|
||||
- GeViServer (C:\GEVISOFT\GeViServer.exe)
|
||||
```
|
||||
|
||||
### Production Environment (Windows Server)
|
||||
```
|
||||
Windows Server 2016+:
|
||||
- GeViServer (native Windows service)
|
||||
- SDK Bridge (Windows service via NSSM)
|
||||
- PostgreSQL (Docker or native)
|
||||
- Redis (Docker or native)
|
||||
- FastAPI (Docker or uvicorn behind nginx)
|
||||
- Nginx (reverse proxy with SSL termination)
|
||||
```
|
||||
|
||||
### Network Requirements
|
||||
- Port 8000: FastAPI (HTTPS in production)
|
||||
- Port 50051: SDK Bridge gRPC (internal only)
|
||||
- Port 5432: PostgreSQL (internal only)
|
||||
- Port 6379: Redis (internal only)
|
||||
- Port 7700-7703: GeViServer (internal only)
|
||||
|
||||
---
|
||||
|
||||
## Technology Choices Rationale
|
||||
|
||||
### Why Python FastAPI?
|
||||
- ✅ Modern async Python framework
|
||||
- ✅ Automatic OpenAPI documentation
|
||||
- ✅ Fast development cycle
|
||||
- ✅ Rich ecosystem (SQLAlchemy, Pydantic)
|
||||
- ✅ Easy to expand with new features
|
||||
|
||||
### Why C# SDK Bridge?
|
||||
- ✅ GeViScope SDK is .NET Framework 4.8
|
||||
- ✅ gRPC provides type-safe communication
|
||||
- ✅ Isolates SDK complexity
|
||||
- ✅ Can run on separate machine if needed
|
||||
|
||||
### Why PostgreSQL?
|
||||
- ✅ Mature, reliable, ACID compliant
|
||||
- ✅ JSON support for flexible audit logs
|
||||
- ✅ Good performance for relational data
|
||||
|
||||
### Why Redis?
|
||||
- ✅ Fast in-memory caching
|
||||
- ✅ Session storage
|
||||
- ✅ Future: pub/sub for events
|
||||
|
||||
### Why gRPC (not REST for SDK Bridge)?
|
||||
- ✅ Type-safe protocol buffers
|
||||
- ✅ Efficient binary protocol
|
||||
- ✅ Streaming support (future)
|
||||
- ✅ Language-agnostic
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements (Phase 2)
|
||||
|
||||
1. **GeViSet Configuration Management**
|
||||
- Retrieve action mappings from GeViServer
|
||||
- Modify configurations via API
|
||||
- Export/import to CSV
|
||||
- Push configurations back to server
|
||||
|
||||
2. **Real-Time Event Stream**
|
||||
- WebSocket endpoint for routing changes
|
||||
- Redis pub/sub for event distribution
|
||||
- Monitor status change notifications
|
||||
|
||||
3. **PTZ Camera Control**
|
||||
- Pan/tilt/zoom commands
|
||||
- Preset positions
|
||||
- Tour sequences
|
||||
|
||||
4. **Multi-Tenancy**
|
||||
- Organization/tenant isolation
|
||||
- Per-tenant GeViServer connections
|
||||
|
||||
5. **Advanced Analytics**
|
||||
- Routing history reports
|
||||
- Usage patterns
|
||||
- Performance metrics
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Setup**: `.\scripts\setup_dev_environment.ps1`
|
||||
2. **Start Services**: `.\scripts\start_services.ps1`
|
||||
3. **Database Migrations**: `alembic upgrade head`
|
||||
4. **Run Tests**: `pytest tests/ -v`
|
||||
5. **Code Quality**: `ruff check src/api` + `black src/api`
|
||||
6. **API Docs**: http://localhost:8000/docs
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **FastAPI Documentation**: https://fastapi.tiangolo.com
|
||||
- **gRPC .NET**: https://grpc.io/docs/languages/csharp/
|
||||
- **GeViScope SDK**: See `docs/SDK_INTEGRATION_LESSONS.md`
|
||||
- **SQLAlchemy**: https://docs.sqlalchemy.org
|
||||
- **Pydantic**: https://docs.pydantic.dev
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Architecture Status**: ✅ Defined, 🔄 In Development
|
||||
**Last Review**: 2025-12-08
|
||||
113
pyproject.toml
Normal file
113
pyproject.toml
Normal file
@@ -0,0 +1,113 @@
|
||||
[project]
|
||||
name = "geutebruck-api"
|
||||
version = "1.0.0"
|
||||
description = "REST API for Geutebruck GeViScope/GeViSoft Cross-Switching Control"
|
||||
authors = [
|
||||
{name = "COLSYS", email = "info@colsys.tech"}
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
readme = "README.md"
|
||||
license = {text = "Proprietary"}
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.colsys.tech/COLSYS/geutebruck-api"
|
||||
Repository = "https://git.colsys.tech/COLSYS/geutebruck-api"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py311']
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
# directories
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| build
|
||||
| dist
|
||||
| migrations
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"C", # flake8-comprehensions
|
||||
"B", # flake8-bugbear
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line too long (handled by black)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
"C901", # too complex
|
||||
"W191", # indentation contains tabs
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["F401"] # unused imports in __init__.py
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-third-party = ["fastapi", "pydantic", "sqlalchemy"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = false
|
||||
disallow_incomplete_defs = false
|
||||
check_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_no_return = true
|
||||
strict_equality = true
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
ignore_errors = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "7.0"
|
||||
addopts = "-ra -q --strict-markers --cov=src/api --cov-report=html --cov-report=term-missing"
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
pythonpath = [
|
||||
"src/api"
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src/api"]
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/migrations/*",
|
||||
"*/__init__.py",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
precision = 2
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"def __repr__",
|
||||
"if TYPE_CHECKING:",
|
||||
"raise AssertionError",
|
||||
"raise NotImplementedError",
|
||||
"if __name__ == .__main__.:",
|
||||
"@abstractmethod",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
57
requirements.txt
Normal file
57
requirements.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
# Web Framework
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
python-multipart==0.0.6
|
||||
|
||||
# Database
|
||||
sqlalchemy==2.0.25
|
||||
alembic==1.13.1
|
||||
psycopg2-binary==2.9.9
|
||||
|
||||
# Redis
|
||||
redis==5.0.1
|
||||
aioredis==2.0.1
|
||||
|
||||
# gRPC
|
||||
grpcio==1.60.0
|
||||
grpcio-tools==1.60.0
|
||||
protobuf==4.25.2
|
||||
|
||||
# Authentication
|
||||
pyjwt==2.8.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-jose[cryptography]==3.3.0
|
||||
|
||||
# Validation
|
||||
pydantic==2.5.3
|
||||
pydantic-settings==2.1.0
|
||||
email-validator==2.1.0
|
||||
|
||||
# WebSocket
|
||||
websockets==12.0
|
||||
|
||||
# HTTP Client
|
||||
httpx==0.26.0
|
||||
aiohttp==3.9.1
|
||||
|
||||
# Testing
|
||||
pytest==7.4.4
|
||||
pytest-asyncio==0.23.3
|
||||
pytest-cov==4.1.0
|
||||
pytest-mock==3.12.0
|
||||
httpx==0.26.0
|
||||
|
||||
# Code Quality
|
||||
ruff==0.1.14
|
||||
black==23.12.1
|
||||
mypy==1.8.0
|
||||
types-redis==4.6.0.20240106
|
||||
|
||||
# Environment
|
||||
python-dotenv==1.0.0
|
||||
|
||||
# Logging
|
||||
structlog==24.1.0
|
||||
|
||||
# Date/Time
|
||||
python-dateutil==2.8.2
|
||||
156
scripts/setup_dev_environment.ps1
Normal file
156
scripts/setup_dev_environment.ps1
Normal file
@@ -0,0 +1,156 @@
|
||||
# Geutebruck API - Development Environment Setup Script
|
||||
# This script sets up the complete development environment
|
||||
|
||||
param(
|
||||
[switch]$SkipPython,
|
||||
[switch]$SkipDotnet,
|
||||
[switch]$SkipDatabase,
|
||||
[switch]$SkipRedis
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Geutebruck API - Development Setup" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
# Function to check if command exists
|
||||
function Test-Command {
|
||||
param($Command)
|
||||
$null = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
return $?
|
||||
}
|
||||
|
||||
# Check Prerequisites
|
||||
Write-Host "[1/8] Checking prerequisites..." -ForegroundColor Yellow
|
||||
|
||||
if (-not $SkipPython) {
|
||||
if (-not (Test-Command python)) {
|
||||
Write-Host "ERROR: Python 3.11+ is required but not found" -ForegroundColor Red
|
||||
Write-Host "Please install Python from https://www.python.org/downloads/" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$pythonVersion = python --version
|
||||
Write-Host " ✓ Python found: $pythonVersion" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if (-not $SkipDotnet) {
|
||||
if (-not (Test-Command dotnet)) {
|
||||
Write-Host "ERROR: .NET 8.0 SDK is required but not found" -ForegroundColor Red
|
||||
Write-Host "Please install from https://dotnet.microsoft.com/download" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$dotnetVersion = dotnet --version
|
||||
Write-Host " ✓ .NET SDK found: $dotnetVersion" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Create .env file if it doesn't exist
|
||||
Write-Host "[2/8] Setting up environment configuration..." -ForegroundColor Yellow
|
||||
if (-not (Test-Path "$RepoRoot\.env")) {
|
||||
Copy-Item "$RepoRoot\.env.example" "$RepoRoot\.env"
|
||||
Write-Host " ✓ Created .env file from .env.example" -ForegroundColor Green
|
||||
Write-Host " ⚠ IMPORTANT: Edit .env to configure your settings!" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " ✓ .env file already exists" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Setup Python virtual environment
|
||||
if (-not $SkipPython) {
|
||||
Write-Host "[3/8] Setting up Python virtual environment..." -ForegroundColor Yellow
|
||||
|
||||
if (-not (Test-Path "$RepoRoot\.venv")) {
|
||||
python -m venv "$RepoRoot\.venv"
|
||||
Write-Host " ✓ Created Python virtual environment" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ✓ Virtual environment already exists" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Activate virtual environment
|
||||
& "$RepoRoot\.venv\Scripts\Activate.ps1"
|
||||
|
||||
# Upgrade pip
|
||||
python -m pip install --upgrade pip | Out-Null
|
||||
|
||||
# Install Python dependencies
|
||||
Write-Host "[4/8] Installing Python dependencies..." -ForegroundColor Yellow
|
||||
pip install -r "$RepoRoot\requirements.txt"
|
||||
Write-Host " ✓ Python dependencies installed" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[3/8] Skipping Python setup" -ForegroundColor Gray
|
||||
Write-Host "[4/8] Skipping Python dependencies" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Build SDK Bridge
|
||||
if (-not $SkipDotnet) {
|
||||
Write-Host "[5/8] Building SDK Bridge (.NET gRPC service)..." -ForegroundColor Yellow
|
||||
|
||||
$sdkBridgePath = "$RepoRoot\src\sdk-bridge\GeViScopeBridge"
|
||||
if (Test-Path "$sdkBridgePath\GeViScopeBridge.csproj") {
|
||||
Push-Location $sdkBridgePath
|
||||
dotnet restore
|
||||
dotnet build --configuration Debug
|
||||
Pop-Location
|
||||
Write-Host " ✓ SDK Bridge built successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ⚠ SDK Bridge project not found, skipping" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[5/8] Skipping .NET build" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Setup PostgreSQL Database
|
||||
if (-not $SkipDatabase) {
|
||||
Write-Host "[6/8] Setting up PostgreSQL database..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Command psql) {
|
||||
# Create database
|
||||
Write-Host " Creating database 'geutebruck_api'..." -ForegroundColor Cyan
|
||||
$createDbCommand = @"
|
||||
CREATE DATABASE geutebruck_api;
|
||||
CREATE USER geutebruck WITH PASSWORD 'geutebruck';
|
||||
GRANT ALL PRIVILEGES ON DATABASE geutebruck_api TO geutebruck;
|
||||
"@
|
||||
|
||||
Write-Host " Run these commands manually in psql:" -ForegroundColor Yellow
|
||||
Write-Host $createDbCommand -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Then run: alembic upgrade head" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " ⚠ PostgreSQL not found. Install PostgreSQL 14+ manually" -ForegroundColor Yellow
|
||||
Write-Host " Download from: https://www.postgresql.org/download/windows/" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[6/8] Skipping database setup" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Check Redis
|
||||
if (-not $SkipRedis) {
|
||||
Write-Host "[7/8] Checking Redis..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Command redis-server) {
|
||||
Write-Host " ✓ Redis found" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ⚠ Redis not found. Install Redis for Windows:" -ForegroundColor Yellow
|
||||
Write-Host " Option 1: choco install redis-64" -ForegroundColor Yellow
|
||||
Write-Host " Option 2: Download from https://redis.io/download" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[7/8] Skipping Redis check" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host "[8/8] Setup complete!" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Next Steps:" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "1. Edit .env file with your GeViServer credentials" -ForegroundColor White
|
||||
Write-Host "2. Ensure PostgreSQL is running and database is created" -ForegroundColor White
|
||||
Write-Host "3. Run database migrations: alembic upgrade head" -ForegroundColor White
|
||||
Write-Host "4. Ensure Redis is running: redis-server" -ForegroundColor White
|
||||
Write-Host "5. Start services: .\scripts\start_services.ps1" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Development Environment Ready! 🚀" -ForegroundColor Green
|
||||
114
scripts/start_services.ps1
Normal file
114
scripts/start_services.ps1
Normal file
@@ -0,0 +1,114 @@
|
||||
# Geutebruck API - Start All Services
|
||||
# This script starts Redis, SDK Bridge, and FastAPI in separate windows
|
||||
|
||||
param(
|
||||
[switch]$SkipRedis,
|
||||
[switch]$SkipSdkBridge,
|
||||
[switch]$SkipApi
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Geutebruck API - Starting Services" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
# Check if .env exists
|
||||
if (-not (Test-Path "$RepoRoot\.env")) {
|
||||
Write-Host "ERROR: .env file not found!" -ForegroundColor Red
|
||||
Write-Host "Run: .\scripts\setup_dev_environment.ps1 first" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to check if port is in use
|
||||
function Test-Port {
|
||||
param([int]$Port)
|
||||
$tcpConnection = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue
|
||||
return $null -ne $tcpConnection
|
||||
}
|
||||
|
||||
# Start Redis
|
||||
if (-not $SkipRedis) {
|
||||
Write-Host "[1/3] Starting Redis..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Port 6379) {
|
||||
Write-Host " ✓ Redis already running on port 6379" -ForegroundColor Green
|
||||
} else {
|
||||
$redisCmd = Get-Command redis-server -ErrorAction SilentlyContinue
|
||||
if ($redisCmd) {
|
||||
Start-Process -FilePath "redis-server" -WindowStyle Normal
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " ✓ Redis started" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ✗ Redis not found. Install with: choco install redis-64" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "[1/3] Skipping Redis" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Start SDK Bridge
|
||||
if (-not $SkipSdkBridge) {
|
||||
Write-Host "[2/3] Starting SDK Bridge (gRPC Service)..." -ForegroundColor Yellow
|
||||
|
||||
$sdkBridgePath = "$RepoRoot\src\sdk-bridge\GeViScopeBridge"
|
||||
$sdkBridgeExe = "$sdkBridgePath\bin\Debug\net8.0\GeViScopeBridge.exe"
|
||||
|
||||
if (Test-Path $sdkBridgeExe) {
|
||||
if (Test-Port 50051) {
|
||||
Write-Host " ✓ SDK Bridge already running on port 50051" -ForegroundColor Green
|
||||
} else {
|
||||
$sdkBridgeTitle = "Geutebruck SDK Bridge"
|
||||
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$sdkBridgePath'; dotnet run --configuration Debug" -WindowStyle Normal
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host " ✓ SDK Bridge started on port 50051" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host " ⚠ SDK Bridge not built yet" -ForegroundColor Yellow
|
||||
Write-Host " Run: cd $sdkBridgePath; dotnet build" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[2/3] Skipping SDK Bridge" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Start FastAPI
|
||||
if (-not $SkipApi) {
|
||||
Write-Host "[3/3] Starting FastAPI Application..." -ForegroundColor Yellow
|
||||
|
||||
$apiPath = "$RepoRoot\src\api"
|
||||
|
||||
if (Test-Port 8000) {
|
||||
Write-Host " ✓ API already running on port 8000" -ForegroundColor Green
|
||||
} else {
|
||||
# Check if virtual environment exists
|
||||
if (Test-Path "$RepoRoot\.venv\Scripts\Activate.ps1") {
|
||||
$apiTitle = "Geutebruck API"
|
||||
$startCommand = "cd '$apiPath'; & '$RepoRoot\.venv\Scripts\Activate.ps1'; uvicorn main:app --reload --host 0.0.0.0 --port 8000"
|
||||
Start-Process powershell -ArgumentList "-NoExit", "-Command", $startCommand -WindowStyle Normal
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host " ✓ FastAPI started on http://localhost:8000" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ✗ Python virtual environment not found" -ForegroundColor Red
|
||||
Write-Host " Run: .\scripts\setup_dev_environment.ps1 first" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "[3/3] Skipping FastAPI" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Services Status:" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Redis: http://localhost:6379" -ForegroundColor White
|
||||
Write-Host "SDK Bridge: http://localhost:50051 (gRPC)" -ForegroundColor White
|
||||
Write-Host "API: http://localhost:8000" -ForegroundColor White
|
||||
Write-Host "API Docs: http://localhost:8000/docs" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "All Services Started! 🚀" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Press Ctrl+C in each window to stop services" -ForegroundColor Yellow
|
||||
411
specs/001-surveillance-api/tasks-revised-mvp.md
Normal file
411
specs/001-surveillance-api/tasks-revised-mvp.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# Tasks: Geutebruck Cross-Switching API (Revised MVP)
|
||||
|
||||
**Scope**: Cross-switching REST API with authentication, focusing on GeViSet-compatible configuration
|
||||
**MVP Goal**: Control GSCView viewers via cross-switching, no UI needed
|
||||
**Future Expansion**: GeViSet configuration management, action mapping, CSV import/export
|
||||
|
||||
---
|
||||
|
||||
## MVP User Stories
|
||||
|
||||
### US1: Authentication & Connection
|
||||
Connect to GeViServer, authenticate users, maintain sessions
|
||||
|
||||
### US2: Camera Discovery
|
||||
List all video inputs (cameras) with metadata
|
||||
|
||||
### US3: Monitor Discovery
|
||||
List all video outputs (GSCView viewers/monitors) with status
|
||||
|
||||
### US4: Cross-Switching Operations
|
||||
Route cameras to viewers, clear viewers, query routing state
|
||||
|
||||
---
|
||||
|
||||
## Revised Data Model (Simplified)
|
||||
|
||||
```
|
||||
User:
|
||||
- id, username, password_hash, role (viewer/operator/admin)
|
||||
|
||||
Camera:
|
||||
- id (channel), name, description, has_ptz, has_video_sensor, status
|
||||
|
||||
Monitor:
|
||||
- id (output channel), name, is_active, current_camera_id
|
||||
|
||||
CrossSwitchRoute:
|
||||
- id, camera_id, monitor_id, switched_at, switched_by_user_id
|
||||
|
||||
AuditLog:
|
||||
- id, user_id, action, target, timestamp, details
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Foundation (Setup & Core Infrastructure)
|
||||
|
||||
**Purpose**: Project structure, dependencies, SDK bridge foundation
|
||||
|
||||
- [ ] T001 Create project structure (src/api, src/sdk-bridge, tests, docs, scripts)
|
||||
- [ ] T002 Create .gitignore for Python and C#
|
||||
- [ ] T003 Create requirements.txt with FastAPI, SQLAlchemy, Redis, grpcio, PyJWT, pytest
|
||||
- [ ] T004 Create SDK Bridge .csproj with .NET 8.0, Grpc.AspNetCore, GeViScope SDK reference
|
||||
- [ ] T005 Create .env.example with config variables (DB, Redis, JWT secret, GeViServer host/credentials)
|
||||
- [ ] T006 Create alembic.ini for database migrations
|
||||
- [ ] T007 [P] Create pyproject.toml with ruff, black, mypy configuration
|
||||
- [ ] T008 [P] Create scripts/setup_dev_environment.ps1 (install dependencies, setup DB, start services)
|
||||
- [ ] T009 [P] Create scripts/start_services.ps1 (start Redis, SDK Bridge, FastAPI)
|
||||
- [ ] T010 [P] Create docs/architecture.md documenting system design
|
||||
|
||||
**Checkpoint**: Project structure complete, dependencies defined
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: SDK Bridge Foundation (C# gRPC Service)
|
||||
|
||||
**Purpose**: Wrap GeViScope SDK with gRPC for Python consumption
|
||||
|
||||
### gRPC Protocol Definitions
|
||||
|
||||
- [ ] T011 Define common.proto (Status, Error, Timestamp, Empty messages)
|
||||
- [ ] T012 Define camera.proto (ListCamerasRequest/Response, CameraInfo with channel, name, has_ptz)
|
||||
- [ ] T013 Define monitor.proto (ListMonitorsRequest/Response, MonitorInfo with channel, name, current_camera)
|
||||
- [ ] T014 Define crossswitch.proto (CrossSwitchRequest, ClearMonitorRequest, GetRoutingStateRequest/Response)
|
||||
|
||||
### SDK Wrapper Implementation
|
||||
|
||||
- [ ] T015 Create GeViDatabaseWrapper.cs (Create, RegisterCallback, Connect, Disconnect, error handling)
|
||||
- [ ] T016 Implement connection lifecycle with retry logic (3 attempts, exponential backoff)
|
||||
- [ ] T017 Create StateQueryHandler.cs for GetFirst/GetNext enumeration pattern
|
||||
- [ ] T018 Implement EnumerateCameras() using CSQGetFirstVideoInput / CSQGetNextVideoInput
|
||||
- [ ] T019 Implement EnumerateMonitors() using CSQGetFirstVideoOutput / CSQGetNextVideoOutput
|
||||
- [ ] T020 Create ErrorTranslator.cs to map Windows error codes to gRPC status codes
|
||||
- [ ] T021 Create ActionDispatcher.cs for sending SDK actions (CrossSwitch, ClearVideoOutput)
|
||||
|
||||
### gRPC Service Implementation
|
||||
|
||||
- [ ] T022 Create CameraService.cs implementing camera.proto with ListCameras RPC
|
||||
- [ ] T023 Create MonitorService.cs implementing monitor.proto with ListMonitors RPC
|
||||
- [ ] T024 Create CrossSwitchService.cs with ExecuteCrossSwitch, ClearMonitor, GetRoutingState RPCs
|
||||
- [ ] T025 Create Program.cs gRPC server with Serilog logging, service registration
|
||||
- [ ] T026 Add configuration loading from appsettings.json (GeViServer host, port, credentials)
|
||||
|
||||
**Checkpoint**: SDK Bridge can connect to GeViServer, enumerate resources, execute cross-switch
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Python API Foundation
|
||||
|
||||
**Purpose**: FastAPI application structure, configuration, database setup
|
||||
|
||||
### Core Setup
|
||||
|
||||
- [ ] T027 Create main.py with FastAPI app, CORS middleware, exception handlers
|
||||
- [ ] T028 Create config.py loading settings from environment (Pydantic BaseSettings)
|
||||
- [ ] T029 Setup PostgreSQL connection with SQLAlchemy async engine in models/__init__.py
|
||||
- [ ] T030 Create initial Alembic migration for users and audit_logs tables
|
||||
- [ ] T031 Setup Redis client with connection pooling in clients/redis_client.py
|
||||
- [ ] T032 Create gRPC SDK Bridge client in clients/sdk_bridge_client.py with connection pooling
|
||||
- [ ] T033 [P] Create JWT utilities in utils/jwt_utils.py (encode, decode, verify)
|
||||
- [ ] T034 [P] Create error translation utilities in utils/error_translation.py (gRPC → HTTP status)
|
||||
- [ ] T035 Implement global error handler middleware in middleware/error_handler.py
|
||||
|
||||
### Database Models
|
||||
|
||||
- [ ] T036 [P] Create User model in models/user.py (id, username, password_hash, role, created_at)
|
||||
- [ ] T037 [P] Create AuditLog model in models/audit_log.py (id, user_id, action, target, timestamp)
|
||||
- [ ] T038 Run alembic upgrade head to create tables
|
||||
|
||||
**Checkpoint**: Python API can start, connect to DB/Redis, communicate with SDK Bridge via gRPC
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Authentication (User Story 1)
|
||||
|
||||
**Purpose**: JWT-based authentication with role-based access control
|
||||
|
||||
### Tests (TDD - Write FIRST, Ensure FAIL)
|
||||
|
||||
- [ ] T039 [P] Write contract test for POST /api/v1/auth/login in tests/api/contract/test_auth.py (should FAIL)
|
||||
- [ ] T040 [P] Write contract test for POST /api/v1/auth/logout in tests/api/contract/test_auth.py (should FAIL)
|
||||
- [ ] T041 [P] Write unit test for AuthService in tests/api/unit/test_auth_service.py (should FAIL)
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] T042 [P] Create auth schemas in schemas/auth.py (LoginRequest, TokenResponse, UserInfo)
|
||||
- [ ] T043 Implement AuthService in services/auth_service.py (login, logout, validate_token, hash_password)
|
||||
- [ ] T044 Implement JWT token generation (access: 1hr, refresh: 7 days) with Redis session storage
|
||||
- [ ] T045 Implement authentication middleware in middleware/auth_middleware.py (verify JWT, extract user)
|
||||
- [ ] T046 Implement role checking decorator in utils/permissions.py (@require_role("operator"))
|
||||
- [ ] T047 Create auth router in routers/auth.py with POST /auth/login, POST /auth/logout
|
||||
- [ ] T048 Add audit logging for authentication attempts (success and failures)
|
||||
|
||||
**Verify**: Run tests T039-T041 - should now PASS
|
||||
|
||||
**Checkpoint**: Can login with credentials, receive JWT token, use token for authenticated requests
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Camera Discovery (User Story 2)
|
||||
|
||||
**Purpose**: List all cameras (video inputs) from GeViServer
|
||||
|
||||
### Tests (TDD - Write FIRST, Ensure FAIL)
|
||||
|
||||
- [ ] T049 [P] Write contract test for GET /api/v1/cameras in tests/api/contract/test_cameras.py (should FAIL)
|
||||
- [ ] T050 [P] Write unit test for CameraService in tests/api/unit/test_camera_service.py (should FAIL)
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] T051 [P] Create camera schemas in schemas/camera.py (CameraInfo, CameraList)
|
||||
- [ ] T052 Implement CameraService in services/camera_service.py (list_cameras via gRPC to SDK Bridge)
|
||||
- [ ] T053 Create cameras router in routers/cameras.py with GET /cameras
|
||||
- [ ] T054 Add permission check: authenticated users only
|
||||
- [ ] T055 Add caching in Redis (cache camera list for 60 seconds to reduce SDK Bridge load)
|
||||
|
||||
**Verify**: Run tests T049-T050 - should now PASS
|
||||
|
||||
**Checkpoint**: GET /api/v1/cameras returns list of all cameras from GeViServer
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Monitor Discovery (User Story 3)
|
||||
|
||||
**Purpose**: List all monitors/viewers (video outputs) from GeViServer
|
||||
|
||||
### Tests (TDD - Write FIRST, Ensure FAIL)
|
||||
|
||||
- [ ] T056 [P] Write contract test for GET /api/v1/monitors in tests/api/contract/test_monitors.py (should FAIL)
|
||||
- [ ] T057 [P] Write unit test for MonitorService in tests/api/unit/test_monitor_service.py (should FAIL)
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] T058 [P] Create monitor schemas in schemas/monitor.py (MonitorInfo, MonitorList)
|
||||
- [ ] T059 Implement MonitorService in services/monitor_service.py (list_monitors via gRPC to SDK Bridge)
|
||||
- [ ] T060 Create monitors router in routers/monitors.py with GET /monitors
|
||||
- [ ] T061 Add permission check: authenticated users only
|
||||
- [ ] T062 Add caching in Redis (cache monitor list for 60 seconds)
|
||||
|
||||
**Verify**: Run tests T056-T057 - should now PASS
|
||||
|
||||
**Checkpoint**: GET /api/v1/monitors returns list of all monitors/viewers from GeViServer
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Cross-Switching Operations (User Story 4)
|
||||
|
||||
**Purpose**: Execute cross-switch, clear monitors, query routing state
|
||||
|
||||
### Tests (TDD - Write FIRST, Ensure FAIL)
|
||||
|
||||
- [ ] T063 [P] Write contract test for POST /api/v1/crossswitch in tests/api/contract/test_crossswitch.py (should FAIL)
|
||||
- [ ] T064 [P] Write contract test for DELETE /api/v1/monitors/{id} in tests/api/contract/test_crossswitch.py (should FAIL)
|
||||
- [ ] T065 [P] Write contract test for GET /api/v1/routing/state in tests/api/contract/test_crossswitch.py (should FAIL)
|
||||
- [ ] T066 [P] Write integration test for cross-switch workflow in tests/api/integration/test_crossswitch.py (should FAIL)
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] T067 [P] Create crossswitch schemas in schemas/crossswitch.py (CrossSwitchRequest, RoutingState, ClearMonitorRequest)
|
||||
- [ ] T068 Create CrossSwitchRoute model in models/crossswitch_route.py (id, camera_id, monitor_id, switched_at, user_id)
|
||||
- [ ] T069 Create Alembic migration for crossswitch_routes table
|
||||
- [ ] T070 Implement CrossSwitchService in services/crossswitch_service.py:
|
||||
- execute_crossswitch(camera_id, monitor_id, mode=0) → gRPC to SDK Bridge
|
||||
- clear_monitor(monitor_id) → gRPC ClearVideoOutput
|
||||
- get_routing_state() → query current routes
|
||||
- [ ] T071 Create crossswitch router in routers/crossswitch.py:
|
||||
- POST /crossswitch (requires operator or admin role)
|
||||
- DELETE /monitors/{id} (requires operator or admin role)
|
||||
- GET /routing/state (all authenticated users)
|
||||
- [ ] T072 Add audit logging for all cross-switch operations
|
||||
- [ ] T073 Add validation: camera_id and monitor_id must exist
|
||||
- [ ] T074 Store routing state in database for history/tracking
|
||||
|
||||
**Verify**: Run tests T063-T066 - should now PASS
|
||||
|
||||
**Checkpoint**: Can execute cross-switch via API, clear monitors, query current routing
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: MVP Polish & Documentation
|
||||
|
||||
**Purpose**: Complete MVP with documentation and deployment readiness
|
||||
|
||||
- [ ] T075 [P] Create API documentation in docs/api-usage.md with curl examples
|
||||
- [ ] T076 [P] Create deployment guide in docs/deployment.md (Windows Server setup, service installation)
|
||||
- [ ] T077 [P] Add Prometheus metrics endpoint at /metrics (request count, latency, active connections)
|
||||
- [ ] T078 [P] Create health check endpoint GET /health (SDK Bridge connectivity, DB, Redis status)
|
||||
- [ ] T079 [P] Add request logging with correlation IDs
|
||||
- [ ] T080 Create README.md with project overview, quick start, architecture diagram
|
||||
- [ ] T081 Update OpenAPI specification to include only MVP endpoints
|
||||
- [ ] T082 Create Postman collection for API testing
|
||||
- [ ] T083 Run full integration tests with actual GeViServer connection
|
||||
- [ ] T084 Security audit: Remove stack traces in production, sanitize logs
|
||||
|
||||
**Checkpoint**: MVP complete - REST API for cross-switching with authentication
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Future - GeViSet Configuration Management (Phase 2)
|
||||
|
||||
**Purpose**: GeViSet-like functionality via API (action mapping configuration)
|
||||
|
||||
**Note**: These tasks will be detailed after MVP is complete and working
|
||||
|
||||
### High-Level Tasks:
|
||||
|
||||
- [ ] T085 Research GeViSet configuration file format and action mapping structure
|
||||
- [ ] T086 Implement GET /api/v1/config/actions to retrieve action mappings from GeViServer
|
||||
- [ ] T087 Implement PUT /api/v1/config/actions to push action mappings to GeViServer
|
||||
- [ ] T088 Implement POST /api/v1/config/actions/export to export configuration to CSV
|
||||
- [ ] T089 Implement POST /api/v1/config/actions/import to import configuration from CSV
|
||||
- [ ] T090 Add validation for action mapping syntax and constraints
|
||||
- [ ] T091 Add versioning for configuration changes (track who changed what, when)
|
||||
- [ ] T092 Add backup/restore functionality for configurations
|
||||
|
||||
**Checkpoint**: GeViSet configuration management available via API
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
```
|
||||
Phase 1 (Setup)
|
||||
↓
|
||||
Phase 2 (SDK Bridge Foundation) ← BLOCKS all Python API work
|
||||
↓
|
||||
Phase 3 (Python API Foundation) ← BLOCKS all feature work
|
||||
↓
|
||||
Phase 4 (Authentication) ← BLOCKS all protected endpoints
|
||||
↓
|
||||
Phases 5, 6, 7 can proceed in parallel (after Phase 4)
|
||||
↓
|
||||
Phase 8 (Polish & Documentation)
|
||||
↓
|
||||
Phase 9 (Future - GeViSet config) ← After MVP validated
|
||||
```
|
||||
|
||||
### Critical Path (Sequential)
|
||||
|
||||
1. Setup → SDK Bridge → Python API → Authentication
|
||||
2. Then parallel: Camera Discovery + Monitor Discovery + Cross-Switching
|
||||
3. Then: Polish & Documentation
|
||||
4. Finally: GeViSet configuration (Phase 2)
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- Phase 2: T020 (ErrorTranslator) parallel with T017-T019 (StateQuery implementation)
|
||||
- Phase 3: T033-T034, T036-T037 can run in parallel
|
||||
- Phase 4: T039-T041 tests can run in parallel
|
||||
- Phase 5-7: These entire phases can run in parallel after Phase 4 completes
|
||||
- Phase 8: T075-T082 can run in parallel
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Week 1: Foundation
|
||||
- Days 1-2: Phase 1 (Setup)
|
||||
- Days 3-5: Phase 2 (SDK Bridge)
|
||||
|
||||
### Week 2: API Core
|
||||
- Days 1-3: Phase 3 (Python API Foundation)
|
||||
- Days 4-5: Phase 4 (Authentication)
|
||||
|
||||
### Week 3: Cross-Switching
|
||||
- Days 1-2: Phase 5 (Camera Discovery)
|
||||
- Days 2-3: Phase 6 (Monitor Discovery)
|
||||
- Days 4-5: Phase 7 (Cross-Switching Operations)
|
||||
|
||||
### Week 4: Polish & Validation
|
||||
- Days 1-3: Phase 8 (Polish, Documentation)
|
||||
- Days 4-5: Integration testing with real GeViServer, bug fixes
|
||||
|
||||
**MVP Delivery**: End of Week 4
|
||||
|
||||
### Week 5+: Phase 2 Features
|
||||
- GeViSet configuration management
|
||||
- Action mapping CRUD
|
||||
- CSV import/export
|
||||
|
||||
---
|
||||
|
||||
## Task Summary
|
||||
|
||||
**MVP Total**: 84 tasks
|
||||
|
||||
**By Phase**:
|
||||
- Phase 1 (Setup): 10 tasks
|
||||
- Phase 2 (SDK Bridge): 16 tasks
|
||||
- Phase 3 (API Foundation): 12 tasks
|
||||
- Phase 4 (Authentication): 10 tasks
|
||||
- Phase 5 (Camera Discovery): 7 tasks
|
||||
- Phase 6 (Monitor Discovery): 7 tasks
|
||||
- Phase 7 (Cross-Switching): 12 tasks
|
||||
- Phase 8 (Polish): 10 tasks
|
||||
|
||||
**Phase 2 (Future)**: 8+ tasks (detailed after MVP)
|
||||
|
||||
**Tests**: 12 test tasks (TDD approach)
|
||||
**Parallel Tasks**: 20+ tasks marked [P]
|
||||
|
||||
**Estimated Timeline**:
|
||||
- MVP: 3-4 weeks (1 developer, focused work)
|
||||
- Phase 2 (GeViSet config): +1-2 weeks
|
||||
|
||||
---
|
||||
|
||||
## MVP Endpoints Summary
|
||||
|
||||
```
|
||||
# Authentication
|
||||
POST /api/v1/auth/login # Get JWT token
|
||||
POST /api/v1/auth/logout # Invalidate token
|
||||
|
||||
# Cameras
|
||||
GET /api/v1/cameras # List all cameras
|
||||
|
||||
# Monitors
|
||||
GET /api/v1/monitors # List all monitors/viewers
|
||||
|
||||
# Cross-Switching
|
||||
POST /api/v1/crossswitch # Execute cross-switch
|
||||
Body: { camera_id: 7, monitor_id: 3, mode: 0 }
|
||||
|
||||
DELETE /api/v1/monitors/{id} # Clear monitor (stop video)
|
||||
|
||||
GET /api/v1/routing/state # Get current routing state
|
||||
|
||||
# System
|
||||
GET /api/v1/health # Health check (SDK Bridge, DB, Redis)
|
||||
GET /metrics # Prometheus metrics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### TDD Approach
|
||||
1. Write contract test (should FAIL)
|
||||
2. Write unit tests (should FAIL)
|
||||
3. Implement feature
|
||||
4. Run tests (should PASS)
|
||||
5. Refactor if needed
|
||||
6. Commit
|
||||
|
||||
### Test Coverage Goal
|
||||
- Minimum 70% coverage for MVP
|
||||
- 100% coverage for authentication and cross-switching logic
|
||||
|
||||
### Manual Testing
|
||||
- Test with Postman collection
|
||||
- Test with curl commands
|
||||
- Integration test with actual GeViServer
|
||||
|
||||
---
|
||||
|
||||
**Generated**: 2025-12-08
|
||||
**Scope**: Cross-switching MVP with authentication, expandable to GeViSet configuration
|
||||
**Architecture**: Python FastAPI + C# gRPC Bridge + GeViScope SDK
|
||||
34
src/sdk-bridge/GeViScopeBridge/GeViScopeBridge.csproj
Normal file
34
src/sdk-bridge/GeViScopeBridge/GeViScopeBridge.csproj
Normal file
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Platforms>x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.60.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.60.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.25.2" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="..\Protos\*.proto" GrpcServices="Server" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="GeViProcAPINET_4_0">
|
||||
<HintPath>C:\GEVISOFT\GeViProcAPINET_4_0.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user