thanhkt commited on
Commit
50a7bf0
·
1 Parent(s): ed7a9c3

implement core api

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.env DELETED
@@ -1,26 +0,0 @@
1
- # OpenAI
2
- OPENAI_API_KEY=""
3
-
4
- # Azure OpenAI
5
- AZURE_API_KEY=""
6
- AZURE_API_BASE=""
7
- AZURE_API_VERSION=""
8
- OPENROUTER_API_KEY = "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"
9
- # Google Vertex AI
10
- VERTEXAI_PROJECT=""
11
- VERTEXAI_LOCATION=""
12
- GOOGLE_APPLICATION_CREDENTIALS=""
13
- GITHUB_API_KEY = "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"
14
- GITHUB_TOKEN = "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"
15
- OPENAI_API_KEY = "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"
16
- # Google Gemini
17
- GEMINI_API_KEY="AIzaSyBUCGQ_hDLAHQN-T1ycWBJV8SGfwusfEjg"
18
-
19
- ...
20
-
21
- # Kokoro TTS Settings
22
- KOKORO_MODEL_PATH="models/kokoro-v0_19.onnx"
23
- KOKORO_VOICES_PATH="models/voices.bin"
24
- KOKORO_DEFAULT_VOICE="af"
25
- KOKORO_DEFAULT_SPEED="1.0"
26
- KOKORO_DEFAULT_LANG="en-us"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.env.example CHANGED
@@ -1,26 +1,77 @@
1
- # OpenAI
2
- OPENAI_API_KEY=""
3
-
4
- # Azure OpenAI
5
- AZURE_API_KEY=""
6
- AZURE_API_BASE=""
7
- AZURE_API_VERSION=""
8
- OPENROUTER_API_KEY = ""
9
- # Google Vertex AI
10
- VERTEXAI_PROJECT=""
11
- VERTEXAI_LOCATION=""
12
- GOOGLE_APPLICATION_CREDENTIALS=""
13
- GITHUB_API_KEY = ""
14
- GITHUB_TOKEN = ""
15
- OPENAI_API_KEY = ""
16
- # Google Gemini
17
- GEMINI_API_KEY=""
18
-
19
- ...
20
-
21
- # Kokoro TTS Settings
22
- KOKORO_MODEL_PATH="models/kokoro-v0_19.onnx"
23
- KOKORO_VOICES_PATH="models/voices.bin"
24
- KOKORO_DEFAULT_VOICE="af"
25
- KOKORO_DEFAULT_SPEED="1.0"
26
- KOKORO_DEFAULT_LANG="en-us"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastAPI Video Backend Environment Configuration
2
+ # Copy this file to .env and update the values
3
+
4
+ # Application Settings
5
+ APP_NAME="FastAPI Video Backend"
6
+ APP_VERSION="0.1.0"
7
+ DEBUG=true
8
+ ENVIRONMENT=development
9
+
10
+ # Server Settings
11
+ HOST=0.0.0.0
12
+ PORT=8000
13
+ RELOAD=true
14
+
15
+ # API Settings
16
+ API_V1_PREFIX="/api/v1"
17
+ DOCS_URL="/docs"
18
+ REDOC_URL="/redoc"
19
+ OPENAPI_URL="/openapi.json"
20
+
21
+ # CORS Settings
22
+ ALLOWED_ORIGINS="http://localhost:3000,http://localhost:8080,http://127.0.0.1:3000"
23
+ ALLOWED_METHODS="GET,POST,PUT,DELETE,OPTIONS"
24
+ ALLOWED_HEADERS="*"
25
+
26
+ # Redis Settings
27
+ REDIS_URL="redis://localhost:6379/0"
28
+ REDIS_HOST=localhost
29
+ REDIS_PORT=6379
30
+ REDIS_DB=0
31
+ REDIS_PASSWORD=
32
+ REDIS_MAX_CONNECTIONS=20
33
+ REDIS_SOCKET_TIMEOUT=5
34
+ REDIS_SOCKET_CONNECT_TIMEOUT=5
35
+
36
+ # Clerk Authentication Settings (REQUIRED)
37
+ CLERK_SECRET_KEY=your_clerk_secret_key_here
38
+ CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key_here
39
+ CLERK_WEBHOOK_SECRET=your_clerk_webhook_secret_here
40
+ CLERK_JWT_VERIFICATION=true
41
+
42
+ # Job Queue Settings
43
+ JOB_QUEUE_NAME=video_generation_queue
44
+ JOB_QUEUE_MAX_SIZE=1000
45
+ JOB_DEFAULT_TIMEOUT=3600
46
+ JOB_RETRY_ATTEMPTS=3
47
+
48
+ # File Storage Settings
49
+ UPLOAD_DIR=./uploads
50
+ MAX_FILE_SIZE=104857600
51
+ ALLOWED_FILE_TYPES="image/jpeg,image/png,image/gif,video/mp4,text/plain"
52
+
53
+ # Rate Limiting Settings
54
+ RATE_LIMIT_REQUESTS=100
55
+ RATE_LIMIT_WINDOW=60
56
+ RATE_LIMIT_PER_USER=50
57
+
58
+ # Logging Settings
59
+ LOG_LEVEL=INFO
60
+ LOG_FORMAT=json
61
+ LOG_FILE=
62
+ LOG_ROTATION="1 day"
63
+ LOG_RETENTION="30 days"
64
+
65
+ # Security Settings (REQUIRED)
66
+ SECRET_KEY=your_super_secret_key_here_change_in_production
67
+ ACCESS_TOKEN_EXPIRE_MINUTES=30
68
+ REFRESH_TOKEN_EXPIRE_DAYS=7
69
+
70
+ # Video Generation Settings
71
+ VIDEO_OUTPUT_DIR=./videos
72
+ VIDEO_QUALITY_DEFAULT=medium
73
+ VIDEO_MAX_DURATION=600
74
+
75
+ # Health Check Settings
76
+ HEALTH_CHECK_INTERVAL=30
77
+ HEALTH_CHECK_TIMEOUT=5
.gitignore CHANGED
@@ -9,6 +9,8 @@ __pycache__/
9
  # Distribution / packaging
10
  .Python
11
  env/
 
 
12
  build/
13
  develop-eggs/
14
  dist/
 
9
  # Distribution / packaging
10
  .Python
11
  env/
12
+ .env
13
+ .*env
14
  build/
15
  develop-eggs/
16
  dist/
.kiro/specs/fastapi-backend/design.md ADDED
@@ -0,0 +1,1055 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Design Document
2
+
3
+ ## Overview
4
+
5
+ This document outlines the design for a simplified FastAPI backend that serves as the primary interface for the multi-agent video generation system. The backend uses Pydantic for all data modeling and validation, Clerk for authentication, and Redis for both caching and job queuing. The design emphasizes simplicity and rapid development while maintaining clean architecture principles.
6
+
7
+ The system provides REST API endpoints for video generation requests and job management, with Redis handling both the job queue and caching layer. Authentication is managed entirely through Clerk, eliminating the need for custom user management.
8
+
9
+ ## Architecture
10
+
11
+ ### High-Level Architecture
12
+
13
+ ```mermaid
14
+ graph TB
15
+ subgraph "Client Layer"
16
+ WEB[Web Frontend]
17
+ MOBILE[Mobile App]
18
+ API_CLIENT[API Clients]
19
+ end
20
+
21
+ subgraph "API Gateway Layer"
22
+ NGINX[Nginx Reverse Proxy]
23
+ RATE_LIMIT[Rate Limiting]
24
+ AUTH_MW[Authentication Middleware]
25
+ end
26
+
27
+ subgraph "FastAPI Application"
28
+ API_ROUTER[API Routers]
29
+ WEBSOCKET[WebSocket Handlers]
30
+ MIDDLEWARE[Custom Middleware]
31
+ DEPS[Dependencies]
32
+ end
33
+
34
+ subgraph "Business Logic Layer"
35
+ VIDEO_SERVICE[Video Generation Service]
36
+ JOB_SERVICE[Job Management Service]
37
+ FILE_SERVICE[File Management Service]
38
+ NOTIFICATION_SERVICE[Notification Service]
39
+ end
40
+
41
+ subgraph "Data Layer"
42
+ REDIS[(Redis)]
43
+ FILE_STORAGE[Local File Storage]
44
+ end
45
+
46
+ subgraph "External Services"
47
+ CLERK[Clerk Authentication]
48
+ end
49
+
50
+ subgraph "External Systems"
51
+ VIDEO_PIPELINE[Multi-Agent Video Pipeline]
52
+ MONITORING[Monitoring & Logging]
53
+ end
54
+
55
+ WEB --> NGINX
56
+ MOBILE --> NGINX
57
+ API_CLIENT --> NGINX
58
+
59
+ NGINX --> RATE_LIMIT
60
+ RATE_LIMIT --> AUTH_MW
61
+ AUTH_MW --> API_ROUTER
62
+ AUTH_MW --> WEBSOCKET
63
+
64
+ API_ROUTER --> MIDDLEWARE
65
+ WEBSOCKET --> MIDDLEWARE
66
+ MIDDLEWARE --> DEPS
67
+
68
+ DEPS --> VIDEO_SERVICE
69
+ DEPS --> JOB_SERVICE
70
+ DEPS --> FILE_SERVICE
71
+ DEPS --> NOTIFICATION_SERVICE
72
+
73
+ VIDEO_SERVICE --> REDIS
74
+ JOB_SERVICE --> REDIS
75
+ FILE_SERVICE --> FILE_STORAGE
76
+ NOTIFICATION_SERVICE --> REDIS
77
+
78
+ AUTH_MW --> CLERK
79
+
80
+ QUEUE --> VIDEO_PIPELINE
81
+ VIDEO_PIPELINE --> MONITORING
82
+ ```
83
+
84
+ ### Project Structure
85
+
86
+ Simplified structure focusing on Pydantic models and Redis:
87
+
88
+ ```
89
+ src/
90
+ ├── app/
91
+ │ ├── main.py # FastAPI application entry point
92
+ │ ├── api/ # API layer
93
+ │ │ ├── dependencies.py # Shared dependencies (Clerk auth, Redis)
94
+ │ │ └── v1/ # API version 1
95
+ │ │ ├── __init__.py
96
+ │ │ ├── videos.py # Video generation endpoints
97
+ │ │ ├── jobs.py # Job management endpoints
98
+ │ │ └── system.py # System health endpoints
99
+ │ ├── core/ # Core utilities and configurations
100
+ │ │ ├── config.py # Application settings
101
+ │ │ ├── redis.py # Redis connection and utilities
102
+ │ │ ├── auth.py # Clerk authentication utilities
103
+ │ │ ├── logger.py # Logging configuration
104
+ │ │ └── exceptions.py # Custom exceptions
105
+ │ ├── services/ # Business logic layer
106
+ │ │ ├── video_service.py # Video generation business logic
107
+ │ │ ├── job_service.py # Job management logic
108
+ │ │ └── queue_service.py # Redis queue management
109
+ │ ├── models/ # Pydantic models only
110
+ │ │ ├── __init__.py
111
+ │ │ ├── job.py # Job data models
112
+ │ │ ├── video.py # Video metadata models
113
+ │ │ ├── user.py # User data models (from Clerk)
114
+ │ │ └── system.py # System status models
115
+ │ ├── middleware/ # Custom middleware
116
+ │ │ ├── __init__.py
117
+ │ │ ├── cors.py # CORS middleware
118
+ │ │ ├── clerk_auth.py # Clerk authentication middleware
119
+ │ │ └── error_handling.py # Global error handling
120
+ │ └── utils/ # Utility functions
121
+ │ ├── __init__.py
122
+ │ ├── file_utils.py # File handling utilities
123
+ │ └── helpers.py # General helper functions
124
+ ├── tests/ # Test suite
125
+ │ ├── conftest.py # Test configuration
126
+ │ ├── test_api/ # API endpoint tests
127
+ │ └── test_services/ # Service layer tests
128
+ └── scripts/ # Utility scripts
129
+ └── setup_redis.py # Redis setup script
130
+ ```
131
+
132
+ ## Components and Interfaces
133
+
134
+ ### API Layer Components
135
+
136
+ #### 1. Video Generation Router (`api/v1/videos.py`)
137
+
138
+ **Endpoints:**
139
+ - `POST /api/v1/videos/generate` - Submit video generation request
140
+ - `POST /api/v1/videos/batch` - Submit batch video generation requests
141
+ - `GET /api/v1/videos/jobs/{job_id}/status` - Get job status
142
+ - `GET /api/v1/videos/jobs/{job_id}/download` - Download completed video
143
+ - `GET /api/v1/videos/jobs/{job_id}/metadata` - Get job metadata
144
+
145
+ **Key Features:**
146
+ - Request validation using Pydantic schemas
147
+ - Async request handling
148
+ - Integration with job service
149
+ - File streaming for downloads
150
+ - Comprehensive error handling
151
+
152
+ #### 2. Job Management Router (`api/v1/jobs.py`)
153
+
154
+ **Endpoints:**
155
+ - `GET /api/v1/jobs` - List jobs with pagination and filtering
156
+ - `POST /api/v1/jobs/{job_id}/cancel` - Cancel job
157
+ - `DELETE /api/v1/jobs/{job_id}` - Delete job (soft delete)
158
+ - `GET /api/v1/jobs/{job_id}/logs` - Get job processing logs
159
+
160
+ **Key Features:**
161
+ - Pagination support using `fastcrud` patterns
162
+ - Advanced filtering and sorting
163
+ - Job lifecycle management
164
+ - Audit trail maintenance
165
+
166
+ #### 3. User Management Router (`api/v1/users.py`)
167
+
168
+ **Endpoints:**
169
+ - `POST /api/v1/users/register` - User registration
170
+ - `POST /api/v1/users/login` - User authentication
171
+ - `GET /api/v1/users/profile` - Get user profile
172
+ - `PUT /api/v1/users/profile` - Update user profile
173
+ - `POST /api/v1/users/verify-email` - Email verification
174
+ - `POST /api/v1/users/reset-password` - Password reset
175
+
176
+ **Key Features:**
177
+ - JWT-based authentication
178
+ - Email verification workflow
179
+ - Password reset functionality
180
+ - Profile management
181
+
182
+ #### 4. Subscription Management Router (`api/v1/subscriptions.py`)
183
+
184
+ **Endpoints:**
185
+ - `GET /api/v1/subscriptions/plans` - List available subscription plans
186
+ - `POST /api/v1/subscriptions/subscribe` - Create new subscription
187
+ - `GET /api/v1/subscriptions/current` - Get current user subscription
188
+ - `PUT /api/v1/subscriptions/upgrade` - Upgrade subscription plan
189
+ - `POST /api/v1/subscriptions/cancel` - Cancel subscription
190
+ - `GET /api/v1/subscriptions/usage` - Get usage statistics
191
+
192
+ **Key Features:**
193
+ - Subscription plan management
194
+ - Credit tracking and usage monitoring
195
+ - Billing integration
196
+ - Usage analytics
197
+
198
+ #### 5. WebSocket Handler (`api/v1/websockets.py`)
199
+
200
+ **Endpoints:**
201
+ - `WS /ws/jobs/{job_id}` - Real-time job status updates
202
+ - `WS /ws/system/health` - System health monitoring
203
+
204
+ **Key Features:**
205
+ - Connection management
206
+ - Real-time status broadcasting
207
+ - Graceful disconnection handling
208
+ - Authentication for WebSocket connections
209
+
210
+ ### Service Layer Components
211
+
212
+ #### 1. Video Generation Service (`services/video_service.py`)
213
+
214
+ **Responsibilities:**
215
+ - Interface with multi-agent video generation pipeline
216
+ - Job queue management
217
+ - Configuration validation
218
+ - Progress tracking
219
+
220
+ **Key Methods:**
221
+ ```python
222
+ async def create_video_job(request: VideoGenerationRequest) -> JobResponse
223
+ async def create_batch_jobs(requests: List[VideoGenerationRequest]) -> BatchJobResponse
224
+ async def get_job_status(job_id: str) -> JobStatus
225
+ async def cancel_job(job_id: str) -> bool
226
+ ```
227
+
228
+ #### 2. Job Management Service (`services/job_service.py`)
229
+
230
+ **Responsibilities:**
231
+ - Job lifecycle management
232
+ - Status updates and notifications
233
+ - Resource allocation
234
+ - Performance monitoring
235
+
236
+ **Key Methods:**
237
+ ```python
238
+ async def update_job_status(job_id: str, status: JobStatus, metadata: dict)
239
+ async def get_jobs_paginated(filters: JobFilters, pagination: PaginationParams) -> PaginatedResponse
240
+ async def cleanup_completed_jobs(retention_days: int)
241
+ ```
242
+
243
+ #### 3. File Management Service (`services/file_service.py`)
244
+
245
+ **Responsibilities:**
246
+ - AWS S3 file upload/download handling
247
+ - Storage management with versioning
248
+ - Security scanning and validation
249
+ - Metadata extraction and storage
250
+
251
+ **Key Methods:**
252
+ ```python
253
+ async def upload_to_s3(file: UploadFile, user_id: str, file_type: str) -> S3FileMetadata
254
+ async def download_from_s3(s3_key: str, bucket: str) -> StreamingResponse
255
+ async def generate_presigned_url(s3_key: str, expiration: int = 3600) -> str
256
+ async def validate_file(file: UploadFile) -> ValidationResult
257
+ async def cleanup_expired_files()
258
+ async def create_video_thumbnail(video_s3_key: str) -> str
259
+ ```
260
+
261
+ #### 4. Subscription Service (`services/subscription_service.py`)
262
+
263
+ **Responsibilities:**
264
+ - Subscription lifecycle management
265
+ - Credit tracking and usage monitoring
266
+ - Billing integration
267
+ - Plan upgrade/downgrade logic
268
+
269
+ **Key Methods:**
270
+ ```python
271
+ async def create_subscription(user_id: str, plan_id: int, payment_method: str) -> Subscription
272
+ async def check_user_credits(user_id: str) -> int
273
+ async def consume_credits(user_id: str, credits: int) -> bool
274
+ async def upgrade_subscription(user_id: str, new_plan_id: int) -> Subscription
275
+ async def cancel_subscription(user_id: str) -> bool
276
+ async def process_billing_cycle()
277
+ ```
278
+
279
+ #### 5. AWS Integration Service (`services/aws_service.py`)
280
+
281
+ **Responsibilities:**
282
+ - AWS service integration and management
283
+ - DynamoDB operations for high-frequency data
284
+ - SQS queue management
285
+ - CloudWatch metrics and logging
286
+
287
+ **Key Methods:**
288
+ ```python
289
+ async def put_job_status_dynamodb(job_id: str, status: dict)
290
+ async def get_job_status_history(job_id: str) -> List[dict]
291
+ async def send_sqs_message(queue_url: str, message: dict)
292
+ async def put_cloudwatch_metric(metric_name: str, value: float, dimensions: dict)
293
+ async def log_user_activity(user_id: str, activity: dict)
294
+ ```
295
+
296
+ ### Data Layer Components
297
+
298
+ #### Pydantic Data Models
299
+
300
+ **Job Model (`models/job.py`):**
301
+ ```python
302
+ from pydantic import BaseModel, Field
303
+ from datetime import datetime
304
+ from enum import Enum
305
+ from typing import Optional, Dict, Any
306
+ import uuid
307
+
308
+ class JobStatus(str, Enum):
309
+ QUEUED = "queued"
310
+ PROCESSING = "processing"
311
+ COMPLETED = "completed"
312
+ FAILED = "failed"
313
+ CANCELLED = "cancelled"
314
+
315
+ class JobCreate(BaseModel):
316
+ topic: str = Field(..., min_length=1, max_length=500)
317
+ context: str = Field(..., min_length=1, max_length=2000)
318
+ model: Optional[str] = None
319
+ quality: str = Field(default="medium")
320
+ use_rag: bool = Field(default=False)
321
+ configuration: Optional[Dict[str, Any]] = None
322
+
323
+ class Job(BaseModel):
324
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
325
+ user_id: str # Clerk user ID
326
+ status: JobStatus = JobStatus.QUEUED
327
+ job_type: str = "video_generation"
328
+ configuration: Dict[str, Any]
329
+ progress: float = Field(default=0.0, ge=0.0, le=100.0)
330
+ error_message: Optional[str] = None
331
+ created_at: datetime = Field(default_factory=datetime.utcnow)
332
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
333
+ completed_at: Optional[datetime] = None
334
+
335
+ class JobResponse(BaseModel):
336
+ job_id: str
337
+ status: JobStatus
338
+ progress: float
339
+ created_at: datetime
340
+ estimated_completion: Optional[datetime] = None
341
+ ```
342
+
343
+ **Video Model (`models/video.py`):**
344
+ ```python
345
+ from pydantic import BaseModel, Field
346
+ from datetime import datetime
347
+ from typing import Optional
348
+ import uuid
349
+
350
+ class VideoMetadata(BaseModel):
351
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
352
+ job_id: str
353
+ filename: str
354
+ file_path: str
355
+ file_size: int = Field(gt=0)
356
+ duration: Optional[float] = Field(None, gt=0)
357
+ resolution: Optional[str] = None
358
+ format: str
359
+ created_at: datetime = Field(default_factory=datetime.utcnow)
360
+
361
+ class VideoResponse(BaseModel):
362
+ video_id: str
363
+ job_id: str
364
+ filename: str
365
+ file_size: int
366
+ duration: Optional[float]
367
+ download_url: str
368
+ created_at: datetime
369
+ ```
370
+
371
+ #### Pydantic Schemas
372
+
373
+ **Request Schemas (`schemas/job.py`):**
374
+ ```python
375
+ class VideoGenerationRequest(BaseModel):
376
+ topic: str = Field(..., min_length=1, max_length=500)
377
+ context: str = Field(..., min_length=1, max_length=2000)
378
+ model: Optional[str] = Field(None, description="AI model to use")
379
+ quality: VideoQuality = Field(VideoQuality.MEDIUM)
380
+ use_rag: bool = Field(False)
381
+ custom_config: Optional[Dict[str, Any]] = Field(None)
382
+
383
+ model_config = ConfigDict(
384
+ json_schema_extra={
385
+ "example": {
386
+ "topic": "Pythagorean Theorem",
387
+ "context": "Explain the mathematical proof with visual demonstration",
388
+ "model": "gemini/gemini-2.5-flash-preview-04-17",
389
+ "quality": "medium",
390
+ "use_rag": True
391
+ }
392
+ }
393
+ )
394
+ ```
395
+
396
+ **Response Schemas:**
397
+ ```python
398
+ class JobResponse(BaseModel):
399
+ job_id: str
400
+ status: JobStatus
401
+ created_at: datetime
402
+ estimated_completion: Optional[datetime] = None
403
+
404
+ class JobStatusResponse(BaseModel):
405
+ job_id: str
406
+ status: JobStatus
407
+ progress: float
408
+ current_stage: Optional[str] = None
409
+ error_message: Optional[str] = None
410
+ created_at: datetime
411
+ updated_at: datetime
412
+ completed_at: Optional[datetime] = None
413
+ ```
414
+
415
+ ## Data Models
416
+
417
+ ### Core Entities
418
+
419
+ #### Job Entity
420
+ - **Primary Key:** UUID string
421
+ - **Status:** Enum (queued, processing, completed, failed, cancelled)
422
+ - **Configuration:** JSON field for flexible job parameters
423
+ - **Progress Tracking:** Float percentage and current stage
424
+ - **Audit Fields:** Created, updated, completed timestamps
425
+ - **Soft Delete:** Support for data retention policies
426
+
427
+ #### User Entity
428
+ - **Authentication:** JWT-based authentication
429
+ - **Authorization:** Role-based access control
430
+ - **Rate Limiting:** Per-user request limits
431
+ - **Audit Trail:** Request logging and monitoring
432
+
433
+ #### Video Entity
434
+ - **Metadata:** File size, duration, resolution, format
435
+ - **Storage:** File path and storage location
436
+ - **Relationships:** Linked to originating job
437
+ - **Lifecycle:** Automatic cleanup policies
438
+
439
+ ### Redis Data Structure Design
440
+
441
+ #### Redis Keys and Data Types
442
+
443
+ **Job Storage (Hash):**
444
+ ```
445
+ jobs:{job_id} -> Hash
446
+ {
447
+ "id": "uuid",
448
+ "user_id": "clerk_user_id",
449
+ "status": "queued|processing|completed|failed|cancelled",
450
+ "job_type": "video_generation",
451
+ "configuration": "json_string",
452
+ "progress": "0.0-100.0",
453
+ "error_message": "optional_error",
454
+ "created_at": "iso_datetime",
455
+ "updated_at": "iso_datetime",
456
+ "completed_at": "optional_iso_datetime"
457
+ }
458
+ ```
459
+
460
+ **Job Queue (List):**
461
+ ```
462
+ job_queue -> List
463
+ ["job_id_1", "job_id_2", "job_id_3", ...]
464
+ ```
465
+
466
+ **User Jobs Index (Set):**
467
+ ```
468
+ user_jobs:{user_id} -> Set
469
+ {"job_id_1", "job_id_2", "job_id_3", ...}
470
+ ```
471
+
472
+ **Video Metadata (Hash):**
473
+ ```
474
+ videos:{video_id} -> Hash
475
+ {
476
+ "id": "uuid",
477
+ "job_id": "job_uuid",
478
+ "filename": "video.mp4",
479
+ "file_path": "/path/to/video.mp4",
480
+ "file_size": "bytes",
481
+ "duration": "seconds",
482
+ "resolution": "1920x1080",
483
+ "format": "mp4",
484
+ "created_at": "iso_datetime"
485
+ }
486
+ ```
487
+
488
+ **Job Status Cache (String with TTL):**
489
+ ```
490
+ job_status:{job_id} -> String (TTL: 300 seconds)
491
+ "processing" | "completed" | "failed"
492
+ ```
493
+
494
+ **System Health (Hash):**
495
+ ```
496
+ system:health -> Hash
497
+ {
498
+ "redis": "healthy",
499
+ "queue_length": "5",
500
+ "active_jobs": "3",
501
+ "last_check": "iso_datetime"
502
+ }
503
+ ```
504
+
505
+ ## Error Handling
506
+
507
+ ### Exception Hierarchy
508
+
509
+ ```python
510
+ class APIException(Exception):
511
+ """Base API exception"""
512
+ def __init__(self, message: str, status_code: int = 500, error_code: str = None):
513
+ self.message = message
514
+ self.status_code = status_code
515
+ self.error_code = error_code
516
+
517
+ class ValidationException(APIException):
518
+ """Request validation errors"""
519
+ def __init__(self, message: str, field_errors: List[dict] = None):
520
+ super().__init__(message, 422, "VALIDATION_ERROR")
521
+ self.field_errors = field_errors or []
522
+
523
+ class NotFoundException(APIException):
524
+ """Resource not found"""
525
+ def __init__(self, resource: str):
526
+ super().__init__(f"{resource} not found", 404, "NOT_FOUND")
527
+
528
+ class ConflictException(APIException):
529
+ """Resource conflict"""
530
+ def __init__(self, message: str):
531
+ super().__init__(message, 409, "CONFLICT")
532
+
533
+ class RateLimitException(APIException):
534
+ """Rate limit exceeded"""
535
+ def __init__(self, retry_after: int = None):
536
+ super().__init__("Rate limit exceeded", 429, "RATE_LIMIT_EXCEEDED")
537
+ self.retry_after = retry_after
538
+ ```
539
+
540
+ ### Global Error Handler
541
+
542
+ ```python
543
+ @app.exception_handler(APIException)
544
+ async def api_exception_handler(request: Request, exc: APIException):
545
+ return JSONResponse(
546
+ status_code=exc.status_code,
547
+ content={
548
+ "error": {
549
+ "message": exc.message,
550
+ "error_code": exc.error_code,
551
+ "timestamp": datetime.utcnow().isoformat(),
552
+ "path": str(request.url.path)
553
+ }
554
+ }
555
+ )
556
+
557
+ @app.exception_handler(ValidationError)
558
+ async def validation_exception_handler(request: Request, exc: ValidationError):
559
+ return JSONResponse(
560
+ status_code=422,
561
+ content={
562
+ "error": {
563
+ "message": "Validation failed",
564
+ "error_code": "VALIDATION_ERROR",
565
+ "details": exc.errors(),
566
+ "timestamp": datetime.utcnow().isoformat(),
567
+ "path": str(request.url.path)
568
+ }
569
+ }
570
+ )
571
+ ```
572
+
573
+ ### Error Response Format
574
+
575
+ All error responses follow a consistent structure:
576
+
577
+ ```json
578
+ {
579
+ "error": {
580
+ "message": "Human-readable error message",
581
+ "error_code": "MACHINE_READABLE_CODE",
582
+ "details": {},
583
+ "timestamp": "2024-01-15T10:30:00Z",
584
+ "path": "/api/v1/videos/generate"
585
+ }
586
+ }
587
+ ```
588
+
589
+ ## Testing Strategy
590
+
591
+ ### Testing Pyramid
592
+
593
+ #### Unit Tests (70%)
594
+ - **Service Layer:** Business logic validation
595
+ - **CRUD Operations:** Database interaction testing
596
+ - **Utility Functions:** Helper function validation
597
+ - **Schema Validation:** Pydantic model testing
598
+
599
+ #### Integration Tests (20%)
600
+ - **API Endpoints:** Full request/response cycle
601
+ - **Database Integration:** Real database operations
602
+ - **External Service Integration:** Mock external dependencies
603
+ - **WebSocket Connections:** Real-time communication testing
604
+
605
+ #### End-to-End Tests (10%)
606
+ - **Complete Workflows:** Full video generation pipeline
607
+ - **User Journeys:** Multi-step user interactions
608
+ - **Performance Testing:** Load and stress testing
609
+ - **Security Testing:** Authentication and authorization
610
+
611
+ ### Test Configuration
612
+
613
+ ```python
614
+ # conftest.py
615
+ @pytest.fixture
616
+ async def test_db():
617
+ """Create test database session"""
618
+ engine = create_async_engine(TEST_DATABASE_URL)
619
+ async with engine.begin() as conn:
620
+ await conn.run_sync(Base.metadata.create_all)
621
+
622
+ async_session = async_sessionmaker(engine, expire_on_commit=False)
623
+ async with async_session() as session:
624
+ yield session
625
+
626
+ async with engine.begin() as conn:
627
+ await conn.run_sync(Base.metadata.drop_all)
628
+
629
+ @pytest.fixture
630
+ def test_client():
631
+ """Create test client"""
632
+ return TestClient(app)
633
+
634
+ @pytest.fixture
635
+ async def authenticated_user(test_db):
636
+ """Create authenticated test user"""
637
+ user = await crud_users.create(
638
+ db=test_db,
639
+ object=UserCreate(
640
+ username="testuser",
641
+ email="[email protected]",
642
+ password="testpass123"
643
+ )
644
+ )
645
+ return user
646
+ ```
647
+
648
+ ### Test Examples
649
+
650
+ ```python
651
+ # Test API endpoint
652
+ async def test_create_video_job(test_client, authenticated_user):
653
+ request_data = {
654
+ "topic": "Test Topic",
655
+ "context": "Test context for video generation",
656
+ "quality": "medium"
657
+ }
658
+
659
+ response = test_client.post(
660
+ "/api/v1/videos/generate",
661
+ json=request_data,
662
+ headers={"Authorization": f"Bearer {authenticated_user.token}"}
663
+ )
664
+
665
+ assert response.status_code == 201
666
+ data = response.json()
667
+ assert "job_id" in data
668
+ assert data["status"] == "queued"
669
+
670
+ # Test service layer
671
+ async def test_video_service_create_job(test_db):
672
+ service = VideoService(test_db)
673
+ request = VideoGenerationRequest(
674
+ topic="Test Topic",
675
+ context="Test context"
676
+ )
677
+
678
+ job = await service.create_video_job(request, user_id=1)
679
+
680
+ assert job.status == JobStatus.QUEUED
681
+ assert job.configuration["topic"] == "Test Topic"
682
+ ```
683
+
684
+ ## Security Considerations
685
+
686
+ ### Authentication & Authorization
687
+
688
+ #### JWT Token-Based Authentication
689
+ - **Access Tokens:** Short-lived (15 minutes) for API access
690
+ - **Refresh Tokens:** Long-lived (7 days) for token renewal
691
+ - **Token Blacklisting:** Support for immediate token revocation
692
+ - **Secure Storage:** HttpOnly cookies for web clients
693
+
694
+ #### Role-Based Access Control (RBAC)
695
+ - **User Roles:** admin, user, readonly
696
+ - **Permission System:** Granular permissions for different operations
697
+ - **Resource Ownership:** Users can only access their own resources
698
+ - **Admin Override:** Administrators can access all resources
699
+
700
+ ### Input Validation & Sanitization
701
+
702
+ #### Request Validation
703
+ - **Pydantic Models:** Automatic type validation and conversion
704
+ - **Field Constraints:** Length limits, format validation, range checks
705
+ - **Custom Validators:** Business rule validation
706
+ - **Sanitization:** XSS prevention and input cleaning
707
+
708
+ #### File Upload Security
709
+ - **File Type Validation:** Whitelist of allowed file types
710
+ - **Size Limits:** Maximum file size enforcement
711
+ - **Virus Scanning:** Integration with antivirus services
712
+ - **Secure Storage:** Isolated file storage with access controls
713
+
714
+ ### Rate Limiting & DDoS Protection
715
+
716
+ #### Multi-Level Rate Limiting
717
+ - **Global Limits:** Overall API request limits
718
+ - **Per-User Limits:** Individual user quotas
719
+ - **Per-Endpoint Limits:** Specific endpoint restrictions
720
+ - **Sliding Window:** Advanced rate limiting algorithms
721
+
722
+ #### Implementation Strategy
723
+ ```python
724
+ from slowapi import Limiter, _rate_limit_exceeded_handler
725
+ from slowapi.util import get_remote_address
726
+
727
+ limiter = Limiter(key_func=get_remote_address)
728
+
729
+ @app.route("/api/v1/videos/generate")
730
+ @limiter.limit("10/minute")
731
+ async def generate_video(request: Request):
732
+ # Endpoint implementation
733
+ pass
734
+ ```
735
+
736
+ ### Data Protection
737
+
738
+ #### Encryption
739
+ - **Data at Rest:** Database encryption for sensitive fields
740
+ - **Data in Transit:** TLS 1.3 for all communications
741
+ - **File Encryption:** Encrypted file storage
742
+ - **Key Management:** Secure key rotation policies
743
+
744
+ #### Privacy Compliance
745
+ - **Data Minimization:** Collect only necessary data
746
+ - **Retention Policies:** Automatic data cleanup
747
+ - **User Rights:** Data export and deletion capabilities
748
+ - **Audit Logging:** Comprehensive access logging
749
+
750
+ ## Performance Optimization
751
+
752
+ ### Caching Strategy
753
+
754
+ #### Multi-Level Caching
755
+ - **Application Cache:** In-memory caching with Redis
756
+ - **Database Query Cache:** SQLAlchemy query result caching
757
+ - **HTTP Response Cache:** CDN and browser caching
758
+ - **File System Cache:** Temporary file caching
759
+
760
+ #### Cache Implementation
761
+ ```python
762
+ from fastapi_cache.decorator import cache
763
+
764
+ @router.get("/api/v1/jobs/{job_id}/status")
765
+ @cache(expire=30) # Cache for 30 seconds
766
+ async def get_job_status(job_id: str):
767
+ return await job_service.get_status(job_id)
768
+ ```
769
+
770
+ ### Database Optimization
771
+
772
+ #### Query Optimization
773
+ - **Eager Loading:** Reduce N+1 query problems
774
+ - **Indexing Strategy:** Optimized database indexes
775
+ - **Connection Pooling:** Efficient database connections
776
+ - **Query Monitoring:** Performance tracking and optimization
777
+
778
+ #### Pagination & Filtering
779
+ ```python
780
+ async def get_jobs_paginated(
781
+ page: int = 1,
782
+ items_per_page: int = 10,
783
+ filters: JobFilters = None
784
+ ) -> PaginatedResponse:
785
+ offset = (page - 1) * items_per_page
786
+
787
+ query = select(Job).where(Job.is_deleted == False)
788
+ if filters:
789
+ query = apply_filters(query, filters)
790
+
791
+ total = await db.scalar(select(func.count()).select_from(query.subquery()))
792
+ jobs = await db.execute(query.offset(offset).limit(items_per_page))
793
+
794
+ return PaginatedResponse(
795
+ data=jobs.scalars().all(),
796
+ total_count=total,
797
+ page=page,
798
+ items_per_page=items_per_page
799
+ )
800
+ ```
801
+
802
+ ### Asynchronous Processing
803
+
804
+ #### Background Tasks
805
+ - **Celery Integration:** Distributed task processing
806
+ - **Job Queues:** Redis-based task queuing
807
+ - **Progress Tracking:** Real-time progress updates
808
+ - **Error Recovery:** Automatic retry mechanisms
809
+
810
+ #### WebSocket Optimization
811
+ - **Connection Pooling:** Efficient WebSocket management
812
+ - **Message Broadcasting:** Efficient multi-client updates
813
+ - **Heartbeat Monitoring:** Connection health checks
814
+ - **Graceful Degradation:** Fallback to polling if needed
815
+
816
+ ## Monitoring & Observability
817
+
818
+ ### Logging Strategy
819
+
820
+ #### Structured Logging
821
+ ```python
822
+ import structlog
823
+
824
+ logger = structlog.get_logger()
825
+
826
+ async def create_video_job(request: VideoGenerationRequest, user_id: int):
827
+ logger.info(
828
+ "Creating video job",
829
+ user_id=user_id,
830
+ topic=request.topic,
831
+ quality=request.quality
832
+ )
833
+
834
+ try:
835
+ job = await video_service.create_job(request, user_id)
836
+ logger.info("Video job created successfully", job_id=job.id)
837
+ return job
838
+ except Exception as e:
839
+ logger.error(
840
+ "Failed to create video job",
841
+ user_id=user_id,
842
+ error=str(e),
843
+ exc_info=True
844
+ )
845
+ raise
846
+ ```
847
+
848
+ ### Metrics Collection
849
+
850
+ #### Application Metrics
851
+ - **Request Metrics:** Response times, status codes, throughput
852
+ - **Business Metrics:** Job completion rates, user activity
853
+ - **System Metrics:** CPU, memory, disk usage
854
+ - **Custom Metrics:** Domain-specific measurements
855
+
856
+ #### Health Checks
857
+ ```python
858
+ @router.get("/health")
859
+ async def health_check():
860
+ checks = {
861
+ "database": await check_database_health(),
862
+ "redis": await check_redis_health(),
863
+ "queue": await check_queue_health(),
864
+ "storage": await check_storage_health()
865
+ }
866
+
867
+ overall_health = all(checks.values())
868
+ status_code = 200 if overall_health else 503
869
+
870
+ return JSONResponse(
871
+ status_code=status_code,
872
+ content={
873
+ "status": "healthy" if overall_health else "unhealthy",
874
+ "checks": checks,
875
+ "timestamp": datetime.utcnow().isoformat()
876
+ }
877
+ )
878
+ ```
879
+
880
+ ### Distributed Tracing
881
+
882
+ #### OpenTelemetry Integration
883
+ - **Request Tracing:** End-to-end request tracking
884
+ - **Service Dependencies:** Inter-service communication mapping
885
+ - **Performance Analysis:** Bottleneck identification
886
+ - **Error Correlation:** Error tracking across services
887
+
888
+ ## Deployment Architecture
889
+
890
+ ### Container Strategy
891
+
892
+ #### Docker Configuration
893
+ ```dockerfile
894
+ FROM python:3.11-slim
895
+
896
+ WORKDIR /app
897
+
898
+ # Install system dependencies
899
+ RUN apt-get update && apt-get install -y \
900
+ gcc \
901
+ && rm -rf /var/lib/apt/lists/*
902
+
903
+ # Install Python dependencies
904
+ COPY requirements.txt .
905
+ RUN pip install --no-cache-dir -r requirements.txt
906
+
907
+ # Copy application code
908
+ COPY src/ ./src/
909
+ COPY migrations/ ./migrations/
910
+
911
+ # Set environment variables
912
+ ENV PYTHONPATH=/app/src
913
+ ENV PYTHONUNBUFFERED=1
914
+
915
+ # Expose port
916
+ EXPOSE 8000
917
+
918
+ # Run application
919
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
920
+ ```
921
+
922
+ #### AWS Deployment Configuration
923
+
924
+ **AWS Services Integration:**
925
+ - **RDS PostgreSQL:** Primary relational database for user data, jobs, subscriptions
926
+ - **DynamoDB:** High-frequency data like job status updates, user activity tracking
927
+ - **S3:** Video files, thumbnails, job outputs, user uploads
928
+ - **ElastiCache Redis:** Session storage, caching, real-time data
929
+ - **SQS:** Job queue management and inter-service communication
930
+ - **CloudFront:** CDN for video delivery and static assets
931
+ - **Lambda:** Serverless functions for background processing
932
+ - **ECS/Fargate:** Container orchestration for API services
933
+
934
+ **Docker Compose for Local Development:**
935
+ ```yaml
936
+ version: '3.8'
937
+
938
+ services:
939
+ api:
940
+ build: .
941
+ ports:
942
+ - "8000:8000"
943
+ environment:
944
+ - AWS_REGION=us-east-1
945
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
946
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
947
+ - DATABASE_URL=${RDS_DATABASE_URL}
948
+ - REDIS_URL=${ELASTICACHE_URL}
949
+ - S3_BUCKET=${S3_BUCKET_NAME}
950
+ - SQS_QUEUE_URL=${SQS_QUEUE_URL}
951
+ - DYNAMODB_TABLE_PREFIX=${DYNAMODB_PREFIX}
952
+ volumes:
953
+ - ./logs:/app/logs
954
+ - ~/.aws:/root/.aws:ro
955
+
956
+ localstack:
957
+ image: localstack/localstack:latest
958
+ ports:
959
+ - "4566:4566"
960
+ environment:
961
+ - SERVICES=s3,sqs,dynamodb,elasticache
962
+ - DEBUG=1
963
+ - DATA_DIR=/tmp/localstack/data
964
+ volumes:
965
+ - localstack_data:/tmp/localstack
966
+
967
+ postgres:
968
+ image: postgres:15
969
+ environment:
970
+ - POSTGRES_DB=videoapi_local
971
+ - POSTGRES_USER=user
972
+ - POSTGRES_PASSWORD=pass
973
+ volumes:
974
+ - postgres_data:/var/lib/postgresql/data
975
+ ports:
976
+ - "5432:5432"
977
+
978
+ volumes:
979
+ postgres_data:
980
+ localstack_data:
981
+ ```
982
+
983
+ **AWS CDK/Terraform Infrastructure:**
984
+ ```typescript
985
+ // AWS CDK example structure
986
+ export class VideoAPIStack extends Stack {
987
+ constructor(scope: Construct, id: string, props?: StackProps) {
988
+ super(scope, id, props);
989
+
990
+ // RDS PostgreSQL
991
+ const database = new rds.DatabaseInstance(this, 'VideoAPIDB', {
992
+ engine: rds.DatabaseInstanceEngine.postgres({
993
+ version: rds.PostgresEngineVersion.VER_15
994
+ }),
995
+ instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
996
+ multiAz: true,
997
+ backupRetention: Duration.days(7)
998
+ });
999
+
1000
+ // S3 Buckets
1001
+ const videoBucket = new s3.Bucket(this, 'VideoBucket', {
1002
+ versioned: true,
1003
+ lifecycleRules: [{
1004
+ id: 'DeleteOldVersions',
1005
+ expiration: Duration.days(90)
1006
+ }]
1007
+ });
1008
+
1009
+ // DynamoDB Tables
1010
+ const jobStatusTable = new dynamodb.Table(this, 'JobStatusTable', {
1011
+ partitionKey: { name: 'job_id', type: dynamodb.AttributeType.STRING },
1012
+ sortKey: { name: 'timestamp', type: dynamodb.AttributeType.NUMBER },
1013
+ timeToLiveAttribute: 'ttl'
1014
+ });
1015
+
1016
+ // ECS Fargate Service
1017
+ const cluster = new ecs.Cluster(this, 'VideoAPICluster');
1018
+ const taskDefinition = new ecs.FargateTaskDefinition(this, 'VideoAPITask');
1019
+
1020
+ const container = taskDefinition.addContainer('api', {
1021
+ image: ecs.ContainerImage.fromRegistry('your-api-image'),
1022
+ environment: {
1023
+ DATABASE_URL: database.instanceEndpoint.socketAddress,
1024
+ S3_BUCKET: videoBucket.bucketName
1025
+ }
1026
+ });
1027
+
1028
+ new ecs.FargateService(this, 'VideoAPIService', {
1029
+ cluster,
1030
+ taskDefinition,
1031
+ desiredCount: 2
1032
+ });
1033
+ }
1034
+ }
1035
+ ```
1036
+
1037
+ ### Production Considerations
1038
+
1039
+ #### Scalability
1040
+ - **Horizontal Scaling:** Multiple API instances behind load balancer
1041
+ - **Database Scaling:** Read replicas and connection pooling
1042
+ - **Cache Scaling:** Redis clustering for high availability
1043
+ - **File Storage:** Distributed storage solutions
1044
+
1045
+ #### Security Hardening
1046
+ - **SSL/TLS:** End-to-end encryption
1047
+ - **Firewall Rules:** Network access restrictions
1048
+ - **Secret Management:** Secure credential storage
1049
+ - **Regular Updates:** Security patch management
1050
+
1051
+ #### Monitoring & Alerting
1052
+ - **Application Monitoring:** APM tools integration
1053
+ - **Infrastructure Monitoring:** System metrics collection
1054
+ - **Log Aggregation:** Centralized logging solution
1055
+ - **Alert Management:** Proactive issue notification
.kiro/specs/fastapi-backend/requirements.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Requirements Document
2
+
3
+ ## Introduction
4
+
5
+ This document outlines the requirements for implementing a simple FastAPI backend for the multi-agent video generation system. The backend will use Pydantic for all data modeling and validation, Clerk for authentication, and Redis for caching and job queuing. The system will provide RESTful API endpoints to manage video generation requests, monitor processing status, and integrate with the existing multi-agent pipeline through Redis queues.
6
+
7
+ ## Requirements
8
+
9
+ ### Requirement 1
10
+
11
+ **User Story:** As a client application, I want to submit video generation requests through a REST API, so that I can programmatically create educational videos from textual descriptions.
12
+
13
+ #### Acceptance Criteria
14
+
15
+ 1. WHEN a POST request is made to `/api/v1/videos/generate` with topic and context data THEN the system SHALL accept the request and return a unique job ID
16
+ 2. WHEN the request includes optional parameters like model selection, quality settings, or RAG configuration THEN the system SHALL validate and apply these parameters
17
+ 3. WHEN the request payload is invalid or missing required fields THEN the system SHALL return a 422 validation error with detailed field-level error messages
18
+ 4. WHEN the system receives a valid request THEN it SHALL queue the job for processing and return a 201 status code with job metadata
19
+
20
+ ### Requirement 2
21
+
22
+ **User Story:** As a client application, I want to monitor the status of video generation jobs, so that I can track progress and know when videos are ready for download.
23
+
24
+ #### Acceptance Criteria
25
+
26
+ 1. WHEN a GET request is made to `/api/v1/videos/jobs/{job_id}/status` THEN the system SHALL return the current job status (queued, processing, completed, failed)
27
+ 2. WHEN a job is in progress THEN the system SHALL return progress information including current stage and percentage completion
28
+ 3. WHEN a job has failed THEN the system SHALL return error details and failure reason
29
+ 4. WHEN a job is completed THEN the system SHALL return metadata about the generated video including file size and duration
30
+ 5. WHEN an invalid job ID is provided THEN the system SHALL return a 404 error
31
+
32
+ ### Requirement 3
33
+
34
+ **User Story:** As a client application, I want to retrieve completed videos and their metadata, so that I can download and use the generated content.
35
+
36
+ #### Acceptance Criteria
37
+
38
+ 1. WHEN a GET request is made to `/api/v1/videos/jobs/{job_id}/download` for a completed job THEN the system SHALL return the video file as a streaming response
39
+ 2. WHEN a GET request is made to `/api/v1/videos/jobs/{job_id}/metadata` THEN the system SHALL return comprehensive job metadata including processing logs and performance metrics
40
+ 3. WHEN a job is not yet completed THEN the download endpoint SHALL return a 409 conflict error
41
+ 4. WHEN the video file is not found THEN the system SHALL return a 404 error
42
+ 5. WHEN downloading large files THEN the system SHALL support HTTP range requests for partial content delivery
43
+
44
+ ### Requirement 4
45
+
46
+ **User Story:** As a system administrator, I want to manage and monitor the video generation pipeline, so that I can ensure optimal system performance and troubleshoot issues.
47
+
48
+ #### Acceptance Criteria
49
+
50
+ 1. WHEN a GET request is made to `/api/v1/system/health` THEN the system SHALL return health status of all components including database, queue, and agent services
51
+ 2. WHEN a GET request is made to `/api/v1/system/metrics` THEN the system SHALL return performance metrics including queue length, processing times, and resource utilization
52
+ 3. WHEN a POST request is made to `/api/v1/system/jobs/{job_id}/cancel` THEN the system SHALL attempt to cancel the job and return cancellation status
53
+ 4. WHEN a GET request is made to `/api/v1/system/jobs` with pagination parameters THEN the system SHALL return a paginated list of all jobs with filtering options
54
+ 5. WHEN system resources are critically low THEN the health endpoint SHALL return a 503 service unavailable status
55
+
56
+ ### Requirement 5
57
+
58
+ **User Story:** As a client application, I want to upload custom content and configurations, so that I can customize the video generation process with specific materials or settings.
59
+
60
+ #### Acceptance Criteria
61
+
62
+ 1. WHEN a POST request is made to `/api/v1/uploads/content` with multipart form data THEN the system SHALL accept and store uploaded files securely
63
+ 2. WHEN uploading files THEN the system SHALL validate file types, sizes, and scan for malicious content
64
+ 3. WHEN a POST request is made to `/api/v1/configurations` with custom settings THEN the system SHALL validate and store the configuration for later use
65
+ 4. WHEN uploaded content exceeds size limits THEN the system SHALL return a 413 payload too large error
66
+ 5. WHEN invalid file types are uploaded THEN the system SHALL return a 415 unsupported media type error
67
+
68
+ ### Requirement 6
69
+
70
+ **User Story:** As a client application, I want to authenticate requests using Clerk authentication, so that the system remains secure and user access is properly managed.
71
+
72
+ #### Acceptance Criteria
73
+
74
+ 1. WHEN a request is made without a valid Clerk session token THEN the system SHALL return a 401 unauthorized error
75
+ 2. WHEN a request is made with an invalid or expired Clerk token THEN the system SHALL return a 401 unauthorized error
76
+ 3. WHEN a valid Clerk token is provided THEN the system SHALL extract user information and process the request
77
+ 4. WHEN user information is needed THEN the system SHALL retrieve it from Clerk's user management system
78
+ 5. WHEN rate limits are exceeded THEN the system SHALL return a 429 too many requests error
79
+
80
+ ### Requirement 7
81
+
82
+ **User Story:** As a developer integrating with the API, I want comprehensive API documentation and client generation capabilities, so that I can efficiently build applications that consume the video generation service.
83
+
84
+ #### Acceptance Criteria
85
+
86
+ 1. WHEN accessing `/docs` THEN the system SHALL provide interactive Swagger UI documentation with all endpoints and schemas
87
+ 2. WHEN accessing `/redoc` THEN the system SHALL provide ReDoc documentation interface
88
+ 3. WHEN accessing `/openapi.json` THEN the system SHALL return the complete OpenAPI specification
89
+ 4. WHEN generating client code THEN the OpenAPI specification SHALL include proper operation IDs and detailed schemas
90
+ 5. WHEN API changes are made THEN the documentation SHALL automatically update to reflect the current API state
91
+
92
+ ### Requirement 8
93
+
94
+ **User Story:** As a system operator, I want the API to handle errors gracefully and provide detailed logging, so that I can maintain system reliability and troubleshoot issues effectively.
95
+
96
+ #### Acceptance Criteria
97
+
98
+ 1. WHEN any error occurs THEN the system SHALL return appropriate HTTP status codes with consistent error response format
99
+ 2. WHEN internal errors occur THEN the system SHALL log detailed error information without exposing sensitive data to clients
100
+ 3. WHEN validation errors occur THEN the system SHALL return specific field-level error messages
101
+ 4. WHEN the system is under high load THEN it SHALL implement proper backpressure and queue management
102
+ 5. WHEN critical errors occur THEN the system SHALL trigger appropriate alerting mechanisms
103
+
104
+ ### Requirement 9
105
+
106
+ **User Story:** As a client application, I want to receive real-time updates about job progress, so that I can provide live feedback to users about video generation status.
107
+
108
+ #### Acceptance Criteria
109
+
110
+ 1. WHEN a WebSocket connection is established to `/ws/jobs/{job_id}` THEN the system SHALL provide real-time status updates
111
+ 2. WHEN job status changes THEN connected WebSocket clients SHALL receive immediate notifications
112
+ 3. WHEN processing stages complete THEN clients SHALL receive detailed progress information
113
+ 4. WHEN WebSocket connections are lost THEN the system SHALL handle reconnection gracefully
114
+ 5. WHEN multiple clients connect to the same job THEN all SHALL receive synchronized updates
115
+
116
+ ### Requirement 10
117
+
118
+ **User Story:** As a system integrator, I want the API to support batch operations and bulk processing, so that I can efficiently handle multiple video generation requests simultaneously.
119
+
120
+ #### Acceptance Criteria
121
+
122
+ 1. WHEN a POST request is made to `/api/v1/videos/batch` with multiple video requests THEN the system SHALL create multiple jobs and return batch job metadata
123
+ 2. WHEN batch processing is requested THEN the system SHALL optimize resource allocation across multiple jobs
124
+ 3. WHEN batch jobs are queried THEN the system SHALL return aggregated status information for all jobs in the batch
125
+ 4. WHEN individual jobs in a batch fail THEN other jobs SHALL continue processing independently
126
+ 5. WHEN batch size exceeds system limits THEN the system SHALL return appropriate error messages with suggested batch sizes
.kiro/specs/fastapi-backend/tasks.md ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Implementation Plan
2
+
3
+ - [ ] 1. Set up project structure and basic configuration
4
+
5
+ - Create FastAPI project directory structure following the simplified design specification
6
+ - Set up pyproject.toml with required dependencies (FastAPI, Pydantic, Redis, Clerk SDK)
7
+ - Create main.py with basic FastAPI application initialization
8
+ - Configure environment-based settings using Pydantic BaseSettings
9
+ - Set up basic logging configuration
10
+ - _Requirements: 1.1, 6.4, 7.5_
11
+
12
+ - [x] 2. Implement Redis infrastructure and connection
13
+
14
+ - [x] 2.1 Set up Redis connection and utilities
15
+
16
+ - Create redis.py with Redis connection management
17
+ - Implement Redis dependency injection for FastAPI endpoints
18
+ - Configure Redis connection pooling and error handling
19
+ - Add Redis health check utilities
20
+ - _Requirements: 1.1, 2.1, 4.1_
21
+
22
+ - [x] 2.2 Create Redis data access patterns
23
+
24
+ - Implement Redis hash operations for job storage
25
+ - Create Redis list operations for job queue management
26
+ - Add Redis set operations for user job indexing
27
+ - Implement Redis key expiration and cleanup utilities
28
+ - _Requirements: 1.1, 2.1, 8.2_
29
+
30
+ - [x] 3. Implement Clerk authentication integration
31
+
32
+ - [x] 3.1 Set up Clerk authentication middleware
33
+
34
+ - Create Clerk SDK integration and configuration
35
+ - Implement Clerk token validation middleware
36
+ - Build authentication dependency for protected endpoints
37
+ - Add user information extraction from Clerk tokens
38
+ - _Requirements: 6.1, 6.2, 6.3_
39
+
40
+ - [x] 3.2 Create user management utilities
41
+
42
+ - Implement user data extraction from Clerk
43
+ - Create user session management utilities
44
+ - Add user permission checking functions
45
+ - _Requirements: 6.1, 6.2, 8.1_
46
+
47
+ - [ ] 4. Create Pydantic data models
48
+
49
+ - [x] 4.1 Define core Pydantic models
50
+
51
+ - Create Job model with validation rules and status enum
52
+ - Implement VideoMetadata model for video information
53
+ - Define User model for Clerk user data
54
+ - Create SystemHealth model for monitoring
55
+ - Add common response models and error schemas
56
+ - _Requirements: 1.1, 1.3, 2.1, 7.3, 8.3_
57
+
58
+ - [x] 4.2 Implement request/response schemas
59
+
60
+ - Create VideoGenerationRequest schema with field validation
61
+ - Define JobResponse and JobStatusResponse schemas
62
+ - Implement pagination and filtering schemas
63
+ - Add error response schemas with consistent structure
64
+ - Create API documentation examples for all schemas
65
+ - _Requirements: 1.1, 1.3, 2.1, 7.3, 8.3_
66
+
67
+ - [x] 5. Build core API endpoints
68
+
69
+ - [x] 5.1 Implement video generation endpoints
70
+
71
+ - Create POST /api/v1/videos/generate endpoint with Pydantic validation
72
+ - Implement GET /api/v1/videos/jobs/{job_id}/status endpoint with Redis data
73
+ - Build GET /api/v1/videos/jobs/{job_id}/download endpoint for file serving
74
+ - Add GET /api/v1/videos/jobs/{job_id}/metadata endpoint
75
+ - _Requirements: 1.1, 1.2, 1.3, 2.1, 2.2, 2.3, 2.4, 3.1, 3.2_
76
+
77
+ - [x] 5.2 Implement job management endpoints
78
+
79
+ - Create GET /api/v1/jobs endpoint with pagination and filtering
80
+ - Implement POST /api/v1/jobs/{job_id}/cancel endpoint
81
+ - Build DELETE /api/v1/jobs/{job_id} endpoint with Redis cleanup
82
+ - Add GET /api/v1/jobs/{job_id}/logs endpoint
83
+ - _Requirements: 2.1, 2.2, 4.3, 10.3_
84
+
85
+ - [x] 5.3 Create system monitoring endpoints
86
+
87
+ - Implement GET /api/v1/system/health endpoint with Redis and queue checks
88
+ - Create GET /api/v1/system/metrics endpoint for basic system stats
89
+ - Add GET /api/v1/system/queue-status endpoint for queue monitoring
90
+ - _Requirements: 4.1, 4.2, 4.5_
91
+
92
+ - [x] 6. Implement business logic services
93
+
94
+ - [x] 6.1 Create video generation service
95
+
96
+ - Implement VideoService class with Redis job queue integration
97
+ - Add job creation and status management methods
98
+ - Create progress tracking and update mechanisms
99
+ - Implement job queue processing logic
100
+ - _Requirements: 1.1, 1.2, 2.1, 2.2, 9.2_
101
+
102
+ - [x] 6.2 Build job management service
103
+
104
+ - Implement JobService with Redis-based storage
105
+ - Add job lifecycle management methods
106
+ - Create job cancellation and cleanup functionality
107
+ - Implement job metrics collection
108
+ - _Requirements: 2.1, 2.2, 4.3, 10.1, 10.2, 10.4_
109
+
110
+ - [x] 6.3 Create queue management service
111
+
112
+ - Implement QueueService for Redis queue operations
113
+
114
+ - Add job queuing and dequeuing methods
115
+ - Create queue monitoring and health check functions
116
+ - Implement queue cleanup and maintenance utilities
117
+ - _Requirements: 1.1, 2.1, 4.2_
118
+
119
+ - [x] 7. Add file handling and storage
120
+
121
+ - [x] 7.1 Implement local file management
122
+
123
+ - Create file upload handling with validation
124
+ - Implement secure file storage with proper permissions
125
+ - Add file metadata extraction and storage in Redis
126
+ - Create file cleanup and maintenance utilities
127
+ - _Requirements: 3.1, 3.2, 3.3, 5.1, 5.2, 5.4, 5.5_
128
+
129
+ - [x] 7.2 Build file serving capabilities
130
+
131
+ - Implement file download endpoints with streaming
132
+ - Add file access control and security checks
133
+ - Create file URL generation for frontend access
134
+ - Implement file caching strategies
135
+ - _Requirements: 3.1, 3.2, 3.3_
136
+
137
+ - [ ] 8. Implement error handling and middleware
138
+
139
+ - [x] 8.1 Create global exception handling
140
+
141
+ - Implement custom exception classes with proper HTTP status codes
142
+ - Create global exception handler middleware
143
+ - Add structured error response formatting
144
+ - Implement error logging with request correlation
145
+ - _Requirements: 8.1, 8.2, 8.3_
146
+
147
+ - [x] 8.2 Build request/response middleware
148
+
149
+ - Implement CORS middleware for cross-origin requests
150
+ - Create request logging middleware with performance metrics
151
+ - Add response compression middleware
152
+ - Implement security headers middleware
153
+ - _Requirements: 8.2, 8.4_
154
+
155
+ - [x] 9. Add caching and performance optimization
156
+
157
+ - [x] 9.1 Implement Redis caching strategies
158
+
159
+ - Create cache decorator for frequently accessed endpoints
160
+ - Implement cache invalidation patterns
161
+
162
+ - Add cache warming strategies for common queries
163
+ - Create cache monitoring and metrics collection
164
+ - _Requirements: 2.1, 4.2_
165
+
166
+ - [x] 9.2 Optimize API performance
167
+
168
+ - Implement response caching for static data
169
+ - Add request deduplication for expensive operations
170
+ - Create connection pooling optimization
171
+ - Implement async processing where beneficial
172
+ - _Requirements: 2.1, 4.2, 10.3_
173
+
174
+ - [ ] 10. Implement batch processing capabilities
175
+
176
+ - [ ] 10.1 Create batch job endpoints
177
+
178
+ - Implement POST /api/v1/videos/batch endpoint
179
+ - Add batch job validation and processing logic
180
+ - Create batch status tracking and reporting
181
+ - _Requirements: 10.1, 10.2_
182
+
183
+ - [ ] 10.2 Build batch job management
184
+ - Implement batch job cancellation and cleanup
185
+ - Add batch job progress aggregation
186
+ - Create batch job completion notifications
187
+ - _Requirements: 10.3, 10.4, 10.5_
188
+
189
+ - [ ] 11. Add comprehensive testing suite
190
+
191
+ - [ ] 11.1 Create unit tests for core functionality
192
+
193
+ - Write unit tests for all service layer methods
194
+ - Create tests for Redis operations and data models
195
+ - Add tests for Clerk authentication integration
196
+ - Test Pydantic schema validation and serialization
197
+ - _Requirements: 1.1, 1.3, 2.1, 6.1, 8.3_
198
+
199
+ - [ ] 11.2 Implement integration tests for API endpoints
200
+
201
+ - Create integration tests for all video generation endpoints
202
+ - Test job management endpoints with Redis operations
203
+ - Add file upload and download functionality tests
204
+ - Test error handling and edge cases
205
+ - _Requirements: 1.1, 2.1, 3.1, 9.1_
206
+
207
+ - [ ] 11.3 Build end-to-end workflow tests
208
+ - Create complete video generation workflow tests
209
+ - Test batch processing end-to-end scenarios
210
+ - Add performance testing for critical endpoints
211
+ - Test system recovery and error scenarios
212
+ - _Requirements: 1.1, 8.1, 10.1_
213
+
214
+ - [ ] 12. Implement rate limiting and security features
215
+
216
+ - [ ] 12.1 Add rate limiting middleware
217
+
218
+ - Implement Redis-based rate limiting per user and endpoint
219
+ - Create rate limit configuration and storage
220
+ - Add rate limit headers and error responses
221
+ - Implement rate limit monitoring and alerting
222
+ - _Requirements: 6.5, 8.4_
223
+
224
+ - [ ] 12.2 Enhance security measures
225
+ - Implement input sanitization and validation
226
+ - Add request/response logging for audit trails
227
+ - Create security headers middleware
228
+ - Implement API key validation for internal services
229
+ - _Requirements: 6.1, 6.3, 8.2_
230
+
231
+ - [x] 13. Create API documentation and client generation
232
+
233
+
234
+
235
+
236
+
237
+ - [x] 13.1 Configure OpenAPI documentation
238
+
239
+
240
+
241
+ - Customize OpenAPI schema generation with proper operation IDs
242
+ - Add comprehensive endpoint descriptions and examples
243
+ - Configure Swagger UI and ReDoc interfaces
244
+ - Add authentication documentation for Clerk integration
245
+ - _Requirements: 7.1, 7.2, 7.3_
246
+
247
+
248
+
249
+ - [ ] 13.2 Set up client code generation
250
+ - Configure OpenAPI specification for client generation
251
+ - Create example client generation scripts
252
+ - Add client SDK documentation and examples
253
+ - Test generated clients with real API endpoints
254
+ - _Requirements: 7.4, 7.5_
255
+
256
+ - [ ] 14. Implement deployment configuration
257
+
258
+ - [ ] 14.1 Create Docker containerization
259
+
260
+ - Write Dockerfile with multi-stage build optimization
261
+ - Create docker-compose.yml for local development with Redis
262
+ - Add production docker-compose with proper networking
263
+ - Configure environment variable management
264
+ - _Requirements: 4.1, 8.4_
265
+
266
+ - [ ] 14.2 Add monitoring and logging
267
+ - Implement structured logging with JSON format
268
+ - Add application metrics collection and export
269
+ - Create health check endpoints for container orchestration
270
+ - Configure log aggregation and monitoring dashboards
271
+ - _Requirements: 4.1, 4.2, 8.2_
272
+
273
+ - [ ] 15. Final integration and optimization
274
+
275
+ - [ ] 15.1 Integrate with existing video generation pipeline
276
+
277
+ - Create Redis queue integration with multi-agent video generation system
278
+ - Implement job queue management with proper error handling
279
+ - Add configuration mapping between API requests and pipeline parameters
280
+ - Test end-to-end video generation workflow
281
+ - _Requirements: 1.1, 1.2, 2.1_
282
+
283
+ - [ ] 15.2 Performance optimization and cleanup
284
+ - Optimize Redis operations and connection management
285
+ - Implement proper resource cleanup and garbage collection
286
+ - Add graceful shutdown handling for long-running operations
287
+ - Create production-ready configuration templates
288
+ - _Requirements: 4.2, 8.4_
289
+
290
+ - [ ] 16. Frontend integration preparation
291
+
292
+ - [ ] 16.1 Create frontend-specific endpoints
293
+
294
+ - Implement GET /api/v1/dashboard/stats endpoint for user dashboard
295
+ - Create GET /api/v1/dashboard/recent-jobs endpoint
296
+ - Add WebSocket endpoints for real-time job updates
297
+ - Implement user preference and settings endpoints
298
+ - _Requirements: 2.1, 9.1, 9.2_
299
+
300
+ - [ ] 16.2 Set up CORS and frontend security
301
+ - Configure CORS middleware for frontend domain access
302
+ - Add rate limiting specific to frontend endpoints
303
+ - Create frontend-specific authentication flows with Clerk
304
+ - Implement proper session management for frontend clients
305
+ - _Requirements: 6.1, 6.5, 8.4_
API_DOCUMENTATION.md ADDED
@@ -0,0 +1,1311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # T2M API Documentation
2
+
3
+ ## Overview
4
+
5
+ This document provides comprehensive documentation for the T2M (Text-to-Media) API endpoints. The API is organized into several modules: Authentication, Files, Jobs, System, and Videos.
6
+
7
+ ## Base URL
8
+
9
+ ```
10
+ https://your-api-domain.com/api/v1
11
+ ```
12
+
13
+ ## Authentication
14
+
15
+ Most endpoints require authentication. Include the authorization token in the request headers:
16
+
17
+ ```
18
+ Authorization: Bearer <your-token>
19
+ ```
20
+
21
+ **Public endpoints** (no authentication required):
22
+
23
+ - `GET /auth/health`
24
+ - `GET /system/health`
25
+
26
+ **Optional authentication** (enhanced data for authenticated users):
27
+
28
+ - All other `/system/*` endpoints
29
+
30
+ ## Common Response Formats
31
+
32
+ ### Success Response
33
+
34
+ ```json
35
+ {
36
+ "success": true,
37
+ "data": {
38
+ "id": "12345",
39
+ "status": "completed"
40
+ },
41
+ "message": "Operation completed successfully"
42
+ }
43
+ ```
44
+
45
+ ### Error Response
46
+
47
+ ```json
48
+ {
49
+ "success": false,
50
+ "error": {
51
+ "code": "AUTH_INVALID",
52
+ "details": "Token has expired or is malformed"
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Pagination Format
58
+
59
+ ```json
60
+ {
61
+ "success": true,
62
+ "data": {
63
+ "items": [...],
64
+ "pagination": {
65
+ "page": 1,
66
+ "items_per_page": 20,
67
+ "total_items": 150,
68
+ "total_pages": 8,
69
+ "has_next": true,
70
+ "has_previous": false,
71
+ "next_page": 2,
72
+ "previous_page": null
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Authentication Endpoints
79
+
80
+ ### Health Check
81
+
82
+ - **Endpoint**: `GET /auth/health`
83
+ - **Description**: Check authentication service health
84
+ - **Authentication**: Not required
85
+ - **Response**: Service health status
86
+ - **Example Response**:
87
+
88
+ ```json
89
+ {
90
+ "status": "healthy",
91
+ "clerk": {
92
+ "status": "healthy",
93
+ "response_time": "45ms",
94
+ "last_check": "2024-01-15T10:30:00Z"
95
+ },
96
+ "message": "Authentication service health check completed"
97
+ }
98
+ ```
99
+
100
+ ### Get Authentication Status
101
+
102
+ - **Endpoint**: `GET /auth/status`
103
+ - **Description**: Get current user authentication status
104
+ - **Headers**:
105
+ - `Authorization: Bearer <token>` (optional)
106
+ - **Response**: Authentication status and user info
107
+ - **Example Response (Authenticated)**:
108
+
109
+ ```json
110
+ {
111
+ "authenticated": true,
112
+ "user_id": "user_12345",
113
+ "email": "[email protected]",
114
+ "email_verified": true,
115
+ "request_context": {
116
+ "path": "/auth/status",
117
+ "method": "GET",
118
+ "client_ip": "192.168.1.100"
119
+ }
120
+ }
121
+ ```
122
+
123
+ - **Example Response (Not Authenticated)**:
124
+
125
+ ```json
126
+ {
127
+ "authenticated": false,
128
+ "message": "No authentication provided",
129
+ "request_context": {
130
+ "path": "/auth/status",
131
+ "method": "GET",
132
+ "client_ip": "192.168.1.100"
133
+ }
134
+ }
135
+ ```
136
+
137
+ ### Get Current User Profile
138
+
139
+ - **Endpoint**: `GET /auth/me`
140
+ - **Description**: Get authenticated user's profile information
141
+ - **Headers**:
142
+ - `Authorization: Bearer <token>` (required)
143
+ - **Response**: User profile data
144
+ - **Example Response**:
145
+
146
+ ```json
147
+ {
148
+ "id": "user_12345",
149
+ "username": "john_doe",
150
+ "full_name": "John Doe",
151
+ "email": "[email protected]",
152
+ "image_url": "https://example.com/avatar.jpg",
153
+ "email_verified": true,
154
+ "created_at": "2024-01-01T00:00:00Z",
155
+ "last_sign_in_at": "2024-01-15T10:30:00Z"
156
+ }
157
+ ```
158
+
159
+ **Note**: `created_at` and `last_sign_in_at` fields may be `null` if not available from the authentication provider.
160
+
161
+ ### Get User Permissions
162
+
163
+ - **Endpoint**: `GET /auth/permissions`
164
+ - **Description**: Get user's permissions and access levels
165
+ - **Headers**:
166
+ - `Authorization: Bearer <token>` (required)
167
+ - **Response**: User permissions and role information
168
+ - **Example Response**:
169
+
170
+ ```json
171
+ {
172
+ "user_id": "user_12345",
173
+ "role": "USER",
174
+ "permissions": [
175
+ "read_files",
176
+ "upload_files",
177
+ "generate_videos"
178
+ ],
179
+ "access_level": "standard"
180
+ }
181
+ ```
182
+
183
+ ### Test Protected Endpoint
184
+
185
+ - **Endpoint**: `GET /auth/test-protected`
186
+ - **Description**: Test endpoint for authenticated users
187
+ - **Headers**:
188
+ - `Authorization: Bearer <token>` (required)
189
+ - **Response**: Test response with user ID
190
+ - **Example Response**:
191
+
192
+ ```json
193
+ {
194
+ "message": "Successfully accessed protected endpoint",
195
+ "user_id": "user_12345",
196
+ "timestamp": "2024-01-15T10:30:00Z"
197
+ }
198
+ ```
199
+
200
+ ### Test Verified Endpoint
201
+
202
+ - **Endpoint**: `GET /auth/test-verified`
203
+ - **Description**: Test endpoint for verified users only
204
+ - **Headers**:
205
+ - `Authorization: Bearer <token>` (required)
206
+ - **Response**: Test response for verified users
207
+ - **Example Response**:
208
+
209
+ ```json
210
+ {
211
+ "message": "Successfully accessed verified user endpoint",
212
+ "user_id": "user_12345",
213
+ "email": "[email protected]",
214
+ "email_verified": true,
215
+ "timestamp": "2024-01-15T10:30:00Z"
216
+ }
217
+ ```
218
+
219
+ ### Verify Token
220
+
221
+ - **Endpoint**: `POST /auth/verify`
222
+ - **Description**: Verify authentication token validity
223
+ - **Headers**:
224
+ - `Authorization: Bearer <token>` (required)
225
+ - **Response**: Token verification status
226
+ - **Example Response**:
227
+
228
+ ```json
229
+ {
230
+ "verified": true,
231
+ "user_id": "user_12345",
232
+ "email": "[email protected]",
233
+ "email_verified": true,
234
+ "message": "Token verified successfully"
235
+ }
236
+ ```
237
+
238
+ ## File Management Endpoints
239
+
240
+ ### Upload Single File
241
+
242
+ - **Endpoint**: `POST /files/upload`
243
+ - **Description**: Upload a single file
244
+ - **Headers**:
245
+ - `Authorization: Bearer <token>` (required)
246
+ - `Content-Type: multipart/form-data`
247
+ - **Parameters**:
248
+ - `file` (file, required): File to upload
249
+ - `file_type` (string, optional): File type category
250
+ - `subdirectory` (string, optional): Target subdirectory
251
+ - `description` (string, optional): File description
252
+ - **Response**: File upload confirmation with file ID
253
+
254
+ ### Batch Upload Files
255
+
256
+ - **Endpoint**: `POST /files/batch-upload`
257
+ - **Description**: Upload multiple files at once
258
+ - **Headers**:
259
+ - `Authorization: Bearer <token>` (required)
260
+ - `Content-Type: multipart/form-data`
261
+ - **Parameters**:
262
+ - `files` (file[], required): Files to upload
263
+ - `file_type` (string, optional): File type for all files
264
+ - `subdirectory` (string, optional): Subdirectory for all files
265
+ - `description` (string, optional): Description for all files
266
+ - **Response**: Batch upload results
267
+
268
+ ### List Files
269
+
270
+ - **Endpoint**: `GET /files`
271
+ - **Description**: List user's files with pagination and filtering
272
+ - **Headers**:
273
+ - `Authorization: Bearer <token>` (required)
274
+ - **Query Parameters**:
275
+
276
+ | Name | Type | Required | Default | Description |
277
+ | ---------------- | ------- | -------- | ------- | --------------------------------------------------- |
278
+ | `file_type` | string | no | - | Filter by file type (document, image, video, audio) |
279
+ | `page` | integer | no | 1 | Page number (≥1) |
280
+ | `items_per_page` | integer | no | 20 | Items per page (1-100) |
281
+
282
+ - **Response**: Paginated list of files
283
+ - **Example Response**:
284
+
285
+ ```json
286
+ {
287
+ "success": true,
288
+ "data": {
289
+ "items": [
290
+ {
291
+ "id": "file_123456",
292
+ "filename": "document.pdf",
293
+ "size": 2048576,
294
+ "file_type": "document",
295
+ "created_at": "2024-01-15T10:30:00Z"
296
+ }
297
+ ],
298
+ "pagination": {
299
+ "page": 1,
300
+ "items_per_page": 20,
301
+ "total_items": 150,
302
+ "total_pages": 8,
303
+ "has_next": true
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ ### Get File Details
310
+
311
+ - **Endpoint**: `GET /files/{file_id}`
312
+ - **Description**: Get comprehensive file information including content details and metadata
313
+ - **Headers**:
314
+ - `Authorization: Bearer <token>` (required)
315
+ - **Path Parameters**:
316
+ - `file_id` (string, required): Unique file identifier
317
+ - **Response**: Complete file details
318
+ - **Example Response**:
319
+
320
+ ```json
321
+ {
322
+ "success": true,
323
+ "data": {
324
+ "id": "file_123456",
325
+ "filename": "document.pdf",
326
+ "size": 2048576,
327
+ "content_type": "application/pdf",
328
+ "file_type": "document",
329
+ "subdirectory": "uploads/2024",
330
+ "description": "Important document",
331
+ "created_at": "2024-01-15T10:30:00Z",
332
+ "updated_at": "2024-01-15T10:30:00Z",
333
+ "download_count": 5,
334
+ "metadata": {
335
+ "pages": 10,
336
+ "author": "John Doe",
337
+ "creation_date": "2024-01-15"
338
+ }
339
+ }
340
+ }
341
+ ```
342
+
343
+ ### Get File Metadata
344
+
345
+ - **Endpoint**: `GET /files/{file_id}/metadata`
346
+ - **Description**: Get file metadata and technical information only
347
+ - **Headers**:
348
+ - `Authorization: Bearer <token>` (required)
349
+ - **Path Parameters**:
350
+ - `file_id` (string, required): Unique file identifier
351
+ - **Response**: File metadata only
352
+ - **Example Response**:
353
+
354
+ ```json
355
+ {
356
+ "success": true,
357
+ "data": {
358
+ "content_type": "application/pdf",
359
+ "size": 2048576,
360
+ "checksum": "sha256:abc123...",
361
+ "metadata": {
362
+ "pages": 10,
363
+ "author": "John Doe",
364
+ "creation_date": "2024-01-15"
365
+ }
366
+ }
367
+ }
368
+ ```
369
+
370
+ ### Download File
371
+
372
+ - **Endpoint**: `GET /files/{file_id}/download`
373
+ - **Description**: Download file content
374
+ - **Headers**:
375
+ - `Authorization: Bearer <token>` (required)
376
+ - **Path Parameters**:
377
+ - `file_id` (string, required): Unique file identifier
378
+ - **Query Parameters**:
379
+ - `inline` (boolean, default: false): Serve inline instead of attachment
380
+ - **Response**: File content
381
+
382
+ ### Stream File
383
+
384
+ - **Endpoint**: `GET /files/{file_id}/stream`
385
+ - **Description**: Stream file content
386
+ - **Headers**:
387
+ - `Authorization: Bearer <token>` (required)
388
+ - **Path Parameters**:
389
+ - `file_id` (string, required): Unique file identifier
390
+ - **Query Parameters**:
391
+ - `quality` (string, default: "auto"): Stream quality
392
+ - **Response**: Streamed file content
393
+
394
+ ### Get File Thumbnail
395
+
396
+ - **Endpoint**: `GET /files/{file_id}/thumbnail`
397
+ - **Description**: Get file thumbnail
398
+ - **Headers**:
399
+ - `Authorization: Bearer <token>` (required)
400
+ - **Path Parameters**:
401
+ - `file_id` (string, required): Unique file identifier
402
+ - **Query Parameters**:
403
+ - `size` (string, default: "medium"): Thumbnail size (small|medium|large)
404
+ - **Response**: Thumbnail image
405
+
406
+ ### Get File Analytics
407
+
408
+ - **Endpoint**: `GET /files/{file_id}/analytics`
409
+ - **Description**: Get file usage analytics
410
+ - **Headers**:
411
+ - `Authorization: Bearer <token>` (required)
412
+ - **Path Parameters**:
413
+ - `file_id` (string, required): Unique file identifier
414
+ - **Response**: File analytics data
415
+
416
+ ### Delete File
417
+
418
+ - **Endpoint**: `DELETE /files/{file_id}`
419
+ - **Description**: Delete a file
420
+ - **Headers**:
421
+ - `Authorization: Bearer <token>` (required)
422
+ - **Path Parameters**:
423
+ - `file_id` (string, required): Unique file identifier
424
+ - **Response**: Deletion confirmation
425
+
426
+ ### Get File Statistics
427
+
428
+ - **Endpoint**: `GET /files/stats`
429
+ - **Description**: Get user's file statistics
430
+ - **Headers**:
431
+ - `Authorization: Bearer <token>` (required)
432
+ - **Response**: File usage statistics
433
+
434
+ ### Cleanup Files
435
+
436
+ - **Endpoint**: `POST /files/cleanup`
437
+ - **Description**: Cleanup files based on criteria
438
+ - **Headers**:
439
+ - `Authorization: Bearer <token>` (required)
440
+ - `Content-Type: application/json`
441
+ - **Request Body**: File cleanup criteria
442
+ - **Response**: Cleanup results
443
+
444
+ ### Secure File Access
445
+
446
+ - **Endpoint**: `GET /files/secure/{file_id}`
447
+ - **Description**: Access files via signed URLs
448
+ - **Query Parameters**:
449
+ - `user_id` (string, required): User ID from signed URL
450
+ - `expires` (string, required): Expiration timestamp
451
+ - `signature` (string, required): URL signature
452
+ - `file_type` (string, optional): File type
453
+ - `inline` (string, default: "false"): Serve inline
454
+ - `size` (string, optional): Thumbnail size
455
+ - `quality` (string, optional): Stream quality
456
+ - **Response**: Secure file access
457
+
458
+ ## Job Management Endpoints
459
+
460
+ ### List Jobs
461
+
462
+ - **Endpoint**: `GET /jobs`
463
+ - **Description**: List user's jobs with pagination and filtering
464
+ - **Headers**:
465
+ - `Authorization: Bearer <token>` (required)
466
+ - **Query Parameters**: Pagination and filtering parameters
467
+ - **Response**: Paginated list of jobs
468
+
469
+ ### Get Job Details
470
+
471
+ - **Endpoint**: `GET /jobs/{job_id}`
472
+ - **Description**: Get comprehensive job information including status, progress, and results
473
+ - **Headers**:
474
+ - `Authorization: Bearer <token>` (required)
475
+ - **Path Parameters**:
476
+ - `job_id` (string, required): Unique job identifier
477
+ - **Response**: Complete job details and status
478
+ - **Example Response**:
479
+
480
+ ```json
481
+ {
482
+ "success": true,
483
+ "data": {
484
+ "id": "job_789012",
485
+ "type": "video_generation",
486
+ "status": "completed",
487
+ "progress": 100,
488
+ "created_at": "2024-01-15T10:30:00Z",
489
+ "started_at": "2024-01-15T10:30:05Z",
490
+ "completed_at": "2024-01-15T10:35:30Z",
491
+ "duration": 325,
492
+ "parameters": {
493
+ "prompt": "A beautiful sunset over mountains",
494
+ "duration": 10,
495
+ "quality": "1080p"
496
+ },
497
+ "result": {
498
+ "file_id": "video_456789",
499
+ "file_size": 15728640,
500
+ "thumbnail_url": "/videos/job_789012/thumbnail"
501
+ },
502
+ "error": null
503
+ }
504
+ }
505
+ ```
506
+
507
+ ### Get Job Logs
508
+
509
+ - **Endpoint**: `GET /jobs/{job_id}/logs`
510
+ - **Description**: Get job execution logs with filtering and pagination
511
+ - **Headers**:
512
+ - `Authorization: Bearer <token>` (required)
513
+ - **Path Parameters**:
514
+ - `job_id` (string, required): Unique job identifier
515
+ - **Query Parameters**:
516
+
517
+ | Name | Type | Required | Default | Description |
518
+ | -------- | ------- | -------- | ------- | ------------------------------------------------- |
519
+ | `limit` | integer | no | 100 | Maximum log entries (1-1000) |
520
+ | `offset` | integer | no | 0 | Log entries to skip (≥0) |
521
+ | `level` | string | no | - | Filter by log level (DEBUG, INFO, WARNING, ERROR) |
522
+
523
+ - **Response**: Job logs with metadata
524
+ - **Example Response**:
525
+
526
+ ```json
527
+ {
528
+ "success": true,
529
+ "data": {
530
+ "logs": [
531
+ {
532
+ "timestamp": "2024-01-15T10:30:15Z",
533
+ "level": "INFO",
534
+ "message": "Video processing started",
535
+ "details": {
536
+ "step": "initialization",
537
+ "progress": 0
538
+ }
539
+ },
540
+ {
541
+ "timestamp": "2024-01-15T10:30:45Z",
542
+ "level": "INFO",
543
+ "message": "Processing frame 100/1000",
544
+ "details": {
545
+ "step": "rendering",
546
+ "progress": 10
547
+ }
548
+ }
549
+ ],
550
+ "total_logs": 250,
551
+ "has_more": true
552
+ }
553
+ }
554
+ ```
555
+
556
+ ### Cancel Job
557
+
558
+ - **Endpoint**: `POST /jobs/{job_id}/cancel`
559
+ - **Description**: Cancel a running job
560
+ - **Headers**:
561
+ - `Authorization: Bearer <token>` (required)
562
+ - **Path Parameters**:
563
+ - `job_id` (string, required): Unique job identifier
564
+ - **Response**: Cancellation confirmation
565
+
566
+ ### Delete Job
567
+
568
+ - **Endpoint**: `DELETE /jobs/{job_id}`
569
+ - **Description**: Delete a job and its data
570
+ - **Headers**:
571
+ - `Authorization: Bearer <token>` (required)
572
+ - **Path Parameters**:
573
+ - `job_id` (string, required): Unique job identifier
574
+ - **Response**: Deletion confirmation
575
+
576
+ ## System Monitoring Endpoints
577
+
578
+ ### System Health Check
579
+
580
+ - **Endpoint**: `GET /system/health`
581
+ - **Description**: Get overall system health status
582
+ - **Headers**:
583
+ - `Authorization: Bearer <token>` (optional)
584
+ - **Response**: System health metrics
585
+
586
+ ### System Metrics
587
+
588
+ - **Endpoint**: `GET /system/metrics`
589
+ - **Description**: Get detailed system metrics
590
+ - **Headers**:
591
+ - `Authorization: Bearer <token>` (optional)
592
+ - **Response**: System performance metrics
593
+
594
+ ### Queue Status
595
+
596
+ - **Endpoint**: `GET /system/queue`
597
+ - **Description**: Get job queue status and statistics
598
+ - **Headers**:
599
+ - `Authorization: Bearer <token>` (optional)
600
+ - **Response**: Queue status and metrics
601
+
602
+ ### Cache Information
603
+
604
+ - **Endpoint**: `GET /system/cache`
605
+ - **Description**: Get cache status and information
606
+ - **Headers**:
607
+ - `Authorization: Bearer <token>` (optional)
608
+ - **Response**: Cache metrics and status
609
+
610
+ ### Cache Metrics
611
+
612
+ - **Endpoint**: `GET /system/cache/metrics`
613
+ - **Description**: Get detailed cache metrics
614
+ - **Headers**:
615
+ - `Authorization: Bearer <token>` (optional)
616
+ - **Response**: Cache performance metrics
617
+
618
+ ### Cache Report
619
+
620
+ - **Endpoint**: `GET /system/cache/report`
621
+ - **Description**: Get comprehensive cache report
622
+ - **Headers**:
623
+ - `Authorization: Bearer <token>` (optional)
624
+ - **Response**: Detailed cache report
625
+
626
+ ### Performance Summary
627
+
628
+ - **Endpoint**: `GET /system/performance`
629
+ - **Description**: Get system performance summary
630
+ - **Headers**:
631
+ - `Authorization: Bearer <token>` (optional)
632
+ - **Query Parameters**:
633
+ - `hours` (integer, default: 1): Time range in hours
634
+ - **Response**: Performance summary
635
+
636
+ ### Connection Statistics
637
+
638
+ - **Endpoint**: `GET /system/connections`
639
+ - **Description**: Get connection statistics
640
+ - **Headers**:
641
+ - `Authorization: Bearer <token>` (optional)
642
+ - **Response**: Connection metrics
643
+
644
+ ### Async Statistics
645
+
646
+ - **Endpoint**: `GET /system/async`
647
+ - **Description**: Get asynchronous processing statistics
648
+ - **Headers**:
649
+ - `Authorization: Bearer <token>` (optional)
650
+ - **Response**: Async processing metrics
651
+
652
+ ### Deduplication Statistics
653
+
654
+ - **Endpoint**: `GET /system/deduplication`
655
+ - **Description**: Get deduplication statistics
656
+ - **Headers**:
657
+ - `Authorization: Bearer <token>` (optional)
658
+ - **Response**: Deduplication metrics
659
+
660
+ ### Invalidate Cache
661
+
662
+ - **Endpoint**: `POST /system/cache/invalidate`
663
+ - **Description**: Invalidate cache entries
664
+ - **Headers**:
665
+ - `Authorization: Bearer <token>` (optional)
666
+ - **Query Parameters**:
667
+ - `pattern` (string, optional): Cache key pattern
668
+ - `user_id` (string, optional): User-specific cache
669
+ - **Response**: Cache invalidation results
670
+
671
+ ### Warm Cache
672
+
673
+ - **Endpoint**: `POST /system/cache/warm`
674
+ - **Description**: Pre-warm cache with frequently accessed data
675
+ - **Headers**:
676
+ - `Authorization: Bearer <token>` (optional)
677
+ - **Response**: Cache warming results
678
+
679
+ ### Optimize Performance
680
+
681
+ - **Endpoint**: `POST /system/optimize`
682
+ - **Description**: Trigger system performance optimization
683
+ - **Headers**:
684
+ - `Authorization: Bearer <token>` (optional)
685
+ - **Response**: Optimization results
686
+
687
+ ## Video Processing Endpoints
688
+
689
+ ### Generate Video
690
+
691
+ - **Endpoint**: `POST /videos/generate`
692
+ - **Description**: Create a new video generation job
693
+ - **Headers**:
694
+ - `Authorization: Bearer <token>` (required)
695
+ - `Content-Type: application/json`
696
+ - **Request Body**: Job creation parameters
697
+ - **Response**: Job creation confirmation with job ID
698
+
699
+ ### Get Job Status
700
+
701
+ - **Endpoint**: `GET /videos/{job_id}/status`
702
+ - **Description**: Get video generation job status
703
+ - **Headers**:
704
+ - `Authorization: Bearer <token>` (required)
705
+ - **Path Parameters**:
706
+ - `job_id` (string, required): Unique job identifier
707
+ - **Response**: Job status and progress
708
+
709
+ ### Download Video
710
+
711
+ - **Endpoint**: `GET /videos/{job_id}/download`
712
+ - **Description**: Download generated video
713
+ - **Headers**:
714
+ - `Authorization: Bearer <token>` (required)
715
+ - **Path Parameters**:
716
+ - `job_id` (string, required): Unique job identifier
717
+ - **Query Parameters**:
718
+ - `inline` (boolean, default: false): Serve inline instead of attachment
719
+ - **Response**: Video file content
720
+
721
+ ### Stream Video
722
+
723
+ - **Endpoint**: `GET /videos/{job_id}/stream`
724
+ - **Description**: Stream generated video
725
+ - **Headers**:
726
+ - `Authorization: Bearer <token>` (required)
727
+ - **Path Parameters**:
728
+ - `job_id` (string, required): Unique job identifier
729
+ - **Query Parameters**:
730
+ - `quality` (string, default: "auto"): Stream quality (auto|720p|1080p)
731
+ - **Response**: Streamed video content
732
+
733
+ ### Get Video Metadata
734
+
735
+ - **Endpoint**: `GET /videos/{job_id}/metadata`
736
+ - **Description**: Get comprehensive video metadata and technical information
737
+ - **Headers**:
738
+ - `Authorization: Bearer <token>` (required)
739
+ - **Path Parameters**:
740
+ - `job_id` (string, required): Unique job identifier
741
+ - **Response**: Detailed video metadata
742
+ - **Example Response**:
743
+
744
+ ```json
745
+ {
746
+ "success": true,
747
+ "data": {
748
+ "job_id": "job_789012",
749
+ "video": {
750
+ "duration": 10.5,
751
+ "width": 1920,
752
+ "height": 1080,
753
+ "fps": 30,
754
+ "bitrate": 5000000,
755
+ "codec": "h264",
756
+ "format": "mp4",
757
+ "file_size": 15728640
758
+ },
759
+ "audio": {
760
+ "codec": "aac",
761
+ "bitrate": 128000,
762
+ "sample_rate": 44100,
763
+ "channels": 2
764
+ },
765
+ "generation": {
766
+ "prompt": "A beautiful sunset over mountains",
767
+ "model": "t2v-v2.1",
768
+ "seed": 12345,
769
+ "created_at": "2024-01-15T10:30:00Z"
770
+ }
771
+ }
772
+ }
773
+ ```
774
+
775
+ ### Get Video Thumbnail
776
+
777
+ - **Endpoint**: `GET /videos/{job_id}/thumbnail`
778
+ - **Description**: Get video thumbnail
779
+ - **Headers**:
780
+ - `Authorization: Bearer <token>` (required)
781
+ - **Path Parameters**:
782
+ - `job_id` (string, required): Unique job identifier
783
+ - **Query Parameters**:
784
+ - `size` (string, default: "medium"): Thumbnail size (small|medium|large)
785
+ - **Response**: Video thumbnail image
786
+
787
+ ## Error Handling
788
+
789
+ ### Error Codes
790
+
791
+ | Code | HTTP Status | Description |
792
+ | --------------------- | ----------- | ------------------------------- |
793
+ | `AUTH_REQUIRED` | 401 | Authentication required |
794
+ | `AUTH_INVALID` | 401 | Invalid authentication token |
795
+ | `AUTH_EXPIRED` | 401 | Authentication token expired |
796
+ | `PERMISSION_DENIED` | 403 | Insufficient permissions |
797
+ | `RESOURCE_NOT_FOUND` | 404 | Requested resource not found |
798
+ | `VALIDATION_ERROR` | 400 | Request validation failed |
799
+ | `RATE_LIMIT_EXCEEDED` | 429 | Rate limit exceeded |
800
+ | `SERVER_ERROR` | 500 | Internal server error |
801
+ | `SERVICE_UNAVAILABLE` | 503 | Service temporarily unavailable |
802
+
803
+ ### Error Response Examples
804
+
805
+ **Invalid Authentication (401)**
806
+
807
+ ```json
808
+ {
809
+ "success": false,
810
+ "error": {
811
+ "code": "AUTH_INVALID",
812
+ "details": "Token has expired or is malformed"
813
+ }
814
+ }
815
+ ```
816
+
817
+ **Resource Not Found (404)**
818
+
819
+ ```json
820
+ {
821
+ "success": false,
822
+ "error": {
823
+ "code": "RESOURCE_NOT_FOUND",
824
+ "message": "File not found",
825
+ "details": "File with ID 'file_123456' does not exist or you don't have access"
826
+ }
827
+ }
828
+ ```
829
+
830
+ **Validation Error (400)**
831
+
832
+ ```json
833
+ {
834
+ "success": false,
835
+ "error": {
836
+ "code": "VALIDATION_ERROR",
837
+ "message": "Request validation failed",
838
+ "details": {
839
+ "file_type": [
840
+ "Invalid file type. Must be one of: document, image, video, audio"
841
+ ],
842
+ "page": ["Page must be greater than 0"]
843
+ }
844
+ }
845
+ }
846
+ ```
847
+
848
+ **Rate Limit Exceeded (429)**
849
+
850
+ ```json
851
+ {
852
+ "success": false,
853
+ "error": {
854
+ "code": "RATE_LIMIT_EXCEEDED",
855
+ "message": "Rate limit exceeded",
856
+ "details": "You have exceeded the limit of 100 uploads per hour. Try again in 45 minutes."
857
+ }
858
+ }
859
+ ```
860
+
861
+ ## Rate Limits
862
+
863
+ - **General API**: 1000 requests per hour per user
864
+ - **File Upload**: 100 uploads per hour per user
865
+ - **Video Generation**: 10 jobs per hour per user
866
+ - **System Endpoints**: 500 requests per hour per user
867
+
868
+ ## Code Examples
869
+
870
+ ### cURL Examples
871
+
872
+ **Upload a file**
873
+
874
+ ```bash
875
+ curl -X POST "https://api.example.com/api/v1/files/upload" \
876
+ -H "Authorization: Bearer your-token-here" \
877
878
+ -F "file_type=document" \
879
+ -F "description=Sample document"
880
+ ```
881
+
882
+ **Generate video**
883
+
884
+ ```bash
885
+ curl -X POST "https://api.example.com/api/v1/videos/generate" \
886
+ -H "Authorization: Bearer your-token-here" \
887
+ -H "Content-Type: application/json" \
888
+ -d '{
889
+ "prompt": "A beautiful sunset over mountains",
890
+ "duration": 10,
891
+ "quality": "1080p"
892
+ }'
893
+ ```
894
+
895
+ **Get job status**
896
+
897
+ ```bash
898
+ curl -X GET "https://api.example.com/api/v1/jobs/job_789012" \
899
+ -H "Authorization: Bearer your-token-here"
900
+ ```
901
+
902
+ **Get system metrics**
903
+
904
+ ```bash
905
+ curl -X GET "https://api.example.com/api/v1/system/metrics" \
906
+ -H "Authorization: Bearer your-token-here"
907
+ ```
908
+
909
+ ### Python Examples
910
+
911
+ **File Management**
912
+
913
+ ```python
914
+ import requests
915
+
916
+ headers = {"Authorization": "Bearer your-token-here"}
917
+ base_url = "https://api.example.com/api/v1"
918
+
919
+ # Upload file
920
+ with open("example.txt", "rb") as f:
921
+ files = {"file": f}
922
+ data = {"file_type": "document", "description": "Sample document"}
923
+ response = requests.post(f"{base_url}/files/upload", headers=headers, files=files, data=data)
924
+ file_data = response.json()
925
+ print(f"File uploaded: {file_data['data']['id']}")
926
+
927
+ # List files
928
+ response = requests.get(f"{base_url}/files", headers=headers, params={"page": 1, "items_per_page": 10})
929
+ files = response.json()
930
+ print(f"Found {files['data']['pagination']['total_items']} files")
931
+ ```
932
+
933
+ **Job Management**
934
+
935
+ ```python
936
+ # Get job logs
937
+ job_id = "job_789012"
938
+ response = requests.get(f"{base_url}/jobs/{job_id}/logs", headers=headers, params={"limit": 50, "level": "INFO"})
939
+ logs = response.json()
940
+ print(f"Retrieved {len(logs['data']['logs'])} log entries")
941
+
942
+ # Cancel job
943
+ response = requests.post(f"{base_url}/jobs/{job_id}/cancel", headers=headers)
944
+ if response.json()['success']:
945
+ print("Job cancelled successfully")
946
+ ```
947
+
948
+ **Video Processing**
949
+
950
+ ```python
951
+ # Generate video
952
+ job_data = {
953
+ "prompt": "A beautiful sunset over mountains",
954
+ "duration": 10,
955
+ "quality": "1080p"
956
+ }
957
+ response = requests.post(f"{base_url}/videos/generate", headers=headers, json=job_data)
958
+ job = response.json()
959
+ job_id = job['data']['job_id']
960
+ print(f"Video generation started: {job_id}")
961
+
962
+ # Check status
963
+ response = requests.get(f"{base_url}/videos/{job_id}/status", headers=headers)
964
+ status = response.json()
965
+ print(f"Job status: {status['data']['status']} ({status['data']['progress']}%)")
966
+ ```
967
+
968
+ **System Monitoring**
969
+
970
+ ```python
971
+ # Get system metrics
972
+ response = requests.get(f"{base_url}/system/metrics", headers=headers)
973
+ metrics = response.json()
974
+ print(f"CPU usage: {metrics['data']['cpu_usage']}%")
975
+ print(f"Memory usage: {metrics['data']['memory_usage']}%")
976
+
977
+ # Get queue status
978
+ response = requests.get(f"{base_url}/system/queue", headers=headers)
979
+ queue = response.json()
980
+ print(f"Jobs in queue: {queue['data']['pending_jobs']}")
981
+ ```
982
+
983
+ ### JavaScript Examples
984
+
985
+ **File Management**
986
+
987
+ ```javascript
988
+ const headers = {
989
+ Authorization: "Bearer your-token-here",
990
+ };
991
+ const baseUrl = "https://api.example.com/api/v1";
992
+
993
+ // Upload file
994
+ const formData = new FormData();
995
+ formData.append("file", fileInput.files[0]);
996
+ formData.append("file_type", "document");
997
+ formData.append("description", "Sample document");
998
+
999
+ fetch(`${baseUrl}/files/upload`, {
1000
+ method: "POST",
1001
+ headers: headers,
1002
+ body: formData,
1003
+ })
1004
+ .then((response) => response.json())
1005
+ .then((data) => console.log("File uploaded:", data.data.id));
1006
+
1007
+ // List files with pagination
1008
+ fetch(`${baseUrl}/files?page=1&items_per_page=10`, {
1009
+ headers: headers,
1010
+ })
1011
+ .then((response) => response.json())
1012
+ .then((data) =>
1013
+ console.log(`Found ${data.data.pagination.total_items} files`)
1014
+ );
1015
+ ```
1016
+
1017
+ **Job Management**
1018
+
1019
+ ```javascript
1020
+ // Get job logs
1021
+ const jobId = "job_789012";
1022
+ fetch(`${baseUrl}/jobs/${jobId}/logs?limit=50&level=INFO`, {
1023
+ headers: headers,
1024
+ })
1025
+ .then((response) => response.json())
1026
+ .then((data) =>
1027
+ console.log(`Retrieved ${data.data.logs.length} log entries`)
1028
+ );
1029
+
1030
+ // Cancel job
1031
+ fetch(`${baseUrl}/jobs/${jobId}/cancel`, {
1032
+ method: "POST",
1033
+ headers: headers,
1034
+ })
1035
+ .then((response) => response.json())
1036
+ .then((data) => {
1037
+ if (data.success) console.log("Job cancelled successfully");
1038
+ });
1039
+ ```
1040
+
1041
+ **Video Processing**
1042
+
1043
+ ```javascript
1044
+ // Generate video
1045
+ const jobData = {
1046
+ prompt: "A beautiful sunset over mountains",
1047
+ duration: 10,
1048
+ quality: "1080p",
1049
+ };
1050
+
1051
+ fetch(`${baseUrl}/videos/generate`, {
1052
+ method: "POST",
1053
+ headers: { ...headers, "Content-Type": "application/json" },
1054
+ body: JSON.stringify(jobData),
1055
+ })
1056
+ .then((response) => response.json())
1057
+ .then((data) => {
1058
+ const jobId = data.data.job_id;
1059
+ console.log("Video generation started:", jobId);
1060
+
1061
+ // Poll for status
1062
+ const checkStatus = () => {
1063
+ fetch(`${baseUrl}/videos/${jobId}/status`, { headers })
1064
+ .then((response) => response.json())
1065
+ .then((status) => {
1066
+ console.log(
1067
+ `Status: ${status.data.status} (${status.data.progress}%)`
1068
+ );
1069
+ if (status.data.status === "processing") {
1070
+ setTimeout(checkStatus, 5000); // Check again in 5 seconds
1071
+ }
1072
+ });
1073
+ };
1074
+ checkStatus();
1075
+ });
1076
+ ```
1077
+
1078
+ **System Monitoring**
1079
+
1080
+ ```javascript
1081
+ // Get system metrics
1082
+ fetch(`${baseUrl}/system/metrics`, { headers })
1083
+ .then((response) => response.json())
1084
+ .then((data) => {
1085
+ console.log(`CPU usage: ${data.data.cpu_usage}%`);
1086
+ console.log(`Memory usage: ${data.data.memory_usage}%`);
1087
+ });
1088
+
1089
+ // Get queue status
1090
+ fetch(`${baseUrl}/system/queue`, { headers })
1091
+ .then((response) => response.json())
1092
+ .then((data) => console.log(`Jobs in queue: ${data.data.pending_jobs}`));
1093
+ ```
1094
+
1095
+ ## Support
1096
+
1097
+ For API support and questions, please contact:
1098
+
1099
+ - Email: [email protected]
1100
+ - Documentation: https://docs.example.com
1101
+ - Status Page: https://status.example.com
1102
+
1103
+ ## Webhooks
1104
+
1105
+ The T2M API supports webhook notifications for long-running operations like video generation.
1106
+
1107
+ ### Webhook Configuration
1108
+
1109
+ Configure webhook URLs in your account settings or via the API:
1110
+
1111
+ ```bash
1112
+ curl -X POST "https://api.example.com/api/v1/webhooks" \
1113
+ -H "Authorization: Bearer your-token-here" \
1114
+ -H "Content-Type: application/json" \
1115
+ -d '{
1116
+ "url": "https://your-app.com/webhooks/t2m",
1117
+ "events": ["job.completed", "job.failed"],
1118
+ "secret": "your-webhook-secret"
1119
+ }'
1120
+ ```
1121
+
1122
+ ### Webhook Events
1123
+
1124
+ | Event | Description |
1125
+ | --------------- | ------------------------------- |
1126
+ | `job.started` | Job processing has begun |
1127
+ | `job.progress` | Job progress update (every 10%) |
1128
+ | `job.completed` | Job completed successfully |
1129
+ | `job.failed` | Job failed with error |
1130
+ | `job.cancelled` | Job was cancelled |
1131
+
1132
+ ### Webhook Payload Example
1133
+
1134
+ **Job Completed**
1135
+
1136
+ ```json
1137
+ {
1138
+ "event": "job.completed",
1139
+ "timestamp": "2024-01-15T10:35:30Z",
1140
+ "data": {
1141
+ "job_id": "job_789012",
1142
+ "type": "video_generation",
1143
+ "status": "completed",
1144
+ "result": {
1145
+ "file_id": "video_456789",
1146
+ "download_url": "https://api.example.com/api/v1/videos/job_789012/download",
1147
+ "thumbnail_url": "https://api.example.com/api/v1/videos/job_789012/thumbnail"
1148
+ }
1149
+ }
1150
+ }
1151
+ ```
1152
+
1153
+ ### Webhook Security
1154
+
1155
+ Verify webhook authenticity using the signature header:
1156
+
1157
+ ```python
1158
+ import hmac
1159
+ import hashlib
1160
+
1161
+ def verify_webhook(payload, signature, secret):
1162
+ expected = hmac.new(
1163
+ secret.encode('utf-8'),
1164
+ payload.encode('utf-8'),
1165
+ hashlib.sha256
1166
+ ).hexdigest()
1167
+ return hmac.compare_digest(f"sha256={expected}", signature)
1168
+
1169
+ # In your webhook handler
1170
+ signature = request.headers.get('X-T2M-Signature')
1171
+ if verify_webhook(request.body, signature, webhook_secret):
1172
+ # Process webhook
1173
+ pass
1174
+ ```
1175
+
1176
+ ## Advanced Features
1177
+
1178
+ ### Batch Operations
1179
+
1180
+ **Batch File Upload**
1181
+
1182
+ ```bash
1183
+ curl -X POST "https://api.example.com/api/v1/files/batch-upload" \
1184
+ -H "Authorization: Bearer your-token-here" \
1185
1186
1187
1188
+ -F "file_type=document"
1189
+ ```
1190
+
1191
+ ### Signed URLs for Secure Access
1192
+
1193
+ Generate temporary signed URLs for file access without authentication:
1194
+
1195
+ ```python
1196
+ # Request signed URL
1197
+ response = requests.post(f"{base_url}/files/{file_id}/signed-url",
1198
+ headers=headers,
1199
+ json={"expires_in": 3600}) # 1 hour
1200
+ signed_url = response.json()['data']['url']
1201
+
1202
+ # Use signed URL (no auth required)
1203
+ file_response = requests.get(signed_url)
1204
+ ```
1205
+
1206
+ ### Streaming and Quality Options
1207
+
1208
+ **Video Streaming with Quality Selection**
1209
+
1210
+ ```bash
1211
+ # Stream in different qualities
1212
+ curl "https://api.example.com/api/v1/videos/job_789012/stream?quality=720p" \
1213
+ -H "Authorization: Bearer your-token-here"
1214
+ ```
1215
+
1216
+ **Thumbnail Sizes**
1217
+
1218
+ ```bash
1219
+ # Get different thumbnail sizes
1220
+ curl "https://api.example.com/api/v1/videos/job_789012/thumbnail?size=large" \
1221
+ -H "Authorization: Bearer your-token-here"
1222
+ ```
1223
+
1224
+ ## Performance Optimization
1225
+
1226
+ ### Caching
1227
+
1228
+ The API implements intelligent caching. Use these endpoints to manage cache:
1229
+
1230
+ ```bash
1231
+ # Warm cache for better performance
1232
+ curl -X POST "https://api.example.com/api/v1/system/cache/warm" \
1233
+ -H "Authorization: Bearer your-token-here"
1234
+
1235
+ # Invalidate specific cache patterns
1236
+ curl -X POST "https://api.example.com/api/v1/system/cache/invalidate" \
1237
+ -H "Authorization: Bearer your-token-here" \
1238
+ -d "pattern=user:123:*"
1239
+ ```
1240
+
1241
+ ### Request Optimization
1242
+
1243
+ - Use pagination for large datasets
1244
+ - Implement client-side caching for frequently accessed data
1245
+ - Use appropriate quality settings for streaming
1246
+ - Batch operations when possible
1247
+
1248
+ ## Monitoring and Analytics
1249
+
1250
+ ### System Health Monitoring
1251
+
1252
+ ```bash
1253
+ # Check overall system health
1254
+ curl "https://api.example.com/api/v1/system/health"
1255
+
1256
+ # Get detailed performance metrics
1257
+ curl "https://api.example.com/api/v1/system/performance?hours=24" \
1258
+ -H "Authorization: Bearer your-token-here"
1259
+ ```
1260
+
1261
+ ### File Analytics
1262
+
1263
+ Track file usage and performance:
1264
+
1265
+ ```bash
1266
+ curl "https://api.example.com/api/v1/files/file_123456/analytics" \
1267
+ -H "Authorization: Bearer your-token-here"
1268
+ ```
1269
+
1270
+ ## Migration Guide
1271
+
1272
+ ### From v1.0 to v1.1
1273
+
1274
+ **Breaking Changes:**
1275
+
1276
+ - `GET /files/{id}/info` is now `GET /files/{id}` (consolidated endpoints)
1277
+ - Error response format now includes `details` field
1278
+ - Pagination format standardized across all endpoints
1279
+
1280
+ **New Features:**
1281
+
1282
+ - Webhook support for job notifications
1283
+ - Batch file operations
1284
+ - Enhanced error details
1285
+ - Signed URL support
1286
+
1287
+ **Migration Steps:**
1288
+
1289
+ 1. Update endpoint URLs for file info
1290
+ 2. Update error handling to use new format
1291
+ 3. Implement webhook handlers for better UX
1292
+ 4. Use batch operations for improved performance
1293
+
1294
+ ## Changelog
1295
+
1296
+ ### v1.1.0 (2024-01-15)
1297
+
1298
+ - Added webhook support
1299
+ - Introduced batch file operations
1300
+ - Enhanced error responses with details
1301
+ - Added signed URL generation
1302
+ - Improved pagination format
1303
+ - Added system performance endpoints
1304
+
1305
+ ### v1.0.0 (2023-12-01)
1306
+
1307
+ - Initial API release
1308
+ - Basic CRUD operations for files
1309
+ - Video generation capabilities
1310
+ - Job management system
1311
+ - System monitoring endpoints
AUTHENTICATION_GUIDE.md ADDED
@@ -0,0 +1,1120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # T2M Authentication & Session Flow Guide
2
+
3
+ ## Overview
4
+
5
+ This guide covers the complete authentication and session management flow for the T2M (Text-to-Media) application using Clerk as the authentication provider. It includes frontend SDK setup, route protection, API token management, and secure file access patterns.
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Clerk Setup & Configuration](#clerk-setup--configuration)
10
+ 2. [Frontend SDK Integration](#frontend-sdk-integration)
11
+ 3. [Route Protection Strategy](#route-protection-strategy)
12
+ 4. [API Token Management](#api-token-management)
13
+ 5. [Secure File Access](#secure-file-access)
14
+ 6. [Session Management](#session-management)
15
+ 7. [Security Best Practices](#security-best-practices)
16
+ 8. [Implementation Examples](#implementation-examples)
17
+
18
+ ## Clerk Setup & Configuration
19
+
20
+ ### 1. Clerk Dashboard Configuration
21
+
22
+ **Environment Setup:**
23
+ - **Development**: `https://dev.t2m-app.com`
24
+ - **Production**: `https://t2m-app.com`
25
+
26
+ **Required Clerk Settings:**
27
+ ```javascript
28
+ // Environment Variables
29
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
30
+ CLERK_SECRET_KEY=sk_test_...
31
+ NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
32
+ NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
33
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
34
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
35
+ ```
36
+
37
+ **Clerk Application Settings:**
38
+ - **Session token lifetime**: 7 days
39
+ - **JWT template**: Custom template for API integration
40
+ - **Allowed origins**: Your frontend domains
41
+ - **Webhook endpoints**: For user lifecycle events
42
+
43
+ ### 2. JWT Template Configuration
44
+
45
+ Create a custom JWT template in Clerk dashboard:
46
+
47
+ ```json
48
+ {
49
+ "aud": "t2m-api",
50
+ "exp": "{{session.expire_at}}",
51
+ "iat": "{{session.created_at}}",
52
+ "iss": "https://clerk.t2m-app.com",
53
+ "sub": "{{user.id}}",
54
+ "user_id": "{{user.id}}",
55
+ "email": "{{user.primary_email_address.email_address}}",
56
+ "role": "{{user.public_metadata.role}}",
57
+ "permissions": "{{user.public_metadata.permissions}}",
58
+ "subscription_tier": "{{user.public_metadata.subscription_tier}}"
59
+ }
60
+ ```
61
+
62
+ ## Frontend SDK Integration
63
+
64
+ ### 1. Next.js Setup (@clerk/nextjs)
65
+
66
+ **Installation:**
67
+ ```bash
68
+ npm install @clerk/nextjs
69
+ ```
70
+
71
+ **App Router Configuration (app/layout.tsx):**
72
+ ```typescript
73
+ import { ClerkProvider } from '@clerk/nextjs'
74
+ import { Inter } from 'next/font/google'
75
+ import './globals.css'
76
+
77
+ const inter = Inter({ subsets: ['latin'] })
78
+
79
+ export default function RootLayout({
80
+ children,
81
+ }: {
82
+ children: React.ReactNode
83
+ }) {
84
+ return (
85
+ <ClerkProvider>
86
+ <html lang="en">
87
+ <body className={inter.className}>
88
+ {children}
89
+ </body>
90
+ </html>
91
+ </ClerkProvider>
92
+ )
93
+ }
94
+ ```
95
+
96
+ **Middleware Setup (middleware.ts):**
97
+ ```typescript
98
+ import { authMiddleware } from "@clerk/nextjs";
99
+
100
+ export default authMiddleware({
101
+ // Public routes that don't require authentication
102
+ publicRoutes: [
103
+ "/",
104
+ "/api/auth/health",
105
+ "/api/system/health",
106
+ "/pricing",
107
+ "/about",
108
+ "/contact"
109
+ ],
110
+
111
+ // Routes that should be ignored by Clerk
112
+ ignoredRoutes: [
113
+ "/api/webhooks/clerk",
114
+ "/api/files/secure/(.*)" // Signed URL access
115
+ ],
116
+
117
+ // API routes that require authentication
118
+ apiRoutes: ["/api/(.*)"],
119
+
120
+ // Redirect after sign in
121
+ afterAuth(auth, req, evt) {
122
+ // Handle users who aren't authenticated
123
+ if (!auth.userId && !auth.isPublicRoute) {
124
+ return redirectToSignIn({ returnBackUrl: req.url });
125
+ }
126
+
127
+ // Redirect authenticated users away from public-only pages
128
+ if (auth.userId && auth.isPublicRoute && req.nextUrl.pathname === "/") {
129
+ const dashboard = new URL("/dashboard", req.url);
130
+ return NextResponse.redirect(dashboard);
131
+ }
132
+ }
133
+ });
134
+
135
+ export const config = {
136
+ matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
137
+ };
138
+ ```
139
+
140
+ ### 2. React Components Setup
141
+
142
+ **Authentication Components:**
143
+ ```typescript
144
+ // components/auth/SignInButton.tsx
145
+ import { SignInButton as ClerkSignInButton } from "@clerk/nextjs";
146
+
147
+ export function SignInButton() {
148
+ return (
149
+ <ClerkSignInButton mode="modal">
150
+ <button className="btn-primary">
151
+ Sign In
152
+ </button>
153
+ </ClerkSignInButton>
154
+ );
155
+ }
156
+
157
+ // components/auth/UserButton.tsx
158
+ import { UserButton as ClerkUserButton } from "@clerk/nextjs";
159
+
160
+ export function UserButton() {
161
+ return (
162
+ <ClerkUserButton
163
+ afterSignOutUrl="/"
164
+ appearance={{
165
+ elements: {
166
+ avatarBox: "w-8 h-8"
167
+ }
168
+ }}
169
+ />
170
+ );
171
+ }
172
+ ```
173
+
174
+ **Protected Page Component:**
175
+ ```typescript
176
+ // components/auth/ProtectedRoute.tsx
177
+ import { useAuth } from "@clerk/nextjs";
178
+ import { useRouter } from "next/navigation";
179
+ import { useEffect } from "react";
180
+ import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
181
+
182
+ interface ProtectedRouteProps {
183
+ children: React.ReactNode;
184
+ requiredRole?: string;
185
+ requiredPermissions?: string[];
186
+ }
187
+
188
+ export function ProtectedRoute({
189
+ children,
190
+ requiredRole,
191
+ requiredPermissions
192
+ }: ProtectedRouteProps) {
193
+ const { isLoaded, isSignedIn, user } = useAuth();
194
+ const router = useRouter();
195
+
196
+ useEffect(() => {
197
+ if (isLoaded && !isSignedIn) {
198
+ router.push("/sign-in");
199
+ }
200
+ }, [isLoaded, isSignedIn, router]);
201
+
202
+ if (!isLoaded) {
203
+ return <LoadingSpinner />;
204
+ }
205
+
206
+ if (!isSignedIn) {
207
+ return null;
208
+ }
209
+
210
+ // Check role-based access
211
+ if (requiredRole) {
212
+ const userRole = user?.publicMetadata?.role as string;
213
+ if (userRole !== requiredRole) {
214
+ return <div>Access denied. Required role: {requiredRole}</div>;
215
+ }
216
+ }
217
+
218
+ // Check permission-based access
219
+ if (requiredPermissions) {
220
+ const userPermissions = user?.publicMetadata?.permissions as string[] || [];
221
+ const hasPermission = requiredPermissions.every(permission =>
222
+ userPermissions.includes(permission)
223
+ );
224
+
225
+ if (!hasPermission) {
226
+ return <div>Access denied. Missing required permissions.</div>;
227
+ }
228
+ }
229
+
230
+ return <>{children}</>;
231
+ }
232
+ ```
233
+
234
+ ## Route Protection Strategy
235
+
236
+ ### 1. Public Routes (No Authentication Required)
237
+
238
+ ```typescript
239
+ // Public routes configuration
240
+ const PUBLIC_ROUTES = [
241
+ "/", // Landing page
242
+ "/pricing", // Pricing information
243
+ "/about", // About page
244
+ "/contact", // Contact page
245
+ "/api/auth/health", // Auth service health
246
+ "/api/system/health", // System health check
247
+ "/legal/privacy", // Privacy policy
248
+ "/legal/terms" // Terms of service
249
+ ];
250
+ ```
251
+
252
+ ### 2. Protected Routes (Authentication Required)
253
+
254
+ ```typescript
255
+ // Protected routes with different access levels
256
+ const PROTECTED_ROUTES = {
257
+ // Basic authenticated routes
258
+ AUTHENTICATED: [
259
+ "/dashboard",
260
+ "/profile",
261
+ "/files",
262
+ "/videos",
263
+ "/jobs"
264
+ ],
265
+
266
+ // Admin-only routes
267
+ ADMIN: [
268
+ "/admin",
269
+ "/admin/users",
270
+ "/admin/system",
271
+ "/admin/analytics"
272
+ ],
273
+
274
+ // Premium subscription routes
275
+ PREMIUM: [
276
+ "/premium/advanced-generation",
277
+ "/premium/batch-processing",
278
+ "/premium/priority-queue"
279
+ ]
280
+ };
281
+ ```
282
+
283
+ ### 3. API Route Protection
284
+
285
+ ```typescript
286
+ // app/api/auth/route-protection.ts
287
+ import { auth } from "@clerk/nextjs";
288
+ import { NextRequest, NextResponse } from "next/server";
289
+
290
+ export async function requireAuth(request: NextRequest) {
291
+ const { userId } = auth();
292
+
293
+ if (!userId) {
294
+ return NextResponse.json(
295
+ { error: "Unauthorized" },
296
+ { status: 401 }
297
+ );
298
+ }
299
+
300
+ return userId;
301
+ }
302
+
303
+ export async function requireRole(request: NextRequest, requiredRole: string) {
304
+ const { userId, sessionClaims } = auth();
305
+
306
+ if (!userId) {
307
+ return NextResponse.json(
308
+ { error: "Unauthorized" },
309
+ { status: 401 }
310
+ );
311
+ }
312
+
313
+ const userRole = sessionClaims?.metadata?.role as string;
314
+
315
+ if (userRole !== requiredRole) {
316
+ return NextResponse.json(
317
+ { error: "Forbidden" },
318
+ { status: 403 }
319
+ );
320
+ }
321
+
322
+ return userId;
323
+ }
324
+
325
+ // Usage in API routes
326
+ // app/api/files/route.ts
327
+ import { requireAuth } from "@/app/api/auth/route-protection";
328
+
329
+ export async function GET(request: NextRequest) {
330
+ const userId = await requireAuth(request);
331
+ if (userId instanceof NextResponse) return userId; // Error response
332
+
333
+ // Continue with authenticated logic
334
+ // ...
335
+ }
336
+ ```
337
+
338
+ ## API Token Management
339
+
340
+ ### 1. Token Retrieval in Frontend
341
+
342
+ ```typescript
343
+ // hooks/useApiToken.ts
344
+ import { useAuth } from "@clerk/nextjs";
345
+ import { useCallback } from "react";
346
+
347
+ export function useApiToken() {
348
+ const { getToken } = useAuth();
349
+
350
+ const getApiToken = useCallback(async () => {
351
+ try {
352
+ // Get token with custom JWT template
353
+ const token = await getToken({ template: "t2m-api" });
354
+ return token;
355
+ } catch (error) {
356
+ console.error("Failed to get API token:", error);
357
+ throw new Error("Authentication failed");
358
+ }
359
+ }, [getToken]);
360
+
361
+ return { getApiToken };
362
+ }
363
+
364
+ // Usage in components
365
+ function VideoUpload() {
366
+ const { getApiToken } = useApiToken();
367
+
368
+ const uploadVideo = async (file: File) => {
369
+ const token = await getApiToken();
370
+
371
+ const formData = new FormData();
372
+ formData.append('file', file);
373
+
374
+ const response = await fetch('/api/files/upload', {
375
+ method: 'POST',
376
+ headers: {
377
+ 'Authorization': `Bearer ${token}`
378
+ },
379
+ body: formData
380
+ });
381
+
382
+ return response.json();
383
+ };
384
+
385
+ // ...
386
+ }
387
+ ```
388
+
389
+ ### 2. API Client with Automatic Token Management
390
+
391
+ ```typescript
392
+ // lib/api-client.ts
393
+ import { useAuth } from "@clerk/nextjs";
394
+
395
+ class ApiClient {
396
+ private baseUrl: string;
397
+ private getToken: () => Promise<string | null>;
398
+
399
+ constructor(baseUrl: string, getToken: () => Promise<string | null>) {
400
+ this.baseUrl = baseUrl;
401
+ this.getToken = getToken;
402
+ }
403
+
404
+ private async request<T>(
405
+ endpoint: string,
406
+ options: RequestInit = {}
407
+ ): Promise<T> {
408
+ const token = await this.getToken();
409
+
410
+ const config: RequestInit = {
411
+ ...options,
412
+ headers: {
413
+ 'Content-Type': 'application/json',
414
+ ...(token && { 'Authorization': `Bearer ${token}` }),
415
+ ...options.headers,
416
+ },
417
+ };
418
+
419
+ const response = await fetch(`${this.baseUrl}${endpoint}`, config);
420
+
421
+ if (!response.ok) {
422
+ const error = await response.json();
423
+ throw new Error(error.message || 'API request failed');
424
+ }
425
+
426
+ return response.json();
427
+ }
428
+
429
+ // File operations
430
+ async uploadFile(file: File, metadata?: any) {
431
+ const formData = new FormData();
432
+ formData.append('file', file);
433
+ if (metadata) {
434
+ Object.entries(metadata).forEach(([key, value]) => {
435
+ formData.append(key, value as string);
436
+ });
437
+ }
438
+
439
+ const token = await this.getToken();
440
+ return fetch(`${this.baseUrl}/files/upload`, {
441
+ method: 'POST',
442
+ headers: {
443
+ ...(token && { 'Authorization': `Bearer ${token}` })
444
+ },
445
+ body: formData
446
+ }).then(res => res.json());
447
+ }
448
+
449
+ async getFiles(params?: any) {
450
+ const query = params ? `?${new URLSearchParams(params)}` : '';
451
+ return this.request(`/files${query}`);
452
+ }
453
+
454
+ // Video operations
455
+ async generateVideo(prompt: string, options?: any) {
456
+ return this.request('/videos/generate', {
457
+ method: 'POST',
458
+ body: JSON.stringify({ prompt, ...options })
459
+ });
460
+ }
461
+
462
+ async getJobStatus(jobId: string) {
463
+ return this.request(`/jobs/${jobId}`);
464
+ }
465
+ }
466
+
467
+ // Hook for using API client
468
+ export function useApiClient() {
469
+ const { getToken } = useAuth();
470
+
471
+ const apiClient = new ApiClient(
472
+ process.env.NEXT_PUBLIC_API_URL || '/api/v1',
473
+ () => getToken({ template: "t2m-api" })
474
+ );
475
+
476
+ return apiClient;
477
+ }
478
+ ```
479
+
480
+ ### 3. Backend Token Validation
481
+
482
+ ```typescript
483
+ // Backend API token validation (if using proxy)
484
+ // app/api/auth/validate-token.ts
485
+ import { verifyToken } from "@clerk/backend";
486
+
487
+ export async function validateClerkToken(token: string) {
488
+ try {
489
+ const payload = await verifyToken(token, {
490
+ jwtKey: process.env.CLERK_JWT_KEY,
491
+ authorizedParties: [process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY]
492
+ });
493
+
494
+ return {
495
+ userId: payload.sub,
496
+ email: payload.email,
497
+ role: payload.role,
498
+ permissions: payload.permissions,
499
+ subscriptionTier: payload.subscription_tier
500
+ };
501
+ } catch (error) {
502
+ throw new Error('Invalid token');
503
+ }
504
+ }
505
+
506
+ // Usage in API routes
507
+ export async function POST(request: NextRequest) {
508
+ const authHeader = request.headers.get('authorization');
509
+ const token = authHeader?.replace('Bearer ', '');
510
+
511
+ if (!token) {
512
+ return NextResponse.json({ error: 'No token provided' }, { status: 401 });
513
+ }
514
+
515
+ try {
516
+ const user = await validateClerkToken(token);
517
+ // Continue with authenticated logic
518
+ } catch (error) {
519
+ return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
520
+ }
521
+ }
522
+ ```
523
+
524
+ ## Secure File Access
525
+
526
+ ### 1. Signed URL Generation
527
+
528
+ ```typescript
529
+ // Backend: Generate signed URLs for secure file access
530
+ // app/api/files/[fileId]/signed-url/route.ts
531
+ import { auth } from "@clerk/nextjs";
532
+ import { createHmac } from "crypto";
533
+
534
+ export async function POST(
535
+ request: NextRequest,
536
+ { params }: { params: { fileId: string } }
537
+ ) {
538
+ const { userId } = auth();
539
+ if (!userId) {
540
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
541
+ }
542
+
543
+ const { fileId } = params;
544
+ const { expiresIn = 3600 } = await request.json(); // Default 1 hour
545
+
546
+ // Verify user owns the file
547
+ const file = await getFileById(fileId);
548
+ if (!file || file.userId !== userId) {
549
+ return NextResponse.json({ error: "File not found" }, { status: 404 });
550
+ }
551
+
552
+ // Generate signed URL
553
+ const expires = Math.floor(Date.now() / 1000) + expiresIn;
554
+ const signature = createHmac('sha256', process.env.FILE_SIGNING_SECRET!)
555
+ .update(`${fileId}:${userId}:${expires}`)
556
+ .digest('hex');
557
+
558
+ const signedUrl = `${process.env.NEXT_PUBLIC_API_URL}/files/secure/${fileId}?` +
559
+ `user_id=${userId}&expires=${expires}&signature=${signature}`;
560
+
561
+ return NextResponse.json({
562
+ success: true,
563
+ data: {
564
+ url: signedUrl,
565
+ expires_at: new Date(expires * 1000).toISOString()
566
+ }
567
+ });
568
+ }
569
+ ```
570
+
571
+ ### 2. Signed URL Validation
572
+
573
+ ```typescript
574
+ // Backend: Validate signed URLs
575
+ // app/api/files/secure/[fileId]/route.ts
576
+ import { createHmac } from "crypto";
577
+ import { NextRequest, NextResponse } from "next/server";
578
+
579
+ export async function GET(
580
+ request: NextRequest,
581
+ { params }: { params: { fileId: string } }
582
+ ) {
583
+ const { fileId } = params;
584
+ const { searchParams } = new URL(request.url);
585
+
586
+ const userId = searchParams.get('user_id');
587
+ const expires = searchParams.get('expires');
588
+ const signature = searchParams.get('signature');
589
+
590
+ if (!userId || !expires || !signature) {
591
+ return NextResponse.json({ error: "Invalid signed URL" }, { status: 400 });
592
+ }
593
+
594
+ // Check expiration
595
+ const expiresTimestamp = parseInt(expires);
596
+ if (Date.now() / 1000 > expiresTimestamp) {
597
+ return NextResponse.json({ error: "Signed URL expired" }, { status: 410 });
598
+ }
599
+
600
+ // Verify signature
601
+ const expectedSignature = createHmac('sha256', process.env.FILE_SIGNING_SECRET!)
602
+ .update(`${fileId}:${userId}:${expires}`)
603
+ .digest('hex');
604
+
605
+ if (signature !== expectedSignature) {
606
+ return NextResponse.json({ error: "Invalid signature" }, { status: 403 });
607
+ }
608
+
609
+ // Serve file
610
+ const file = await getFileById(fileId);
611
+ if (!file || file.userId !== userId) {
612
+ return NextResponse.json({ error: "File not found" }, { status: 404 });
613
+ }
614
+
615
+ // Return file stream
616
+ const fileStream = await getFileStream(file.path);
617
+ return new NextResponse(fileStream, {
618
+ headers: {
619
+ 'Content-Type': file.contentType,
620
+ 'Content-Disposition': `attachment; filename="${file.filename}"`,
621
+ 'Cache-Control': 'private, max-age=3600'
622
+ }
623
+ });
624
+ }
625
+ ```
626
+
627
+ ### 3. Frontend: Secure Video Player
628
+
629
+ ```typescript
630
+ // components/VideoPlayer.tsx
631
+ import { useApiClient } from "@/lib/api-client";
632
+ import { useEffect, useState } from "react";
633
+
634
+ interface VideoPlayerProps {
635
+ jobId: string;
636
+ autoplay?: boolean;
637
+ }
638
+
639
+ export function VideoPlayer({ jobId, autoplay = false }: VideoPlayerProps) {
640
+ const [signedUrl, setSignedUrl] = useState<string | null>(null);
641
+ const [loading, setLoading] = useState(true);
642
+ const [error, setError] = useState<string | null>(null);
643
+ const apiClient = useApiClient();
644
+
645
+ useEffect(() => {
646
+ async function getSignedUrl() {
647
+ try {
648
+ setLoading(true);
649
+
650
+ // Get signed URL for video
651
+ const response = await apiClient.request(`/videos/${jobId}/signed-url`, {
652
+ method: 'POST',
653
+ body: JSON.stringify({ expiresIn: 3600 }) // 1 hour
654
+ });
655
+
656
+ setSignedUrl(response.data.url);
657
+ } catch (err) {
658
+ setError(err instanceof Error ? err.message : 'Failed to load video');
659
+ } finally {
660
+ setLoading(false);
661
+ }
662
+ }
663
+
664
+ getSignedUrl();
665
+ }, [jobId, apiClient]);
666
+
667
+ if (loading) return <div>Loading video...</div>;
668
+ if (error) return <div>Error: {error}</div>;
669
+ if (!signedUrl) return <div>Video not available</div>;
670
+
671
+ return (
672
+ <video
673
+ src={signedUrl}
674
+ controls
675
+ autoPlay={autoplay}
676
+ className="w-full h-auto rounded-lg"
677
+ onError={() => setError('Failed to load video')}
678
+ >
679
+ Your browser does not support the video tag.
680
+ </video>
681
+ );
682
+ }
683
+ ```
684
+
685
+ ## Session Management
686
+
687
+ ### 1. Session Configuration
688
+
689
+ ```typescript
690
+ // lib/session-config.ts
691
+ export const SESSION_CONFIG = {
692
+ // Session duration
693
+ maxAge: 7 * 24 * 60 * 60, // 7 days in seconds
694
+
695
+ // Token refresh threshold
696
+ refreshThreshold: 5 * 60, // Refresh if expires in 5 minutes
697
+
698
+ // Automatic logout on inactivity
699
+ inactivityTimeout: 30 * 60, // 30 minutes
700
+
701
+ // Remember me option
702
+ rememberMe: {
703
+ enabled: true,
704
+ duration: 30 * 24 * 60 * 60 // 30 days
705
+ }
706
+ };
707
+ ```
708
+
709
+ ### 2. Session Monitoring Hook
710
+
711
+ ```typescript
712
+ // hooks/useSessionMonitor.ts
713
+ import { useAuth } from "@clerk/nextjs";
714
+ import { useEffect, useRef } from "react";
715
+
716
+ export function useSessionMonitor() {
717
+ const { isSignedIn, signOut } = useAuth();
718
+ const lastActivityRef = useRef(Date.now());
719
+ const inactivityTimerRef = useRef<NodeJS.Timeout>();
720
+
721
+ const resetInactivityTimer = () => {
722
+ lastActivityRef.current = Date.now();
723
+
724
+ if (inactivityTimerRef.current) {
725
+ clearTimeout(inactivityTimerRef.current);
726
+ }
727
+
728
+ inactivityTimerRef.current = setTimeout(() => {
729
+ if (isSignedIn) {
730
+ signOut();
731
+ alert('You have been logged out due to inactivity.');
732
+ }
733
+ }, SESSION_CONFIG.inactivityTimeout * 1000);
734
+ };
735
+
736
+ useEffect(() => {
737
+ if (!isSignedIn) return;
738
+
739
+ const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
740
+
741
+ events.forEach(event => {
742
+ document.addEventListener(event, resetInactivityTimer, true);
743
+ });
744
+
745
+ resetInactivityTimer(); // Initialize timer
746
+
747
+ return () => {
748
+ events.forEach(event => {
749
+ document.removeEventListener(event, resetInactivityTimer, true);
750
+ });
751
+
752
+ if (inactivityTimerRef.current) {
753
+ clearTimeout(inactivityTimerRef.current);
754
+ }
755
+ };
756
+ }, [isSignedIn]);
757
+ }
758
+ ```
759
+
760
+ ### 3. Token Refresh Management
761
+
762
+ ```typescript
763
+ // hooks/useTokenRefresh.ts
764
+ import { useAuth } from "@clerk/nextjs";
765
+ import { useEffect, useCallback } from "react";
766
+
767
+ export function useTokenRefresh() {
768
+ const { getToken, isSignedIn } = useAuth();
769
+
770
+ const checkTokenExpiry = useCallback(async () => {
771
+ if (!isSignedIn) return;
772
+
773
+ try {
774
+ const token = await getToken({ template: "t2m-api" });
775
+ if (!token) return;
776
+
777
+ // Decode JWT to check expiry
778
+ const payload = JSON.parse(atob(token.split('.')[1]));
779
+ const expiryTime = payload.exp * 1000; // Convert to milliseconds
780
+ const currentTime = Date.now();
781
+ const timeUntilExpiry = expiryTime - currentTime;
782
+
783
+ // Refresh if token expires within threshold
784
+ if (timeUntilExpiry < SESSION_CONFIG.refreshThreshold * 1000) {
785
+ await getToken({ template: "t2m-api", skipCache: true });
786
+ }
787
+ } catch (error) {
788
+ console.error('Token refresh failed:', error);
789
+ }
790
+ }, [getToken, isSignedIn]);
791
+
792
+ useEffect(() => {
793
+ if (!isSignedIn) return;
794
+
795
+ // Check token expiry every minute
796
+ const interval = setInterval(checkTokenExpiry, 60 * 1000);
797
+
798
+ return () => clearInterval(interval);
799
+ }, [isSignedIn, checkTokenExpiry]);
800
+ }
801
+ ```
802
+
803
+ ## Security Best Practices
804
+
805
+ ### 1. Token Security
806
+
807
+ ```typescript
808
+ // Security guidelines for token handling
809
+
810
+ // ✅ DO: Use secure token storage
811
+ const { getToken } = useAuth();
812
+ const token = await getToken({ template: "t2m-api" });
813
+
814
+ // ❌ DON'T: Store tokens in localStorage or sessionStorage
815
+ localStorage.setItem('token', token); // NEVER DO THIS
816
+
817
+ // ✅ DO: Use tokens for API calls only
818
+ const response = await fetch('/api/files', {
819
+ headers: { 'Authorization': `Bearer ${token}` }
820
+ });
821
+
822
+ // ❌ DON'T: Embed tokens in URLs or HTML
823
+ const videoUrl = `/video?token=${token}`; // NEVER DO THIS
824
+ ```
825
+
826
+ ### 2. CSRF Protection
827
+
828
+ ```typescript
829
+ // middleware.ts - CSRF protection
830
+ import { NextRequest, NextResponse } from "next/server";
831
+
832
+ export function middleware(request: NextRequest) {
833
+ // CSRF protection for state-changing operations
834
+ if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method)) {
835
+ const origin = request.headers.get('origin');
836
+ const host = request.headers.get('host');
837
+
838
+ if (origin && !origin.includes(host!)) {
839
+ return NextResponse.json(
840
+ { error: 'CSRF protection: Invalid origin' },
841
+ { status: 403 }
842
+ );
843
+ }
844
+ }
845
+
846
+ return NextResponse.next();
847
+ }
848
+ ```
849
+
850
+ ### 3. Rate Limiting
851
+
852
+ ```typescript
853
+ // lib/rate-limiter.ts
854
+ import { Ratelimit } from "@upstash/ratelimit";
855
+ import { Redis } from "@upstash/redis";
856
+
857
+ const redis = new Redis({
858
+ url: process.env.UPSTASH_REDIS_REST_URL!,
859
+ token: process.env.UPSTASH_REDIS_REST_TOKEN!,
860
+ });
861
+
862
+ export const rateLimiter = new Ratelimit({
863
+ redis,
864
+ limiter: Ratelimit.slidingWindow(100, "1 h"), // 100 requests per hour
865
+ analytics: true,
866
+ });
867
+
868
+ // Usage in API routes
869
+ export async function POST(request: NextRequest) {
870
+ const { userId } = auth();
871
+ if (!userId) {
872
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
873
+ }
874
+
875
+ const { success, limit, reset, remaining } = await rateLimiter.limit(userId);
876
+
877
+ if (!success) {
878
+ return NextResponse.json(
879
+ { error: "Rate limit exceeded" },
880
+ {
881
+ status: 429,
882
+ headers: {
883
+ 'X-RateLimit-Limit': limit.toString(),
884
+ 'X-RateLimit-Remaining': remaining.toString(),
885
+ 'X-RateLimit-Reset': reset.toString(),
886
+ }
887
+ }
888
+ );
889
+ }
890
+
891
+ // Continue with request processing
892
+ }
893
+ ```
894
+
895
+ ## Implementation Examples
896
+
897
+ ### 1. Complete Authentication Flow
898
+
899
+ ```typescript
900
+ // app/dashboard/page.tsx
901
+ import { ProtectedRoute } from "@/components/auth/ProtectedRoute";
902
+ import { useSessionMonitor } from "@/hooks/useSessionMonitor";
903
+ import { useTokenRefresh } from "@/hooks/useTokenRefresh";
904
+
905
+ export default function DashboardPage() {
906
+ useSessionMonitor(); // Monitor for inactivity
907
+ useTokenRefresh(); // Handle token refresh
908
+
909
+ return (
910
+ <ProtectedRoute>
911
+ <div className="dashboard">
912
+ <h1>Welcome to T2M Dashboard</h1>
913
+ {/* Dashboard content */}
914
+ </div>
915
+ </ProtectedRoute>
916
+ );
917
+ }
918
+ ```
919
+
920
+ ### 2. File Upload with Progress
921
+
922
+ ```typescript
923
+ // components/FileUpload.tsx
924
+ import { useApiClient } from "@/lib/api-client";
925
+ import { useState } from "react";
926
+
927
+ export function FileUpload() {
928
+ const [uploading, setUploading] = useState(false);
929
+ const [progress, setProgress] = useState(0);
930
+ const apiClient = useApiClient();
931
+
932
+ const handleUpload = async (file: File) => {
933
+ setUploading(true);
934
+ setProgress(0);
935
+
936
+ try {
937
+ // Create XMLHttpRequest for progress tracking
938
+ const formData = new FormData();
939
+ formData.append('file', file);
940
+
941
+ const token = await apiClient.getApiToken();
942
+
943
+ const xhr = new XMLHttpRequest();
944
+
945
+ xhr.upload.addEventListener('progress', (e) => {
946
+ if (e.lengthComputable) {
947
+ setProgress((e.loaded / e.total) * 100);
948
+ }
949
+ });
950
+
951
+ xhr.addEventListener('load', () => {
952
+ if (xhr.status === 200) {
953
+ const response = JSON.parse(xhr.responseText);
954
+ console.log('Upload successful:', response);
955
+ }
956
+ });
957
+
958
+ xhr.open('POST', '/api/files/upload');
959
+ xhr.setRequestHeader('Authorization', `Bearer ${token}`);
960
+ xhr.send(formData);
961
+
962
+ } catch (error) {
963
+ console.error('Upload failed:', error);
964
+ } finally {
965
+ setUploading(false);
966
+ }
967
+ };
968
+
969
+ return (
970
+ <div className="file-upload">
971
+ <input
972
+ type="file"
973
+ onChange={(e) => {
974
+ const file = e.target.files?.[0];
975
+ if (file) handleUpload(file);
976
+ }}
977
+ disabled={uploading}
978
+ />
979
+
980
+ {uploading && (
981
+ <div className="progress-bar">
982
+ <div
983
+ className="progress-fill"
984
+ style={{ width: `${progress}%` }}
985
+ />
986
+ <span>{Math.round(progress)}%</span>
987
+ </div>
988
+ )}
989
+ </div>
990
+ );
991
+ }
992
+ ```
993
+
994
+ ### 3. Video Generation with Real-time Updates
995
+
996
+ ```typescript
997
+ // components/VideoGenerator.tsx
998
+ import { useApiClient } from "@/lib/api-client";
999
+ import { useState, useEffect } from "react";
1000
+
1001
+ export function VideoGenerator() {
1002
+ const [prompt, setPrompt] = useState("");
1003
+ const [jobId, setJobId] = useState<string | null>(null);
1004
+ const [status, setStatus] = useState<string>("idle");
1005
+ const [progress, setProgress] = useState(0);
1006
+ const apiClient = useApiClient();
1007
+
1008
+ const generateVideo = async () => {
1009
+ try {
1010
+ setStatus("starting");
1011
+
1012
+ const response = await apiClient.generateVideo(prompt, {
1013
+ duration: 10,
1014
+ quality: "1080p"
1015
+ });
1016
+
1017
+ setJobId(response.data.job_id);
1018
+ setStatus("processing");
1019
+ } catch (error) {
1020
+ console.error('Video generation failed:', error);
1021
+ setStatus("error");
1022
+ }
1023
+ };
1024
+
1025
+ // Poll for job status
1026
+ useEffect(() => {
1027
+ if (!jobId || status === "completed" || status === "error") return;
1028
+
1029
+ const pollStatus = async () => {
1030
+ try {
1031
+ const response = await apiClient.getJobStatus(jobId);
1032
+ setStatus(response.data.status);
1033
+ setProgress(response.data.progress || 0);
1034
+ } catch (error) {
1035
+ console.error('Status check failed:', error);
1036
+ }
1037
+ };
1038
+
1039
+ const interval = setInterval(pollStatus, 2000); // Poll every 2 seconds
1040
+ return () => clearInterval(interval);
1041
+ }, [jobId, status, apiClient]);
1042
+
1043
+ return (
1044
+ <div className="video-generator">
1045
+ <textarea
1046
+ value={prompt}
1047
+ onChange={(e) => setPrompt(e.target.value)}
1048
+ placeholder="Describe your video..."
1049
+ disabled={status === "processing"}
1050
+ />
1051
+
1052
+ <button
1053
+ onClick={generateVideo}
1054
+ disabled={!prompt || status === "processing"}
1055
+ >
1056
+ {status === "processing" ? "Generating..." : "Generate Video"}
1057
+ </button>
1058
+
1059
+ {status === "processing" && (
1060
+ <div className="status">
1061
+ <div className="progress-bar">
1062
+ <div style={{ width: `${progress}%` }} />
1063
+ </div>
1064
+ <p>Progress: {progress}%</p>
1065
+ </div>
1066
+ )}
1067
+
1068
+ {status === "completed" && jobId && (
1069
+ <VideoPlayer jobId={jobId} />
1070
+ )}
1071
+ </div>
1072
+ );
1073
+ }
1074
+ ```
1075
+
1076
+ ## Troubleshooting
1077
+
1078
+ ### Common Issues
1079
+
1080
+ 1. **Token Expiry**: Implement automatic token refresh
1081
+ 2. **CORS Issues**: Configure Clerk allowed origins properly
1082
+ 3. **Webhook Failures**: Verify webhook URL accessibility
1083
+ 4. **Rate Limiting**: Implement proper rate limiting and user feedback
1084
+
1085
+ ### Debug Tools
1086
+
1087
+ ```typescript
1088
+ // Debug helper for authentication issues
1089
+ export function useAuthDebug() {
1090
+ const { isLoaded, isSignedIn, user, getToken } = useAuth();
1091
+
1092
+ const debugAuth = async () => {
1093
+ console.log('Auth Debug Info:', {
1094
+ isLoaded,
1095
+ isSignedIn,
1096
+ userId: user?.id,
1097
+ email: user?.primaryEmailAddress?.emailAddress,
1098
+ metadata: user?.publicMetadata
1099
+ });
1100
+
1101
+ if (isSignedIn) {
1102
+ try {
1103
+ const token = await getToken({ template: "t2m-api" });
1104
+ console.log('Token:', token);
1105
+
1106
+ if (token) {
1107
+ const payload = JSON.parse(atob(token.split('.')[1]));
1108
+ console.log('Token payload:', payload);
1109
+ }
1110
+ } catch (error) {
1111
+ console.error('Token error:', error);
1112
+ }
1113
+ }
1114
+ };
1115
+
1116
+ return { debugAuth };
1117
+ }
1118
+ ```
1119
+
1120
+ This comprehensive guide covers all aspects of authentication and session management for the T2M application, ensuring secure and efficient user experience while maintaining best practices for token handling and file access.
CLERK_TOKEN_GUIDE.md ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Your Clerk Token - Step by Step Guide
2
+
3
+ ## 🔍 Your Clerk Configuration
4
+
5
+ Based on the API response, your Clerk app (`poetic-primate-48.clerk.accounts.dev`) is configured to only support:
6
+ - ✅ Google OAuth (`oauth_google`)
7
+ - ✅ Ticket-based authentication (`ticket`)
8
+ - ❌ Email/Password authentication (not enabled)
9
+
10
+ ## 🎯 Recommended Method: Browser Console
11
+
12
+ This is the easiest and most reliable method:
13
+
14
+ ### Step 1: Sign in via Google OAuth
15
+ 1. Open your browser
16
+ 2. Go to: `https://poetic-primate-48.clerk.accounts.dev`
17
+ 3. Click "Sign in with Google"
18
+ 4. Complete the Google OAuth flow
19
+
20
+ ### Step 2: Get the Token
21
+ 1. Once signed in, open Developer Tools (Press F12)
22
+ 2. Go to the **Console** tab
23
+ 3. Paste this command and press Enter:
24
+ ```javascript
25
+ window.Clerk.session.getToken().then(token => console.log('TOKEN:', token))
26
+ ```
27
+ 4. Copy the token (the long string after "TOKEN:")
28
+
29
+ ### Step 3: Test Your API
30
+ ```bash
31
+ python test_current_api.py http://localhost:8000/api/v1 YOUR_TOKEN_HERE
32
+ ```
33
+
34
+ ## 🚀 Quick Start Scripts
35
+
36
+ ### Option A: Use the OAuth Helper
37
+ ```bash
38
+ python get_clerk_token_oauth.py
39
+ ```
40
+ Choose option 2 (Manual browser method)
41
+
42
+ ### Option B: Use the Simple Helper
43
+ ```bash
44
+ python get_token_simple.py
45
+ ```
46
+ Choose option 1 (Browser Console Method)
47
+
48
+ ## 📋 Complete Example
49
+
50
+ 1. **Open browser and sign in:**
51
+ - Go to: https://poetic-primate-48.clerk.accounts.dev
52
+ - Sign in with Google
53
+
54
+ 2. **Get token in console:**
55
+ ```javascript
56
+ window.Clerk.session.getToken().then(token => console.log('TOKEN:', token))
57
+ ```
58
+
59
+ 3. **Copy the token** (looks like: `eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18...`)
60
+
61
+ 4. **Test your API:**
62
+ ```bash
63
+ python test_current_api.py http://localhost:8000/api/v1 eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18...
64
+ ```
65
+
66
+ ## 🔧 Why Email/Password Didn't Work
67
+
68
+ The API response showed:
69
+ ```json
70
+ {
71
+ "status": "needs_identifier",
72
+ "supported_first_factors": [
73
+ {"strategy": "ticket"},
74
+ {"strategy": "oauth_google"}
75
+ ]
76
+ }
77
+ ```
78
+
79
+ This means your Clerk app is configured to only allow:
80
+ - Google OAuth sign-in
81
+ - Ticket-based authentication (for programmatic access)
82
+
83
+ Email/password authentication is not enabled in your Clerk dashboard.
84
+
85
+ ## 🎯 Next Steps
86
+
87
+ Once you have your token:
88
+
89
+ 1. **Test basic connectivity:**
90
+ ```bash
91
+ python test_current_api.py http://localhost:8000/api/v1 YOUR_TOKEN
92
+ ```
93
+
94
+ 2. **Test video generation:**
95
+ ```bash
96
+ python test_video_generation.py --base-url http://localhost:8000/api/v1 --token YOUR_TOKEN --quick
97
+ ```
98
+
99
+ 3. **Run comprehensive tests:**
100
+ ```bash
101
+ python test_video_generation.py --base-url http://localhost:8000/api/v1 --token YOUR_TOKEN
102
+ ```
103
+
104
+ ## 💡 Pro Tips
105
+
106
+ - **Tokens expire**: You may need to get a fresh token periodically
107
+ - **Save your token**: The scripts will save it to `auth_token.txt` for reuse
108
+ - **Use the browser method**: It's the most reliable approach
109
+ - **Check token format**: Valid tokens start with `eyJ`
110
+
111
+ ## 🆘 Troubleshooting
112
+
113
+ **If the browser console method doesn't work:**
114
+ 1. Make sure you're signed in to your app
115
+ 2. Check that `window.Clerk` exists by typing it in console
116
+ 3. Try refreshing the page after signing in
117
+ 4. Make sure you're on the correct domain
118
+
119
+ **If you get "Clerk not found" errors:**
120
+ 1. Make sure you're on a page where Clerk is loaded
121
+ 2. Try going to the main app page after signing in
122
+ 3. Check the Network tab for any Clerk-related requests
Dockerfile CHANGED
@@ -1,105 +1,28 @@
1
- # Optimized Dockerfile for Hugging Face Spaces
2
- FROM python:3.12-slim
3
 
4
  # Set working directory
5
  WORKDIR /app
6
 
7
- # Set environment variables for HF Spaces
8
- ENV DEBIAN_FRONTEND=noninteractive \
9
- PYTHONUNBUFFERED=1 \
10
- PYTHONPATH=/app:/app/src \
11
- GRADIO_SERVER_NAME=0.0.0.0 \
12
- GRADIO_SERVER_PORT=7860 \
13
- HF_HOME=/app/.cache/huggingface \
14
- HF_HUB_CACHE=/app/.cache/huggingface/hub \
15
- TRANSFORMERS_CACHE=/app/.cache/transformers \
16
- SENTENCE_TRANSFORMERS_HOME=/app/.cache/sentence_transformers \
17
- PATH="/root/.TinyTeX/bin/x86_64-linux:$PATH" \
18
- GRADIO_ALLOW_FLAGGING=never \
19
- GRADIO_ANALYTICS_ENABLED=False \
20
- HF_HUB_DISABLE_TELEMETRY=1
21
-
22
- # Install system dependencies in single layer
23
- RUN apt-get update && apt-get install -y --no-install-recommends \
24
- wget \
25
- curl \
26
- git \
27
- gcc \
28
- g++ \
29
  build-essential \
30
- pkg-config \
31
- portaudio19-dev \
32
- libasound2-dev \
33
- libsdl-pango-dev \
34
- libcairo2-dev \
35
- libpango1.0-dev \
36
- sox \
37
- ffmpeg \
38
- texlive-full \
39
- dvisvgm \
40
- ghostscript \
41
- ca-certificates \
42
- && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
43
-
44
 
45
- # Copy requirements and install Python dependencies
46
  COPY requirements.txt .
47
- RUN pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu \
48
- && pip install --no-cache-dir -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu \
49
- && python -c "import gradio; print(f'Gradio version: {gradio.__version__}')" \
50
- && find /usr/local -name "*.pyc" -delete \
51
- && find /usr/local -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
52
-
53
- # Ensure Hugging Face cache directories are writable
54
 
55
- # Create models directory and download models efficiently
56
- RUN mkdir -p models && cd models \
57
- && echo "Downloading models for HF Spaces..." \
58
- && wget --progress=dot:giga --timeout=30 --tries=3 \
59
- -O kokoro-v0_19.onnx \
60
- "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/kokoro-v0_19.onnx" \
61
- && wget --progress=dot:giga --timeout=30 --tries=3 \
62
- -O voices.bin \
63
- "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/voices.bin" \
64
- && ls -la /app/models/
65
 
66
- # Copy all project files and folders
67
  COPY . .
68
 
69
- ENV PYTHONPATH="/app:$PYTHONPATH"
70
- RUN echo "export PYTHONPATH=/app:\$PYTHONPATH" >> ~/.bashrc
71
-
72
- # Run embedding creation script at build time
73
- RUN python create_embeddings.py
74
-
75
- # Ensure all files are writable (fix PermissionError for log file)
76
- RUN chmod -R a+w /app
77
-
78
- # Create output directory
79
- RUN mkdir -p output tmp
80
- # Ensure output and tmp directories are writable (fix PermissionError for session_id.txt)
81
- RUN chmod -R a+w /app/output /app/tmp || true
82
-
83
- RUN mkdir -p output tmp logs \
84
- && mkdir -p /app/.cache/huggingface/hub \
85
- && mkdir -p /app/.cache/transformers \
86
- && mkdir -p /app/.cache/sentence_transformers \
87
- && chmod -R 755 /app/.cache \
88
- && chmod 755 /app/models \
89
- && ls -la /app/models/ \
90
- && echo "Cache directories created with proper permissions"
91
- # Add HF Spaces specific metadata
92
- LABEL space.title="Text 2 Mnaim" \
93
- space.sdk="docker" \
94
- space.author="khanhthanhdev" \
95
- space.description="Text to science video using multi Agent"
96
-
97
- # Expose the port that HF Spaces expects
98
- EXPOSE 7860
99
 
100
- # Health check optimized for HF Spaces
101
- HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=2 \
102
- CMD curl -f http://localhost:7860/ || exit 1
103
 
104
- # Run the Gradio app with HF Spaces optimized settings
105
- CMD ["python", "gradio_app.py"]
 
1
+ FROM python:3.11-slim
 
2
 
3
  # Set working directory
4
  WORKDIR /app
5
 
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  build-essential \
9
+ curl \
10
+ && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ # Copy requirements first for better caching
13
  COPY requirements.txt .
 
 
 
 
 
 
 
14
 
15
+ # Install Python dependencies
16
+ RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
 
 
 
17
 
18
+ # Copy application code
19
  COPY . .
20
 
21
+ # Create necessary directories
22
+ RUN mkdir -p uploads videos logs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ # Expose port
25
+ EXPOSE 8000
 
26
 
27
+ # Command to run the application
28
+ CMD ["python", "-m", "uvicorn", "src.app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
FASTAPI_SETUP_GUIDE.md ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastAPI Server Setup Guide
2
+
3
+ This guide will help you set up and run the FastAPI server for the T2M (Text-to-Media) video generation system.
4
+
5
+ ## Prerequisites
6
+
7
+ - Python 3.11 or higher
8
+ - Redis server (for caching and job queuing)
9
+ - Git (for cloning the repository)
10
+
11
+ ## Quick Start
12
+
13
+ ### 1. Environment Setup
14
+
15
+ Your `.env` file is already configured with your Clerk keys. You just need to add the webhook secret once you create the webhook endpoint.
16
+
17
+ ### 2. Get ngrok Auth Token (if not already set)
18
+
19
+ If you don't have an ngrok auth token in your `.env` file:
20
+
21
+ 1. Go to [ngrok Dashboard](https://dashboard.ngrok.com/get-started/your-authtoken)
22
+ 2. Sign up/login and copy your auth token
23
+ 3. Add it to your `.env` file:
24
+ ```env
25
+ NGROK_AUTHTOKEN=your_token_here
26
+ ```
27
+
28
+ ### 3. Quick Setup with Docker (Recommended)
29
+
30
+ Run the automated setup script:
31
+
32
+ **On Windows:**
33
+
34
+ ```cmd
35
+ setup-dev.bat
36
+ ```
37
+
38
+ **On Linux/macOS:**
39
+
40
+ ```bash
41
+ chmod +x setup-dev.sh
42
+ ./setup-dev.sh
43
+ ```
44
+
45
+ **Or manually with Make:**
46
+
47
+ ```bash
48
+ make dev-services
49
+ ```
50
+
51
+ ### 4. Get Your Public ngrok URL
52
+
53
+ After running the setup script:
54
+
55
+ 1. Visit the ngrok dashboard: http://localhost:4040
56
+ 2. Copy the HTTPS URL (e.g., `https://abc123.ngrok.io`)
57
+ 3. This is your public URL for webhooks
58
+
59
+ **Or get it via command:**
60
+
61
+ ```bash
62
+ make get-ngrok-url
63
+ ```
64
+
65
+ ### 5. Configure Clerk Webhook
66
+
67
+ 1. Go to [Clerk Dashboard](https://dashboard.clerk.com/)
68
+ 2. Navigate to **Webhooks**
69
+ 3. Click **"Add Endpoint"**
70
+ 4. Enter your ngrok URL + webhook path:
71
+ ```
72
+ https://your-ngrok-url.ngrok.io/api/v1/auth/webhooks/clerk
73
+ ```
74
+ 5. Select events: `user.created`, `user.updated`, `user.deleted`, `session.created`, `session.ended`
75
+ 6. Copy the **Signing Secret** and add it to your `.env`:
76
+ ```env
77
+ CLERK_WEBHOOK_SECRET=whsec_your_webhook_signing_secret_here
78
+ ```
79
+
80
+ ### 6. Install Python Dependencies
81
+
82
+ ```bash
83
+ pip install -r requirements.txt
84
+ ```
85
+
86
+ ### 7. Start the FastAPI Server
87
+
88
+ #### Method 1: Using the Makefile (Recommended)
89
+
90
+ ```bash
91
+ make serve-api
92
+ ```
93
+
94
+ #### Method 2: Using uvicorn directly
95
+
96
+ ```bash
97
+ python -m uvicorn src.app.main:app --reload --host 0.0.0.0 --port 8000
98
+ ```
99
+
100
+ #### Method 3: Full development workflow (services + API)
101
+
102
+ ```bash
103
+ make dev-start
104
+ ```
105
+
106
+ ## Verification
107
+
108
+ Once the server is running, you can verify it's working by:
109
+
110
+ ### 1. Health Check
111
+
112
+ ```bash
113
+ curl http://localhost:8000/health
114
+ ```
115
+
116
+ Expected response:
117
+
118
+ ```json
119
+ {
120
+ "status": "healthy",
121
+ "app_name": "FastAPI Video Backend",
122
+ "version": "0.1.0",
123
+ "environment": "development"
124
+ }
125
+ ```
126
+
127
+ ### 2. API Documentation
128
+
129
+ Visit these URLs in your browser:
130
+
131
+ - **Local Swagger UI**: http://localhost:8000/docs
132
+ - **Public Swagger UI**: https://your-ngrok-url.ngrok.io/docs
133
+ - **ReDoc**: http://localhost:8000/redoc
134
+ - **OpenAPI JSON**: http://localhost:8000/openapi.json
135
+ - **ngrok Dashboard**: http://localhost:4040
136
+
137
+ ### 3. Test Public Endpoint
138
+
139
+ ```bash
140
+ # Test local endpoint
141
+ curl http://localhost:8000/
142
+
143
+ # Test public ngrok endpoint
144
+ curl https://your-ngrok-url.ngrok.io/health
145
+ ```
146
+
147
+ ## Docker Services Management
148
+
149
+ ### Available Commands
150
+
151
+ ```bash
152
+ # Start development services (Redis + ngrok)
153
+ make dev-services
154
+
155
+ # Stop development services
156
+ make dev-services-stop
157
+
158
+ # View service logs
159
+ make dev-services-logs
160
+
161
+ # Get current ngrok public URL
162
+ make get-ngrok-url
163
+
164
+ # Full development workflow
165
+ make dev-start
166
+ ```
167
+
168
+ ### Manual Docker Commands
169
+
170
+ ```bash
171
+ # Start services
172
+ docker-compose -f docker-compose.dev.yml up -d
173
+
174
+ # Stop services
175
+ docker-compose -f docker-compose.dev.yml down
176
+
177
+ # View logs
178
+ docker-compose -f docker-compose.dev.yml logs -f
179
+
180
+ # Check service status
181
+ docker-compose -f docker-compose.dev.yml ps
182
+ ```
183
+
184
+ ## Available API Endpoints
185
+
186
+ The server provides the following main endpoint groups:
187
+
188
+ - **Authentication** (`/api/v1/auth/*`) - User authentication and authorization
189
+ - **Videos** (`/api/v1/videos/*`) - Video generation and management
190
+ - **Jobs** (`/api/v1/jobs/*`) - Background job management
191
+ - **Files** (`/api/v1/files/*`) - File upload and management
192
+ - **System** (`/api/v1/system/*`) - System health and monitoring
193
+
194
+ ## Development Features
195
+
196
+ ### Auto-reload
197
+
198
+ The server runs with auto-reload enabled in development mode, so changes to your code will automatically restart the server.
199
+
200
+ ### Debug Mode
201
+
202
+ When `DEBUG=true` in your `.env` file:
203
+
204
+ - Detailed error messages are shown
205
+ - API documentation is available
206
+ - CORS is configured for local development
207
+
208
+ ### Logging
209
+
210
+ The application uses structured logging. Logs will show:
211
+
212
+ - Request/response information
213
+ - Performance metrics
214
+ - Error details
215
+ - Authentication events
216
+
217
+ ## Troubleshooting
218
+
219
+ ### Common Issues
220
+
221
+ #### 1. Redis Connection Error
222
+
223
+ ```
224
+ Failed to initialize Redis: [Errno 111] Connection refused
225
+ ```
226
+
227
+ **Solution**: Make sure Redis server is running on the configured host and port.
228
+
229
+ #### 2. Clerk Authentication Error
230
+
231
+ ```
232
+ Failed to initialize Clerk: Invalid secret key
233
+ ```
234
+
235
+ **Solution**: Verify your Clerk API keys in the `.env` file are correct.
236
+
237
+ #### 3. Port Already in Use
238
+
239
+ ```
240
+ [Errno 48] Address already in use
241
+ ```
242
+
243
+ **Solution**: Either stop the process using port 8000 or change the `PORT` in your `.env` file.
244
+
245
+ #### 4. Import Errors
246
+
247
+ ```
248
+ ModuleNotFoundError: No module named 'src'
249
+ ```
250
+
251
+ **Solution**: Make sure you're running the server from the project root directory.
252
+
253
+ ### Checking Server Status
254
+
255
+ ```bash
256
+ # Check if server is running
257
+ curl -f http://localhost:8000/health
258
+
259
+ # Check Redis connection
260
+ redis-cli ping
261
+
262
+ # View server logs (if running in background)
263
+ tail -f logs/app.log
264
+ ```
265
+
266
+ ## Production Deployment
267
+
268
+ For production deployment:
269
+
270
+ 1. Set `ENVIRONMENT=production` in your `.env`
271
+ 2. Set `DEBUG=false`
272
+ 3. Use a strong `SECRET_KEY`
273
+ 4. Configure proper `ALLOWED_ORIGINS` for CORS
274
+ 5. Set up proper Redis configuration with authentication
275
+ 6. Use a production ASGI server like Gunicorn with Uvicorn workers
276
+
277
+ ```bash
278
+ # Production server example
279
+ gunicorn src.app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
280
+ ```
281
+
282
+ ## Next Steps
283
+
284
+ After setting up the server:
285
+
286
+ 1. **Generate Client SDKs**: Use `make generate-clients` to create client libraries
287
+ 2. **Test the API**: Use `make test-clients` to run integration tests
288
+ 3. **Explore the Documentation**: Visit `/docs` to understand available endpoints
289
+ 4. **Set up Authentication**: Configure Clerk for user management
290
+ 5. **Upload Files**: Test file upload functionality through the API
291
+
292
+ ## Support
293
+
294
+ If you encounter issues:
295
+
296
+ 1. Check the server logs for detailed error messages
297
+ 2. Verify all environment variables are set correctly
298
+ 3. Ensure all dependencies are installed
299
+ 4. Make sure Redis is running and accessible
300
+ 5. Check that your API keys are valid and have proper permissions
301
+
302
+ For additional help, refer to the project documentation or create an issue in the repository.
Makefile ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Video Generation API - Client Generation Makefile
2
+ # This Makefile provides convenient commands for generating and testing client SDKs
3
+
4
+ .PHONY: help install-deps generate-clients test-clients clean-clients docs serve-api
5
+
6
+ # Default target
7
+ help:
8
+ @echo "Video Generation API - Client Generation"
9
+ @echo ""
10
+ @echo "Available targets:"
11
+ @echo " help Show this help message"
12
+ @echo " install-deps Install required dependencies"
13
+ @echo " generate-clients Generate all client SDKs"
14
+ @echo " generate-ts Generate TypeScript client only"
15
+ @echo " generate-python Generate Python client only"
16
+ @echo " generate-java Generate Java client only"
17
+ @echo " test-clients Test generated clients"
18
+ @echo " clean-clients Clean generated clients"
19
+ @echo " serve-api Start API server for testing"
20
+ @echo " docs Generate documentation"
21
+ @echo ""
22
+ @echo "Environment variables:"
23
+ @echo " API_URL API base URL (default: http://localhost:8000)"
24
+ @echo " OUTPUT_DIR Output directory (default: generated_clients)"
25
+ @echo " LANGUAGES Space-separated list of languages"
26
+
27
+ # Configuration
28
+ API_URL ?= http://localhost:8000
29
+ OUTPUT_DIR ?= generated_clients
30
+ LANGUAGES ?= typescript python java csharp go php ruby
31
+
32
+ # Install dependencies
33
+ install-deps:
34
+ @echo "Installing dependencies..."
35
+ @command -v node >/dev/null 2>&1 || { echo "Node.js is required but not installed. Please install Node.js first."; exit 1; }
36
+ @command -v npm >/dev/null 2>&1 || { echo "npm is required but not installed. Please install npm first."; exit 1; }
37
+ @echo "Installing OpenAPI Generator CLI..."
38
+ npm install -g @openapitools/openapi-generator-cli
39
+ @echo "Dependencies installed successfully!"
40
+
41
+ # Generate all clients
42
+ generate-clients: check-api
43
+ @echo "Generating client SDKs for languages: $(LANGUAGES)"
44
+ python scripts/generate_clients.py \
45
+ --api-url $(API_URL) \
46
+ --output-dir $(OUTPUT_DIR) \
47
+ --languages $(LANGUAGES)
48
+
49
+ # Generate TypeScript client
50
+ generate-ts: check-api
51
+ @echo "Generating TypeScript client..."
52
+ python scripts/generate_clients.py \
53
+ --api-url $(API_URL) \
54
+ --output-dir $(OUTPUT_DIR) \
55
+ --languages typescript
56
+
57
+ # Generate Python client
58
+ generate-python: check-api
59
+ @echo "Generating Python client..."
60
+ python scripts/generate_clients.py \
61
+ --api-url $(API_URL) \
62
+ --output-dir $(OUTPUT_DIR) \
63
+ --languages python
64
+
65
+ # Generate Java client
66
+ generate-java: check-api
67
+ @echo "Generating Java client..."
68
+ python scripts/generate_clients.py \
69
+ --api-url $(API_URL) \
70
+ --output-dir $(OUTPUT_DIR) \
71
+ --languages java
72
+
73
+ # Test generated clients
74
+ test-clients:
75
+ @echo "Testing generated clients..."
76
+ python scripts/test_clients.py \
77
+ --api-url $(API_URL) \
78
+ --clients-dir $(OUTPUT_DIR) \
79
+ --verbose
80
+
81
+ # Clean generated clients
82
+ clean-clients:
83
+ @echo "Cleaning generated clients..."
84
+ rm -rf $(OUTPUT_DIR)
85
+ @echo "Generated clients cleaned!"
86
+
87
+ # Check if API is running
88
+ check-api:
89
+ @echo "Checking if API is running at $(API_URL)..."
90
+ @curl -s -f $(API_URL)/health > /dev/null || { \
91
+ echo "API is not running at $(API_URL)"; \
92
+ echo "Please start the API server first with: make serve-api"; \
93
+ exit 1; \
94
+ }
95
+ @echo "API is running!"
96
+
97
+ # Start API server
98
+ serve-api:
99
+ @echo "Starting API server..."
100
+ python -m uvicorn src.app.main:app --reload --host 0.0.0.0 --port 8000
101
+
102
+ # Development environment setup
103
+ dev-setup-full: install-deps
104
+ @echo "Setting up full development environment with Docker..."
105
+ @chmod +x setup-dev.sh
106
+ @./setup-dev.sh
107
+
108
+ # Start development services (Redis + ngrok)
109
+ dev-services:
110
+ @echo "Starting development services (Redis + ngrok)..."
111
+ docker-compose -f docker-compose.dev.yml up -d
112
+ @echo "Services started! Visit http://localhost:4040 for ngrok dashboard"
113
+
114
+ # Stop development services
115
+ dev-services-stop:
116
+ @echo "Stopping development services..."
117
+ docker-compose -f docker-compose.dev.yml down
118
+
119
+ # View development services logs
120
+ dev-services-logs:
121
+ docker-compose -f docker-compose.dev.yml logs -f
122
+
123
+ # Full development workflow
124
+ dev-start: dev-services serve-api
125
+
126
+ # Get ngrok public URL
127
+ get-ngrok-url:
128
+ @echo "Getting ngrok public URL..."
129
+ @curl -s http://localhost:4040/api/tunnels | python -c "import sys, json; data = json.load(sys.stdin); print(data['tunnels'][0]['public_url'] if data['tunnels'] else 'No tunnels found')" 2>/dev/null || echo "ngrok not running or no tunnels found"
130
+
131
+ # Generate documentation
132
+ docs:
133
+ @echo "Generating documentation..."
134
+ @mkdir -p docs/generated
135
+ @echo "Documentation generated in docs/ directory"
136
+
137
+ # Development targets
138
+ dev-setup: install-deps
139
+ @echo "Setting up development environment..."
140
+ @chmod +x scripts/generate_clients.sh
141
+ @echo "Development environment ready!"
142
+
143
+ # Quick test - generate and test TypeScript client
144
+ quick-test: generate-ts
145
+ @echo "Running quick test with TypeScript client..."
146
+ cd $(OUTPUT_DIR)/typescript && npm install --silent
147
+ @echo "Quick test completed!"
148
+
149
+ # Full workflow - generate all clients and test
150
+ full-workflow: generate-clients test-clients
151
+ @echo "Full client generation and testing workflow completed!"
152
+
153
+ # Docker targets
154
+ docker-generate:
155
+ @echo "Generating clients using Docker..."
156
+ docker run --rm \
157
+ -v $(PWD):/workspace \
158
+ -w /workspace \
159
+ node:18-alpine \
160
+ sh -c "npm install -g @openapitools/openapi-generator-cli && python scripts/generate_clients.py"
161
+
162
+ # CI/CD targets
163
+ ci-test: install-deps generate-clients test-clients
164
+ @echo "CI/CD pipeline completed successfully!"
165
+
166
+ # Validate OpenAPI spec
167
+ validate-spec: check-api
168
+ @echo "Validating OpenAPI specification..."
169
+ curl -s $(API_URL)/openapi.json | python -m json.tool > /dev/null
170
+ @echo "OpenAPI specification is valid!"
171
+
172
+ # Show client statistics
173
+ stats:
174
+ @echo "Client generation statistics:"
175
+ @if [ -d "$(OUTPUT_DIR)" ]; then \
176
+ echo "Generated clients:"; \
177
+ for dir in $(OUTPUT_DIR)/*/; do \
178
+ if [ -d "$$dir" ]; then \
179
+ lang=$$(basename "$$dir"); \
180
+ files=$$(find "$$dir" -type f | wc -l); \
181
+ size=$$(du -sh "$$dir" | cut -f1); \
182
+ echo " $$lang: $$files files, $$size"; \
183
+ fi; \
184
+ done; \
185
+ else \
186
+ echo "No clients generated yet. Run 'make generate-clients' first."; \
187
+ fi
188
+
189
+ # Package clients for distribution
190
+ package:
191
+ @echo "Packaging clients for distribution..."
192
+ @mkdir -p dist
193
+ @if [ -d "$(OUTPUT_DIR)" ]; then \
194
+ cd $(OUTPUT_DIR) && \
195
+ for dir in */; do \
196
+ if [ -d "$$dir" ]; then \
197
+ lang=$$(basename "$$dir"); \
198
+ echo "Packaging $$lang client..."; \
199
+ tar -czf ../dist/video-api-client-$$lang.tar.gz "$$dir"; \
200
+ fi; \
201
+ done; \
202
+ fi
203
+ @echo "Clients packaged in dist/ directory"
204
+
205
+ # Publish clients (placeholder - customize for your needs)
206
+ publish:
207
+ @echo "Publishing clients..."
208
+ @echo "This is a placeholder. Customize for your package managers."
209
+ @if [ -d "$(OUTPUT_DIR)/typescript" ]; then \
210
+ echo "Would publish TypeScript client to npm"; \
211
+ fi
212
+ @if [ -d "$(OUTPUT_DIR)/python" ]; then \
213
+ echo "Would publish Python client to PyPI"; \
214
+ fi
215
+ @if [ -d "$(OUTPUT_DIR)/java" ]; then \
216
+ echo "Would publish Java client to Maven Central"; \
217
+ fi
218
+
219
+ # Watch for changes and regenerate
220
+ watch:
221
+ @echo "Watching for API changes and regenerating clients..."
222
+ @echo "This requires 'entr' to be installed: brew install entr"
223
+ @echo "Watching src/app/ for changes..."
224
+ find src/app -name "*.py" | entr -r make generate-clients
225
+
226
+ # Benchmark client generation
227
+ benchmark:
228
+ @echo "Benchmarking client generation..."
229
+ @time make generate-clients
230
+ @echo "Benchmark completed!"
231
+
232
+ # Show OpenAPI spec
233
+ show-spec: check-api
234
+ @echo "OpenAPI Specification:"
235
+ @curl -s $(API_URL)/openapi.json | python -m json.tool
236
+
237
+ # Interactive mode
238
+ interactive:
239
+ @echo "Interactive client generation mode"
240
+ @echo "Available languages: typescript python java csharp go php ruby"
241
+ @read -p "Enter languages to generate (space-separated): " langs; \
242
+ make generate-clients LANGUAGES="$$langs"
243
+
244
+ # Health check
245
+ health:
246
+ @echo "Performing health check..."
247
+ @make check-api
248
+ @command -v openapi-generator-cli >/dev/null 2>&1 || { echo "OpenAPI Generator CLI not found"; exit 1; }
249
+ @command -v python >/dev/null 2>&1 || { echo "Python not found"; exit 1; }
250
+ @echo "All dependencies are available!"
251
+
252
+ # Show version information
253
+ version:
254
+ @echo "Version information:"
255
+ @echo "Node.js: $$(node --version 2>/dev/null || echo 'Not installed')"
256
+ @echo "npm: $$(npm --version 2>/dev/null || echo 'Not installed')"
257
+ @echo "Python: $$(python --version 2>/dev/null || echo 'Not installed')"
258
+ @echo "OpenAPI Generator: $$(openapi-generator-cli version 2>/dev/null || echo 'Not installed')"
259
+
260
+ # Example usage
261
+ example:
262
+ @echo "Example usage:"
263
+ @echo ""
264
+ @echo "1. Start the API server:"
265
+ @echo " make serve-api"
266
+ @echo ""
267
+ @echo "2. In another terminal, generate clients:"
268
+ @echo " make generate-clients"
269
+ @echo ""
270
+ @echo "3. Test the generated clients:"
271
+ @echo " make test-clients"
272
+ @echo ""
273
+ @echo "4. Generate specific language only:"
274
+ @echo " make generate-ts"
275
+ @echo " make generate-python"
276
+ @echo ""
277
+ @echo "5. Clean up:"
278
+ @echo " make clean-clients"
README.md CHANGED
@@ -1,306 +1,205 @@
1
- ---
2
- title: AI Animation & Voice Studio
3
- emoji: 🎬
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: docker
7
- app_port: 7860
8
- suggested_hardware: cpu-upgrade
9
- suggested_storage: large
10
- pinned: true
11
- license: apache-2.0
12
- short_description: "Create mathematical animations with AI-powered using Manim"
13
- tags:
14
- - text-to-speech
15
- - animation
16
- - mathematics
17
- - manim
18
- - ai-voice
19
- - educational
20
- - visualization
21
- models:
22
- - kokoro-onnx/kokoro-v0_19
23
- datasets: []
24
- startup_duration_timeout: 30m
25
- fullWidth: true
26
- header: default
27
- disable_embedding: false
28
- preload_from_hub: []
29
- ---
30
-
31
- # AI Animation & Voice Studio 🎬
32
-
33
- A powerful application that combines AI-powered text-to-speech with mathematical animation generation using Manim and Kokoro TTS. Create stunning educational content with synchronized voice narration and mathematical visualizations.
34
-
35
- ## 🚀 Features
36
-
37
- - **Text-to-Speech**: High-quality voice synthesis using Kokoro ONNX models
38
- - **Mathematical Animations**: Create stunning mathematical visualizations with Manim
39
- - **LaTeX Support**: Full LaTeX rendering capabilities with TinyTeX
40
- - **Interactive Interface**: User-friendly Gradio web interface
41
- - **Audio Processing**: Advanced audio manipulation with FFmpeg and SoX
42
-
43
- ## 🛠️ Technology Stack
44
-
45
- - **Frontend**: Gradio for interactive web interface
46
- - **Backend**: Python with FastAPI/Flask
47
- - **Animation**: Manim (Mathematical Animation Engine)
48
- - **TTS**: Kokoro ONNX for text-to-speech synthesis
49
- - **LaTeX**: TinyTeX for mathematical typesetting
50
- - **Audio**: FFmpeg, SoX, PortAudio for audio processing
51
- - **Deployment**: Docker container optimized for Hugging Face Spaces
52
-
53
- ## 📦 Models
54
-
55
- This application uses the following pre-trained models:
56
-
57
- - **Kokoro TTS**: `kokoro-v0_19.onnx` - High-quality neural text-to-speech model
58
- - **Voice Models**: `voices.bin` - Voice embedding models for different speaker characteristics
59
-
60
- Models are automatically downloaded during the Docker build process from the official releases.
61
-
62
- ## 🏃‍♂️ Quick Start
63
-
64
- ### Using Hugging Face Spaces
65
-
66
- 1. Visit the [Space](https://huggingface.co/spaces/your-username/ai-animation-voice-studio)
67
- 2. Wait for the container to load (initial startup may take 3-5 minutes due to model loading)
68
- 3. Upload your script or enter text directly
69
- 4. Choose animation settings and voice parameters
70
- 5. Generate your animated video with AI narration!
71
-
72
- ### Local Development
73
 
74
- ```bash
75
- # Clone the repository
76
- git clone https://huggingface.co/spaces/your-username/ai-animation-voice-studio
77
- cd ai-animation-voice-studio
78
-
79
- # Build the Docker image
80
- docker build -t ai-animation-studio .
81
-
82
- # Run the container
83
- docker run -p 7860:7860 ai-animation-studio
84
- ```
85
-
86
- Access the application at `http://localhost:7860`
87
-
88
- ### Environment Setup
89
-
90
- Create a `.env` file with your configuration:
91
-
92
- ```env
93
- # Application settings
94
- DEBUG=false
95
- MAX_WORKERS=4
96
-
97
- # Model settings
98
- MODEL_PATH=/app/models
99
- CACHE_DIR=/tmp/cache
100
-
101
- # Optional: API keys if needed
102
- # OPENAI_API_KEY=your_key_here
103
- ```
104
 
105
- ## 🎯 Usage Examples
106
 
107
- ### Basic Text-to-Speech
 
 
 
 
 
 
 
 
108
 
109
- ```python
110
- # Example usage in your code
111
- from src.tts import generate_speech
112
 
113
- audio = generate_speech(
114
- text="Hello, this is a test of the text-to-speech system",
115
- voice="default",
116
- speed=1.0
117
- )
118
- ```
119
-
120
- ### Mathematical Animation
121
-
122
- ```python
123
- # Example Manim scene
124
- from manim import *
125
-
126
- class Example(Scene):
127
- def construct(self):
128
- # Your animation code here
129
- pass
130
- ```
131
 
132
- ## 📁 Project Structure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  ```
135
- ├── src/ # Source code
136
- ├── tts/ # Text-to-speech modules
137
- │ ├── manim_scenes/ # Manim animation scenes
138
- └── utils/ # Utility functions
139
- ├── models/ # Pre-trained models (auto-downloaded)
140
- ├── output/ # Generated content output
141
- ├── requirements.txt # Python dependencies
142
- ├── Dockerfile # Container configuration
143
- ├── gradio_app.py # Main application entry point
144
- └── README.md # This file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  ```
146
 
147
- ## ⚙️ Configuration
148
 
149
- ### Docker Environment Variables
150
 
151
- - `GRADIO_SERVER_NAME`: Server host (default: 0.0.0.0)
152
- - `GRADIO_SERVER_PORT`: Server port (default: 7860)
153
- - `PYTHONPATH`: Python path configuration
154
- - `HF_HOME`: Hugging Face cache directory
155
 
156
- ### Application Settings
157
-
158
- Modify settings in your `.env` file or through environment variables:
159
-
160
- - Model parameters
161
- - Audio quality settings
162
- - Animation render settings
163
- - Cache configurations
164
-
165
- ## 🔧 Development
166
-
167
- ### Prerequisites
168
 
169
- - Docker and Docker Compose
170
- - Python 3.12+
171
- - Git
172
 
173
- ### Setting Up Development Environment
174
 
175
  ```bash
176
- # Install dependencies locally for development
177
- pip install -r requirements.txt
178
 
179
- # Run tests (if available)
180
- python -m pytest tests/
181
 
182
- # Format code
183
- black .
184
- isort .
185
-
186
- # Lint code
187
- flake8 .
188
  ```
189
 
190
- ### Building and Testing
191
 
192
  ```bash
193
- # Build the Docker image
194
- docker build -t your-app-name:dev .
195
-
196
- # Test the container locally
197
- docker run --rm -p 7860:7860 your-app-name:dev
198
-
199
- # Check container health
200
- docker run --rm your-app-name:dev python -c "import src; print('Import successful')"
201
- ```
202
 
203
- ## 📊 Performance & Hardware
 
204
 
205
- ### Recommended Specs for Hugging Face Spaces
 
206
 
207
- - **Hardware**: `cpu-upgrade` (recommended for faster rendering)
208
- - **Storage**: `small` (sufficient for models and temporary files)
209
- - **Startup Time**: ~3-5 minutes (due to model loading and TinyTeX setup)
210
- - **Memory Usage**: ~2-3GB during operation
211
 
212
- ### System Requirements
213
 
214
- - **Memory**: Minimum 2GB RAM, Recommended 4GB+
215
- - **CPU**: Multi-core processor recommended for faster animation rendering
216
- - **Storage**: ~1.5GB for models and dependencies
217
- - **Network**: Stable connection for initial model downloads
218
 
219
- ### Optimization Tips
220
 
221
- - Models are cached after first download
222
- - Gradio interface uses efficient streaming for large outputs
223
- - Docker multi-stage builds minimize final image size
224
- - TinyTeX installation is optimized for essential packages only
225
 
226
- ## 🐛 Troubleshooting
 
 
227
 
228
- ### Common Issues
229
 
230
- **Build Failures**:
231
- ```bash
232
- # Clear Docker cache if build fails
233
- docker system prune -a
234
- docker build --no-cache -t your-app-name .
235
- ```
236
 
237
- **Model Download Issues**:
238
- - Check internet connection
239
- - Verify model URLs are accessible
240
- - Models will be re-downloaded if corrupted
241
 
242
- **Memory Issues**:
243
- - Reduce batch sizes in configuration
244
- - Monitor memory usage with `docker stats`
245
 
246
- **Audio Issues**:
247
- - Ensure audio drivers are properly installed
248
- - Check PortAudio configuration
249
 
250
- ### Getting Help
 
251
 
252
- 1. Check the [Discussions](https://huggingface.co/spaces/your-username/ai-animation-voice-studio/discussions) tab
253
- 2. Review container logs in the Space settings
254
- 3. Enable debug mode in configuration
255
- 4. Report issues in the Community tab
256
 
257
- ### Common Configuration Issues
258
 
259
- **Space Configuration**:
260
- - Ensure `app_port: 7860` is set in README.md front matter
261
- - Check that `sdk: docker` is properly configured
262
- - Verify hardware suggestions match your needs
 
263
 
264
- **Model Loading**:
265
- - Models download automatically on first run
266
- - Check Space logs for download progress
267
- - Restart Space if models fail to load
268
-
269
- ## 🤝 Contributing
270
-
271
- We welcome contributions! Please see our contributing guidelines:
272
 
273
  1. Fork the repository
274
  2. Create a feature branch
275
  3. Make your changes
276
- 4. Add tests if applicable
277
- 5. Submit a pull request
278
-
279
- ### Code Style
280
-
281
- - Follow PEP 8 for Python code
282
- - Use Black for code formatting
283
- - Add docstrings for functions and classes
284
- - Include type hints where appropriate
285
-
286
- ## 📄 License
287
-
288
- This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
289
-
290
- ## 🙏 Acknowledgments
291
-
292
- - [Manim Community](https://www.manim.community/) for the animation engine
293
- - [Kokoro TTS](https://github.com/thewh1teagle/kokoro-onnx) for text-to-speech models
294
- - [Gradio](https://gradio.app/) for the web interface framework
295
- - [Hugging Face](https://huggingface.co/) for hosting and infrastructure
296
-
297
- ## 📞 Contact
298
-
299
- - **Author**: Your Name
300
- - **Email**: [email protected]
301
- - **GitHub**: [@your-username](https://github.com/your-username)
302
- - **Hugging Face**: [@your-username](https://huggingface.co/your-username)
303
 
304
- ---
305
 
306
- *Built with ❤️ for the open-source community*
 
1
+ # FastAPI Video Backend
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ A FastAPI backend for the multi-agent video generation system using Pydantic for data modeling, Clerk for authentication, and Redis for caching and job queuing.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ ## Features
6
 
7
+ - **FastAPI Framework**: Modern, fast web framework for building APIs
8
+ - **Pydantic Models**: Data validation and serialization
9
+ - **Clerk Authentication**: Secure user authentication and management
10
+ - **Redis Integration**: Caching and job queue management
11
+ - **Structured Logging**: JSON-formatted logging with structlog
12
+ - **Environment Configuration**: Flexible configuration with Pydantic Settings
13
+ - **CORS Support**: Cross-origin resource sharing configuration
14
+ - **Health Checks**: Built-in health monitoring endpoints
15
+ - **Testing Suite**: Comprehensive test coverage with pytest
16
 
17
+ ## Quick Start
 
 
18
 
19
+ ### Prerequisites
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ - Python 3.11+
22
+ - Redis server
23
+ - Clerk account (for authentication)
24
+
25
+ ### Installation
26
+
27
+ 1. **Clone the repository**
28
+ ```bash
29
+ git clone <repository-url>
30
+ cd fastapi-video-backend
31
+ ```
32
+
33
+ 2. **Install dependencies**
34
+ ```bash
35
+ pip install -e .
36
+ ```
37
+
38
+ 3. **Set up development environment**
39
+ ```bash
40
+ python scripts/setup.py
41
+ ```
42
+
43
+ 4. **Configure environment variables**
44
+
45
+ Update the `.env` file with your actual configuration:
46
+ ```bash
47
+ # Required: Get these from your Clerk dashboard
48
+ CLERK_SECRET_KEY=your_actual_clerk_secret_key
49
+ CLERK_PUBLISHABLE_KEY=your_actual_clerk_publishable_key
50
+
51
+ # Required: Generate a secure secret key
52
+ SECRET_KEY=your_super_secret_key_here
53
+ ```
54
+
55
+ 5. **Start Redis server**
56
+ ```bash
57
+ redis-server
58
+ ```
59
+
60
+ 6. **Run the application**
61
+ ```bash
62
+ # Development mode
63
+ python -m src.app.main
64
+
65
+ # Or using the script
66
+ python -m uvicorn src.app.main:app --reload
67
+ ```
68
+
69
+ 7. **Access the API**
70
+ - API Documentation: http://localhost:8000/docs
71
+ - Alternative Docs: http://localhost:8000/redoc
72
+ - Health Check: http://localhost:8000/health
73
+
74
+ ## Project Structure
75
 
76
  ```
77
+ src/
78
+ ├── app/
79
+ │ ├── main.py # FastAPI application entry point
80
+ ├── api/ # API layer
81
+ │ │ ├── dependencies.py # Shared dependencies
82
+ │ │ └── v1/ # API version 1
83
+ │ │ ├── videos.py # Video generation endpoints
84
+ │ │ ├── jobs.py # Job management endpoints
85
+ │ │ └── system.py # System health endpoints
86
+ │ ├── core/ # Core utilities and configurations
87
+ │ │ ├── config.py # Application settings
88
+ │ │ ├── redis.py # Redis connection and utilities
89
+ │ │ ├── auth.py # Clerk authentication utilities
90
+ │ │ ├── logger.py # Logging configuration
91
+ │ │ └── exceptions.py # Custom exceptions
92
+ │ ├── services/ # Business logic layer
93
+ │ │ ├── video_service.py # Video generation business logic
94
+ │ │ ├── job_service.py # Job management logic
95
+ │ │ └── queue_service.py # Redis queue management
96
+ │ ├── models/ # Pydantic models
97
+ │ │ ├── job.py # Job data models
98
+ │ │ ├── video.py # Video metadata models
99
+ │ │ ├── user.py # User data models
100
+ │ │ └── system.py # System status models
101
+ │ ├── middleware/ # Custom middleware
102
+ │ │ ├── cors.py # CORS middleware
103
+ │ │ ├── clerk_auth.py # Clerk authentication middleware
104
+ │ │ └── error_handling.py # Global error handling
105
+ │ └── utils/ # Utility functions
106
+ │ ├── file_utils.py # File handling utilities
107
+ │ └── helpers.py # General helper functions
108
+ ├── tests/ # Test suite
109
+ └── scripts/ # Utility scripts
110
  ```
111
 
112
+ ## Configuration
113
 
114
+ The application uses environment-based configuration with Pydantic Settings. All configuration options are documented in `.env.example`.
115
 
116
+ ### Key Configuration Sections
 
 
 
117
 
118
+ - **Application Settings**: Basic app configuration
119
+ - **Server Settings**: Host, port, and server options
120
+ - **Redis Settings**: Redis connection and caching configuration
121
+ - **Clerk Settings**: Authentication configuration
122
+ - **Security Settings**: JWT and security configuration
123
+ - **Logging Settings**: Structured logging configuration
 
 
 
 
 
 
124
 
125
+ ## Development
 
 
126
 
127
+ ### Running Tests
128
 
129
  ```bash
130
+ # Run all tests
131
+ pytest
132
 
133
+ # Run with coverage
134
+ pytest --cov=src
135
 
136
+ # Run specific test file
137
+ pytest tests/test_main.py
 
 
 
 
138
  ```
139
 
140
+ ### Code Quality
141
 
142
  ```bash
143
+ # Format code
144
+ black src/ tests/
 
 
 
 
 
 
 
145
 
146
+ # Sort imports
147
+ isort src/ tests/
148
 
149
+ # Lint code
150
+ flake8 src/ tests/
151
 
152
+ # Type checking
153
+ mypy src/
154
+ ```
 
155
 
156
+ ### Development Scripts
157
 
158
+ - `python scripts/setup.py` - Set up development environment
159
+ - `python -m src.app.main` - Run development server
160
+ - `pytest` - Run test suite
 
161
 
162
+ ## API Documentation
163
 
164
+ Once the application is running, you can access:
 
 
 
165
 
166
+ - **Swagger UI**: http://localhost:8000/docs
167
+ - **ReDoc**: http://localhost:8000/redoc
168
+ - **OpenAPI JSON**: http://localhost:8000/openapi.json
169
 
170
+ ## Health Monitoring
171
 
172
+ The application includes built-in health check endpoints:
 
 
 
 
 
173
 
174
+ - `GET /health` - Basic health status
175
+ - `GET /` - Root endpoint with basic information
 
 
176
 
177
+ ## Logging
 
 
178
 
179
+ The application uses structured logging with configurable output formats:
 
 
180
 
181
+ - **Development**: Colorized console output
182
+ - **Production**: JSON-formatted logs
183
 
184
+ Log levels and formats can be configured via environment variables.
 
 
 
185
 
186
+ ## Security
187
 
188
+ - **Authentication**: Clerk-based JWT authentication
189
+ - **CORS**: Configurable cross-origin resource sharing
190
+ - **Rate Limiting**: Built-in rate limiting support
191
+ - **Input Validation**: Pydantic-based request validation
192
+ - **Security Headers**: Automatic security header injection
193
 
194
+ ## Contributing
 
 
 
 
 
 
 
195
 
196
  1. Fork the repository
197
  2. Create a feature branch
198
  3. Make your changes
199
+ 4. Add tests for new functionality
200
+ 5. Ensure all tests pass
201
+ 6. Submit a pull request
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ ## License
204
 
205
+ This project is licensed under the MIT License - see the LICENSE file for details.
README_API_TESTING.md ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # T2M API Testing Guide
2
+
3
+ This directory contains comprehensive test scripts for the T2M API endpoints.
4
+
5
+ ## Files
6
+
7
+ - `test_api_endpoints.py` - Main test script with full endpoint coverage
8
+ - `run_api_tests.py` - Simple runner using configuration file
9
+ - `test_config.json` - Configuration file for API settings
10
+ - `requirements-test.txt` - Python dependencies for testing
11
+
12
+ ## Quick Start
13
+
14
+ ### 1. Install Dependencies
15
+
16
+ ```bash
17
+ pip install -r requirements-test.txt
18
+ ```
19
+
20
+ ### 2. Configure API Settings
21
+
22
+ Edit `test_config.json` with your API details:
23
+
24
+ ```json
25
+ {
26
+ "api_config": {
27
+ "base_url": "https://your-api-domain.com/api/v1",
28
+ "token": "your-actual-bearer-token"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### 3. Run Tests
34
+
35
+ **Option A: Using configuration file**
36
+ ```bash
37
+ python run_api_tests.py
38
+ ```
39
+
40
+ **Option B: Direct command line**
41
+ ```bash
42
+ python test_api_endpoints.py --base-url https://your-api-domain.com/api/v1 --token your-token
43
+ ```
44
+
45
+ **Option C: Token from file**
46
+ ```bash
47
+ echo "your-token-here" > token.txt
48
+ python test_api_endpoints.py --base-url https://your-api-domain.com/api/v1 --token-file token.txt
49
+ ```
50
+
51
+ ## Test Coverage
52
+
53
+ The test script covers all major API endpoints:
54
+
55
+ ### 🔓 Public Endpoints (No Auth Required)
56
+ - `GET /auth/health` - Authentication service health
57
+ - `GET /system/health` - System health check
58
+
59
+ ### 🔐 Authentication Endpoints
60
+ - `GET /auth/status` - Authentication status
61
+ - `GET /auth/profile` - User profile
62
+ - `GET /auth/permissions` - User permissions
63
+ - `GET /auth/test/protected` - Protected endpoint test
64
+ - `GET /auth/test/verified` - Verified user test
65
+ - `POST /auth/verify` - Token verification
66
+
67
+ ### 📁 File Management Endpoints
68
+ - `POST /files/upload` - Single file upload
69
+ - `GET /files` - List files with pagination
70
+ - `GET /files/{id}` - File details
71
+ - `GET /files/{id}/metadata` - File metadata
72
+ - `GET /files/{id}/thumbnail` - File thumbnail
73
+ - `GET /files/stats` - File statistics
74
+ - `DELETE /files/{id}` - File deletion (cleanup)
75
+
76
+ ### ⚙️ Job Management Endpoints
77
+ - `GET /jobs` - List jobs
78
+ - `GET /jobs/{id}` - Job details
79
+ - `GET /jobs/{id}/logs` - Job logs
80
+ - `POST /jobs/{id}/cancel` - Cancel job (cleanup)
81
+ - `DELETE /jobs/{id}` - Delete job (cleanup)
82
+
83
+ ### 🖥️ System Monitoring Endpoints
84
+ - `GET /system/metrics` - System metrics
85
+ - `GET /system/queue` - Queue status
86
+ - `GET /system/cache` - Cache information
87
+ - `GET /system/cache/metrics` - Cache metrics
88
+ - `GET /system/cache/report` - Cache report
89
+ - `GET /system/performance` - Performance summary
90
+ - `GET /system/connections` - Connection statistics
91
+ - `GET /system/async` - Async statistics
92
+ - `GET /system/deduplication` - Deduplication statistics
93
+
94
+ ### 🎥 Video Processing Endpoints
95
+ - `POST /videos/generate` - Generate video
96
+ - `GET /videos/{id}/status` - Video job status
97
+ - `GET /videos/{id}/metadata` - Video metadata
98
+ - `GET /videos/{id}/thumbnail` - Video thumbnail
99
+
100
+ ## Test Results
101
+
102
+ After running tests, you'll get:
103
+
104
+ 1. **Console output** with real-time test results
105
+ 2. **Summary statistics** showing pass/fail rates
106
+ 3. **JSON report** saved to `api_test_results.json`
107
+
108
+ ### Example Output
109
+
110
+ ```
111
+ 🚀 Starting T2M API Endpoint Tests
112
+ Base URL: https://api.example.com/api/v1
113
+ Token: Provided
114
+
115
+ 🔓 Testing Public Endpoints
116
+ ✅ PASS GET /auth/health
117
+ Status: 200
118
+ ✅ PASS GET /system/health
119
+ Status: 200
120
+
121
+ 🔐 Testing Authentication Endpoints
122
+ ✅ PASS GET /auth/status
123
+ Status: 200
124
+ ✅ PASS GET /auth/profile
125
+ Status: 200
126
+
127
+ 📊 TEST SUMMARY
128
+ ==================================================
129
+ Total Tests: 25
130
+ Passed: 23 ✅
131
+ Failed: 2 ❌
132
+ Success Rate: 92.0%
133
+ ```
134
+
135
+ ## Test Features
136
+
137
+ ### 🧹 Automatic Cleanup
138
+ - Deletes uploaded test files
139
+ - Cancels/deletes created jobs
140
+ - Prevents resource accumulation
141
+
142
+ ### 📊 Comprehensive Reporting
143
+ - Real-time console feedback
144
+ - Detailed JSON results file
145
+ - Pass/fail statistics
146
+ - Error details for debugging
147
+
148
+ ### 🔧 Flexible Configuration
149
+ - Command line arguments
150
+ - Configuration file support
151
+ - Token file support
152
+ - Environment variable support
153
+
154
+ ### 🛡️ Error Handling
155
+ - Network timeout handling
156
+ - Graceful failure handling
157
+ - Detailed error reporting
158
+ - Resource cleanup on failure
159
+
160
+ ## Advanced Usage
161
+
162
+ ### Testing Specific Endpoints
163
+
164
+ You can modify the test script to focus on specific endpoint groups:
165
+
166
+ ```python
167
+ # Only test public endpoints
168
+ tester.test_public_endpoints()
169
+
170
+ # Only test file operations
171
+ tester.test_file_endpoints()
172
+
173
+ # Only test system monitoring
174
+ tester.test_system_endpoints()
175
+ ```
176
+
177
+ ### Custom Test Data
178
+
179
+ Modify `test_config.json` to customize test parameters:
180
+
181
+ ```json
182
+ {
183
+ "test_data": {
184
+ "video_generation": {
185
+ "prompt": "Your custom test prompt",
186
+ "duration": 10,
187
+ "quality": "1080p"
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Environment Variables
194
+
195
+ You can also use environment variables:
196
+
197
+ ```bash
198
+ export T2M_API_URL="https://your-api-domain.com/api/v1"
199
+ export T2M_API_TOKEN="your-token-here"
200
+ python test_api_endpoints.py --base-url $T2M_API_URL --token $T2M_API_TOKEN
201
+ ```
202
+
203
+ ## Troubleshooting
204
+
205
+ ### Common Issues
206
+
207
+ **Connection Errors**
208
+ - Verify the base URL is correct
209
+ - Check network connectivity
210
+ - Ensure API server is running
211
+
212
+ **Authentication Errors**
213
+ - Verify token is valid and not expired
214
+ - Check token format (should be just the token, not "Bearer token")
215
+ - Ensure user has required permissions
216
+
217
+ **Rate Limiting**
218
+ - Tests may hit rate limits on busy servers
219
+ - Add delays between requests if needed
220
+ - Run tests during off-peak hours
221
+
222
+ **Resource Cleanup Failures**
223
+ - Some resources may not be cleaned up if tests fail
224
+ - Manually delete test files/jobs if needed
225
+ - Check API logs for cleanup issues
226
+
227
+ ### Debug Mode
228
+
229
+ For more detailed debugging, modify the test script to add verbose logging:
230
+
231
+ ```python
232
+ import logging
233
+ logging.basicConfig(level=logging.DEBUG)
234
+ ```
235
+
236
+ ## Integration with CI/CD
237
+
238
+ The test script returns appropriate exit codes for CI/CD integration:
239
+
240
+ ```bash
241
+ # Run tests and capture exit code
242
+ python test_api_endpoints.py --base-url $API_URL --token $API_TOKEN
243
+ if [ $? -eq 0 ]; then
244
+ echo "All tests passed"
245
+ else
246
+ echo "Some tests failed"
247
+ exit 1
248
+ fi
249
+ ```
250
+
251
+ ## Contributing
252
+
253
+ To add new test cases:
254
+
255
+ 1. Add the endpoint to the appropriate test method
256
+ 2. Follow the existing pattern for error handling
257
+ 3. Add cleanup logic if the endpoint creates resources
258
+ 4. Update this README with the new endpoint coverage
SYSTEM_OVERVIEW.md ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-Agent Video Generation System - Architecture Overview
2
+
3
+ ## 🎯 System Purpose
4
+ This is a sophisticated **multi-agent system** that automatically generates educational videos using Manim (Mathematical Animation Engine). The system transforms textual descriptions of mathematical concepts, theorems, and educational content into high-quality animated videos through coordinated AI agents.
5
+
6
+ ## 🏗️ System Architecture
7
+
8
+ ```mermaid
9
+ flowchart TD
10
+ %% Input Layer
11
+ U["User Input<br/>(Topic & Context)"]:::input
12
+ GV["generate_video.py<br/>(Main Orchestrator)"]:::input
13
+ ES["evaluate.py<br/>(Quality Assessment)"]:::input
14
+
15
+ %% Configuration and Data
16
+ CONF["Configuration<br/>(.env, src/config)"]:::config
17
+ DATA["Data Repository<br/>(data/)"]:::data
18
+
19
+ %% Core Generation Pipeline
20
+ subgraph "Core Multi-Agent Pipeline"
21
+ CG["Code Generation Agent<br/>(src/core/code_generator.py)"]:::core
22
+ VP["Video Planning Agent<br/>(src/core/video_planner.py)"]:::core
23
+ VR["Video Rendering Agent<br/>(src/core/video_renderer.py)"]:::core
24
+ end
25
+
26
+ %% Retrieval & Augmentation (RAG)
27
+ RAG["RAG Intelligence Agent<br/>(src/rag/rag_integration.py,<br/>src/rag/vector_store.py)"]:::rag
28
+
29
+ %% Task & Prompt Generation
30
+ TASK["Task & Prompt Generation<br/>(task_generator/)"]:::task
31
+
32
+ %% External LLM & Model Tools
33
+ LLM["LLM Provider Agents<br/>(mllm_tools/)"]:::ai
34
+
35
+ %% Voiceover & Utilities
36
+ VOX["Utility Services<br/>(src/utils/)"]:::voice
37
+
38
+ %% Evaluation Module
39
+ EVAL["Quality Evaluation Agent<br/>(eval_suite/)"]:::eval
40
+
41
+ %% Connections
42
+ U -->|"provides data"| GV
43
+ GV -->|"reads configuration"| CONF
44
+ CONF -->|"configures processing"| CG
45
+ CONF -->|"fetches theorem data"| DATA
46
+
47
+ %% Core Pipeline Flow
48
+ GV -->|"orchestrates generation"| CG
49
+ CG -->|"sends code/instructions"| VP
50
+ VP -->|"plans scenes"| VR
51
+ VR -->|"integrates audio"| VOX
52
+ VOX -->|"produces final video"| EVAL
53
+
54
+ %% Cross Module Integrations
55
+ TASK -->|"supplies prompt templates"| CG
56
+ TASK -->|"guides scene planning"| VP
57
+ CG -->|"augments with retrieval"| RAG
58
+ VP -->|"queries documentation"| RAG
59
+ LLM -->|"supports AI generation"| CG
60
+ LLM -->|"supports task generation"| TASK
61
+
62
+ %% Evaluation Script
63
+ ES -->|"evaluates output"| EVAL
64
+
65
+ %% Styles
66
+ classDef input fill:#FFD580,stroke:#333,stroke-width:2px;
67
+ classDef config fill:#B3E5FC,stroke:#333,stroke-width:2px;
68
+ classDef data fill:#C8E6C9,stroke:#333,stroke-width:2px;
69
+ classDef core fill:#FFF59D,stroke:#333,stroke-width:2px;
70
+ classDef rag fill:#FFCC80,stroke:#333,stroke-width:2px;
71
+ classDef task fill:#D1C4E9,stroke:#333,stroke-width:2px;
72
+ classDef ai fill:#B2EBF2,stroke:#333,stroke-width:2px;
73
+ classDef voice fill:#FFE0B2,stroke:#333,stroke-width:2px;
74
+ classDef eval fill:#E1BEE7,stroke:#333,stroke-width:2px;
75
+ ```
76
+
77
+ ## 🤖 Core Agents & Responsibilities
78
+
79
+ ### 1. **🎬 Video Planning Agent** (`src/core/video_planner.py`)
80
+ **Role**: Strategic planning and scene orchestration
81
+
82
+ **Key Capabilities**:
83
+ - Scene outline generation and decomposition
84
+ - Storyboard creation with visual descriptions
85
+ - Technical implementation planning
86
+ - Concurrent scene processing with enhanced parallelization
87
+ - Context learning from previous examples
88
+ - RAG integration for Manim documentation retrieval
89
+
90
+ **Key Methods**:
91
+ - `generate_scene_outline()` - Creates overall video structure
92
+ - `generate_scene_implementation_concurrently_enhanced()` - Parallel scene planning
93
+ - `_initialize_context_examples()` - Loads learning contexts
94
+
95
+ ### 2. **⚡ Code Generation Agent** (`src/core/code_generator.py`)
96
+ **Role**: Manim code synthesis and optimization
97
+
98
+ **Key Capabilities**:
99
+ - Intelligent Manim code generation from scene descriptions
100
+ - Automatic error detection and fixing
101
+ - Visual self-reflection for code quality
102
+ - RAG-enhanced code generation with documentation context
103
+ - Context learning from successful examples
104
+ - Banned reasoning prevention
105
+
106
+ **Key Methods**:
107
+ - `generate_manim_code()` - Primary code generation
108
+ - `fix_code_errors()` - Intelligent error correction
109
+ - `visual_self_reflection()` - Quality validation
110
+
111
+ ### 3. **🎞️ Video Rendering Agent** (`src/core/video_renderer.py`)
112
+ **Role**: Video compilation and optimization
113
+
114
+ **Key Capabilities**:
115
+ - Optimized Manim scene rendering
116
+ - Intelligent caching system for performance
117
+ - Parallel scene processing
118
+ - Quality preset management (preview/low/medium/high/production)
119
+ - GPU acceleration support
120
+ - Video combination and assembly
121
+
122
+ **Key Methods**:
123
+ - `render_scene_optimized()` - Enhanced scene rendering
124
+ - `combine_videos_optimized()` - Final video assembly
125
+ - `_get_code_hash()` - Intelligent caching
126
+
127
+ ### 4. **🔍 RAG Intelligence Agent** (`src/rag/rag_integration.py`, `src/rag/vector_store.py`)
128
+ **Role**: Knowledge retrieval and context augmentation
129
+
130
+ **Key Capabilities**:
131
+ - Manim documentation retrieval
132
+ - Plugin detection and relevance scoring
133
+ - Vector store management with ChromaDB
134
+ - Query generation for technical contexts
135
+ - Enhanced document embedding and retrieval
136
+
137
+ **Key Methods**:
138
+ - `detect_relevant_plugins()` - Smart plugin identification
139
+ - `retrieve_relevant_docs()` - Context-aware documentation retrieval
140
+ - `generate_rag_queries()` - Intelligent query formulation
141
+
142
+ ### 5. **📝 Task & Prompt Generation Service** (`task_generator/`)
143
+ **Role**: Template management and prompt engineering
144
+
145
+ **Key Capabilities**:
146
+ - Dynamic prompt template generation
147
+ - Context-aware prompt customization
148
+ - Banned reasoning pattern management
149
+ - Multi-modal prompt support
150
+
151
+ **Key Components**:
152
+ - `parse_prompt.py` - Template processing
153
+ - `prompts_raw/` - Prompt template repository
154
+
155
+ ### 6. **🤖 LLM Provider Agents** (`mllm_tools/`)
156
+ **Role**: AI model abstraction and management
157
+
158
+ **Key Capabilities**:
159
+ - Multi-provider LLM support (OpenAI, Gemini, Vertex AI, OpenRouter)
160
+ - Unified interface for different AI models
161
+ - Cost tracking and usage monitoring
162
+ - Langfuse integration for observability
163
+
164
+ **Key Components**:
165
+ - `litellm.py` - LiteLLM wrapper for multiple providers
166
+ - `openrouter.py` - OpenRouter integration
167
+ - `gemini.py` - Google Gemini integration
168
+ - `vertex_ai.py` - Google Cloud Vertex AI
169
+
170
+ ### 7. **✅ Quality Evaluation Agent** (`eval_suite/`)
171
+ **Role**: Output validation and quality assurance
172
+
173
+ **Key Capabilities**:
174
+ - Multi-modal content evaluation (text, image, video)
175
+ - Automated quality scoring
176
+ - Error pattern detection
177
+ - Performance metrics collection
178
+
179
+ **Key Components**:
180
+ - `text_utils.py` - Text quality evaluation
181
+ - `image_utils.py` - Visual content assessment
182
+ - `video_utils.py` - Video quality metrics
183
+
184
+ ## 🔄 Multi-Agent Workflow
185
+
186
+ ### **Phase 1: Initialization & Planning**
187
+ 1. **System Orchestrator** (`generate_video.py`) receives user input
188
+ 2. **Configuration Manager** loads system settings and model configurations
189
+ 3. **Session Manager** creates/loads session for continuity
190
+ 4. **Video Planning Agent** analyzes topic and creates scene breakdown
191
+ 5. **RAG Agent** detects relevant plugins and retrieves documentation
192
+
193
+ ### **Phase 2: Implementation Planning**
194
+ 1. **Video Planning Agent** generates detailed implementation plans for each scene
195
+ 2. **Task Generator** provides appropriate prompt templates
196
+ 3. **RAG Agent** augments plans with relevant technical documentation
197
+ 4. **Scene Analyzer** validates plan completeness
198
+
199
+ ### **Phase 3: Code Generation**
200
+ 1. **Code Generation Agent** transforms scene plans into Manim code
201
+ 2. **RAG Agent** provides contextual documentation for complex animations
202
+ 3. **Error Detection** validates code syntax and logic
203
+ 4. **Quality Assurance** ensures code meets standards
204
+
205
+ ### **Phase 4: Rendering & Assembly**
206
+ 1. **Video Rendering Agent** executes Manim code to generate scenes
207
+ 2. **Caching System** optimizes performance through intelligent storage
208
+ 3. **Parallel Processing** renders multiple scenes concurrently
209
+ 4. **Quality Control** validates rendered output
210
+
211
+ ### **Phase 5: Final Assembly**
212
+ 1. **Video Rendering Agent** combines individual scenes
213
+ 2. **Audio Integration** adds voiceovers and sound effects
214
+ 3. **Quality Evaluation Agent** performs final validation
215
+ 4. **Output Manager** delivers final video with metadata
216
+
217
+ ## 🏛️ Design Principles
218
+
219
+ ### **SOLID Principles Implementation**
220
+
221
+ 1. **Single Responsibility Principle**
222
+ - Each agent has a focused, well-defined purpose
223
+ - Clear separation of concerns across components
224
+
225
+ 2. **Open/Closed Principle**
226
+ - System extensible through composition and interfaces
227
+ - New agents can be added without modifying existing code
228
+
229
+ 3. **Liskov Substitution Principle**
230
+ - Agents implement common interfaces for interchangeability
231
+ - Protocol-based design ensures compatibility
232
+
233
+ 4. **Interface Segregation Principle**
234
+ - Clean, focused interfaces for agent communication
235
+ - No forced dependencies on unused functionality
236
+
237
+ 5. **Dependency Inversion Principle**
238
+ - High-level modules depend on abstractions
239
+ - Factory pattern for component creation
240
+
241
+ ### **Multi-Agent Coordination Patterns**
242
+
243
+ 1. **Pipeline Architecture**: Sequential processing with clear handoffs
244
+ 2. **Publish-Subscribe**: Event-driven communication between agents
245
+ 3. **Factory Pattern**: Dynamic agent creation and configuration
246
+ 4. **Strategy Pattern**: Pluggable algorithms for different tasks
247
+ 5. **Observer Pattern**: Monitoring and logging across agents
248
+
249
+ ## ⚡ Performance Optimizations
250
+
251
+ ### **Concurrency & Parallelization**
252
+ - **Async/Await**: Non-blocking agent coordination
253
+ - **Semaphore Control**: Intelligent resource management
254
+ - **Thread Pools**: Parallel I/O operations
255
+ - **Concurrent Scene Processing**: Multiple scenes rendered simultaneously
256
+
257
+ ### **Intelligent Caching**
258
+ - **Code Hash-based Caching**: Avoid redundant renders
259
+ - **Context Caching**: Reuse prompt templates and examples
260
+ - **Vector Store Caching**: Optimized document retrieval
261
+
262
+ ### **Resource Management**
263
+ - **GPU Acceleration**: Hardware-accelerated rendering
264
+ - **Memory Optimization**: Efficient data structures
265
+ - **Quality Presets**: Speed vs. quality tradeoffs
266
+
267
+ ## 🔧 Configuration Management
268
+
269
+ ### **Environment Configuration** (`.env`, `src/config/config.py`)
270
+ ```python
271
+ class VideoGenerationConfig:
272
+ planner_model: str # Primary AI model
273
+ scene_model: Optional[str] = None # Scene-specific model
274
+ helper_model: Optional[str] = None # Helper tasks model
275
+ max_scene_concurrency: int = 5 # Parallel scene limit
276
+ use_rag: bool = False # RAG integration
277
+ enable_caching: bool = True # Performance caching
278
+ use_gpu_acceleration: bool = False # Hardware acceleration
279
+ ```
280
+
281
+ ### **Model Provider Configuration**
282
+ - Support for multiple LLM providers (OpenAI, Gemini, Claude, etc.)
283
+ - Unified interface through LiteLLM
284
+ - Cost tracking and usage monitoring
285
+ - Automatic failover capabilities
286
+
287
+ ## 📊 Data Flow Architecture
288
+
289
+ ### **Input Data Sources**
290
+ - **Theorem Datasets**: JSON files with mathematical concepts (`data/thb_*/`)
291
+ - **Context Learning**: Historical examples (`data/context_learning/`)
292
+ - **RAG Documentation**: Manim docs and plugins (`data/rag/manim_docs/`)
293
+
294
+ ### **Processing Pipeline**
295
+ ```
296
+ User Input → Topic Analysis → Scene Planning → Code Generation → Rendering → Quality Check → Final Output
297
+ ↓ ↓ ↓ ↓ ↓ ↓
298
+ Configuration → RAG Context → Implementation → Error Fixing → Optimization → Validation
299
+ ```
300
+
301
+ ### **Output Artifacts**
302
+ - **Scene Outlines**: Structured video plans
303
+ - **Implementation Plans**: Technical specifications
304
+ - **Manim Code**: Executable animation scripts
305
+ - **Rendered Videos**: Individual scene outputs
306
+ - **Combined Videos**: Final assembled content
307
+ - **Metadata**: Processing logs and metrics
308
+
309
+ ## 🎪 Advanced Features
310
+
311
+ ### **Error Recovery & Self-Healing**
312
+ - **Multi-layer Retry Logic**: Automatic error recovery at each agent level
313
+ - **Intelligent Error Analysis**: Pattern recognition for common failures
314
+ - **Self-Reflection**: Code quality validation through visual analysis
315
+ - **Fallback Strategies**: Alternative approaches when primary methods fail
316
+
317
+ ### **Monitoring & Observability**
318
+ - **Langfuse Integration**: Comprehensive LLM call tracking
319
+ - **Performance Metrics**: Render times, success rates, resource usage
320
+ - **Status Dashboard**: Real-time pipeline state visualization
321
+ - **Cost Tracking**: Token usage and API cost monitoring
322
+
323
+ ### **Scalability Features**
324
+ - **Horizontal Scaling**: Multiple concurrent topic processing
325
+ - **Resource Pooling**: Shared computational resources
326
+ - **Load Balancing**: Intelligent task distribution
327
+ - **State Persistence**: Resume interrupted processing
328
+
329
+ ## 🚀 Usage Examples
330
+
331
+ ### **Single Topic Generation**
332
+ ```bash
333
+ python generate_video.py \
334
+ --topic "Pythagorean Theorem" \
335
+ --context "Explain the mathematical proof and visual demonstration" \
336
+ --model "gemini/gemini-2.5-flash-preview-04-17" \
337
+ --use_rag \
338
+ --quality medium
339
+ ```
340
+
341
+ ### **Batch Processing**
342
+ ```bash
343
+ python generate_video.py \
344
+ --theorems_path data/thb_easy/math.json \
345
+ --sample_size 5 \
346
+ --max_scene_concurrency 3 \
347
+ --use_context_learning \
348
+ --enable_caching
349
+ ```
350
+
351
+ ### **Status Monitoring**
352
+ ```bash
353
+ python generate_video.py \
354
+ --theorems_path data/thb_easy/math.json \
355
+ --check_status
356
+ ```
357
+
358
+ ## 📈 System Metrics & KPIs
359
+
360
+ ### **Performance Indicators**
361
+ - **Scene Generation Speed**: Average time per scene
362
+ - **Rendering Efficiency**: Cache hit rates and parallel utilization
363
+ - **Quality Scores**: Automated evaluation metrics
364
+ - **Success Rates**: Completion percentage across pipeline stages
365
+
366
+ ### **Resource Utilization**
367
+ - **LLM Token Usage**: Cost optimization and efficiency
368
+ - **Computational Resources**: CPU/GPU utilization
369
+ - **Storage Efficiency**: Cache effectiveness and data management
370
+ - **Memory Footprint**: System resource consumption
371
+
372
+ ## 🔮 Future Enhancements
373
+
374
+ ### **Planned Agent Improvements**
375
+ - **Advanced Visual Agent**: Enhanced image understanding and generation
376
+ - **Audio Synthesis Agent**: Dynamic voiceover generation
377
+ - **Interactive Agent**: Real-time user feedback integration
378
+ - **Curriculum Agent**: Adaptive learning path generation
379
+
380
+ ### **Technical Roadmap**
381
+ - **Distributed Processing**: Multi-node agent deployment
382
+ - **Real-time Streaming**: Live video generation capabilities
383
+ - **Mobile Integration**: Responsive design for mobile platforms
384
+ - **API Gateway**: RESTful service architecture
385
+
386
+ ---
387
+
388
+ ## 📚 Related Documentation
389
+
390
+ - **[API Reference](docs/api_reference.md)** - Detailed method documentation
391
+ - **[Configuration Guide](docs/configuration.md)** - Setup and customization
392
+ - **[Development Guide](docs/development.md)** - Contributing and extending
393
+ - **[Troubleshooting](docs/troubleshooting.md)** - Common issues and solutions
394
+
395
+ ---
396
+
397
+ **Last Updated**: August 25, 2025
398
+ **Version**: Multi-Agent Enhanced Pipeline v2.0
399
+ **Maintainer**: T2M Development Team
WORKING_SETUP.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ Working FastAPI + Docker + ngrok Setup
2
+
3
+ ## 🎯 Current Status: WORKING!
4
+
5
+ Your development environment is successfully set up with:
6
+ - ✅ Redis running in Docker
7
+ - ✅ ngrok exposing your local server publicly
8
+ - ✅ FastAPI server running locally
9
+ - ✅ Public HTTPS URL: `https://d4e9601ecb72.ngrok-free.app`
10
+
11
+ ## 🚀 Quick Start Commands
12
+
13
+ ### 1. Start Services (Redis + ngrok)
14
+ ```bash
15
+ ./start-services.sh
16
+ ```
17
+
18
+ ### 2. Start FastAPI Server
19
+ ```bash
20
+ # Simple working version
21
+ python simple_app.py
22
+
23
+ # Or the full version (once config is fixed)
24
+ python -m uvicorn src.app.main:app --reload --host 0.0.0.0 --port 8000
25
+ ```
26
+
27
+ ### 3. Get Your Public URL
28
+ ```bash
29
+ curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'
30
+ ```
31
+
32
+ ## 🌐 Your URLs
33
+
34
+ - **Local API**: http://localhost:8000
35
+ - **Public API**: https://d4e9601ecb72.ngrok-free.app
36
+ - **API Docs**: https://d4e9601ecb72.ngrok-free.app/docs (when using simple_app.py)
37
+ - **ngrok Dashboard**: http://localhost:4040
38
+ - **Redis**: localhost:6379
39
+
40
+ ## 🔧 Configure Clerk Webhook
41
+
42
+ Now you can set up your Clerk webhook:
43
+
44
+ 1. Go to [Clerk Dashboard](https://dashboard.clerk.com/)
45
+ 2. Navigate to **Webhooks** → **Add Endpoint**
46
+ 3. Enter your webhook URL:
47
+ ```
48
+ https://d4e9601ecb72.ngrok-free.app/api/v1/auth/webhooks/clerk
49
+ ```
50
+ 4. Select events: `user.created`, `user.updated`, `user.deleted`, `session.created`, `session.ended`
51
+ 5. Copy the **Signing Secret** and add to your `.env`:
52
+ ```env
53
+ CLERK_WEBHOOK_SECRET=whsec_your_webhook_signing_secret_here
54
+ ```
55
+
56
+ ## 📝 Available Endpoints (Simple App)
57
+
58
+ - `GET /` - Welcome message
59
+ - `GET /health` - Health check
60
+ - `GET /config` - Configuration info
61
+
62
+ Test them:
63
+ ```bash
64
+ curl https://d4e9601ecb72.ngrok-free.app/
65
+ curl https://d4e9601ecb72.ngrok-free.app/health
66
+ curl https://d4e9601ecb72.ngrok-free.app/config
67
+ ```
68
+
69
+ ## 🛠️ Service Management
70
+
71
+ ### Check Services
72
+ ```bash
73
+ # Check Redis
74
+ redis-cli ping
75
+
76
+ # Check ngrok tunnels
77
+ curl -s http://localhost:4040/api/tunnels
78
+
79
+ # Check Docker containers
80
+ docker ps
81
+ ```
82
+
83
+ ### Stop Services
84
+ ```bash
85
+ # Stop all related containers
86
+ docker stop $(docker ps -q --filter 'ancestor=redis:7-alpine' --filter 'ancestor=ngrok/ngrok:latest')
87
+ ```
88
+
89
+ ### Restart Services
90
+ ```bash
91
+ ./start-services.sh
92
+ ```
93
+
94
+ ## 🔍 Troubleshooting
95
+
96
+ ### If ngrok URL changes:
97
+ The ngrok URL will change each time you restart ngrok (unless you have a paid plan). Get the new URL with:
98
+ ```bash
99
+ curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'
100
+ ```
101
+
102
+ ### If Redis connection fails:
103
+ ```bash
104
+ # Check if Redis is running
105
+ docker ps | grep redis
106
+
107
+ # Restart Redis if needed
108
+ docker restart $(docker ps -q --filter 'ancestor=redis:7-alpine')
109
+ ```
110
+
111
+ ### If FastAPI config fails:
112
+ Use the simple app for now:
113
+ ```bash
114
+ python simple_app.py
115
+ ```
116
+
117
+ ## 🎉 Next Steps
118
+
119
+ 1. **Test your webhook**: Use the public URL to set up Clerk webhooks
120
+ 2. **Fix the main FastAPI app**: The config parsing issue needs to be resolved
121
+ 3. **Add authentication**: Implement Clerk authentication middleware
122
+ 4. **Add your business logic**: Build your video generation endpoints
123
+
124
+ ## 📋 Working Files
125
+
126
+ - `start-services.sh` - Starts Redis and ngrok
127
+ - `simple_app.py` - Working FastAPI application
128
+ - `simple_config.py` - Working configuration
129
+ - `.env` - Environment variables (configured)
130
+
131
+ Your development environment is ready for webhook testing! 🚀
auth_token.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ eyJhbGciOiJSUzI1NiIsImNhdCI6ImNsX0I3ZDRQRDExMUFBQSIsImtpZCI6Imluc18zMXBCZktEanhrSFJDUUFUTDN6Q29pUmxad2YiLCJ0eXAiOiJKV1QifQ.eyJhenAiOiJodHRwczovL3BvZXRpYy1wcmltYXRlLTQ4LmFjY291bnRzLmRldiIsImV4cCI6MTc1NjI3NzQ3OCwiZnZhIjpbMTcsLTFdLCJpYXQiOjE3NTYyNzc0MTgsImlzcyI6Imh0dHBzOi8vcG9ldGljLXByaW1hdGUtNDguY2xlcmsuYWNjb3VudHMuZGV2IiwibmJmIjoxNzU2Mjc3NDA4LCJzaWQiOiJzZXNzXzMxckpnS3R1Z1BKU2VlNVFJRXp5NGpYcW1NbyIsInN0cyI6ImFjdGl2ZSIsInN1YiI6InVzZXJfMzFxbHNuWDZYZTh4TmRXYnNHTDVNU3hnQnhIIiwidiI6Mn0.XUCI_plOZmMBfZhMMHViCj4KXpMWobjQp-AJf7VgdeALSi6lPKdzKA6vDLyjFngnY-XrFCDP4UI7iNNqRw32_Mvr9ipxqzWKAtL6P_KP3HonOEGfrsMibFUZdUF2cv9N3aJ-sL64QteIV0-NAdlprjM_vbYiW8dlJPuNlpOoFF9fWGPr8s3uVwEK6BPSrzbBSkcIHSbAGiBpzRaFdaupOZBgC-WAxj-_cPQIA0AKr_9nrG-UFozjdfbi74AGM0MWSP7GLe_pDQ-t1FKAFXMNtS0ZB5ylwnZpnAnxPBMG4UPQzHXjo5xeibtRb0-4JSUu-_kCmvTxWuvcHqsjqFwXpg
docker-compose.dev.yml ADDED
File without changes
docker-compose.simple.yml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ # Redis service for caching and job queuing
5
+ redis:
6
+ image: redis:7-alpine
7
+ container_name: t2m-redis
8
+ ports:
9
+ - "6379:6379"
10
+ volumes:
11
+ - redis_data:/data
12
+ command: redis-server --appendonly yes
13
+ healthcheck:
14
+ test: ["CMD", "redis-cli", "ping"]
15
+ interval: 10s
16
+ timeout: 3s
17
+ retries: 3
18
+ restart: unless-stopped
19
+
20
+ # ngrok service - simple HTTP tunnel
21
+ ngrok:
22
+ image: ngrok/ngrok:latest
23
+ container_name: t2m-ngrok
24
+ restart: unless-stopped
25
+ command: ["http", "host.docker.internal:8000"]
26
+ ports:
27
+ - "4040:4040"
28
+ environment:
29
+ - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN}
30
+ extra_hosts:
31
+ - "host.docker.internal:host-gateway"
32
+
33
+ volumes:
34
+ redis_data:
docker-compose.test.yml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ theoremexplain:
5
+ build:
6
+ context: .
7
+ dockerfile: dockerfile
8
+ container_name: theoremexplain-agent
9
+ ports:
10
+ - "7860:7860"
11
+ volumes:
12
+ # Mount output directory to persist generated videos
13
+ - ./output:/app/output
14
+ # Mount models directory if you want to use local models
15
+ - ./models:/app/models
16
+ # Mount data directory for RAG and datasets
17
+ - ./data:/app/data
18
+ environment:
19
+ # Copy environment variables from host .env file
20
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
21
+ - GEMINI_API_KEY=${GEMINI_API_KEY}
22
+ # Kokoro TTS settings
23
+ - KOKORO_MODEL_PATH=models/kokoro-v0_19.onnx
24
+ - KOKORO_VOICES_PATH=models/voices.bin
25
+ - KOKORO_DEFAULT_VOICE=af
26
+ - KOKORO_DEFAULT_SPEED=1.0
27
+ - KOKORO_DEFAULT_LANG=en-us
28
+ # Python path
29
+ - PYTHONPATH=/app:$PYTHONPATH
30
+ restart: unless-stopped
31
+ healthcheck:
32
+ test: ["uv", "run" , "manim" ,"checkhealth"]
33
+ interval: 30s
34
+ timeout: 10s
35
+ retries: 3
36
+ start_period: 60s
37
+
38
+ # Optional: Add a service for running batch generation
39
+ theoremexplain-batch:
40
+ build:
41
+ context: .
42
+ dockerfile: dockerfile
43
+ container_name: theoremexplain-batch
44
+ profiles:
45
+ - batch
46
+ volumes:
47
+ - ./output:/app/output
48
+ - ./models:/app/models
49
+ - ./data:/app/data
50
+ environment:
51
+ # Same environment variables as main service
52
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
53
+ - GEMINI_API_KEY=${GEMINI_API_KEY}
54
+ - KOKORO_MODEL_PATH=models/kokoro-v0_19.onnx
55
+ - KOKORO_VOICES_PATH=models/voices.bin
56
+ - KOKORO_DEFAULT_VOICE=af
57
+ - KOKORO_DEFAULT_SPEED=1.0
58
+ - KOKORO_DEFAULT_LANG=en-us
59
+ - PYTHONPATH=/app:$PYTHONPATH
60
+ command: >
61
+ uv run python generate_video.py
62
+ --model "openai/gpt-4o-mini"
63
+ --helper_model "openai/gpt-4o-mini"
64
+ --output_dir "output/batch_generation"
65
+ --theorems_path "data/thb_easy/math.json"
66
+ --max_scene_concurrency 3
67
+ --max_topic_concurrency 5
68
+ restart: no
docker-compose.yml CHANGED
@@ -1,80 +0,0 @@
1
- version: '3.8'
2
-
3
- services:
4
- theoremexplain:
5
- build:
6
- context: .
7
- dockerfile: dockerfile
8
- container_name: theoremexplain-agent
9
- ports:
10
- - "7860:7860"
11
- volumes:
12
- # Mount output directory to persist generated videos
13
- - ./output:/app/output
14
- # Mount models directory if you want to use local models
15
- - ./models:/app/models
16
- # Mount data directory for RAG and datasets
17
- - ./data:/app/data
18
- environment:
19
- # Copy environment variables from host .env file
20
- - OPENAI_API_KEY=${OPENAI_API_KEY}
21
- - AZURE_API_KEY=${AZURE_API_KEY}
22
- - AZURE_API_BASE=${AZURE_API_BASE}
23
- - AZURE_API_VERSION=${AZURE_API_VERSION}
24
- - VERTEXAI_PROJECT=${VERTEXAI_PROJECT}
25
- - VERTEXAI_LOCATION=${VERTEXAI_LOCATION}
26
- - GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS}
27
- - GEMINI_API_KEY=${GEMINI_API_KEY}
28
- # Kokoro TTS settings
29
- - KOKORO_MODEL_PATH=models/kokoro-v0_19.onnx
30
- - KOKORO_VOICES_PATH=models/voices.bin
31
- - KOKORO_DEFAULT_VOICE=af
32
- - KOKORO_DEFAULT_SPEED=1.0
33
- - KOKORO_DEFAULT_LANG=en-us
34
- # Python path
35
- - PYTHONPATH=/app:$PYTHONPATH
36
- restart: unless-stopped
37
- healthcheck:
38
- test: ["CMD", "conda", "run", "-n", "tea", "python", "-c", "import src; import manim; print('Health check passed')"]
39
- interval: 30s
40
- timeout: 10s
41
- retries: 3
42
- start_period: 60s
43
-
44
- # Optional: Add a service for running batch generation
45
- theoremexplain-batch:
46
- build:
47
- context: .
48
- dockerfile: dockerfile
49
- container_name: theoremexplain-batch
50
- profiles:
51
- - batch
52
- volumes:
53
- - ./output:/app/output
54
- - ./models:/app/models
55
- - ./data:/app/data
56
- environment:
57
- # Same environment variables as main service
58
- - OPENAI_API_KEY=${OPENAI_API_KEY}
59
- - AZURE_API_KEY=${AZURE_API_KEY}
60
- - AZURE_API_BASE=${AZURE_API_BASE}
61
- - AZURE_API_VERSION=${AZURE_API_VERSION}
62
- - VERTEXAI_PROJECT=${VERTEXAI_PROJECT}
63
- - VERTEXAI_LOCATION=${VERTEXAI_LOCATION}
64
- - GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS}
65
- - GEMINI_API_KEY=${GEMINI_API_KEY}
66
- - KOKORO_MODEL_PATH=models/kokoro-v0_19.onnx
67
- - KOKORO_VOICES_PATH=models/voices.bin
68
- - KOKORO_DEFAULT_VOICE=af
69
- - KOKORO_DEFAULT_SPEED=1.0
70
- - KOKORO_DEFAULT_LANG=en-us
71
- - PYTHONPATH=/app:$PYTHONPATH
72
- command: >
73
- conda run --no-capture-output -n tea python generate_video.py
74
- --model "openai/gpt-4o-mini"
75
- --helper_model "openai/gpt-4o-mini"
76
- --output_dir "output/batch_generation"
77
- --theorems_path "data/thb_easy/math.json"
78
- --max_scene_concurrency 3
79
- --max_topic_concurrency 5
80
- restart: no
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/client-generation.md ADDED
@@ -0,0 +1,525 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Client SDK Generation Guide
2
+
3
+ This guide explains how to generate and use client SDKs for the Video Generation API.
4
+
5
+ ## Overview
6
+
7
+ The Video Generation API supports automatic client SDK generation in multiple programming languages using OpenAPI Generator. This allows developers to quickly integrate with the API using idiomatic code in their preferred language.
8
+
9
+ ## Supported Languages
10
+
11
+ - **TypeScript/JavaScript** - Modern TypeScript client with fetch API
12
+ - **Python** - Python client with requests library
13
+ - **Java** - Java client with OkHttp and Gson
14
+ - **C#** - .NET Standard 2.0 compatible client
15
+ - **Go** - Go client with native HTTP library
16
+ - **PHP** - PHP client with Guzzle HTTP
17
+ - **Ruby** - Ruby client with Faraday HTTP
18
+
19
+ ## Prerequisites
20
+
21
+ ### Required Tools
22
+
23
+ 1. **Node.js and npm** - For OpenAPI Generator CLI
24
+ 2. **OpenAPI Generator CLI** - For generating clients
25
+ 3. **Language-specific tools** (optional, for testing):
26
+ - TypeScript: Node.js, npm
27
+ - Python: Python 3.7+, pip
28
+ - Java: JDK 8+, Maven
29
+ - C#: .NET Core SDK
30
+ - Go: Go 1.16+
31
+ - PHP: PHP 7.4+, Composer
32
+ - Ruby: Ruby 2.7+, Bundler
33
+
34
+ ### Installation
35
+
36
+ Install OpenAPI Generator CLI:
37
+
38
+ ```bash
39
+ npm install -g @openapitools/openapi-generator-cli
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### Using the Python Script
45
+
46
+ The easiest way to generate clients is using the provided Python script:
47
+
48
+ ```bash
49
+ # Generate all supported clients
50
+ python scripts/generate_clients.py
51
+
52
+ # Generate specific languages
53
+ python scripts/generate_clients.py --languages typescript python
54
+
55
+ # Use custom API URL
56
+ python scripts/generate_clients.py --api-url https://api.example.com
57
+
58
+ # Custom output directory
59
+ python scripts/generate_clients.py --output-dir my_clients
60
+ ```
61
+
62
+ ### Using the Shell Script
63
+
64
+ Alternatively, use the shell script:
65
+
66
+ ```bash
67
+ # Generate all clients
68
+ ./scripts/generate_clients.sh
69
+
70
+ # Generate specific languages
71
+ ./scripts/generate_clients.sh typescript python java
72
+
73
+ # Use custom API URL
74
+ ./scripts/generate_clients.sh --api-url https://api.example.com typescript
75
+ ```
76
+
77
+ ### Manual Generation
78
+
79
+ For manual control, use OpenAPI Generator CLI directly:
80
+
81
+ ```bash
82
+ # Fetch OpenAPI specification
83
+ curl -o openapi.json http://localhost:8000/openapi.json
84
+
85
+ # Generate TypeScript client
86
+ openapi-generator-cli generate \
87
+ -i openapi.json \
88
+ -g typescript-fetch \
89
+ -o clients/typescript \
90
+ --package-name video-api-client
91
+
92
+ # Generate Python client
93
+ openapi-generator-cli generate \
94
+ -i openapi.json \
95
+ -g python \
96
+ -o clients/python \
97
+ --package-name video_api_client
98
+ ```
99
+
100
+ ## Generated Client Structure
101
+
102
+ Each generated client includes:
103
+
104
+ ```
105
+ clients/
106
+ ├── typescript/
107
+ │ ├── src/
108
+ │ │ ├── apis/
109
+ │ │ ├── models/
110
+ │ │ └── index.ts
111
+ │ ├── package.json
112
+ │ ├── README.md
113
+ │ └── example.ts
114
+ ├── python/
115
+ │ ├── video_api_client/
116
+ │ │ ├── api/
117
+ │ │ ├── models/
118
+ │ │ └── __init__.py
119
+ │ ├── setup.py
120
+ │ ├── README.md
121
+ │ └── example.py
122
+ └── ...
123
+ ```
124
+
125
+ ## Usage Examples
126
+
127
+ ### TypeScript
128
+
129
+ ```typescript
130
+ import { Configuration, VideosApi } from 'video-api-client';
131
+
132
+ const config = new Configuration({
133
+ basePath: 'https://api.example.com',
134
+ accessToken: 'your-clerk-session-token'
135
+ });
136
+
137
+ const videosApi = new VideosApi(config);
138
+
139
+ async function generateVideo() {
140
+ try {
141
+ const jobResponse = await videosApi.createVideoGenerationJob({
142
+ configuration: {
143
+ topic: "Pythagorean Theorem",
144
+ context: "Explain the mathematical proof",
145
+ quality: "medium",
146
+ use_rag: true
147
+ }
148
+ });
149
+
150
+ console.log('Job created:', jobResponse.job_id);
151
+
152
+ // Poll for completion
153
+ let status = 'queued';
154
+ while (status !== 'completed' && status !== 'failed') {
155
+ await new Promise(resolve => setTimeout(resolve, 5000));
156
+
157
+ const statusResponse = await videosApi.getVideoJobStatus(jobResponse.job_id);
158
+ status = statusResponse.status;
159
+
160
+ console.log(`Status: ${status} (${statusResponse.progress.percentage}%)`);
161
+ }
162
+
163
+ if (status === 'completed') {
164
+ const videoBlob = await videosApi.downloadVideoFile(jobResponse.job_id);
165
+ console.log('Video downloaded');
166
+ }
167
+
168
+ } catch (error) {
169
+ console.error('Error:', error);
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### Python
175
+
176
+ ```python
177
+ import time
178
+ from video_api_client import Configuration, ApiClient, VideosApi
179
+ from video_api_client.models import JobCreateRequest, JobConfiguration
180
+
181
+ configuration = Configuration(
182
+ host="https://api.example.com",
183
+ access_token="your-clerk-session-token"
184
+ )
185
+
186
+ with ApiClient(configuration) as api_client:
187
+ videos_api = VideosApi(api_client)
188
+
189
+ try:
190
+ # Create job
191
+ job_request = JobCreateRequest(
192
+ configuration=JobConfiguration(
193
+ topic="Pythagorean Theorem",
194
+ context="Explain the mathematical proof",
195
+ quality="medium",
196
+ use_rag=True
197
+ )
198
+ )
199
+
200
+ job_response = videos_api.create_video_generation_job(job_request)
201
+ print(f"Job created: {job_response.job_id}")
202
+
203
+ # Poll for completion
204
+ status = "queued"
205
+ while status not in ["completed", "failed"]:
206
+ time.sleep(5)
207
+
208
+ status_response = videos_api.get_video_job_status(job_response.job_id)
209
+ status = status_response.status
210
+
211
+ print(f"Status: {status} ({status_response.progress.percentage}%)")
212
+
213
+ if status == "completed":
214
+ video_data = videos_api.download_video_file(job_response.job_id)
215
+ with open("video.mp4", "wb") as f:
216
+ f.write(video_data)
217
+ print("Video downloaded")
218
+
219
+ except Exception as e:
220
+ print(f"Error: {e}")
221
+ ```
222
+
223
+ ### Java
224
+
225
+ ```java
226
+ import com.example.videoapiclient.ApiClient;
227
+ import com.example.videoapiclient.Configuration;
228
+ import com.example.videoapiclient.api.VideosApi;
229
+ import com.example.videoapiclient.model.*;
230
+
231
+ public class VideoApiExample {
232
+ public static void main(String[] args) {
233
+ ApiClient client = Configuration.getDefaultApiClient();
234
+ client.setBasePath("https://api.example.com");
235
+ client.setAccessToken("your-clerk-session-token");
236
+
237
+ VideosApi videosApi = new VideosApi(client);
238
+
239
+ try {
240
+ JobConfiguration config = new JobConfiguration()
241
+ .topic("Pythagorean Theorem")
242
+ .context("Explain the mathematical proof")
243
+ .quality(VideoQuality.MEDIUM)
244
+ .useRag(true);
245
+
246
+ JobCreateRequest request = new JobCreateRequest().configuration(config);
247
+ JobResponse jobResponse = videosApi.createVideoGenerationJob(request);
248
+
249
+ System.out.println("Job created: " + jobResponse.getJobId());
250
+
251
+ // Poll for completion
252
+ String status = "queued";
253
+ while (!"completed".equals(status) && !"failed".equals(status)) {
254
+ Thread.sleep(5000);
255
+
256
+ JobStatusResponse statusResponse = videosApi.getVideoJobStatus(jobResponse.getJobId());
257
+ status = statusResponse.getStatus().getValue();
258
+
259
+ System.out.println("Status: " + status + " (" +
260
+ statusResponse.getProgress().getPercentage() + "%)");
261
+ }
262
+
263
+ if ("completed".equals(status)) {
264
+ System.out.println("Video generation completed!");
265
+ }
266
+
267
+ } catch (Exception e) {
268
+ System.err.println("Error: " + e.getMessage());
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ ## Authentication
275
+
276
+ All clients support Clerk authentication. Set your session token when configuring the client:
277
+
278
+ ### TypeScript
279
+ ```typescript
280
+ const config = new Configuration({
281
+ accessToken: 'your-clerk-session-token'
282
+ });
283
+ ```
284
+
285
+ ### Python
286
+ ```python
287
+ configuration.access_token = 'your-clerk-session-token'
288
+ ```
289
+
290
+ ### Java
291
+ ```java
292
+ client.setAccessToken("your-clerk-session-token");
293
+ ```
294
+
295
+ ## Error Handling
296
+
297
+ All clients provide structured error handling:
298
+
299
+ ### TypeScript
300
+ ```typescript
301
+ try {
302
+ const response = await api.createVideoGenerationJob(request);
303
+ } catch (error) {
304
+ if (error.status === 422) {
305
+ console.error('Validation error:', error.body);
306
+ } else if (error.status === 429) {
307
+ console.error('Rate limit exceeded');
308
+ } else {
309
+ console.error('API error:', error);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### Python
315
+ ```python
316
+ from video_api_client.exceptions import ApiException
317
+
318
+ try:
319
+ response = api.create_video_generation_job(request)
320
+ except ApiException as e:
321
+ if e.status == 422:
322
+ print(f"Validation error: {e.body}")
323
+ elif e.status == 429:
324
+ print("Rate limit exceeded")
325
+ else:
326
+ print(f"API error: {e}")
327
+ ```
328
+
329
+ ## Testing Generated Clients
330
+
331
+ Use the provided testing script to validate generated clients:
332
+
333
+ ```bash
334
+ # Test all generated clients
335
+ python scripts/test_clients.py
336
+
337
+ # Test with custom API URL
338
+ python scripts/test_clients.py --api-url https://api.example.com
339
+
340
+ # Save test results
341
+ python scripts/test_clients.py --output-file test_results.json
342
+
343
+ # Verbose output
344
+ python scripts/test_clients.py --verbose
345
+ ```
346
+
347
+ The test script validates:
348
+ - Client structure and files
349
+ - Build/compilation success
350
+ - Import/usage capability
351
+ - API connectivity
352
+ - Basic functionality
353
+
354
+ ## Customization
355
+
356
+ ### Custom Templates
357
+
358
+ Create custom templates in the `templates/` directory:
359
+
360
+ ```
361
+ templates/
362
+ ├── typescript/
363
+ │ ├── api.mustache
364
+ │ ├── model.mustache
365
+ │ └── README.mustache
366
+ ├── python/
367
+ │ ├── api.mustache
368
+ │ └── model.mustache
369
+ └── ...
370
+ ```
371
+
372
+ ### Configuration File
373
+
374
+ Modify `openapi-generator-config.yaml` to customize generation:
375
+
376
+ ```yaml
377
+ typescript:
378
+ generatorName: typescript-fetch
379
+ additionalProperties:
380
+ npmName: my-custom-client
381
+ supportsES6: true
382
+ withInterfaces: true
383
+ ```
384
+
385
+ ### Custom Properties
386
+
387
+ Pass additional properties during generation:
388
+
389
+ ```bash
390
+ openapi-generator-cli generate \
391
+ -i openapi.json \
392
+ -g typescript-fetch \
393
+ -o clients/typescript \
394
+ --additional-properties=npmName=my-client,supportsES6=true
395
+ ```
396
+
397
+ ## Publishing Clients
398
+
399
+ ### NPM (TypeScript/JavaScript)
400
+
401
+ ```bash
402
+ cd clients/typescript
403
+ npm publish
404
+ ```
405
+
406
+ ### PyPI (Python)
407
+
408
+ ```bash
409
+ cd clients/python
410
+ python setup.py sdist bdist_wheel
411
+ twine upload dist/*
412
+ ```
413
+
414
+ ### Maven Central (Java)
415
+
416
+ ```bash
417
+ cd clients/java
418
+ mvn deploy
419
+ ```
420
+
421
+ ### NuGet (C#)
422
+
423
+ ```bash
424
+ cd clients/csharp
425
+ dotnet pack
426
+ dotnet nuget push *.nupkg
427
+ ```
428
+
429
+ ## Troubleshooting
430
+
431
+ ### Common Issues
432
+
433
+ 1. **OpenAPI Generator not found**
434
+ ```bash
435
+ npm install -g @openapitools/openapi-generator-cli
436
+ ```
437
+
438
+ 2. **API server not running**
439
+ ```bash
440
+ # Start the API server first
441
+ python -m uvicorn src.app.main:app --reload
442
+ ```
443
+
444
+ 3. **Build failures**
445
+ - Check language-specific requirements
446
+ - Verify generated code syntax
447
+ - Review error messages in build logs
448
+
449
+ 4. **Import errors**
450
+ - Ensure proper installation
451
+ - Check Python path and virtual environments
452
+ - Verify package structure
453
+
454
+ ### Debug Mode
455
+
456
+ Enable debug output for troubleshooting:
457
+
458
+ ```bash
459
+ # Python script
460
+ python scripts/generate_clients.py --verbose
461
+
462
+ # OpenAPI Generator
463
+ openapi-generator-cli generate \
464
+ --verbose \
465
+ --debug-operations \
466
+ -i openapi.json \
467
+ -g typescript-fetch \
468
+ -o clients/typescript
469
+ ```
470
+
471
+ ### Validation
472
+
473
+ Validate OpenAPI specification before generation:
474
+
475
+ ```bash
476
+ # Using OpenAPI Generator
477
+ openapi-generator-cli validate -i openapi.json
478
+
479
+ # Using Swagger Editor online
480
+ # Visit: https://editor.swagger.io/
481
+ ```
482
+
483
+ ## Best Practices
484
+
485
+ 1. **Version Management**
486
+ - Tag client versions with API versions
487
+ - Maintain backward compatibility
488
+ - Document breaking changes
489
+
490
+ 2. **Testing**
491
+ - Test clients against real API
492
+ - Include integration tests
493
+ - Validate error handling
494
+
495
+ 3. **Documentation**
496
+ - Include usage examples
497
+ - Document authentication setup
498
+ - Provide troubleshooting guides
499
+
500
+ 4. **Distribution**
501
+ - Use semantic versioning
502
+ - Publish to appropriate package managers
503
+ - Maintain changelogs
504
+
505
+ ## Support
506
+
507
+ For issues with client generation:
508
+
509
+ - Check the [OpenAPI Generator documentation](https://openapi-generator.tech/)
510
+ - Review API documentation at `/docs`
511
+ - Contact support at [email protected]
512
+ - File issues on GitHub
513
+
514
+ ## Contributing
515
+
516
+ To contribute to client generation:
517
+
518
+ 1. Fork the repository
519
+ 2. Create custom templates or improve scripts
520
+ 3. Test with multiple languages
521
+ 4. Submit pull request with documentation
522
+
523
+ ## License
524
+
525
+ Generated clients inherit the same license as the API project (MIT License).
extract_token.html ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Clerk Token Extractor</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #f5f5f5;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ }
25
+ .step {
26
+ margin: 20px 0;
27
+ padding: 15px;
28
+ background: #f8f9fa;
29
+ border-left: 4px solid #007bff;
30
+ border-radius: 5px;
31
+ }
32
+ .step h3 {
33
+ margin-top: 0;
34
+ color: #007bff;
35
+ }
36
+ button {
37
+ background: #007bff;
38
+ color: white;
39
+ border: none;
40
+ padding: 10px 20px;
41
+ border-radius: 5px;
42
+ cursor: pointer;
43
+ font-size: 16px;
44
+ margin: 10px 5px;
45
+ }
46
+ button:hover {
47
+ background: #0056b3;
48
+ }
49
+ .token-display {
50
+ background: #e9ecef;
51
+ padding: 15px;
52
+ border-radius: 5px;
53
+ font-family: monospace;
54
+ word-break: break-all;
55
+ margin: 10px 0;
56
+ min-height: 50px;
57
+ }
58
+ .success {
59
+ color: #28a745;
60
+ font-weight: bold;
61
+ }
62
+ .error {
63
+ color: #dc3545;
64
+ font-weight: bold;
65
+ }
66
+ .info {
67
+ background: #d1ecf1;
68
+ border: 1px solid #bee5eb;
69
+ color: #0c5460;
70
+ padding: 10px;
71
+ border-radius: 5px;
72
+ margin: 10px 0;
73
+ }
74
+ </style>
75
+ </head>
76
+ <body>
77
+ <div class="container">
78
+ <h1>🎫 Clerk Token Extractor</h1>
79
+ <p>This tool helps you extract your Clerk authentication token for API testing.</p>
80
+
81
+ <div class="info">
82
+ <strong>Prerequisites:</strong> You must be signed in to your application in this browser session.
83
+ </div>
84
+
85
+ <div class="step">
86
+ <h3>Step 1: Check if Clerk is Available</h3>
87
+ <button onclick="checkClerk()">Check Clerk Status</button>
88
+ <div id="clerkStatus"></div>
89
+ </div>
90
+
91
+ <div class="step">
92
+ <h3>Step 2: Extract Token</h3>
93
+ <button onclick="extractToken()">Get Current Token</button>
94
+ <button onclick="extractTokenAsync()">Get Fresh Token</button>
95
+ <div id="tokenResult"></div>
96
+ <div id="tokenDisplay" class="token-display"></div>
97
+ </div>
98
+
99
+ <div class="step">
100
+ <h3>Step 3: Copy Token</h3>
101
+ <button onclick="copyToken()" id="copyBtn" disabled>Copy Token to Clipboard</button>
102
+ <button onclick="downloadToken()" id="downloadBtn" disabled>Download as File</button>
103
+ <div id="copyResult"></div>
104
+ </div>
105
+
106
+ <div class="step">
107
+ <h3>Step 4: Test Your Token</h3>
108
+ <p>Use your token with the test scripts:</p>
109
+ <code>python test_video_simple.py https://your-api-url.com/api/v1 YOUR_TOKEN_HERE</code>
110
+ </div>
111
+
112
+ <div class="step">
113
+ <h3>Alternative Methods</h3>
114
+ <details>
115
+ <summary>Manual Browser Console Method</summary>
116
+ <ol>
117
+ <li>Open Developer Tools (F12)</li>
118
+ <li>Go to Console tab</li>
119
+ <li>Run: <code>window.Clerk.session.getToken().then(token => console.log(token))</code></li>
120
+ <li>Copy the token from console output</li>
121
+ </ol>
122
+ </details>
123
+
124
+ <details>
125
+ <summary>Browser Storage Method</summary>
126
+ <ol>
127
+ <li>Open Developer Tools (F12)</li>
128
+ <li>Go to Application/Storage tab</li>
129
+ <li>Look in Local Storage for keys starting with '__clerk_'</li>
130
+ <li>Find the session data and extract the JWT token</li>
131
+ </ol>
132
+ </details>
133
+ </div>
134
+ </div>
135
+
136
+ <script>
137
+ let currentToken = null;
138
+
139
+ function checkClerk() {
140
+ const statusDiv = document.getElementById('clerkStatus');
141
+
142
+ if (typeof window.Clerk === 'undefined') {
143
+ statusDiv.innerHTML = '<span class="error">❌ Clerk not found. Make sure you\'re on a page with Clerk loaded.</span>';
144
+ return false;
145
+ }
146
+
147
+ if (!window.Clerk.session) {
148
+ statusDiv.innerHTML = '<span class="error">❌ No active Clerk session. Please sign in first.</span>';
149
+ return false;
150
+ }
151
+
152
+ statusDiv.innerHTML = '<span class="success">✅ Clerk is available and you have an active session!</span>';
153
+ return true;
154
+ }
155
+
156
+ function extractToken() {
157
+ if (!checkClerk()) return;
158
+
159
+ const resultDiv = document.getElementById('tokenResult');
160
+ const tokenDiv = document.getElementById('tokenDisplay');
161
+
162
+ try {
163
+ // Try to get token synchronously first
164
+ const session = window.Clerk.session;
165
+ if (session && session.getToken) {
166
+ // This might be async, so we'll handle both cases
167
+ const tokenResult = session.getToken();
168
+
169
+ if (tokenResult && typeof tokenResult.then === 'function') {
170
+ // It's a promise
171
+ resultDiv.innerHTML = '<span class="info">Getting token asynchronously...</span>';
172
+ tokenResult.then(token => {
173
+ if (token) {
174
+ currentToken = token;
175
+ tokenDiv.textContent = token;
176
+ resultDiv.innerHTML = '<span class="success">✅ Token extracted successfully!</span>';
177
+ enableButtons();
178
+ } else {
179
+ resultDiv.innerHTML = '<span class="error">❌ Token is null or empty</span>';
180
+ }
181
+ }).catch(error => {
182
+ resultDiv.innerHTML = `<span class="error">❌ Error getting token: ${error.message}</span>`;
183
+ });
184
+ } else if (tokenResult) {
185
+ // It's synchronous
186
+ currentToken = tokenResult;
187
+ tokenDiv.textContent = tokenResult;
188
+ resultDiv.innerHTML = '<span class="success">✅ Token extracted successfully!</span>';
189
+ enableButtons();
190
+ } else {
191
+ resultDiv.innerHTML = '<span class="error">❌ Could not get token</span>';
192
+ }
193
+ } else {
194
+ resultDiv.innerHTML = '<span class="error">❌ Session.getToken method not available</span>';
195
+ }
196
+ } catch (error) {
197
+ resultDiv.innerHTML = `<span class="error">❌ Error: ${error.message}</span>`;
198
+ }
199
+ }
200
+
201
+ function extractTokenAsync() {
202
+ if (!checkClerk()) return;
203
+
204
+ const resultDiv = document.getElementById('tokenResult');
205
+ const tokenDiv = document.getElementById('tokenDisplay');
206
+
207
+ try {
208
+ resultDiv.innerHTML = '<span class="info">Getting fresh token...</span>';
209
+
210
+ window.Clerk.session.getToken()
211
+ .then(token => {
212
+ if (token) {
213
+ currentToken = token;
214
+ tokenDiv.textContent = token;
215
+ resultDiv.innerHTML = '<span class="success">✅ Fresh token extracted successfully!</span>';
216
+ enableButtons();
217
+ } else {
218
+ resultDiv.innerHTML = '<span class="error">❌ Token is null or empty</span>';
219
+ }
220
+ })
221
+ .catch(error => {
222
+ resultDiv.innerHTML = `<span class="error">❌ Error getting token: ${error.message}</span>`;
223
+ });
224
+ } catch (error) {
225
+ resultDiv.innerHTML = `<span class="error">❌ Error: ${error.message}</span>`;
226
+ }
227
+ }
228
+
229
+ function enableButtons() {
230
+ document.getElementById('copyBtn').disabled = false;
231
+ document.getElementById('downloadBtn').disabled = false;
232
+ }
233
+
234
+ function copyToken() {
235
+ if (!currentToken) {
236
+ document.getElementById('copyResult').innerHTML = '<span class="error">❌ No token to copy</span>';
237
+ return;
238
+ }
239
+
240
+ navigator.clipboard.writeText(currentToken).then(() => {
241
+ document.getElementById('copyResult').innerHTML = '<span class="success">✅ Token copied to clipboard!</span>';
242
+ }).catch(err => {
243
+ // Fallback for older browsers
244
+ const textArea = document.createElement('textarea');
245
+ textArea.value = currentToken;
246
+ document.body.appendChild(textArea);
247
+ textArea.select();
248
+ document.execCommand('copy');
249
+ document.body.removeChild(textArea);
250
+ document.getElementById('copyResult').innerHTML = '<span class="success">✅ Token copied to clipboard!</span>';
251
+ });
252
+ }
253
+
254
+ function downloadToken() {
255
+ if (!currentToken) {
256
+ document.getElementById('copyResult').innerHTML = '<span class="error">❌ No token to download</span>';
257
+ return;
258
+ }
259
+
260
+ const blob = new Blob([currentToken], { type: 'text/plain' });
261
+ const url = window.URL.createObjectURL(blob);
262
+ const a = document.createElement('a');
263
+ a.href = url;
264
+ a.download = 'clerk_token.txt';
265
+ document.body.appendChild(a);
266
+ a.click();
267
+ document.body.removeChild(a);
268
+ window.URL.revokeObjectURL(url);
269
+
270
+ document.getElementById('copyResult').innerHTML = '<span class="success">✅ Token downloaded as file!</span>';
271
+ }
272
+
273
+ // Auto-check Clerk status on page load
274
+ window.addEventListener('load', () => {
275
+ setTimeout(checkClerk, 1000);
276
+ });
277
+ </script>
278
+ </body>
279
+ </html>
fastapi-backend-pyproject.toml ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "fastapi-video-backend"
7
+ version = "0.1.0"
8
+ description = "FastAPI backend for multi-agent video generation system"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Video Generation Team"},
14
+ ]
15
+ keywords = ["fastapi", "video", "generation", "api"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+
25
+ dependencies = [
26
+ # FastAPI and ASGI server
27
+ "fastapi>=0.104.0",
28
+ "uvicorn[standard]>=0.24.0",
29
+
30
+ # Pydantic for data validation
31
+ "pydantic>=2.5.0",
32
+ "pydantic-settings>=2.1.0",
33
+
34
+ # Redis for caching and job queuing
35
+ "redis>=5.0.0",
36
+ "hiredis>=2.2.0", # C extension for better Redis performance
37
+
38
+ # Clerk SDK for authentication
39
+ "clerk-backend-api>=1.0.0",
40
+
41
+ # HTTP client for external API calls
42
+ "httpx>=0.25.0",
43
+
44
+ # JSON Web Token handling
45
+ "pyjwt[crypto]>=2.8.0",
46
+
47
+ # File handling and validation
48
+ "python-multipart>=0.0.6", # For file uploads
49
+ "python-magic>=0.4.27", # File type detection
50
+
51
+ # Logging and monitoring
52
+ "structlog>=23.2.0",
53
+
54
+ # Environment variable management
55
+ "python-dotenv>=1.0.0",
56
+
57
+ # Date and time utilities
58
+ "python-dateutil>=2.8.2",
59
+
60
+ # Async utilities
61
+ "asyncio-mqtt>=0.16.0", # If needed for real-time updates
62
+ ]
63
+
64
+ [project.optional-dependencies]
65
+ dev = [
66
+ # Testing
67
+ "pytest>=7.4.0",
68
+ "pytest-asyncio>=0.21.0",
69
+ "pytest-cov>=4.1.0",
70
+ "httpx>=0.25.0", # For testing FastAPI endpoints
71
+
72
+ # Code quality
73
+ "black>=23.0.0",
74
+ "isort>=5.12.0",
75
+ "flake8>=6.0.0",
76
+ "mypy>=1.7.0",
77
+
78
+ # Development tools
79
+ "pre-commit>=3.5.0",
80
+ "watchfiles>=0.21.0", # For auto-reload during development
81
+ ]
82
+
83
+ production = [
84
+ # Production ASGI server
85
+ "gunicorn>=21.2.0",
86
+
87
+ # Monitoring and observability
88
+ "prometheus-client>=0.19.0",
89
+ "opentelemetry-api>=1.21.0",
90
+ "opentelemetry-sdk>=1.21.0",
91
+ "opentelemetry-instrumentation-fastapi>=0.42b0",
92
+ ]
93
+
94
+ [project.urls]
95
+ Homepage = "https://github.com/your-org/fastapi-video-backend"
96
+ Documentation = "https://your-org.github.io/fastapi-video-backend"
97
+ Repository = "https://github.com/your-org/fastapi-video-backend.git"
98
+ Issues = "https://github.com/your-org/fastapi-video-backend/issues"
99
+
100
+ [tool.hatch.build.targets.wheel]
101
+ packages = ["src/app"]
102
+
103
+ [tool.black]
104
+ line-length = 88
105
+ target-version = ['py311']
106
+ include = '\.pyi?$'
107
+ extend-exclude = '''
108
+ /(
109
+ # directories
110
+ \.eggs
111
+ | \.git
112
+ | \.hg
113
+ | \.mypy_cache
114
+ | \.tox
115
+ | \.venv
116
+ | build
117
+ | dist
118
+ )/
119
+ '''
120
+
121
+ [tool.isort]
122
+ profile = "black"
123
+ multi_line_output = 3
124
+ line_length = 88
125
+ known_first_party = ["app"]
126
+
127
+ [tool.mypy]
128
+ python_version = "3.11"
129
+ warn_return_any = true
130
+ warn_unused_configs = true
131
+ disallow_untyped_defs = true
132
+ disallow_incomplete_defs = true
133
+ check_untyped_defs = true
134
+ disallow_untyped_decorators = true
135
+ no_implicit_optional = true
136
+ warn_redundant_casts = true
137
+ warn_unused_ignores = true
138
+ warn_no_return = true
139
+ warn_unreachable = true
140
+ strict_equality = true
141
+
142
+ [[tool.mypy.overrides]]
143
+ module = [
144
+ "redis.*",
145
+ "clerk_backend_api.*",
146
+ "structlog.*",
147
+ ]
148
+ ignore_missing_imports = true
149
+
150
+ [tool.pytest.ini_options]
151
+ minversion = "7.0"
152
+ addopts = "-ra -q --strict-markers --strict-config"
153
+ testpaths = ["tests"]
154
+ python_files = ["test_*.py", "*_test.py"]
155
+ python_classes = ["Test*"]
156
+ python_functions = ["test_*"]
157
+ asyncio_mode = "auto"
158
+
159
+ [tool.coverage.run]
160
+ source = ["src"]
161
+ omit = [
162
+ "*/tests/*",
163
+ "*/test_*",
164
+ "*/__pycache__/*",
165
+ ]
166
+
167
+ [tool.coverage.report]
168
+ exclude_lines = [
169
+ "pragma: no cover",
170
+ "def __repr__",
171
+ "if self.debug:",
172
+ "if settings.DEBUG",
173
+ "raise AssertionError",
174
+ "raise NotImplementedError",
175
+ "if 0:",
176
+ "if __name__ == .__main__.:",
177
+ "class .*\\bProtocol\\):",
178
+ "@(abc\\.)?abstractmethod",
179
+ ]
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ def main():
2
+ print("Hello from t2m!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
ngrok-dev.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "2"
2
+ authtoken: ${NGROK_AUTHTOKEN}
3
+
4
+ tunnels:
5
+ fastapi-dev:
6
+ addr: host.docker.internal:8000 # Points to localhost:8000 on host
7
+ proto: http
8
+ schemes: [https, http]
9
+ inspect: true
10
+ bind_tls: true
11
+
12
+ web_addr: 0.0.0.0:4040
ngrok.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "2"
2
+ authtoken: ${NGROK_AUTHTOKEN}
3
+
4
+ tunnels:
5
+ fastapi:
6
+ addr: fastapi:8000
7
+ proto: http
8
+ schemes: [https, http]
9
+ inspect: true
10
+ bind_tls: true
11
+
12
+ web_addr: 0.0.0.0:4040
openapi-generator-config.yaml ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenAPI Generator Configuration for Video Generation API
2
+ # This file contains global configuration for generating client SDKs
3
+
4
+ # Global properties
5
+ globalProperties:
6
+ models: true
7
+ apis: true
8
+ supportingFiles: true
9
+ modelTests: true
10
+ apiTests: true
11
+ modelDocs: true
12
+ apiDocs: true
13
+
14
+ # TypeScript configuration
15
+ typescript:
16
+ generatorName: typescript-fetch
17
+ outputDir: clients/typescript
18
+ additionalProperties:
19
+ npmName: video-api-client
20
+ npmVersion: 1.0.0
21
+ supportsES6: true
22
+ withInterfaces: true
23
+ typescriptThreePlus: true
24
+ enumPropertyNaming: PascalCase
25
+ modelPropertyNaming: camelCase
26
+ paramNaming: camelCase
27
+ stringEnums: true
28
+ withSeparateModelsAndApi: true
29
+ apiPackage: api
30
+ modelPackage: models
31
+ templateDir: templates/typescript
32
+
33
+ # Python configuration
34
+ python:
35
+ generatorName: python
36
+ outputDir: clients/python
37
+ additionalProperties:
38
+ packageName: video_api_client
39
+ projectName: video-api-client
40
+ packageVersion: 1.0.0
41
+ packageUrl: https://github.com/example/video-api-client-python
42
+ packageDescription: Python client library for Video Generation API
43
+ authorName: API Team
44
+ authorEmail: [email protected]
45
+ library: urllib3
46
+ generateSourceCodeOnly: false
47
+ templateDir: templates/python
48
+
49
+ # Java configuration
50
+ java:
51
+ generatorName: java
52
+ outputDir: clients/java
53
+ additionalProperties:
54
+ groupId: com.example
55
+ artifactId: video-api-client
56
+ artifactVersion: 1.0.0
57
+ artifactDescription: Java client library for Video Generation API
58
+ library: okhttp-gson
59
+ java8: true
60
+ dateLibrary: java8
61
+ serializationLibrary: gson
62
+ hideGenerationTimestamp: true
63
+ templateDir: templates/java
64
+
65
+ # C# configuration
66
+ csharp:
67
+ generatorName: csharp
68
+ outputDir: clients/csharp
69
+ additionalProperties:
70
+ packageName: VideoApiClient
71
+ packageVersion: 1.0.0
72
+ clientPackage: VideoApiClient
73
+ packageCompany: Example Inc
74
+ packageAuthors: API Team
75
+ packageCopyright: Copyright 2024
76
+ packageDescription: C# client library for Video Generation API
77
+ targetFramework: netstandard2.0
78
+ library: httpclient
79
+ generatePropertyChanged: true
80
+ templateDir: templates/csharp
81
+
82
+ # Go configuration
83
+ go:
84
+ generatorName: go
85
+ outputDir: clients/go
86
+ additionalProperties:
87
+ packageName: videoapiclient
88
+ packageVersion: 1.0.0
89
+ packageUrl: github.com/example/video-api-client-go
90
+ hideGenerationTimestamp: true
91
+ withGoCodegenComment: true
92
+ enumClassPrefix: true
93
+ templateDir: templates/go
94
+
95
+ # PHP configuration
96
+ php:
97
+ generatorName: php
98
+ outputDir: clients/php
99
+ additionalProperties:
100
+ packageName: VideoApiClient
101
+ composerVendorName: example
102
+ composerProjectName: video-api-client
103
+ packageVersion: 1.0.0
104
+ invokerPackage: VideoApiClient
105
+ srcBasePath: src
106
+ hideGenerationTimestamp: true
107
+ templateDir: templates/php
108
+
109
+ # Ruby configuration
110
+ ruby:
111
+ generatorName: ruby
112
+ outputDir: clients/ruby
113
+ additionalProperties:
114
+ gemName: video_api_client
115
+ gemVersion: 1.0.0
116
+ gemHomepage: https://github.com/example/video-api-client-ruby
117
+ gemSummary: Ruby client library for Video Generation API
118
+ gemDescription: Ruby client library for the Video Generation API
119
+ gemAuthor: API Team
120
+ gemAuthorEmail: [email protected]
121
+ moduleName: VideoApiClient
122
+ hideGenerationTimestamp: true
123
+ templateDir: templates/ruby
124
+
125
+ # Custom templates directory structure
126
+ templates:
127
+ typescript:
128
+ - api.mustache
129
+ - model.mustache
130
+ - README.mustache
131
+ - package.mustache
132
+ python:
133
+ - api.mustache
134
+ - model.mustache
135
+ - README.mustache
136
+ - setup.mustache
137
+ java:
138
+ - api.mustache
139
+ - model.mustache
140
+ - README.mustache
141
+ - pom.mustache
142
+ csharp:
143
+ - api.mustache
144
+ - model.mustache
145
+ - README.mustache
146
+ - Project.mustache
147
+ go:
148
+ - api.mustache
149
+ - model.mustache
150
+ - README.mustache
151
+ - go.mustache
152
+ php:
153
+ - api.mustache
154
+ - model.mustache
155
+ - README.mustache
156
+ - composer.mustache
157
+ ruby:
158
+ - api.mustache
159
+ - model.mustache
160
+ - README.mustache
161
+ - gemspec.mustache
162
+
163
+ # Documentation configuration
164
+ documentation:
165
+ generateApiDocs: true
166
+ generateModelDocs: true
167
+ generateExamples: true
168
+ includeAuthentication: true
169
+ includeErrorHandling: true
170
+ includeRateLimiting: true
171
+
172
+ # Validation rules
173
+ validation:
174
+ validateSpec: true
175
+ strictMode: false
176
+ allowAdditionalPropertiesWithComposedSchema: true
177
+
178
+ # Post-processing options
179
+ postProcessing:
180
+ removeUnusedImports: true
181
+ formatCode: true
182
+ generateTests: true
183
+ generateExamples: true
pyproject.toml ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "t2m"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "annotated-types~=0.7.0",
9
+ "asyncio-mqtt>=0.16.0",
10
+ "azure-cognitiveservices-speech~=1.41.1",
11
+ "boto3~=1.36.9",
12
+ "cachetools~=5.5.0",
13
+ "cairosvg>=2.8.2",
14
+ "certifi~=2024.8.30",
15
+ "charset-normalizer~=3.4.0",
16
+ "chromadb~=0.6.3",
17
+ "clerk-backend-api>=1.0.0",
18
+ "click~=8.1.7",
19
+ "cloup~=3.0.5",
20
+ "cython~=3.0.11",
21
+ "decorator~=5.1.1",
22
+ "fastapi>=0.116.1",
23
+ "ffmpeg-python~=0.2.0",
24
+ "glcontext~=3.0.0",
25
+ "google-ai-generativelanguage~=0.6.10",
26
+ "google-api-core~=2.22.0",
27
+ "google-api-python-client~=2.151.0",
28
+ "google-auth~=2.35.0",
29
+ "google-auth-httplib2~=0.2.0",
30
+ "google-cloud-aiplatform~=1.79.0",
31
+ "google-generativeai~=0.8.3",
32
+ "googleapis-common-protos~=1.65.0",
33
+ "gradio>=5.43.1",
34
+ "grpcio~=1.67.1",
35
+ "grpcio-status~=1.67.1",
36
+ "gtts~=2.5.3",
37
+ "hiredis>=2.2.0",
38
+ "httplib2~=0.22.0",
39
+ "httpx>=0.25.0",
40
+ "idna~=3.10",
41
+ "imageio-ffmpeg~=0.5.1",
42
+ "inquirer>=3.4.1",
43
+ "isosurfaces~=0.1.2",
44
+ "kokoro-onnx>=0.4.9",
45
+ "krippendorff~=0.8.1",
46
+ "langchain~=0.3.14",
47
+ "langchain-community~=0.3.14",
48
+ "langfuse~=2.58.1",
49
+ "litellm~=1.60.5",
50
+ "manim~=0.18.1",
51
+ "manim-chemistry~=0.4.4",
52
+ "manim-circuit~=0.0.3",
53
+ "manim-dsa~=0.2.0",
54
+ "manim-ml~=0.0.24",
55
+ "manim-physics~=0.4.0",
56
+ "manim-voiceover~=0.3.7",
57
+ "manimpango~=0.6.0",
58
+ "mapbox-earcut~=1.0.2",
59
+ "markdown-it-py~=3.0.0",
60
+ "mdurl~=0.1.2",
61
+ "moderngl~=5.12.0",
62
+ "moviepy~=2.1.2",
63
+ "multipledispatch~=1.0.0",
64
+ "mutagen~=1.47.0",
65
+ "networkx~=3.4.2",
66
+ "numpy~=2.2.2",
67
+ "openai~=1.61.0",
68
+ "opencv-python~=4.11.0",
69
+ "pillow>=10.4.0",
70
+ "proto-plus~=1.25.0",
71
+ "protobuf~=5.28.3",
72
+ "psutil>=7.0.0",
73
+ "pyasn1~=0.6.1",
74
+ "pyasn1-modules~=0.4.1",
75
+ "pyaudio~=0.2.14",
76
+ "pycairo~=1.27.0",
77
+ "pydantic>=2.5.0",
78
+ "pydantic-core~=2.23.4",
79
+ "pydantic-settings>=2.1.0",
80
+ "pydub~=0.25.1",
81
+ "pyglet~=2.0.18",
82
+ "pygments~=2.18.0",
83
+ "pyjwt[crypto]>=2.8.0",
84
+ "pylatexenc~=2.10",
85
+ "pyparsing~=3.2.0",
86
+ "pyrr~=0.10.3",
87
+ "pysrt>=1.1.2",
88
+ "pytest>=8.4.1",
89
+ "pytest-asyncio>=1.1.0",
90
+ "python-dateutil>=2.8.2",
91
+ "python-dotenv~=0.21.1",
92
+ "python-magic>=0.4.27",
93
+ "python-multipart>=0.0.6",
94
+ "python-slugify~=8.0.4",
95
+ "redis>=5.0.0",
96
+ "requests~=2.32.3",
97
+ "rich~=13.9.3",
98
+ "rsa~=4.9",
99
+ "scipy~=1.14.1",
100
+ "screeninfo~=0.8.1",
101
+ "sentence-transformers>=5.1.0",
102
+ "sentencepiece>=0.2.1",
103
+ "skia-pathops~=0.8.0.post2",
104
+ "soundfile~=0.13.1",
105
+ "sox~=1.5.0",
106
+ "speechrecognition~=3.14.1",
107
+ "srt~=3.5.3",
108
+ "statsmodels~=0.14.4",
109
+ "structlog>=23.2.0",
110
+ "svgelements~=1.9.6",
111
+ "text-unidecode~=1.3",
112
+ "tiktoken~=0.8.0",
113
+ "timm>=1.0.19",
114
+ "tqdm~=4.66.5",
115
+ "transformers>=4.55.4",
116
+ "typing-extensions~=4.12.2",
117
+ "uritemplate~=4.1.1",
118
+ "urllib3~=2.2.3",
119
+ "uvicorn[standard]>=0.24.0",
120
+ "watchdog~=5.0.3",
121
+ "yt-dlp>=2025.8.22",
122
+ ]
quick_api_test.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quick API Test - Just check if your API is running
4
+ """
5
+
6
+ import requests
7
+ import sys
8
+
9
+ def quick_test(base_url):
10
+ """Quick test to see if API is responding"""
11
+ print(f"🔍 Quick API Test: {base_url}")
12
+ print("-" * 40)
13
+
14
+ # Test endpoints that should work
15
+ endpoints = [
16
+ ('/system/health', 'System Health'),
17
+ ('/auth/health', 'Auth Health'),
18
+ ('/auth/status', 'Auth Status'),
19
+ ('/', 'Root (may require auth)')
20
+ ]
21
+
22
+ working_endpoints = 0
23
+
24
+ for endpoint, name in endpoints:
25
+ try:
26
+ url = f"{base_url}{endpoint}"
27
+ response = requests.get(url, timeout=10)
28
+
29
+ if response.status_code == 200:
30
+ print(f"✅ {name}: Working (200)")
31
+ working_endpoints += 1
32
+ elif response.status_code == 401:
33
+ print(f"🔐 {name}: Requires auth (401)")
34
+ working_endpoints += 1 # Still counts as working
35
+ else:
36
+ print(f"⚠️ {name}: Status {response.status_code}")
37
+
38
+ except requests.exceptions.ConnectionError:
39
+ print(f"❌ {name}: Connection failed - API not running?")
40
+ except requests.exceptions.Timeout:
41
+ print(f"❌ {name}: Timeout")
42
+ except Exception as e:
43
+ print(f"❌ {name}: Error - {e}")
44
+
45
+ print(f"\n📊 Result: {working_endpoints}/{len(endpoints)} endpoints responding")
46
+
47
+ if working_endpoints >= 2:
48
+ print("✅ API appears to be running!")
49
+ print("\nNext steps:")
50
+ print("1. Get your Clerk token: python get_token_simple.py")
51
+ print("2. Test with token: python test_current_api.py <base_url> <token>")
52
+ return True
53
+ else:
54
+ print("❌ API may not be running or accessible")
55
+ print("\nTroubleshooting:")
56
+ print("- Check if your FastAPI server is running")
57
+ print("- Verify the URL is correct")
58
+ print("- Check for firewall/network issues")
59
+ return False
60
+
61
+ def main():
62
+ if len(sys.argv) != 2:
63
+ print("Usage: python quick_api_test.py <base_url>")
64
+ print("Example: python quick_api_test.py http://localhost:8000/api/v1")
65
+ return 1
66
+
67
+ base_url = sys.argv[1].rstrip('/')
68
+ success = quick_test(base_url)
69
+ return 0 if success else 1
70
+
71
+ if __name__ == '__main__':
72
+ exit(main())
requirements-test.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Requirements for API testing
2
+ requests>=2.31.0
3
+ urllib3>=2.0.0
requirements.txt CHANGED
@@ -97,4 +97,38 @@ soundfile~=0.13.1
97
  krippendorff~=0.8.1
98
  statsmodels~=0.14.4
99
  opencv-python~=4.11.0
100
- gradio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  krippendorff~=0.8.1
98
  statsmodels~=0.14.4
99
  opencv-python~=4.11.0
100
+ gradio
101
+ # FastAPI and ASGI server
102
+ fastapi
103
+ uvicorn[standard]>=0.24.0
104
+
105
+ # Pydantic for data validation
106
+ pydantic>=2.5.0
107
+ pydantic-settings>=2.1.0
108
+
109
+ # Redis for caching and job queuing
110
+ redis>=5.0.0
111
+ hiredis>=2.2.0 # C extension for better Redis performanc
112
+
113
+ # Clerk SDK for authentication
114
+ clerk-backend-api>=1.0.0
115
+
116
+ # HTTP client for external API calls
117
+ httpx>=0.25.0
118
+
119
+ # JSON Web Token handling
120
+ pyjwt[crypto]>=2.8.0
121
+
122
+ # File handling and validation
123
+ python-multipart>=0.0.6 # For file uploads
124
+ python-magic>=0.4.27 # File type detection
125
+
126
+ # Logging and monitoring
127
+ structlog>=23.2.0
128
+
129
+
130
+ # Date and time utilities
131
+ python-dateutil>=2.8.2
132
+
133
+ # Async utilities
134
+ asyncio-mqtt>=0.16.0 # If needed for real-time updates
run_api_tests.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple runner for T2M API tests with configuration file support
4
+ """
5
+
6
+ import json
7
+ import sys
8
+ import os
9
+ from pathlib import Path
10
+ from test_api_endpoints import T2MAPITester
11
+
12
+ def load_config(config_file: str = "test_config.json") -> dict:
13
+ """Load configuration from JSON file"""
14
+ try:
15
+ with open(config_file, 'r') as f:
16
+ return json.load(f)
17
+ except FileNotFoundError:
18
+ print(f"❌ Configuration file '{config_file}' not found")
19
+ print("Please create a test_config.json file or use test_api_endpoints.py directly")
20
+ return None
21
+ except json.JSONDecodeError as e:
22
+ print(f"❌ Invalid JSON in configuration file: {e}")
23
+ return None
24
+
25
+ def main():
26
+ # Load configuration
27
+ config = load_config()
28
+ if not config:
29
+ return 1
30
+
31
+ api_config = config.get('api_config', {})
32
+ base_url = api_config.get('base_url')
33
+ token = api_config.get('token')
34
+
35
+ if not base_url:
36
+ print("❌ base_url not specified in configuration")
37
+ return 1
38
+
39
+ if not token or token == "your-bearer-token-here":
40
+ print("⚠️ No valid token provided - only public endpoints will be tested")
41
+ token = None
42
+
43
+ print("🔧 Configuration loaded:")
44
+ print(f" Base URL: {base_url}")
45
+ print(f" Token: {'Provided' if token else 'Not provided'}")
46
+
47
+ # Create and run tester
48
+ tester = T2MAPITester(base_url, token)
49
+ tester.run_all_tests()
50
+
51
+ return 0
52
+
53
+ if __name__ == '__main__':
54
+ exit(main())
run_debug.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Run debug and comprehensive tests
4
+ """
5
+
6
+ import subprocess
7
+ import sys
8
+
9
+ def run_debug():
10
+ print("🔍 Running Authentication Debug")
11
+ print("=" * 50)
12
+
13
+ try:
14
+ result = subprocess.run([sys.executable, "debug_auth.py"],
15
+ capture_output=True, text=True, timeout=30)
16
+ print(result.stdout)
17
+ if result.stderr:
18
+ print("STDERR:", result.stderr)
19
+
20
+ if result.returncode != 0:
21
+ print(f"Debug script failed with code {result.returncode}")
22
+
23
+ except subprocess.TimeoutExpired:
24
+ print("Debug script timed out")
25
+ except Exception as e:
26
+ print(f"Error running debug script: {e}")
27
+
28
+ print("\n" + "=" * 50)
29
+ print("🎬 Running Comprehensive Test")
30
+ print("=" * 50)
31
+
32
+ try:
33
+ result = subprocess.run([sys.executable, "test_api_comprehensive.py"],
34
+ capture_output=True, text=True, timeout=60)
35
+ print(result.stdout)
36
+ if result.stderr:
37
+ print("STDERR:", result.stderr)
38
+
39
+ if result.returncode == 0:
40
+ print("✅ All tests passed!")
41
+ else:
42
+ print(f"❌ Some tests failed (code {result.returncode})")
43
+
44
+ except subprocess.TimeoutExpired:
45
+ print("Test script timed out")
46
+ except Exception as e:
47
+ print(f"Error running test script: {e}")
48
+
49
+ if __name__ == '__main__':
50
+ run_debug()
run_video_test.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple runner for video generation API tests
4
+ """
5
+
6
+ import json
7
+ import sys
8
+ import os
9
+ from test_video_generation import VideoGenerationTester
10
+
11
+ def load_config(config_file: str = "video_test_config.json") -> dict:
12
+ """Load configuration from JSON file"""
13
+ try:
14
+ with open(config_file, 'r') as f:
15
+ return json.load(f)
16
+ except FileNotFoundError:
17
+ print(f"❌ Configuration file '{config_file}' not found")
18
+ print("Please create a video_test_config.json file or use test_video_generation.py directly")
19
+ return None
20
+ except json.JSONDecodeError as e:
21
+ print(f"❌ Invalid JSON in configuration file: {e}")
22
+ return None
23
+
24
+ def main():
25
+ # Load configuration
26
+ config = load_config()
27
+ if not config:
28
+ return 1
29
+
30
+ api_config = config.get('api', {})
31
+ base_url = api_config.get('base_url')
32
+ token = api_config.get('token')
33
+
34
+ if not base_url:
35
+ print("❌ base_url not specified in configuration")
36
+ return 1
37
+
38
+ if not token or token == "your-bearer-token-here":
39
+ print("❌ Valid authentication token is required for video generation testing")
40
+ return 1
41
+
42
+ print("🎬 Video Generation Test Configuration:")
43
+ print(f" Base URL: {base_url}")
44
+ print(f" Token: {'*' * (len(token) - 4) + token[-4:]}")
45
+
46
+ # Get test settings
47
+ test_settings = config.get('test_settings', {})
48
+ monitor_progress = test_settings.get('monitor_progress', True)
49
+
50
+ # Create and run tester
51
+ tester = VideoGenerationTester(base_url, token)
52
+ tester.run_comprehensive_test(monitor_progress)
53
+
54
+ return 0
55
+
56
+ if __name__ == '__main__':
57
+ exit(main())
scripts/generate_clients.py ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Client SDK generation script for FastAPI Video Generation Backend.
4
+
5
+ This script generates client SDKs in multiple languages using OpenAPI Generator.
6
+ It fetches the OpenAPI specification from the running API and generates clients
7
+ for TypeScript, Python, Java, C#, Go, PHP, and Ruby.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import json
13
+ import subprocess
14
+ import argparse
15
+ import requests
16
+ from pathlib import Path
17
+ from typing import Dict, List, Optional
18
+ import tempfile
19
+ import shutil
20
+
21
+
22
+ class ClientGenerator:
23
+ """Client SDK generator using OpenAPI Generator."""
24
+
25
+ SUPPORTED_LANGUAGES = {
26
+ "typescript": {
27
+ "generator": "typescript-fetch",
28
+ "output_dir": "clients/typescript",
29
+ "package_name": "video-api-client",
30
+ "additional_properties": {
31
+ "npmName": "video-api-client",
32
+ "npmVersion": "1.0.0",
33
+ "supportsES6": "true",
34
+ "withInterfaces": "true",
35
+ "typescriptThreePlus": "true"
36
+ }
37
+ },
38
+ "python": {
39
+ "generator": "python",
40
+ "output_dir": "clients/python",
41
+ "package_name": "video_api_client",
42
+ "additional_properties": {
43
+ "packageName": "video_api_client",
44
+ "projectName": "video-api-client",
45
+ "packageVersion": "1.0.0",
46
+ "packageUrl": "https://github.com/example/video-api-client-python"
47
+ }
48
+ },
49
+ "java": {
50
+ "generator": "java",
51
+ "output_dir": "clients/java",
52
+ "package_name": "com.example.videoapiclient",
53
+ "additional_properties": {
54
+ "groupId": "com.example",
55
+ "artifactId": "video-api-client",
56
+ "artifactVersion": "1.0.0",
57
+ "library": "okhttp-gson",
58
+ "java8": "true"
59
+ }
60
+ },
61
+ "csharp": {
62
+ "generator": "csharp",
63
+ "output_dir": "clients/csharp",
64
+ "package_name": "VideoApiClient",
65
+ "additional_properties": {
66
+ "packageName": "VideoApiClient",
67
+ "packageVersion": "1.0.0",
68
+ "clientPackage": "VideoApiClient",
69
+ "packageCompany": "Example Inc",
70
+ "packageAuthors": "API Team",
71
+ "packageCopyright": "Copyright 2024",
72
+ "packageDescription": "Video Generation API Client for .NET",
73
+ "targetFramework": "netstandard2.0"
74
+ }
75
+ },
76
+ "go": {
77
+ "generator": "go",
78
+ "output_dir": "clients/go",
79
+ "package_name": "videoapiclient",
80
+ "additional_properties": {
81
+ "packageName": "videoapiclient",
82
+ "packageVersion": "1.0.0",
83
+ "packageUrl": "github.com/example/video-api-client-go"
84
+ }
85
+ },
86
+ "php": {
87
+ "generator": "php",
88
+ "output_dir": "clients/php",
89
+ "package_name": "VideoApiClient",
90
+ "additional_properties": {
91
+ "packageName": "VideoApiClient",
92
+ "composerVendorName": "example",
93
+ "composerProjectName": "video-api-client",
94
+ "packageVersion": "1.0.0"
95
+ }
96
+ },
97
+ "ruby": {
98
+ "generator": "ruby",
99
+ "output_dir": "clients/ruby",
100
+ "package_name": "video_api_client",
101
+ "additional_properties": {
102
+ "gemName": "video_api_client",
103
+ "gemVersion": "1.0.0",
104
+ "gemHomepage": "https://github.com/example/video-api-client-ruby",
105
+ "gemSummary": "Video Generation API Client for Ruby",
106
+ "gemDescription": "Ruby client library for the Video Generation API"
107
+ }
108
+ }
109
+ }
110
+
111
+ def __init__(self, api_url: str = "http://localhost:8000", output_base_dir: str = "generated_clients"):
112
+ self.api_url = api_url.rstrip("/")
113
+ self.output_base_dir = Path(output_base_dir)
114
+ self.openapi_spec_path: Optional[Path] = None
115
+
116
+ def fetch_openapi_spec(self) -> Path:
117
+ """Fetch OpenAPI specification from the API."""
118
+ try:
119
+ print(f"Fetching OpenAPI specification from {self.api_url}/openapi.json")
120
+ response = requests.get(f"{self.api_url}/openapi.json", timeout=30)
121
+ response.raise_for_status()
122
+
123
+ # Create temporary file for OpenAPI spec
124
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
125
+ json.dump(response.json(), temp_file, indent=2)
126
+ temp_file.close()
127
+
128
+ self.openapi_spec_path = Path(temp_file.name)
129
+ print(f"OpenAPI specification saved to {self.openapi_spec_path}")
130
+ return self.openapi_spec_path
131
+
132
+ except requests.RequestException as e:
133
+ print(f"Error fetching OpenAPI specification: {e}")
134
+ sys.exit(1)
135
+
136
+ def check_openapi_generator(self) -> bool:
137
+ """Check if OpenAPI Generator is available."""
138
+ try:
139
+ result = subprocess.run(
140
+ ["openapi-generator-cli", "version"],
141
+ capture_output=True,
142
+ text=True,
143
+ timeout=10
144
+ )
145
+ if result.returncode == 0:
146
+ print(f"OpenAPI Generator found: {result.stdout.strip()}")
147
+ return True
148
+ else:
149
+ print("OpenAPI Generator not found or not working properly")
150
+ return False
151
+ except (subprocess.TimeoutExpired, FileNotFoundError):
152
+ print("OpenAPI Generator CLI not found")
153
+ return False
154
+
155
+ def install_openapi_generator(self) -> bool:
156
+ """Install OpenAPI Generator CLI using npm."""
157
+ try:
158
+ print("Installing OpenAPI Generator CLI...")
159
+ result = subprocess.run(
160
+ ["npm", "install", "-g", "@openapitools/openapi-generator-cli"],
161
+ capture_output=True,
162
+ text=True,
163
+ timeout=120
164
+ )
165
+ if result.returncode == 0:
166
+ print("OpenAPI Generator CLI installed successfully")
167
+ return True
168
+ else:
169
+ print(f"Failed to install OpenAPI Generator CLI: {result.stderr}")
170
+ return False
171
+ except (subprocess.TimeoutExpired, FileNotFoundError):
172
+ print("npm not found. Please install Node.js and npm first.")
173
+ return False
174
+
175
+ def generate_client(self, language: str, custom_config: Optional[Dict] = None) -> bool:
176
+ """Generate client SDK for specified language."""
177
+ if language not in self.SUPPORTED_LANGUAGES:
178
+ print(f"Unsupported language: {language}")
179
+ return False
180
+
181
+ config = self.SUPPORTED_LANGUAGES[language].copy()
182
+ if custom_config:
183
+ config.update(custom_config)
184
+
185
+ output_dir = self.output_base_dir / config["output_dir"]
186
+ output_dir.mkdir(parents=True, exist_ok=True)
187
+
188
+ # Build OpenAPI Generator command
189
+ cmd = [
190
+ "openapi-generator-cli",
191
+ "generate",
192
+ "-i", str(self.openapi_spec_path),
193
+ "-g", config["generator"],
194
+ "-o", str(output_dir),
195
+ "--package-name", config["package_name"]
196
+ ]
197
+
198
+ # Add additional properties
199
+ if "additional_properties" in config:
200
+ for key, value in config["additional_properties"].items():
201
+ cmd.extend(["--additional-properties", f"{key}={value}"])
202
+
203
+ # Add global properties for better client generation
204
+ cmd.extend([
205
+ "--global-property",
206
+ "models,apis,supportingFiles,modelTests,apiTests,modelDocs,apiDocs"
207
+ ])
208
+
209
+ print(f"Generating {language} client...")
210
+ print(f"Command: {' '.join(cmd)}")
211
+
212
+ try:
213
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
214
+ if result.returncode == 0:
215
+ print(f"✅ {language} client generated successfully in {output_dir}")
216
+ self._post_process_client(language, output_dir)
217
+ return True
218
+ else:
219
+ print(f"❌ Failed to generate {language} client:")
220
+ print(result.stderr)
221
+ return False
222
+ except subprocess.TimeoutExpired:
223
+ print(f"❌ Timeout generating {language} client")
224
+ return False
225
+
226
+ def _post_process_client(self, language: str, output_dir: Path) -> None:
227
+ """Post-process generated client with additional files and documentation."""
228
+ # Create README for the client
229
+ readme_content = self._generate_client_readme(language)
230
+ readme_path = output_dir / "README.md"
231
+ readme_path.write_text(readme_content)
232
+
233
+ # Create example usage file
234
+ example_content = self._generate_client_example(language)
235
+ if example_content:
236
+ example_extensions = {
237
+ "typescript": "ts",
238
+ "python": "py",
239
+ "java": "java",
240
+ "csharp": "cs",
241
+ "go": "go",
242
+ "php": "php",
243
+ "ruby": "rb"
244
+ }
245
+ extension = example_extensions.get(language, "txt")
246
+ example_path = output_dir / f"example.{extension}"
247
+ example_path.write_text(example_content)
248
+
249
+ # Language-specific post-processing
250
+ if language == "typescript":
251
+ self._post_process_typescript(output_dir)
252
+ elif language == "python":
253
+ self._post_process_python(output_dir)
254
+
255
+ def _post_process_typescript(self, output_dir: Path) -> None:
256
+ """Post-process TypeScript client."""
257
+ # Create package.json if it doesn't exist
258
+ package_json_path = output_dir / "package.json"
259
+ if not package_json_path.exists():
260
+ package_json = {
261
+ "name": "video-api-client",
262
+ "version": "1.0.0",
263
+ "description": "TypeScript client for Video Generation API",
264
+ "main": "dist/index.js",
265
+ "types": "dist/index.d.ts",
266
+ "scripts": {
267
+ "build": "tsc",
268
+ "test": "jest",
269
+ "lint": "eslint src/**/*.ts"
270
+ },
271
+ "dependencies": {
272
+ "node-fetch": "^2.6.7"
273
+ },
274
+ "devDependencies": {
275
+ "typescript": "^4.9.0",
276
+ "@types/node": "^18.0.0",
277
+ "jest": "^29.0.0",
278
+ "@types/jest": "^29.0.0",
279
+ "eslint": "^8.0.0",
280
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
281
+ "@typescript-eslint/parser": "^5.0.0"
282
+ },
283
+ "keywords": ["video", "api", "client", "typescript"],
284
+ "author": "API Team",
285
+ "license": "MIT"
286
+ }
287
+ package_json_path.write_text(json.dumps(package_json, indent=2))
288
+
289
+ def _post_process_python(self, output_dir: Path) -> None:
290
+ """Post-process Python client."""
291
+ # Create setup.py if it doesn't exist
292
+ setup_py_path = output_dir / "setup.py"
293
+ if not setup_py_path.exists():
294
+ setup_py_content = '''
295
+ from setuptools import setup, find_packages
296
+
297
+ setup(
298
+ name="video-api-client",
299
+ version="1.0.0",
300
+ description="Python client for Video Generation API",
301
+ long_description=open("README.md").read(),
302
+ long_description_content_type="text/markdown",
303
+ author="API Team",
304
+ author_email="[email protected]",
305
+ url="https://github.com/example/video-api-client-python",
306
+ packages=find_packages(),
307
+ install_requires=[
308
+ "requests>=2.25.0",
309
+ "urllib3>=1.26.0",
310
+ "python-dateutil>=2.8.0"
311
+ ],
312
+ extras_require={
313
+ "dev": [
314
+ "pytest>=6.0.0",
315
+ "pytest-cov>=2.10.0",
316
+ "black>=21.0.0",
317
+ "flake8>=3.8.0",
318
+ "mypy>=0.800"
319
+ ]
320
+ },
321
+ classifiers=[
322
+ "Development Status :: 4 - Beta",
323
+ "Intended Audience :: Developers",
324
+ "License :: OSI Approved :: MIT License",
325
+ "Programming Language :: Python :: 3",
326
+ "Programming Language :: Python :: 3.7",
327
+ "Programming Language :: Python :: 3.8",
328
+ "Programming Language :: Python :: 3.9",
329
+ "Programming Language :: Python :: 3.10",
330
+ "Programming Language :: Python :: 3.11",
331
+ ],
332
+ python_requires=">=3.7",
333
+ )
334
+ '''
335
+ setup_py_path.write_text(setup_py_content.strip())
336
+
337
+ def _generate_client_readme(self, language: str) -> str:
338
+ """Generate README content for client SDK."""
339
+ return f"""# Video Generation API Client - {language.title()}
340
+
341
+ This is an auto-generated client library for the Video Generation API.
342
+
343
+ ## Installation
344
+
345
+ ### {language.title()}
346
+ {self._get_installation_instructions(language)}
347
+
348
+ ## Quick Start
349
+
350
+ ```{self._get_code_block_language(language)}
351
+ {self._get_quick_start_example(language)}
352
+ ```
353
+
354
+ ## Authentication
355
+
356
+ This API uses Clerk authentication. You need to provide a valid Clerk session token:
357
+
358
+ ```{self._get_code_block_language(language)}
359
+ {self._get_auth_example(language)}
360
+ ```
361
+
362
+ ## API Documentation
363
+
364
+ For complete API documentation, visit: https://docs.example.com
365
+
366
+ ## Examples
367
+
368
+ See `example.{self._get_file_extension(language)}` for more usage examples.
369
+
370
+ ## Support
371
+
372
+ - GitHub Issues: https://github.com/example/video-api-client-{language}/issues
373
+ - Documentation: https://docs.example.com
374
+ - Email: [email protected]
375
+
376
+ ## License
377
+
378
+ MIT License - see LICENSE file for details.
379
+ """
380
+
381
+ def _generate_client_example(self, language: str) -> Optional[str]:
382
+ """Generate example usage code for client SDK."""
383
+ examples = {
384
+ "typescript": '''
385
+ import { Configuration, VideosApi, JobsApi } from './src';
386
+
387
+ // Configure the client
388
+ const config = new Configuration({
389
+ basePath: 'https://api.example.com',
390
+ accessToken: 'your-clerk-session-token'
391
+ });
392
+
393
+ const videosApi = new VideosApi(config);
394
+ const jobsApi = new JobsApi(config);
395
+
396
+ async function generateVideo() {
397
+ try {
398
+ // Create a video generation job
399
+ const jobResponse = await videosApi.createVideoGenerationJob({
400
+ configuration: {
401
+ topic: "Pythagorean Theorem",
402
+ context: "Explain the mathematical proof with visual demonstration",
403
+ quality: "medium",
404
+ use_rag: true
405
+ }
406
+ });
407
+
408
+ console.log('Job created:', jobResponse.job_id);
409
+
410
+ // Poll for job completion
411
+ let status = 'queued';
412
+ while (status !== 'completed' && status !== 'failed') {
413
+ await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
414
+
415
+ const statusResponse = await videosApi.getVideoJobStatus(jobResponse.job_id);
416
+ status = statusResponse.status;
417
+
418
+ console.log(`Job status: ${status} (${statusResponse.progress.percentage}%)`);
419
+ }
420
+
421
+ if (status === 'completed') {
422
+ console.log('Video generation completed!');
423
+ // Download the video
424
+ const videoBlob = await videosApi.downloadVideoFile(jobResponse.job_id);
425
+ console.log('Video downloaded');
426
+ } else {
427
+ console.log('Video generation failed');
428
+ }
429
+
430
+ } catch (error) {
431
+ console.error('Error:', error);
432
+ }
433
+ }
434
+
435
+ generateVideo();
436
+ ''',
437
+ "python": '''
438
+ import time
439
+ from video_api_client import Configuration, ApiClient, VideosApi, JobsApi
440
+ from video_api_client.models import JobCreateRequest, JobConfiguration
441
+
442
+ # Configure the client
443
+ configuration = Configuration(
444
+ host="https://api.example.com",
445
+ access_token="your-clerk-session-token"
446
+ )
447
+
448
+ with ApiClient(configuration) as api_client:
449
+ videos_api = VideosApi(api_client)
450
+ jobs_api = JobsApi(api_client)
451
+
452
+ try:
453
+ # Create a video generation job
454
+ job_request = JobCreateRequest(
455
+ configuration=JobConfiguration(
456
+ topic="Pythagorean Theorem",
457
+ context="Explain the mathematical proof with visual demonstration",
458
+ quality="medium",
459
+ use_rag=True
460
+ )
461
+ )
462
+
463
+ job_response = videos_api.create_video_generation_job(job_request)
464
+ print(f"Job created: {job_response.job_id}")
465
+
466
+ # Poll for job completion
467
+ status = "queued"
468
+ while status not in ["completed", "failed"]:
469
+ time.sleep(5) # Wait 5 seconds
470
+
471
+ status_response = videos_api.get_video_job_status(job_response.job_id)
472
+ status = status_response.status
473
+
474
+ print(f"Job status: {status} ({status_response.progress.percentage}%)")
475
+
476
+ if status == "completed":
477
+ print("Video generation completed!")
478
+ # Download the video
479
+ video_data = videos_api.download_video_file(job_response.job_id)
480
+ with open("generated_video.mp4", "wb") as f:
481
+ f.write(video_data)
482
+ print("Video downloaded as generated_video.mp4")
483
+ else:
484
+ print("Video generation failed")
485
+
486
+ except Exception as e:
487
+ print(f"Error: {e}")
488
+ ''',
489
+ "java": '''
490
+ import com.example.videoapiclient.ApiClient;
491
+ import com.example.videoapiclient.Configuration;
492
+ import com.example.videoapiclient.api.VideosApi;
493
+ import com.example.videoapiclient.api.JobsApi;
494
+ import com.example.videoapiclient.model.*;
495
+
496
+ public class VideoApiExample {
497
+ public static void main(String[] args) {
498
+ // Configure the client
499
+ ApiClient client = Configuration.getDefaultApiClient();
500
+ client.setBasePath("https://api.example.com");
501
+ client.setAccessToken("your-clerk-session-token");
502
+
503
+ VideosApi videosApi = new VideosApi(client);
504
+ JobsApi jobsApi = new JobsApi(client);
505
+
506
+ try {
507
+ // Create a video generation job
508
+ JobConfiguration config = new JobConfiguration()
509
+ .topic("Pythagorean Theorem")
510
+ .context("Explain the mathematical proof with visual demonstration")
511
+ .quality(VideoQuality.MEDIUM)
512
+ .useRag(true);
513
+
514
+ JobCreateRequest request = new JobCreateRequest().configuration(config);
515
+ JobResponse jobResponse = videosApi.createVideoGenerationJob(request);
516
+
517
+ System.out.println("Job created: " + jobResponse.getJobId());
518
+
519
+ // Poll for job completion
520
+ String status = "queued";
521
+ while (!"completed".equals(status) && !"failed".equals(status)) {
522
+ Thread.sleep(5000); // Wait 5 seconds
523
+
524
+ JobStatusResponse statusResponse = videosApi.getVideoJobStatus(jobResponse.getJobId());
525
+ status = statusResponse.getStatus().getValue();
526
+
527
+ System.out.println("Job status: " + status + " (" +
528
+ statusResponse.getProgress().getPercentage() + "%)");
529
+ }
530
+
531
+ if ("completed".equals(status)) {
532
+ System.out.println("Video generation completed!");
533
+ // Download logic would go here
534
+ } else {
535
+ System.out.println("Video generation failed");
536
+ }
537
+
538
+ } catch (Exception e) {
539
+ System.err.println("Error: " + e.getMessage());
540
+ }
541
+ }
542
+ }
543
+ '''
544
+ }
545
+
546
+ return examples.get(language)
547
+
548
+ def _get_installation_instructions(self, language: str) -> str:
549
+ """Get installation instructions for each language."""
550
+ instructions = {
551
+ "typescript": "```bash\nnpm install video-api-client\n```",
552
+ "python": "```bash\npip install video-api-client\n```",
553
+ "java": "Add to your `pom.xml`:\n```xml\n<dependency>\n <groupId>com.example</groupId>\n <artifactId>video-api-client</artifactId>\n <version>1.0.0</version>\n</dependency>\n```",
554
+ "csharp": "```bash\ndotnet add package VideoApiClient\n```",
555
+ "go": "```bash\ngo get github.com/example/video-api-client-go\n```",
556
+ "php": "```bash\ncomposer require example/video-api-client\n```",
557
+ "ruby": "```bash\ngem install video_api_client\n```"
558
+ }
559
+ return instructions.get(language, "See documentation for installation instructions.")
560
+
561
+ def _get_code_block_language(self, language: str) -> str:
562
+ """Get code block language identifier."""
563
+ mapping = {
564
+ "typescript": "typescript",
565
+ "python": "python",
566
+ "java": "java",
567
+ "csharp": "csharp",
568
+ "go": "go",
569
+ "php": "php",
570
+ "ruby": "ruby"
571
+ }
572
+ return mapping.get(language, language)
573
+
574
+ def _get_file_extension(self, language: str) -> str:
575
+ """Get file extension for each language."""
576
+ extensions = {
577
+ "typescript": "ts",
578
+ "python": "py",
579
+ "java": "java",
580
+ "csharp": "cs",
581
+ "go": "go",
582
+ "php": "php",
583
+ "ruby": "rb"
584
+ }
585
+ return extensions.get(language, "txt")
586
+
587
+ def _get_quick_start_example(self, language: str) -> str:
588
+ """Get quick start example for each language."""
589
+ examples = {
590
+ "typescript": "import { VideosApi } from 'video-api-client';\n\nconst api = new VideosApi();\nconst job = await api.createVideoGenerationJob({...});",
591
+ "python": "from video_api_client import VideosApi\n\napi = VideosApi()\njob = api.create_video_generation_job(...)",
592
+ "java": "VideosApi api = new VideosApi();\nJobResponse job = api.createVideoGenerationJob(...);",
593
+ "csharp": "var api = new VideosApi();\nvar job = await api.CreateVideoGenerationJobAsync(...);",
594
+ "go": "client := videoapiclient.NewAPIClient(config)\njob, _, err := client.VideosApi.CreateVideoGenerationJob(...)",
595
+ "php": "$api = new VideosApi();\n$job = $api->createVideoGenerationJob(...);",
596
+ "ruby": "api = VideoApiClient::VideosApi.new\njob = api.create_video_generation_job(...)"
597
+ }
598
+ return examples.get(language, "// See documentation for usage examples")
599
+
600
+ def _get_auth_example(self, language: str) -> str:
601
+ """Get authentication example for each language."""
602
+ examples = {
603
+ "typescript": "const config = new Configuration({\n accessToken: 'your-clerk-session-token'\n});",
604
+ "python": "configuration.access_token = 'your-clerk-session-token'",
605
+ "java": "client.setAccessToken(\"your-clerk-session-token\");",
606
+ "csharp": "Configuration.Default.AccessToken = \"your-clerk-session-token\";",
607
+ "go": "auth := context.WithValue(context.Background(), sw.ContextAccessToken, \"your-clerk-session-token\")",
608
+ "php": "$config->setAccessToken('your-clerk-session-token');",
609
+ "ruby": "VideoApiClient.configure { |c| c.access_token = 'your-clerk-session-token' }"
610
+ }
611
+ return examples.get(language, "// Set your authentication token")
612
+
613
+ def generate_all_clients(self, languages: Optional[List[str]] = None) -> Dict[str, bool]:
614
+ """Generate client SDKs for all specified languages."""
615
+ if languages is None:
616
+ languages = list(self.SUPPORTED_LANGUAGES.keys())
617
+
618
+ # Ensure OpenAPI spec is available
619
+ if not self.openapi_spec_path:
620
+ self.fetch_openapi_spec()
621
+
622
+ results = {}
623
+ for language in languages:
624
+ if language in self.SUPPORTED_LANGUAGES:
625
+ results[language] = self.generate_client(language)
626
+ else:
627
+ print(f"Skipping unsupported language: {language}")
628
+ results[language] = False
629
+
630
+ return results
631
+
632
+ def cleanup(self):
633
+ """Clean up temporary files."""
634
+ if self.openapi_spec_path and self.openapi_spec_path.exists():
635
+ self.openapi_spec_path.unlink()
636
+ print(f"Cleaned up temporary OpenAPI spec file")
637
+
638
+
639
+ def main():
640
+ """Main function for command-line usage."""
641
+ parser = argparse.ArgumentParser(description="Generate client SDKs for Video Generation API")
642
+ parser.add_argument(
643
+ "--api-url",
644
+ default="http://localhost:8000",
645
+ help="Base URL of the API (default: http://localhost:8000)"
646
+ )
647
+ parser.add_argument(
648
+ "--output-dir",
649
+ default="generated_clients",
650
+ help="Output directory for generated clients (default: generated_clients)"
651
+ )
652
+ parser.add_argument(
653
+ "--languages",
654
+ nargs="+",
655
+ choices=list(ClientGenerator.SUPPORTED_LANGUAGES.keys()),
656
+ help="Languages to generate (default: all supported languages)"
657
+ )
658
+ parser.add_argument(
659
+ "--install-generator",
660
+ action="store_true",
661
+ help="Install OpenAPI Generator CLI if not found"
662
+ )
663
+
664
+ args = parser.parse_args()
665
+
666
+ generator = ClientGenerator(args.api_url, args.output_dir)
667
+
668
+ # Check if OpenAPI Generator is available
669
+ if not generator.check_openapi_generator():
670
+ if args.install_generator:
671
+ if not generator.install_openapi_generator():
672
+ print("Failed to install OpenAPI Generator. Please install it manually.")
673
+ sys.exit(1)
674
+ else:
675
+ print("OpenAPI Generator CLI not found. Use --install-generator to install it automatically.")
676
+ sys.exit(1)
677
+
678
+ try:
679
+ # Generate clients
680
+ results = generator.generate_all_clients(args.languages)
681
+
682
+ # Print summary
683
+ print("\n" + "="*50)
684
+ print("CLIENT GENERATION SUMMARY")
685
+ print("="*50)
686
+
687
+ successful = []
688
+ failed = []
689
+
690
+ for language, success in results.items():
691
+ if success:
692
+ successful.append(language)
693
+ print(f"✅ {language}: SUCCESS")
694
+ else:
695
+ failed.append(language)
696
+ print(f"❌ {language}: FAILED")
697
+
698
+ print(f"\nSuccessful: {len(successful)}")
699
+ print(f"Failed: {len(failed)}")
700
+
701
+ if successful:
702
+ print(f"\nGenerated clients are available in: {generator.output_base_dir}")
703
+
704
+ # Exit with error code if any generation failed
705
+ sys.exit(1 if failed else 0)
706
+
707
+ finally:
708
+ generator.cleanup()
709
+
710
+
711
+ if __name__ == "__main__":
712
+ main()
scripts/generate_clients.sh ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Video Generation API Client Generator Script
4
+ # This script generates client SDKs for multiple programming languages
5
+
6
+ set -e # Exit on any error
7
+
8
+ # Configuration
9
+ API_URL="${API_URL:-http://localhost:8000}"
10
+ OUTPUT_DIR="${OUTPUT_DIR:-generated_clients}"
11
+ CONFIG_FILE="openapi-generator-config.yaml"
12
+
13
+ # Colors for output
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ BLUE='\033[0;34m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # Logging functions
21
+ log_info() {
22
+ echo -e "${BLUE}[INFO]${NC} $1"
23
+ }
24
+
25
+ log_success() {
26
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
27
+ }
28
+
29
+ log_warning() {
30
+ echo -e "${YELLOW}[WARNING]${NC} $1"
31
+ }
32
+
33
+ log_error() {
34
+ echo -e "${RED}[ERROR]${NC} $1"
35
+ }
36
+
37
+ # Function to check if a command exists
38
+ command_exists() {
39
+ command -v "$1" >/dev/null 2>&1
40
+ }
41
+
42
+ # Function to check prerequisites
43
+ check_prerequisites() {
44
+ log_info "Checking prerequisites..."
45
+
46
+ # Check if Node.js and npm are installed
47
+ if ! command_exists node; then
48
+ log_error "Node.js is not installed. Please install Node.js first."
49
+ exit 1
50
+ fi
51
+
52
+ if ! command_exists npm; then
53
+ log_error "npm is not installed. Please install npm first."
54
+ exit 1
55
+ fi
56
+
57
+ # Check if OpenAPI Generator CLI is installed
58
+ if ! command_exists openapi-generator-cli; then
59
+ log_warning "OpenAPI Generator CLI not found. Installing..."
60
+ npm install -g @openapitools/openapi-generator-cli
61
+
62
+ if ! command_exists openapi-generator-cli; then
63
+ log_error "Failed to install OpenAPI Generator CLI"
64
+ exit 1
65
+ fi
66
+ log_success "OpenAPI Generator CLI installed successfully"
67
+ else
68
+ log_success "OpenAPI Generator CLI found"
69
+ fi
70
+
71
+ # Check if Python is available (for the Python script)
72
+ if ! command_exists python3; then
73
+ log_warning "Python 3 not found. Some features may not work."
74
+ fi
75
+ }
76
+
77
+ # Function to fetch OpenAPI specification
78
+ fetch_openapi_spec() {
79
+ log_info "Fetching OpenAPI specification from $API_URL..."
80
+
81
+ # Create temporary directory
82
+ TEMP_DIR=$(mktemp -d)
83
+ OPENAPI_SPEC="$TEMP_DIR/openapi.json"
84
+
85
+ # Fetch the OpenAPI spec
86
+ if curl -s -f "$API_URL/openapi.json" -o "$OPENAPI_SPEC"; then
87
+ log_success "OpenAPI specification fetched successfully"
88
+ echo "$OPENAPI_SPEC"
89
+ else
90
+ log_error "Failed to fetch OpenAPI specification from $API_URL"
91
+ log_error "Make sure the API server is running and accessible"
92
+ exit 1
93
+ fi
94
+ }
95
+
96
+ # Function to generate client for a specific language
97
+ generate_client() {
98
+ local language=$1
99
+ local spec_file=$2
100
+
101
+ log_info "Generating $language client..."
102
+
103
+ case $language in
104
+ "typescript")
105
+ openapi-generator-cli generate \
106
+ -i "$spec_file" \
107
+ -g typescript-fetch \
108
+ -o "$OUTPUT_DIR/typescript" \
109
+ --package-name video-api-client \
110
+ --additional-properties=npmName=video-api-client,npmVersion=1.0.0,supportsES6=true,withInterfaces=true,typescriptThreePlus=true
111
+ ;;
112
+ "python")
113
+ openapi-generator-cli generate \
114
+ -i "$spec_file" \
115
+ -g python \
116
+ -o "$OUTPUT_DIR/python" \
117
+ --package-name video_api_client \
118
+ --additional-properties=packageName=video_api_client,projectName=video-api-client,packageVersion=1.0.0
119
+ ;;
120
+ "java")
121
+ openapi-generator-cli generate \
122
+ -i "$spec_file" \
123
+ -g java \
124
+ -o "$OUTPUT_DIR/java" \
125
+ --package-name com.example.videoapiclient \
126
+ --additional-properties=groupId=com.example,artifactId=video-api-client,artifactVersion=1.0.0,library=okhttp-gson,java8=true
127
+ ;;
128
+ "csharp")
129
+ openapi-generator-cli generate \
130
+ -i "$spec_file" \
131
+ -g csharp \
132
+ -o "$OUTPUT_DIR/csharp" \
133
+ --package-name VideoApiClient \
134
+ --additional-properties=packageName=VideoApiClient,packageVersion=1.0.0,clientPackage=VideoApiClient,targetFramework=netstandard2.0
135
+ ;;
136
+ "go")
137
+ openapi-generator-cli generate \
138
+ -i "$spec_file" \
139
+ -g go \
140
+ -o "$OUTPUT_DIR/go" \
141
+ --package-name videoapiclient \
142
+ --additional-properties=packageName=videoapiclient,packageVersion=1.0.0,packageUrl=github.com/example/video-api-client-go
143
+ ;;
144
+ "php")
145
+ openapi-generator-cli generate \
146
+ -i "$spec_file" \
147
+ -g php \
148
+ -o "$OUTPUT_DIR/php" \
149
+ --package-name VideoApiClient \
150
+ --additional-properties=packageName=VideoApiClient,composerVendorName=example,composerProjectName=video-api-client,packageVersion=1.0.0
151
+ ;;
152
+ "ruby")
153
+ openapi-generator-cli generate \
154
+ -i "$spec_file" \
155
+ -g ruby \
156
+ -o "$OUTPUT_DIR/ruby" \
157
+ --package-name video_api_client \
158
+ --additional-properties=gemName=video_api_client,gemVersion=1.0.0,moduleName=VideoApiClient
159
+ ;;
160
+ *)
161
+ log_error "Unsupported language: $language"
162
+ return 1
163
+ ;;
164
+ esac
165
+
166
+ if [ $? -eq 0 ]; then
167
+ log_success "$language client generated successfully"
168
+ post_process_client "$language"
169
+ return 0
170
+ else
171
+ log_error "Failed to generate $language client"
172
+ return 1
173
+ fi
174
+ }
175
+
176
+ # Function to post-process generated clients
177
+ post_process_client() {
178
+ local language=$1
179
+ local client_dir="$OUTPUT_DIR/$language"
180
+
181
+ log_info "Post-processing $language client..."
182
+
183
+ # Create README if it doesn't exist
184
+ if [ ! -f "$client_dir/README.md" ]; then
185
+ create_readme "$language" "$client_dir"
186
+ fi
187
+
188
+ # Create example file
189
+ create_example "$language" "$client_dir"
190
+
191
+ # Language-specific post-processing
192
+ case $language in
193
+ "typescript")
194
+ post_process_typescript "$client_dir"
195
+ ;;
196
+ "python")
197
+ post_process_python "$client_dir"
198
+ ;;
199
+ "java")
200
+ post_process_java "$client_dir"
201
+ ;;
202
+ esac
203
+ }
204
+
205
+ # Function to create README for client
206
+ create_readme() {
207
+ local language=$1
208
+ local client_dir=$2
209
+
210
+ cat > "$client_dir/README.md" << EOF
211
+ # Video Generation API Client - ${language^}
212
+
213
+ This is an auto-generated client library for the Video Generation API.
214
+
215
+ ## Installation
216
+
217
+ See the installation instructions in the main documentation.
218
+
219
+ ## Quick Start
220
+
221
+ \`\`\`${language}
222
+ // See example.${language} for usage examples
223
+ \`\`\`
224
+
225
+ ## Authentication
226
+
227
+ This API uses Clerk authentication. You need to provide a valid Clerk session token.
228
+
229
+ ## Documentation
230
+
231
+ For complete API documentation, visit: https://docs.example.com
232
+
233
+ ## Support
234
+
235
+ - GitHub Issues: https://github.com/example/video-api-client-${language}/issues
236
+ - Documentation: https://docs.example.com
237
+ - Email: [email protected]
238
+
239
+ ## License
240
+
241
+ MIT License - see LICENSE file for details.
242
+ EOF
243
+ }
244
+
245
+ # Function to create example file
246
+ create_example() {
247
+ local language=$1
248
+ local client_dir=$2
249
+
250
+ case $language in
251
+ "typescript")
252
+ cat > "$client_dir/example.ts" << 'EOF'
253
+ import { Configuration, VideosApi } from './src';
254
+
255
+ const config = new Configuration({
256
+ basePath: 'https://api.example.com',
257
+ accessToken: 'your-clerk-session-token'
258
+ });
259
+
260
+ const videosApi = new VideosApi(config);
261
+
262
+ async function generateVideo() {
263
+ try {
264
+ const jobResponse = await videosApi.createVideoGenerationJob({
265
+ configuration: {
266
+ topic: "Pythagorean Theorem",
267
+ context: "Explain the mathematical proof",
268
+ quality: "medium",
269
+ use_rag: true
270
+ }
271
+ });
272
+
273
+ console.log('Job created:', jobResponse.job_id);
274
+ } catch (error) {
275
+ console.error('Error:', error);
276
+ }
277
+ }
278
+
279
+ generateVideo();
280
+ EOF
281
+ ;;
282
+ "python")
283
+ cat > "$client_dir/example.py" << 'EOF'
284
+ from video_api_client import Configuration, ApiClient, VideosApi
285
+ from video_api_client.models import JobCreateRequest, JobConfiguration
286
+
287
+ configuration = Configuration(
288
+ host="https://api.example.com",
289
+ access_token="your-clerk-session-token"
290
+ )
291
+
292
+ with ApiClient(configuration) as api_client:
293
+ videos_api = VideosApi(api_client)
294
+
295
+ try:
296
+ job_request = JobCreateRequest(
297
+ configuration=JobConfiguration(
298
+ topic="Pythagorean Theorem",
299
+ context="Explain the mathematical proof",
300
+ quality="medium",
301
+ use_rag=True
302
+ )
303
+ )
304
+
305
+ job_response = videos_api.create_video_generation_job(job_request)
306
+ print(f"Job created: {job_response.job_id}")
307
+
308
+ except Exception as e:
309
+ print(f"Error: {e}")
310
+ EOF
311
+ ;;
312
+ esac
313
+ }
314
+
315
+ # Language-specific post-processing functions
316
+ post_process_typescript() {
317
+ local client_dir=$1
318
+
319
+ # Create package.json if it doesn't exist
320
+ if [ ! -f "$client_dir/package.json" ]; then
321
+ cat > "$client_dir/package.json" << 'EOF'
322
+ {
323
+ "name": "video-api-client",
324
+ "version": "1.0.0",
325
+ "description": "TypeScript client for Video Generation API",
326
+ "main": "dist/index.js",
327
+ "types": "dist/index.d.ts",
328
+ "scripts": {
329
+ "build": "tsc",
330
+ "test": "jest"
331
+ },
332
+ "dependencies": {
333
+ "node-fetch": "^2.6.7"
334
+ },
335
+ "devDependencies": {
336
+ "typescript": "^4.9.0",
337
+ "@types/node": "^18.0.0"
338
+ },
339
+ "keywords": ["video", "api", "client", "typescript"],
340
+ "author": "API Team",
341
+ "license": "MIT"
342
+ }
343
+ EOF
344
+ fi
345
+ }
346
+
347
+ post_process_python() {
348
+ local client_dir=$1
349
+
350
+ # Create setup.py if it doesn't exist
351
+ if [ ! -f "$client_dir/setup.py" ]; then
352
+ cat > "$client_dir/setup.py" << 'EOF'
353
+ from setuptools import setup, find_packages
354
+
355
+ setup(
356
+ name="video-api-client",
357
+ version="1.0.0",
358
+ description="Python client for Video Generation API",
359
+ packages=find_packages(),
360
+ install_requires=[
361
+ "requests>=2.25.0",
362
+ "urllib3>=1.26.0",
363
+ "python-dateutil>=2.8.0"
364
+ ],
365
+ author="API Team",
366
+ author_email="[email protected]",
367
+ license="MIT"
368
+ )
369
+ EOF
370
+ fi
371
+ }
372
+
373
+ post_process_java() {
374
+ local client_dir=$1
375
+
376
+ # Java post-processing would go here
377
+ log_info "Java client post-processing completed"
378
+ }
379
+
380
+ # Function to test generated clients
381
+ test_clients() {
382
+ log_info "Testing generated clients..."
383
+
384
+ # Test TypeScript client
385
+ if [ -d "$OUTPUT_DIR/typescript" ]; then
386
+ log_info "Testing TypeScript client..."
387
+ cd "$OUTPUT_DIR/typescript"
388
+ if [ -f "package.json" ]; then
389
+ npm install --silent
390
+ npm run build --silent 2>/dev/null || log_warning "TypeScript build failed"
391
+ fi
392
+ cd - > /dev/null
393
+ fi
394
+
395
+ # Test Python client
396
+ if [ -d "$OUTPUT_DIR/python" ]; then
397
+ log_info "Testing Python client..."
398
+ cd "$OUTPUT_DIR/python"
399
+ if [ -f "setup.py" ] && command_exists python3; then
400
+ python3 setup.py check --quiet 2>/dev/null || log_warning "Python setup check failed"
401
+ fi
402
+ cd - > /dev/null
403
+ fi
404
+ }
405
+
406
+ # Function to create documentation
407
+ create_documentation() {
408
+ log_info "Creating client documentation..."
409
+
410
+ cat > "$OUTPUT_DIR/README.md" << EOF
411
+ # Video Generation API Clients
412
+
413
+ This directory contains auto-generated client libraries for the Video Generation API in multiple programming languages.
414
+
415
+ ## Available Clients
416
+
417
+ EOF
418
+
419
+ for dir in "$OUTPUT_DIR"/*; do
420
+ if [ -d "$dir" ]; then
421
+ language=$(basename "$dir")
422
+ echo "- [$language](./$language/)" >> "$OUTPUT_DIR/README.md"
423
+ fi
424
+ done
425
+
426
+ cat >> "$OUTPUT_DIR/README.md" << EOF
427
+
428
+ ## Quick Start
429
+
430
+ Each client directory contains:
431
+ - Generated API client code
432
+ - README with installation and usage instructions
433
+ - Example code demonstrating basic usage
434
+ - Package/project files for the respective language
435
+
436
+ ## Authentication
437
+
438
+ All clients support Clerk authentication. Set your session token when configuring the client.
439
+
440
+ ## Documentation
441
+
442
+ - API Documentation: https://docs.example.com
443
+ - OpenAPI Specification: $API_URL/openapi.json
444
+
445
+ ## Support
446
+
447
+ For issues with the generated clients:
448
+ - Check the individual client README files
449
+ - Visit our documentation at https://docs.example.com
450
+ - Contact support at [email protected]
451
+
452
+ Generated on: $(date)
453
+ EOF
454
+ }
455
+
456
+ # Main function
457
+ main() {
458
+ local languages=("$@")
459
+
460
+ # Default to all languages if none specified
461
+ if [ ${#languages[@]} -eq 0 ]; then
462
+ languages=("typescript" "python" "java" "csharp" "go" "php" "ruby")
463
+ fi
464
+
465
+ log_info "Starting client generation for languages: ${languages[*]}"
466
+
467
+ # Check prerequisites
468
+ check_prerequisites
469
+
470
+ # Fetch OpenAPI specification
471
+ OPENAPI_SPEC=$(fetch_openapi_spec)
472
+
473
+ # Create output directory
474
+ mkdir -p "$OUTPUT_DIR"
475
+
476
+ # Generate clients
477
+ local successful=()
478
+ local failed=()
479
+
480
+ for language in "${languages[@]}"; do
481
+ if generate_client "$language" "$OPENAPI_SPEC"; then
482
+ successful+=("$language")
483
+ else
484
+ failed+=("$language")
485
+ fi
486
+ done
487
+
488
+ # Test clients
489
+ test_clients
490
+
491
+ # Create documentation
492
+ create_documentation
493
+
494
+ # Print summary
495
+ echo
496
+ echo "=================================================="
497
+ echo "CLIENT GENERATION SUMMARY"
498
+ echo "=================================================="
499
+
500
+ if [ ${#successful[@]} -gt 0 ]; then
501
+ log_success "Successfully generated: ${successful[*]}"
502
+ fi
503
+
504
+ if [ ${#failed[@]} -gt 0 ]; then
505
+ log_error "Failed to generate: ${failed[*]}"
506
+ fi
507
+
508
+ echo
509
+ log_info "Generated clients are available in: $OUTPUT_DIR"
510
+
511
+ # Cleanup
512
+ rm -rf "$(dirname "$OPENAPI_SPEC")"
513
+
514
+ # Exit with error if any generation failed
515
+ if [ ${#failed[@]} -gt 0 ]; then
516
+ exit 1
517
+ fi
518
+ }
519
+
520
+ # Parse command line arguments
521
+ while [[ $# -gt 0 ]]; do
522
+ case $1 in
523
+ --api-url)
524
+ API_URL="$2"
525
+ shift 2
526
+ ;;
527
+ --output-dir)
528
+ OUTPUT_DIR="$2"
529
+ shift 2
530
+ ;;
531
+ --help)
532
+ echo "Usage: $0 [OPTIONS] [LANGUAGES...]"
533
+ echo ""
534
+ echo "Generate client SDKs for Video Generation API"
535
+ echo ""
536
+ echo "Options:"
537
+ echo " --api-url URL API base URL (default: http://localhost:8000)"
538
+ echo " --output-dir DIR Output directory (default: generated_clients)"
539
+ echo " --help Show this help message"
540
+ echo ""
541
+ echo "Languages:"
542
+ echo " typescript python java csharp go php ruby"
543
+ echo ""
544
+ echo "Examples:"
545
+ echo " $0 # Generate all clients"
546
+ echo " $0 typescript python # Generate only TypeScript and Python"
547
+ echo " $0 --api-url https://api.example.com typescript"
548
+ exit 0
549
+ ;;
550
+ *)
551
+ break
552
+ ;;
553
+ esac
554
+ done
555
+
556
+ # Run main function with remaining arguments as languages
557
+ main "$@"
scripts/setup.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Development setup script.
4
+ Creates necessary directories and validates configuration.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ def create_directories():
13
+ """Create necessary directories for the application."""
14
+ directories = [
15
+ "uploads",
16
+ "videos",
17
+ "logs",
18
+ "data",
19
+ ]
20
+
21
+ for directory in directories:
22
+ Path(directory).mkdir(exist_ok=True)
23
+ print(f"✓ Created directory: {directory}")
24
+
25
+
26
+ def check_env_file():
27
+ """Check if .env file exists and create from example if not."""
28
+ env_file = Path(".env")
29
+ env_example = Path(".env.example")
30
+
31
+ if not env_file.exists():
32
+ if env_example.exists():
33
+ # Copy example to .env
34
+ env_file.write_text(env_example.read_text())
35
+ print("✓ Created .env file from .env.example")
36
+ print("⚠️ Please update the .env file with your actual configuration values")
37
+ else:
38
+ print("❌ .env.example file not found")
39
+ return False
40
+ else:
41
+ print("✓ .env file already exists")
42
+
43
+ return True
44
+
45
+
46
+ def validate_required_env_vars():
47
+ """Validate that required environment variables are set."""
48
+ from dotenv import load_dotenv
49
+
50
+ load_dotenv()
51
+
52
+ required_vars = [
53
+ "CLERK_SECRET_KEY",
54
+ "CLERK_PUBLISHABLE_KEY",
55
+ "SECRET_KEY",
56
+ ]
57
+
58
+ missing_vars = []
59
+ for var in required_vars:
60
+ if not os.getenv(var) or os.getenv(var) == f"your_{var.lower()}_here":
61
+ missing_vars.append(var)
62
+
63
+ if missing_vars:
64
+ print("❌ Missing required environment variables:")
65
+ for var in missing_vars:
66
+ print(f" - {var}")
67
+ print("\nPlease update your .env file with actual values.")
68
+ return False
69
+
70
+ print("✓ All required environment variables are set")
71
+ return True
72
+
73
+
74
+ def main():
75
+ """Main setup function."""
76
+ print("🚀 Setting up FastAPI Video Backend development environment...\n")
77
+
78
+ # Create directories
79
+ create_directories()
80
+ print()
81
+
82
+ # Check .env file
83
+ if not check_env_file():
84
+ sys.exit(1)
85
+ print()
86
+
87
+ # Validate environment variables
88
+ try:
89
+ if not validate_required_env_vars():
90
+ print("\n⚠️ Setup completed with warnings. Please fix the issues above.")
91
+ sys.exit(1)
92
+ except ImportError:
93
+ print("⚠️ python-dotenv not installed. Skipping environment validation.")
94
+ print(" Install dependencies with: pip install -e .")
95
+
96
+ print("\n✅ Development environment setup completed!")
97
+ print("\nNext steps:")
98
+ print("1. Install dependencies: pip install -e .")
99
+ print("2. Update .env file with your actual configuration values")
100
+ print("3. Start Redis server: redis-server")
101
+ print("4. Run the application: python -m src.app.main")
102
+ print("5. Visit http://localhost:8000/docs for API documentation")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()
scripts/test_clients.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Client SDK testing script for FastAPI Video Generation Backend.
4
+
5
+ This script tests the generated client SDKs to ensure they work correctly
6
+ with the API endpoints. It performs basic functionality tests and validates
7
+ the client-server communication.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import json
13
+ import time
14
+ import asyncio
15
+ import requests
16
+ import subprocess
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Any
19
+ import tempfile
20
+ import logging
21
+
22
+ # Set up logging
23
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class ClientTester:
28
+ """Test suite for generated client SDKs."""
29
+
30
+ def __init__(self, api_url: str = "http://localhost:8000", clients_dir: str = "generated_clients"):
31
+ self.api_url = api_url.rstrip("/")
32
+ self.clients_dir = Path(clients_dir)
33
+ self.test_results: Dict[str, Dict[str, Any]] = {}
34
+
35
+ def check_api_availability(self) -> bool:
36
+ """Check if the API server is running and accessible."""
37
+ try:
38
+ logger.info(f"Checking API availability at {self.api_url}")
39
+ response = requests.get(f"{self.api_url}/health", timeout=10)
40
+ response.raise_for_status()
41
+ logger.info("API server is available")
42
+ return True
43
+ except requests.RequestException as e:
44
+ logger.error(f"API server is not available: {e}")
45
+ return False
46
+
47
+ def test_openapi_spec(self) -> bool:
48
+ """Test if OpenAPI specification is accessible."""
49
+ try:
50
+ logger.info("Testing OpenAPI specification accessibility")
51
+ response = requests.get(f"{self.api_url}/openapi.json", timeout=10)
52
+ response.raise_for_status()
53
+
54
+ spec = response.json()
55
+
56
+ # Validate basic OpenAPI structure
57
+ required_fields = ["openapi", "info", "paths"]
58
+ for field in required_fields:
59
+ if field not in spec:
60
+ logger.error(f"OpenAPI spec missing required field: {field}")
61
+ return False
62
+
63
+ logger.info("OpenAPI specification is valid")
64
+ return True
65
+
66
+ except requests.RequestException as e:
67
+ logger.error(f"Failed to fetch OpenAPI specification: {e}")
68
+ return False
69
+ except json.JSONDecodeError as e:
70
+ logger.error(f"Invalid JSON in OpenAPI specification: {e}")
71
+ return False
72
+
73
+ def test_typescript_client(self) -> Dict[str, Any]:
74
+ """Test TypeScript client."""
75
+ client_dir = self.clients_dir / "typescript"
76
+ result = {
77
+ "language": "typescript",
78
+ "exists": False,
79
+ "builds": False,
80
+ "has_examples": False,
81
+ "has_docs": False,
82
+ "errors": []
83
+ }
84
+
85
+ try:
86
+ if not client_dir.exists():
87
+ result["errors"].append("Client directory does not exist")
88
+ return result
89
+
90
+ result["exists"] = True
91
+
92
+ # Check for essential files
93
+ essential_files = ["package.json", "README.md"]
94
+ for file in essential_files:
95
+ if not (client_dir / file).exists():
96
+ result["errors"].append(f"Missing {file}")
97
+
98
+ # Check for example file
99
+ if (client_dir / "example.ts").exists():
100
+ result["has_examples"] = True
101
+
102
+ # Check for documentation
103
+ if (client_dir / "README.md").exists():
104
+ result["has_docs"] = True
105
+
106
+ # Try to build the client
107
+ if (client_dir / "package.json").exists():
108
+ logger.info("Testing TypeScript client build")
109
+
110
+ # Install dependencies
111
+ install_result = subprocess.run(
112
+ ["npm", "install"],
113
+ cwd=client_dir,
114
+ capture_output=True,
115
+ text=True,
116
+ timeout=120
117
+ )
118
+
119
+ if install_result.returncode != 0:
120
+ result["errors"].append(f"npm install failed: {install_result.stderr}")
121
+ else:
122
+ # Try to build
123
+ build_result = subprocess.run(
124
+ ["npm", "run", "build"],
125
+ cwd=client_dir,
126
+ capture_output=True,
127
+ text=True,
128
+ timeout=60
129
+ )
130
+
131
+ if build_result.returncode == 0:
132
+ result["builds"] = True
133
+ else:
134
+ result["errors"].append(f"Build failed: {build_result.stderr}")
135
+
136
+ except subprocess.TimeoutExpired:
137
+ result["errors"].append("Build timeout")
138
+ except Exception as e:
139
+ result["errors"].append(f"Unexpected error: {str(e)}")
140
+
141
+ return result
142
+
143
+ def test_python_client(self) -> Dict[str, Any]:
144
+ """Test Python client."""
145
+ client_dir = self.clients_dir / "python"
146
+ result = {
147
+ "language": "python",
148
+ "exists": False,
149
+ "installs": False,
150
+ "imports": False,
151
+ "has_examples": False,
152
+ "has_docs": False,
153
+ "errors": []
154
+ }
155
+
156
+ try:
157
+ if not client_dir.exists():
158
+ result["errors"].append("Client directory does not exist")
159
+ return result
160
+
161
+ result["exists"] = True
162
+
163
+ # Check for essential files
164
+ essential_files = ["setup.py", "README.md"]
165
+ for file in essential_files:
166
+ if not (client_dir / file).exists():
167
+ result["errors"].append(f"Missing {file}")
168
+
169
+ # Check for example file
170
+ if (client_dir / "example.py").exists():
171
+ result["has_examples"] = True
172
+
173
+ # Check for documentation
174
+ if (client_dir / "README.md").exists():
175
+ result["has_docs"] = True
176
+
177
+ # Try to install the client in development mode
178
+ if (client_dir / "setup.py").exists():
179
+ logger.info("Testing Python client installation")
180
+
181
+ install_result = subprocess.run(
182
+ [sys.executable, "setup.py", "develop", "--user"],
183
+ cwd=client_dir,
184
+ capture_output=True,
185
+ text=True,
186
+ timeout=120
187
+ )
188
+
189
+ if install_result.returncode == 0:
190
+ result["installs"] = True
191
+
192
+ # Try to import the client
193
+ try:
194
+ import importlib.util
195
+
196
+ # Find the main module
197
+ main_module_path = None
198
+ for py_file in client_dir.rglob("*.py"):
199
+ if "__init__.py" in py_file.name:
200
+ main_module_path = py_file
201
+ break
202
+
203
+ if main_module_path:
204
+ spec = importlib.util.spec_from_file_location("test_client", main_module_path)
205
+ if spec and spec.loader:
206
+ module = importlib.util.module_from_spec(spec)
207
+ spec.loader.exec_module(module)
208
+ result["imports"] = True
209
+
210
+ except Exception as e:
211
+ result["errors"].append(f"Import failed: {str(e)}")
212
+ else:
213
+ result["errors"].append(f"Installation failed: {install_result.stderr}")
214
+
215
+ except subprocess.TimeoutExpired:
216
+ result["errors"].append("Installation timeout")
217
+ except Exception as e:
218
+ result["errors"].append(f"Unexpected error: {str(e)}")
219
+
220
+ return result
221
+
222
+ def test_java_client(self) -> Dict[str, Any]:
223
+ """Test Java client."""
224
+ client_dir = self.clients_dir / "java"
225
+ result = {
226
+ "language": "java",
227
+ "exists": False,
228
+ "compiles": False,
229
+ "has_examples": False,
230
+ "has_docs": False,
231
+ "errors": []
232
+ }
233
+
234
+ try:
235
+ if not client_dir.exists():
236
+ result["errors"].append("Client directory does not exist")
237
+ return result
238
+
239
+ result["exists"] = True
240
+
241
+ # Check for essential files
242
+ essential_files = ["pom.xml", "README.md"]
243
+ for file in essential_files:
244
+ if not (client_dir / file).exists():
245
+ result["errors"].append(f"Missing {file}")
246
+
247
+ # Check for example file
248
+ if (client_dir / "example.java").exists():
249
+ result["has_examples"] = True
250
+
251
+ # Check for documentation
252
+ if (client_dir / "README.md").exists():
253
+ result["has_docs"] = True
254
+
255
+ # Try to compile the client (if Maven is available)
256
+ if (client_dir / "pom.xml").exists():
257
+ logger.info("Testing Java client compilation")
258
+
259
+ # Check if Maven is available
260
+ maven_check = subprocess.run(
261
+ ["mvn", "--version"],
262
+ capture_output=True,
263
+ text=True,
264
+ timeout=10
265
+ )
266
+
267
+ if maven_check.returncode == 0:
268
+ compile_result = subprocess.run(
269
+ ["mvn", "compile"],
270
+ cwd=client_dir,
271
+ capture_output=True,
272
+ text=True,
273
+ timeout=180
274
+ )
275
+
276
+ if compile_result.returncode == 0:
277
+ result["compiles"] = True
278
+ else:
279
+ result["errors"].append(f"Compilation failed: {compile_result.stderr}")
280
+ else:
281
+ result["errors"].append("Maven not available for compilation test")
282
+
283
+ except subprocess.TimeoutExpired:
284
+ result["errors"].append("Compilation timeout")
285
+ except Exception as e:
286
+ result["errors"].append(f"Unexpected error: {str(e)}")
287
+
288
+ return result
289
+
290
+ def test_client_structure(self, language: str) -> Dict[str, Any]:
291
+ """Test the structure of a generated client."""
292
+ client_dir = self.clients_dir / language
293
+ result = {
294
+ "language": language,
295
+ "exists": False,
296
+ "structure_valid": False,
297
+ "has_api_files": False,
298
+ "has_model_files": False,
299
+ "has_docs": False,
300
+ "file_count": 0,
301
+ "errors": []
302
+ }
303
+
304
+ try:
305
+ if not client_dir.exists():
306
+ result["errors"].append("Client directory does not exist")
307
+ return result
308
+
309
+ result["exists"] = True
310
+
311
+ # Count files
312
+ all_files = list(client_dir.rglob("*"))
313
+ result["file_count"] = len([f for f in all_files if f.is_file()])
314
+
315
+ # Check for API files
316
+ api_files = list(client_dir.rglob("*api*"))
317
+ if api_files:
318
+ result["has_api_files"] = True
319
+
320
+ # Check for model files
321
+ model_files = list(client_dir.rglob("*model*"))
322
+ if model_files:
323
+ result["has_model_files"] = True
324
+
325
+ # Check for documentation
326
+ doc_files = list(client_dir.rglob("README*")) + list(client_dir.rglob("*.md"))
327
+ if doc_files:
328
+ result["has_docs"] = True
329
+
330
+ # Basic structure validation
331
+ if result["has_api_files"] and result["has_model_files"]:
332
+ result["structure_valid"] = True
333
+
334
+ except Exception as e:
335
+ result["errors"].append(f"Unexpected error: {str(e)}")
336
+
337
+ return result
338
+
339
+ def run_functional_tests(self) -> Dict[str, Any]:
340
+ """Run functional tests against the API using generated clients."""
341
+ result = {
342
+ "api_reachable": False,
343
+ "openapi_valid": False,
344
+ "endpoints_tested": 0,
345
+ "endpoints_passed": 0,
346
+ "errors": []
347
+ }
348
+
349
+ try:
350
+ # Test API availability
351
+ result["api_reachable"] = self.check_api_availability()
352
+
353
+ # Test OpenAPI spec
354
+ result["openapi_valid"] = self.test_openapi_spec()
355
+
356
+ if not result["api_reachable"]:
357
+ result["errors"].append("API not reachable - skipping functional tests")
358
+ return result
359
+
360
+ # Test basic endpoints
361
+ endpoints_to_test = [
362
+ {"method": "GET", "path": "/health", "expected_status": 200},
363
+ {"method": "GET", "path": "/", "expected_status": 200},
364
+ {"method": "GET", "path": "/openapi.json", "expected_status": 200},
365
+ ]
366
+
367
+ for endpoint in endpoints_to_test:
368
+ try:
369
+ result["endpoints_tested"] += 1
370
+
371
+ response = requests.request(
372
+ endpoint["method"],
373
+ f"{self.api_url}{endpoint['path']}",
374
+ timeout=10
375
+ )
376
+
377
+ if response.status_code == endpoint["expected_status"]:
378
+ result["endpoints_passed"] += 1
379
+ else:
380
+ result["errors"].append(
381
+ f"{endpoint['method']} {endpoint['path']} returned {response.status_code}, "
382
+ f"expected {endpoint['expected_status']}"
383
+ )
384
+
385
+ except requests.RequestException as e:
386
+ result["errors"].append(f"Failed to test {endpoint['path']}: {str(e)}")
387
+
388
+ except Exception as e:
389
+ result["errors"].append(f"Functional test error: {str(e)}")
390
+
391
+ return result
392
+
393
+ def run_all_tests(self) -> Dict[str, Any]:
394
+ """Run all client tests."""
395
+ logger.info("Starting comprehensive client testing")
396
+
397
+ # Test API functionality
398
+ functional_results = self.run_functional_tests()
399
+
400
+ # Test individual clients
401
+ client_results = {}
402
+
403
+ # Test TypeScript client
404
+ if (self.clients_dir / "typescript").exists():
405
+ logger.info("Testing TypeScript client")
406
+ client_results["typescript"] = self.test_typescript_client()
407
+
408
+ # Test Python client
409
+ if (self.clients_dir / "python").exists():
410
+ logger.info("Testing Python client")
411
+ client_results["python"] = self.test_python_client()
412
+
413
+ # Test Java client
414
+ if (self.clients_dir / "java").exists():
415
+ logger.info("Testing Java client")
416
+ client_results["java"] = self.test_java_client()
417
+
418
+ # Test structure for all clients
419
+ structure_results = {}
420
+ for client_dir in self.clients_dir.iterdir():
421
+ if client_dir.is_dir():
422
+ language = client_dir.name
423
+ logger.info(f"Testing {language} client structure")
424
+ structure_results[language] = self.test_client_structure(language)
425
+
426
+ return {
427
+ "functional_tests": functional_results,
428
+ "client_tests": client_results,
429
+ "structure_tests": structure_results,
430
+ "summary": self._generate_summary(functional_results, client_results, structure_results)
431
+ }
432
+
433
+ def _generate_summary(self, functional: Dict, clients: Dict, structures: Dict) -> Dict[str, Any]:
434
+ """Generate test summary."""
435
+ total_clients = len(structures)
436
+ working_clients = 0
437
+
438
+ for language, result in clients.items():
439
+ if language == "typescript" and result.get("builds", False):
440
+ working_clients += 1
441
+ elif language == "python" and result.get("imports", False):
442
+ working_clients += 1
443
+ elif language == "java" and result.get("compiles", False):
444
+ working_clients += 1
445
+
446
+ return {
447
+ "total_clients_found": total_clients,
448
+ "clients_tested": len(clients),
449
+ "working_clients": working_clients,
450
+ "api_functional": functional.get("api_reachable", False),
451
+ "openapi_valid": functional.get("openapi_valid", False),
452
+ "endpoints_success_rate": (
453
+ functional.get("endpoints_passed", 0) / max(functional.get("endpoints_tested", 1), 1) * 100
454
+ )
455
+ }
456
+
457
+ def print_results(self, results: Dict[str, Any]) -> None:
458
+ """Print test results in a formatted way."""
459
+ print("\n" + "="*60)
460
+ print("CLIENT SDK TEST RESULTS")
461
+ print("="*60)
462
+
463
+ # Print summary
464
+ summary = results["summary"]
465
+ print(f"\nSUMMARY:")
466
+ print(f" Total clients found: {summary['total_clients_found']}")
467
+ print(f" Clients tested: {summary['clients_tested']}")
468
+ print(f" Working clients: {summary['working_clients']}")
469
+ print(f" API functional: {'✅' if summary['api_functional'] else '❌'}")
470
+ print(f" OpenAPI valid: {'✅' if summary['openapi_valid'] else '❌'}")
471
+ print(f" Endpoints success rate: {summary['endpoints_success_rate']:.1f}%")
472
+
473
+ # Print functional test results
474
+ print(f"\nFUNCTIONAL TESTS:")
475
+ functional = results["functional_tests"]
476
+ print(f" API reachable: {'✅' if functional['api_reachable'] else '❌'}")
477
+ print(f" OpenAPI valid: {'✅' if functional['openapi_valid'] else '❌'}")
478
+ print(f" Endpoints tested: {functional['endpoints_tested']}")
479
+ print(f" Endpoints passed: {functional['endpoints_passed']}")
480
+
481
+ if functional["errors"]:
482
+ print(" Errors:")
483
+ for error in functional["errors"]:
484
+ print(f" - {error}")
485
+
486
+ # Print client test results
487
+ print(f"\nCLIENT TESTS:")
488
+ for language, result in results["client_tests"].items():
489
+ print(f"\n {language.upper()}:")
490
+ print(f" Exists: {'✅' if result['exists'] else '❌'}")
491
+
492
+ if language == "typescript":
493
+ print(f" Builds: {'✅' if result['builds'] else '❌'}")
494
+ elif language == "python":
495
+ print(f" Installs: {'✅' if result['installs'] else '❌'}")
496
+ print(f" Imports: {'✅' if result['imports'] else '❌'}")
497
+ elif language == "java":
498
+ print(f" Compiles: {'✅' if result['compiles'] else '❌'}")
499
+
500
+ print(f" Has examples: {'✅' if result['has_examples'] else '❌'}")
501
+ print(f" Has docs: {'✅' if result['has_docs'] else '❌'}")
502
+
503
+ if result["errors"]:
504
+ print(" Errors:")
505
+ for error in result["errors"]:
506
+ print(f" - {error}")
507
+
508
+ # Print structure test results
509
+ print(f"\nSTRUCTURE TESTS:")
510
+ for language, result in results["structure_tests"].items():
511
+ print(f"\n {language.upper()}:")
512
+ print(f" Exists: {'✅' if result['exists'] else '❌'}")
513
+ print(f" Structure valid: {'✅' if result['structure_valid'] else '❌'}")
514
+ print(f" Has API files: {'✅' if result['has_api_files'] else '❌'}")
515
+ print(f" Has model files: {'✅' if result['has_model_files'] else '❌'}")
516
+ print(f" Has docs: {'✅' if result['has_docs'] else '❌'}")
517
+ print(f" File count: {result['file_count']}")
518
+
519
+ if result["errors"]:
520
+ print(" Errors:")
521
+ for error in result["errors"]:
522
+ print(f" - {error}")
523
+
524
+ print("\n" + "="*60)
525
+
526
+
527
+ def main():
528
+ """Main function for command-line usage."""
529
+ import argparse
530
+
531
+ parser = argparse.ArgumentParser(description="Test generated client SDKs")
532
+ parser.add_argument(
533
+ "--api-url",
534
+ default="http://localhost:8000",
535
+ help="Base URL of the API (default: http://localhost:8000)"
536
+ )
537
+ parser.add_argument(
538
+ "--clients-dir",
539
+ default="generated_clients",
540
+ help="Directory containing generated clients (default: generated_clients)"
541
+ )
542
+ parser.add_argument(
543
+ "--output-file",
544
+ help="Save test results to JSON file"
545
+ )
546
+ parser.add_argument(
547
+ "--verbose",
548
+ action="store_true",
549
+ help="Enable verbose logging"
550
+ )
551
+
552
+ args = parser.parse_args()
553
+
554
+ if args.verbose:
555
+ logging.getLogger().setLevel(logging.DEBUG)
556
+
557
+ # Create tester instance
558
+ tester = ClientTester(args.api_url, args.clients_dir)
559
+
560
+ # Run tests
561
+ results = tester.run_all_tests()
562
+
563
+ # Print results
564
+ tester.print_results(results)
565
+
566
+ # Save results to file if requested
567
+ if args.output_file:
568
+ with open(args.output_file, 'w') as f:
569
+ json.dump(results, f, indent=2, default=str)
570
+ logger.info(f"Test results saved to {args.output_file}")
571
+
572
+ # Exit with error code if tests failed
573
+ summary = results["summary"]
574
+ if not summary["api_functional"] or summary["working_clients"] == 0:
575
+ sys.exit(1)
576
+
577
+
578
+ if __name__ == "__main__":
579
+ main()
setup-dev.bat ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ REM T2M FastAPI Development Setup Script for Windows
3
+ REM This script sets up Redis, ngrok, and provides instructions for FastAPI
4
+
5
+ echo 🚀 Setting up T2M FastAPI Development Environment
6
+ echo ================================================
7
+
8
+ REM Check if Docker is installed
9
+ docker --version >nul 2>&1
10
+ if %errorlevel% neq 0 (
11
+ echo ❌ Docker is not installed. Please install Docker Desktop first.
12
+ pause
13
+ exit /b 1
14
+ )
15
+
16
+ REM Check if Docker Compose is installed
17
+ docker-compose --version >nul 2>&1
18
+ if %errorlevel% neq 0 (
19
+ echo ❌ Docker Compose is not installed. Please install Docker Compose first.
20
+ pause
21
+ exit /b 1
22
+ )
23
+
24
+ REM Check if .env file exists
25
+ if not exist ".env" (
26
+ echo ❌ .env file not found. Please copy .env.example to .env and configure it.
27
+ pause
28
+ exit /b 1
29
+ )
30
+
31
+ REM Check if NGROK_AUTHTOKEN is set
32
+ findstr /C:"NGROK_AUTHTOKEN=" .env >nul
33
+ if %errorlevel% neq 0 (
34
+ echo ❌ NGROK_AUTHTOKEN is not set in .env file.
35
+ echo Please get your auth token from https://dashboard.ngrok.com/get-started/your-authtoken
36
+ echo and add it to your .env file: NGROK_AUTHTOKEN=your_token_here
37
+ pause
38
+ exit /b 1
39
+ )
40
+
41
+ echo ✅ Prerequisites check passed
42
+ echo.
43
+
44
+ REM Start Redis and ngrok services
45
+ echo 🔧 Starting Redis and ngrok services...
46
+ docker-compose -f docker-compose.dev.yml up -d
47
+
48
+ echo.
49
+ echo ⏳ Waiting for services to start...
50
+ timeout /t 5 /nobreak >nul
51
+
52
+ REM Check if services are running
53
+ docker-compose -f docker-compose.dev.yml ps
54
+
55
+ echo.
56
+ echo 🎯 Next Steps:
57
+ echo ==============
58
+ echo.
59
+ echo 1. Start your FastAPI server locally:
60
+ echo python -m uvicorn src.app.main:app --reload --host 0.0.0.0 --port 8000
61
+ echo.
62
+ echo 2. Get your public ngrok URL:
63
+ echo - Visit: http://localhost:4040
64
+ echo - Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
65
+ echo.
66
+ echo 3. Configure Clerk webhook:
67
+ echo - Go to https://dashboard.clerk.com/
68
+ echo - Navigate to Webhooks
69
+ echo - Add endpoint: https://your-ngrok-url.ngrok.io/api/v1/auth/webhooks/clerk
70
+ echo - Copy the signing secret to CLERK_WEBHOOK_SECRET in .env
71
+ echo.
72
+ echo 4. Test your setup:
73
+ echo curl https://your-ngrok-url.ngrok.io/health
74
+ echo.
75
+ echo 📝 Useful Commands:
76
+ echo ==================
77
+ echo • View logs: docker-compose -f docker-compose.dev.yml logs -f
78
+ echo • Stop services: docker-compose -f docker-compose.dev.yml down
79
+ echo • Restart services: docker-compose -f docker-compose.dev.yml restart
80
+ echo • Check Redis: redis-cli ping
81
+ echo.
82
+ echo 🌐 URLs:
83
+ echo ========
84
+ echo • FastAPI (local): http://localhost:8000
85
+ echo • FastAPI Docs: http://localhost:8000/docs
86
+ echo • ngrok Dashboard: http://localhost:4040
87
+ echo • Redis: localhost:6379
88
+ echo.
89
+ echo ✨ Development environment is ready!
90
+ pause
setup-dev.sh ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # T2M FastAPI Development Setup Script
4
+ # This script sets up Redis, ngrok, and provides instructions for FastAPI
5
+
6
+ set -e
7
+
8
+ echo "🚀 Setting up T2M FastAPI Development Environment"
9
+ echo "================================================"
10
+
11
+ # Check if Docker is installed
12
+ if ! command -v docker &> /dev/null; then
13
+ echo "❌ Docker is not installed. Please install Docker first."
14
+ exit 1
15
+ fi
16
+
17
+ # Check if Docker Compose is installed
18
+ if ! command -v docker-compose &> /dev/null; then
19
+ echo "❌ Docker Compose is not installed. Please install Docker Compose first."
20
+ exit 1
21
+ fi
22
+
23
+ # Check if .env file exists
24
+ if [ ! -f ".env" ]; then
25
+ echo "❌ .env file not found. Please copy .env.example to .env and configure it."
26
+ exit 1
27
+ fi
28
+
29
+ # Check if NGROK_AUTHTOKEN is set
30
+ if ! grep -q "NGROK_AUTHTOKEN=" .env || grep -q "NGROK_AUTHTOKEN=$" .env; then
31
+ echo "❌ NGROK_AUTHTOKEN is not set in .env file."
32
+ echo " Please get your auth token from https://dashboard.ngrok.com/get-started/your-authtoken"
33
+ echo " and add it to your .env file: NGROK_AUTHTOKEN=your_token_here"
34
+ exit 1
35
+ fi
36
+
37
+ echo "✅ Prerequisites check passed"
38
+ echo ""
39
+
40
+ # Start Redis and ngrok services
41
+ echo "🔧 Starting Redis and ngrok services..."
42
+
43
+ # Try the simple compose file first
44
+ if docker-compose -f docker-compose.simple.yml up -d; then
45
+ echo "✅ Using simple Docker Compose configuration"
46
+ COMPOSE_FILE="docker-compose.simple.yml"
47
+ else
48
+ echo "⚠️ Falling back to development configuration"
49
+ docker-compose -f docker-compose.dev.yml up -d
50
+ COMPOSE_FILE="docker-compose.dev.yml"
51
+ fi
52
+
53
+ echo ""
54
+ echo "⏳ Waiting for services to start..."
55
+ sleep 5
56
+
57
+ # Check if Redis is running
58
+ if docker-compose -f $COMPOSE_FILE ps redis | grep -q "Up"; then
59
+ echo "✅ Redis is running on localhost:6379"
60
+ else
61
+ echo "❌ Redis failed to start"
62
+ exit 1
63
+ fi
64
+
65
+ # Check if ngrok is running
66
+ if docker-compose -f $COMPOSE_FILE ps ngrok | grep -q "Up"; then
67
+ echo "✅ ngrok is running"
68
+ echo " 📊 ngrok Web Interface: http://localhost:4040"
69
+ else
70
+ echo "❌ ngrok failed to start"
71
+ exit 1
72
+ fi
73
+
74
+ echo ""
75
+ echo "🎯 Next Steps:"
76
+ echo "=============="
77
+ echo ""
78
+ echo "1. Start your FastAPI server locally:"
79
+ echo " python -m uvicorn src.app.main:app --reload --host 0.0.0.0 --port 8000"
80
+ echo ""
81
+ echo "2. Get your public ngrok URL:"
82
+ echo " - Visit: http://localhost:4040"
83
+ echo " - Copy the HTTPS URL (e.g., https://abc123.ngrok.io)"
84
+ echo ""
85
+ echo "3. Configure Clerk webhook:"
86
+ echo " - Go to https://dashboard.clerk.com/"
87
+ echo " - Navigate to Webhooks"
88
+ echo " - Add endpoint: https://your-ngrok-url.ngrok.io/api/v1/auth/webhooks/clerk"
89
+ echo " - Copy the signing secret to CLERK_WEBHOOK_SECRET in .env"
90
+ echo ""
91
+ echo "4. Test your setup:"
92
+ echo " curl https://your-ngrok-url.ngrok.io/health"
93
+ echo ""
94
+ echo "📝 Useful Commands:"
95
+ echo "=================="
96
+ echo "• View logs: docker-compose -f $COMPOSE_FILE logs -f"
97
+ echo "• Stop services: docker-compose -f $COMPOSE_FILE down"
98
+ echo "• Restart services: docker-compose -f $COMPOSE_FILE restart"
99
+ echo "• Check Redis: redis-cli ping"
100
+ echo ""
101
+ echo "🌐 URLs:"
102
+ echo "========"
103
+ echo "• FastAPI (local): http://localhost:8000"
104
+ echo "• FastAPI Docs: http://localhost:8000/docs"
105
+ echo "• ngrok Dashboard: http://localhost:4040"
106
+ echo "• Redis: localhost:6379"
107
+ echo ""
108
+ echo "✨ Development environment is ready!"
simple_app.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from fastapi import FastAPI
4
+ from pydantic import BaseModel
5
+ from simple_config import SimpleSettings
6
+
7
+ # Create settings
8
+ settings = SimpleSettings()
9
+
10
+ # Create FastAPI app
11
+ app = FastAPI(
12
+ title=settings.app_name,
13
+ debug=settings.debug,
14
+ description="A simple FastAPI application for testing",
15
+ version="1.0.0",
16
+ )
17
+
18
+ # Pydantic models for API documentation
19
+ class MessageRequest(BaseModel):
20
+ message: str
21
+ user_id: str = None
22
+
23
+ class MessageResponse(BaseModel):
24
+ response: str
25
+ timestamp: str
26
+
27
+ @app.get("/")
28
+ async def root():
29
+ return {
30
+ "message": f"Welcome to {settings.app_name}",
31
+ "status": "running"
32
+ }
33
+
34
+ @app.get("/health")
35
+ async def health():
36
+ return {
37
+ "status": "healthy",
38
+ "app": settings.app_name,
39
+ "redis_host": settings.redis_host,
40
+ "redis_port": settings.redis_port,
41
+ }
42
+
43
+ @app.get("/config")
44
+ async def config():
45
+ return {
46
+ "origins": settings.get_allowed_origins(),
47
+ "methods": settings.get_allowed_methods(),
48
+ "clerk_configured": bool(settings.clerk_secret_key),
49
+ }
50
+
51
+ @app.post("/api/message", response_model=MessageResponse)
52
+ async def send_message(request: MessageRequest):
53
+ """Send a message and get a response."""
54
+ from datetime import datetime
55
+ return MessageResponse(
56
+ response=f"Received: {request.message}",
57
+ timestamp=datetime.now().isoformat()
58
+ )
59
+
60
+ @app.get("/api/users/{user_id}")
61
+ async def get_user(user_id: str):
62
+ """Get user information by ID."""
63
+ return {
64
+ "user_id": user_id,
65
+ "name": f"User {user_id}",
66
+ "status": "active"
67
+ }
68
+
69
+ if __name__ == "__main__":
70
+ import uvicorn
71
+ uvicorn.run(
72
+ "simple_app:app",
73
+ host=settings.host,
74
+ port=settings.port,
75
+ reload=True,
76
+ )
simple_config.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import List
4
+ from pydantic import Field
5
+ from pydantic_settings import BaseSettings
6
+
7
+
8
+ class SimpleSettings(BaseSettings):
9
+ """Simple settings for testing."""
10
+
11
+ # Application settings
12
+ app_name: str = Field(default="FastAPI Video Backend", env="APP_NAME")
13
+ debug: bool = Field(default=False, env="DEBUG")
14
+ host: str = Field(default="0.0.0.0", env="HOST")
15
+ port: int = Field(default=8000, env="PORT")
16
+
17
+ # CORS settings as strings
18
+ allowed_origins: str = Field(
19
+ default="http://localhost:3000,http://localhost:8080",
20
+ env="ALLOWED_ORIGINS"
21
+ )
22
+ allowed_methods: str = Field(
23
+ default="GET,POST,PUT,DELETE,OPTIONS",
24
+ env="ALLOWED_METHODS"
25
+ )
26
+
27
+ # Redis settings
28
+ redis_host: str = Field(default="localhost", env="REDIS_HOST")
29
+ redis_port: int = Field(default=6379, env="REDIS_PORT")
30
+
31
+ # Clerk settings
32
+ clerk_secret_key: str = Field(default="", env="CLERK_SECRET_KEY")
33
+ clerk_publishable_key: str = Field(default="", env="CLERK_PUBLISHABLE_KEY")
34
+
35
+ def get_allowed_origins(self) -> List[str]:
36
+ return [origin.strip() for origin in self.allowed_origins.split(",")]
37
+
38
+ def get_allowed_methods(self) -> List[str]:
39
+ return [method.strip() for method in self.allowed_methods.split(",")]
40
+
41
+
42
+ if __name__ == "__main__":
43
+ settings = SimpleSettings()
44
+ print("✅ Simple config loaded successfully!")
45
+ print(f"App: {settings.app_name}")
46
+ print(f"Origins: {settings.get_allowed_origins()}")
47
+ print(f"Methods: {settings.get_allowed_methods()}")
48
+ print(f"Clerk Secret: {settings.clerk_secret_key[:20]}..." if settings.clerk_secret_key else "No Clerk secret")
src/app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # FastAPI Video Generation Backend
src/app/api/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # API layer
src/app/api/dependencies.py ADDED
@@ -0,0 +1,490 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI dependencies for authentication, services, and common utilities.
3
+
4
+ This module provides reusable dependencies for FastAPI endpoints,
5
+ including authentication, service injection, and request validation.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, Optional
10
+ from fastapi import Depends, HTTPException, status, Request, Header
11
+ from redis.asyncio import Redis
12
+
13
+ from ..core.auth import verify_clerk_token, AuthenticationError, extract_bearer_token
14
+ from ..core.redis import get_redis
15
+ from ..services.video_service import VideoService
16
+ from ..services.job_service import JobService
17
+ from ..services.queue_service import QueueService
18
+ from ..services.file_service import FileService
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ async def get_current_user(
24
+ authorization: Optional[str] = Header(None),
25
+ redis_client: Redis = Depends(get_redis)
26
+ ) -> Dict[str, Any]:
27
+ """
28
+ FastAPI dependency to get current authenticated user.
29
+
30
+ Extracts and validates the Clerk session token from the Authorization header,
31
+ then returns user information and token claims.
32
+
33
+ Args:
34
+ authorization: Authorization header with Bearer token
35
+ redis_client: Redis client for caching user info
36
+
37
+ Returns:
38
+ Dict containing user information and token claims
39
+
40
+ Raises:
41
+ HTTPException: If authentication fails
42
+ """
43
+ try:
44
+ if not authorization:
45
+ raise AuthenticationError("Missing authorization header")
46
+
47
+ # Extract bearer token
48
+ token = extract_bearer_token(authorization)
49
+
50
+ # Verify token and get user info
51
+ auth_info = await verify_clerk_token(token)
52
+
53
+ # Extract email safely for logging
54
+ user_info = auth_info["user_info"]
55
+ user_id = user_info.get("id", "unknown")
56
+
57
+ # Extract email from email_addresses array if available
58
+ email = user_info.get("email")
59
+ if not email and "email_addresses" in user_info:
60
+ email_addresses = user_info.get("email_addresses", [])
61
+ if email_addresses:
62
+ primary_email_id = user_info.get("primary_email_address_id")
63
+ if primary_email_id:
64
+ primary_email = next((e for e in email_addresses if e.get("id") == primary_email_id), None)
65
+ if primary_email:
66
+ email = primary_email.get("email_address")
67
+ # Fallback to first email
68
+ if not email:
69
+ email = email_addresses[0].get("email_address")
70
+
71
+ logger.debug(
72
+ "User authenticated successfully",
73
+ user_id=user_id,
74
+ email=email or "no-email"
75
+ )
76
+
77
+ return auth_info
78
+
79
+ except AuthenticationError as e:
80
+ logger.warning(f"Authentication failed: {e}")
81
+ raise HTTPException(
82
+ status_code=status.HTTP_401_UNAUTHORIZED,
83
+ detail=str(e),
84
+ headers={"WWW-Authenticate": "Bearer"}
85
+ )
86
+ except Exception as e:
87
+ logger.error(f"Authentication error: {e}", exc_info=True)
88
+ raise HTTPException(
89
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
90
+ detail="Authentication service error"
91
+ )
92
+
93
+
94
+ async def get_optional_user(
95
+ authorization: Optional[str] = Header(None),
96
+ redis_client: Redis = Depends(get_redis)
97
+ ) -> Optional[Dict[str, Any]]:
98
+ """
99
+ FastAPI dependency to get current user if authenticated (optional).
100
+
101
+ Similar to get_current_user but returns None if no authentication
102
+ is provided instead of raising an exception.
103
+
104
+ Args:
105
+ authorization: Authorization header with Bearer token (optional)
106
+ redis_client: Redis client for caching user info
107
+
108
+ Returns:
109
+ Dict containing user information or None if not authenticated
110
+ """
111
+ try:
112
+ if not authorization:
113
+ return None
114
+
115
+ return await get_current_user(authorization, redis_client)
116
+
117
+ except HTTPException:
118
+ # Return None for optional authentication
119
+ return None
120
+ except Exception as e:
121
+ logger.error(f"Optional authentication error: {e}", exc_info=True)
122
+ return None
123
+
124
+
125
+ def get_video_service(
126
+ redis_client: Redis = Depends(get_redis)
127
+ ) -> VideoService:
128
+ """
129
+ FastAPI dependency to get VideoService instance.
130
+
131
+ Args:
132
+ redis_client: Redis client dependency
133
+
134
+ Returns:
135
+ VideoService instance
136
+ """
137
+ return VideoService(redis_client)
138
+
139
+
140
+ def get_job_service(
141
+ redis_client: Redis = Depends(get_redis)
142
+ ) -> JobService:
143
+ """
144
+ FastAPI dependency to get JobService instance.
145
+
146
+ Args:
147
+ redis_client: Redis client dependency
148
+
149
+ Returns:
150
+ JobService instance
151
+ """
152
+ return JobService(redis_client)
153
+
154
+
155
+ def get_queue_service(
156
+ redis_client: Redis = Depends(get_redis)
157
+ ) -> QueueService:
158
+ """
159
+ FastAPI dependency to get QueueService instance.
160
+
161
+ Args:
162
+ redis_client: Redis client dependency
163
+
164
+ Returns:
165
+ QueueService instance
166
+ """
167
+ return QueueService(redis_client)
168
+
169
+
170
+ def get_file_service(
171
+ redis_client: Redis = Depends(get_redis)
172
+ ) -> FileService:
173
+ """
174
+ FastAPI dependency to get FileService instance.
175
+
176
+ Args:
177
+ redis_client: Redis client dependency
178
+
179
+ Returns:
180
+ FileService instance
181
+ """
182
+ return FileService(redis_client)
183
+
184
+
185
+ class PaginationParams:
186
+ """
187
+ Pagination parameters for list endpoints.
188
+ """
189
+
190
+ def __init__(
191
+ self,
192
+ page: int = 1,
193
+ items_per_page: int = 10,
194
+ max_items_per_page: int = 100
195
+ ):
196
+ # Validate page number
197
+ if page < 1:
198
+ raise HTTPException(
199
+ status_code=status.HTTP_400_BAD_REQUEST,
200
+ detail="Page number must be greater than 0"
201
+ )
202
+
203
+ # Validate items per page
204
+ if items_per_page < 1:
205
+ raise HTTPException(
206
+ status_code=status.HTTP_400_BAD_REQUEST,
207
+ detail="Items per page must be greater than 0"
208
+ )
209
+
210
+ if items_per_page > max_items_per_page:
211
+ raise HTTPException(
212
+ status_code=status.HTTP_400_BAD_REQUEST,
213
+ detail=f"Items per page cannot exceed {max_items_per_page}"
214
+ )
215
+
216
+ self.page = page
217
+ self.items_per_page = items_per_page
218
+ self.offset = (page - 1) * items_per_page
219
+
220
+ @property
221
+ def limit(self) -> int:
222
+ """Get limit for database queries."""
223
+ return self.items_per_page
224
+
225
+
226
+ def get_pagination_params(
227
+ page: int = 1,
228
+ items_per_page: int = 10
229
+ ) -> PaginationParams:
230
+ """
231
+ FastAPI dependency to get pagination parameters.
232
+
233
+ Args:
234
+ page: Page number (1-based)
235
+ items_per_page: Number of items per page
236
+
237
+ Returns:
238
+ PaginationParams instance
239
+
240
+ Raises:
241
+ HTTPException: If parameters are invalid
242
+ """
243
+ return PaginationParams(page=page, items_per_page=items_per_page)
244
+
245
+
246
+ class JobFilters:
247
+ """
248
+ Filtering parameters for job list endpoints.
249
+ """
250
+
251
+ def __init__(
252
+ self,
253
+ status: Optional[str] = None,
254
+ job_type: Optional[str] = None,
255
+ priority: Optional[str] = None,
256
+ created_after: Optional[str] = None,
257
+ created_before: Optional[str] = None
258
+ ):
259
+ self.status = status
260
+ self.job_type = job_type
261
+ self.priority = priority
262
+ self.created_after = created_after
263
+ self.created_before = created_before
264
+
265
+ def to_dict(self) -> Dict[str, Any]:
266
+ """Convert filters to dictionary for service layer."""
267
+ return {
268
+ k: v for k, v in {
269
+ "status": self.status,
270
+ "job_type": self.job_type,
271
+ "priority": self.priority,
272
+ "created_after": self.created_after,
273
+ "created_before": self.created_before
274
+ }.items() if v is not None
275
+ }
276
+
277
+
278
+ def get_job_filters(
279
+ status: Optional[str] = None,
280
+ job_type: Optional[str] = None,
281
+ priority: Optional[str] = None,
282
+ created_after: Optional[str] = None,
283
+ created_before: Optional[str] = None
284
+ ) -> JobFilters:
285
+ """
286
+ FastAPI dependency to get job filtering parameters.
287
+
288
+ Args:
289
+ status: Filter by job status
290
+ job_type: Filter by job type
291
+ priority: Filter by job priority
292
+ created_after: Filter jobs created after this date (ISO format)
293
+ created_before: Filter jobs created before this date (ISO format)
294
+
295
+ Returns:
296
+ JobFilters instance
297
+ """
298
+ return JobFilters(
299
+ status=status,
300
+ job_type=job_type,
301
+ priority=priority,
302
+ created_after=created_after,
303
+ created_before=created_before
304
+ )
305
+
306
+
307
+ def validate_job_ownership(
308
+ job_user_id: str,
309
+ current_user: Dict[str, Any]
310
+ ) -> None:
311
+ """
312
+ Validate that the current user owns the specified job.
313
+
314
+ Args:
315
+ job_user_id: User ID from the job
316
+ current_user: Current authenticated user
317
+
318
+ Raises:
319
+ HTTPException: If user doesn't own the job
320
+ """
321
+ if job_user_id != current_user["user_info"]["id"]:
322
+ raise HTTPException(
323
+ status_code=status.HTTP_403_FORBIDDEN,
324
+ detail="Access denied: You don't own this resource"
325
+ )
326
+
327
+
328
+ def validate_request_size(
329
+ request: Request,
330
+ max_size_mb: int = 10
331
+ ) -> None:
332
+ """
333
+ Validate request content size.
334
+
335
+ Args:
336
+ request: FastAPI request object
337
+ max_size_mb: Maximum allowed size in MB
338
+
339
+ Raises:
340
+ HTTPException: If request is too large
341
+ """
342
+ content_length = request.headers.get("content-length")
343
+ if content_length:
344
+ size_mb = int(content_length) / (1024 * 1024)
345
+ if size_mb > max_size_mb:
346
+ raise HTTPException(
347
+ status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
348
+ detail=f"Request too large. Maximum size: {max_size_mb}MB"
349
+ )
350
+
351
+
352
+ async def rate_limit_check(
353
+ current_user: Dict[str, Any],
354
+ redis_client: Redis,
355
+ operation: str,
356
+ limit: int = 10,
357
+ window_seconds: int = 60
358
+ ) -> None:
359
+ """
360
+ Check rate limits for user operations.
361
+
362
+ Args:
363
+ current_user: Current authenticated user
364
+ redis_client: Redis client for rate limit storage
365
+ operation: Operation name for rate limiting
366
+ limit: Maximum operations per window
367
+ window_seconds: Time window in seconds
368
+
369
+ Raises:
370
+ HTTPException: If rate limit exceeded
371
+ """
372
+ user_id = current_user["user_info"]["id"]
373
+ key = f"rate_limit:{user_id}:{operation}"
374
+
375
+ try:
376
+ # Get current count
377
+ current_count = await redis_client.get(key)
378
+ current_count = int(current_count) if current_count else 0
379
+
380
+ if current_count >= limit:
381
+ raise HTTPException(
382
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
383
+ detail=f"Rate limit exceeded for {operation}. Try again later.",
384
+ headers={"Retry-After": str(window_seconds)}
385
+ )
386
+
387
+ # Increment counter
388
+ pipe = redis_client.pipeline()
389
+ pipe.incr(key)
390
+ pipe.expire(key, window_seconds)
391
+ await pipe.execute()
392
+
393
+ except HTTPException:
394
+ raise
395
+ except Exception as e:
396
+ logger.error(f"Rate limit check failed: {e}", exc_info=True)
397
+ # Don't block requests if rate limiting fails
398
+ pass
399
+
400
+
401
+ # Additional authentication dependencies for compatibility
402
+ async def get_authenticated_user(
403
+ authorization: Optional[str] = Header(None),
404
+ redis_client: Redis = Depends(get_redis)
405
+ ) -> Dict[str, Any]:
406
+ """
407
+ Alias for get_current_user for backward compatibility.
408
+
409
+ Returns the user_info portion of the authentication data.
410
+ """
411
+ auth_info = await get_current_user(authorization, redis_client)
412
+ return auth_info["user_info"]
413
+
414
+
415
+ async def get_authenticated_user_id(
416
+ authorization: Optional[str] = Header(None),
417
+ redis_client: Redis = Depends(get_redis)
418
+ ) -> str:
419
+ """
420
+ Get just the user ID from authentication.
421
+
422
+ Returns:
423
+ str: The authenticated user's ID
424
+ """
425
+ auth_info = await get_current_user(authorization, redis_client)
426
+ return auth_info["user_info"]["id"]
427
+
428
+
429
+ async def get_verified_user(
430
+ authorization: Optional[str] = Header(None),
431
+ redis_client: Redis = Depends(get_redis)
432
+ ) -> Dict[str, Any]:
433
+ """
434
+ Get authenticated user that has verified email.
435
+
436
+ Returns:
437
+ Dict containing verified user information
438
+
439
+ Raises:
440
+ HTTPException: If user is not verified
441
+ """
442
+ auth_info = await get_current_user(authorization, redis_client)
443
+ user_info = auth_info["user_info"]
444
+
445
+ # Check email verification status from email_addresses array
446
+ email_verified = user_info.get("email_verified", False)
447
+
448
+ # If not directly available, check in email_addresses array
449
+ if not email_verified and "email_addresses" in user_info:
450
+ email_addresses = user_info.get("email_addresses", [])
451
+ if email_addresses:
452
+ primary_email_id = user_info.get("primary_email_address_id")
453
+ if primary_email_id:
454
+ primary_email = next((e for e in email_addresses if e.get("id") == primary_email_id), None)
455
+ if primary_email:
456
+ verification = primary_email.get("verification", {})
457
+ email_verified = verification.get("status") == "verified"
458
+
459
+ # Fallback to first email verification
460
+ if not email_verified and email_addresses:
461
+ first_email = email_addresses[0]
462
+ verification = first_email.get("verification", {})
463
+ email_verified = verification.get("status") == "verified"
464
+
465
+ if not email_verified:
466
+ raise HTTPException(
467
+ status_code=status.HTTP_403_FORBIDDEN,
468
+ detail="Email verification required"
469
+ )
470
+
471
+ return user_info
472
+
473
+
474
+ async def get_request_context(request: Request) -> Dict[str, Any]:
475
+ """
476
+ Get request context information.
477
+
478
+ Args:
479
+ request: FastAPI request object
480
+
481
+ Returns:
482
+ Dict containing request context
483
+ """
484
+ return {
485
+ "path": str(request.url.path),
486
+ "method": request.method,
487
+ "client_ip": request.client.host if request.client else "unknown",
488
+ "user_agent": request.headers.get("user-agent", "unknown"),
489
+ "timestamp": logger.info("Request context retrieved")
490
+ }
src/app/api/v1/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # API version 1
src/app/api/v1/auth.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication endpoints for testing Clerk integration.
3
+
4
+ This module provides endpoints for testing authentication functionality
5
+ and retrieving user information.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, Optional
10
+ from datetime import datetime
11
+ from fastapi import APIRouter, Depends, HTTPException, status
12
+ from fastapi.responses import JSONResponse
13
+
14
+ from ...models.user import UserProfile, UserPermissions, UserRole, AuthResponse
15
+ from ...api.dependencies import (
16
+ get_authenticated_user,
17
+ get_authenticated_user_id,
18
+ get_verified_user,
19
+ get_optional_user,
20
+ get_request_context
21
+ )
22
+ from ...core.auth import clerk_manager
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ router = APIRouter(prefix="/auth", tags=["Authentication"])
27
+
28
+
29
+ @router.get("/me", response_model=UserProfile)
30
+ async def get_current_user_profile(
31
+ user_info: Dict[str, Any] = Depends(get_authenticated_user)
32
+ ) -> UserProfile:
33
+ """
34
+ Get current authenticated user profile.
35
+
36
+ Returns:
37
+ UserProfile: Current user's profile information
38
+ """
39
+ try:
40
+ # Handle potentially missing or invalid datetime fields from Clerk
41
+ def parse_datetime_field(value) -> Optional[datetime]:
42
+ """Parse datetime field that might be None, timestamp (ms), or string."""
43
+ if value is None:
44
+ return None
45
+ if isinstance(value, datetime):
46
+ return value
47
+ if isinstance(value, (int, float)):
48
+ try:
49
+ # Convert milliseconds timestamp to datetime
50
+ return datetime.fromtimestamp(value / 1000)
51
+ except (ValueError, OSError):
52
+ return None
53
+ if isinstance(value, str):
54
+ try:
55
+ # Handle ISO format with Z suffix
56
+ return datetime.fromisoformat(value.replace('Z', '+00:00'))
57
+ except (ValueError, AttributeError):
58
+ return None
59
+ return None
60
+
61
+ # Extract actual user data from nested structure
62
+ # Handle both nested ('user_info') and direct user data structures
63
+ if isinstance(user_info, dict) and 'user_info' in user_info:
64
+ # Nested structure from get_current_user
65
+ actual_user_info = user_info['user_info']
66
+ else:
67
+ # Direct user_info from get_verified_user
68
+ actual_user_info = user_info
69
+
70
+ # Debug logging to understand the Clerk API response structure
71
+ logger.debug(f"Processing user profile for user_id: {actual_user_info.get('id', 'unknown')}")
72
+ if "email_addresses" in actual_user_info:
73
+ logger.debug(f"Email addresses found: {len(actual_user_info['email_addresses'])} entries")
74
+
75
+ # Validate we have the required user ID
76
+ if not actual_user_info.get('id'):
77
+ logger.error(f"No user ID found in user_info: {user_info}")
78
+ raise HTTPException(
79
+ status_code=status.HTTP_401_UNAUTHORIZED,
80
+ detail="Invalid user information"
81
+ )
82
+
83
+ # Extract email from email_addresses array (Clerk standard structure)
84
+ email = actual_user_info.get("email")
85
+ email_verified = actual_user_info.get("email_verified", False)
86
+
87
+ # Look for email in email_addresses array if not found directly
88
+ email_addresses = actual_user_info.get("email_addresses", [])
89
+ if not email and email_addresses:
90
+ # Get primary email or first verified email
91
+ primary_email_id = actual_user_info.get("primary_email_address_id")
92
+ if primary_email_id:
93
+ primary_email = next((e for e in email_addresses if e.get("id") == primary_email_id), None)
94
+ if primary_email:
95
+ email = primary_email.get("email_address")
96
+ # Check verification status from the email object
97
+ verification = primary_email.get("verification", {})
98
+ email_verified = verification.get("status") == "verified"
99
+
100
+ # Fallback to first email if no primary found
101
+ if not email and email_addresses:
102
+ first_email = email_addresses[0]
103
+ email = first_email.get("email_address")
104
+ verification = first_email.get("verification", {})
105
+ email_verified = verification.get("status") == "verified"
106
+
107
+ # Build full name with proper fallbacks
108
+ first_name = actual_user_info.get('first_name') or ''
109
+ last_name = actual_user_info.get('last_name') or ''
110
+ full_name = f"{first_name} {last_name}".strip()
111
+
112
+ # Fallback chain for full_name
113
+ if not full_name:
114
+ full_name = actual_user_info.get("username") or email or "Unknown User"
115
+
116
+ # Convert Clerk user info to UserProfile
117
+ user_profile = UserProfile(
118
+ id=actual_user_info["id"],
119
+ username=actual_user_info.get("username"),
120
+ full_name=full_name,
121
+ email=email,
122
+ image_url=actual_user_info.get("image_url"),
123
+ email_verified=email_verified,
124
+ created_at=parse_datetime_field(actual_user_info.get("created_at")),
125
+ last_sign_in_at=parse_datetime_field(actual_user_info.get("last_sign_in_at"))
126
+ )
127
+
128
+ logger.info(f"User profile retrieved successfully for user {actual_user_info['id']}: {user_profile.dict()}")
129
+ return user_profile
130
+
131
+ except Exception as e:
132
+ logger.error(f"Failed to get user profile: {e}", exc_info=True)
133
+ raise HTTPException(
134
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
135
+ detail="Failed to retrieve user profile"
136
+ )
137
+
138
+
139
+ @router.get("/permissions")
140
+ async def get_user_permissions(
141
+ user_info: Dict[str, Any] = Depends(get_verified_user)
142
+ ) -> UserPermissions:
143
+ """
144
+ Get current user's permissions.
145
+
146
+ Returns:
147
+ UserPermissions: User's permissions and access levels
148
+ """
149
+ try:
150
+ # Extract actual user data from nested structure
151
+ if isinstance(user_info, dict) and 'user_info' in user_info:
152
+ # Nested structure from get_current_user
153
+ actual_user_info = user_info['user_info']
154
+ else:
155
+ # Direct user_info from get_verified_user
156
+ actual_user_info = user_info
157
+
158
+ # Validate user_info structure
159
+ if not actual_user_info or not actual_user_info.get("id"):
160
+ logger.error(f"Invalid user_info structure: {user_info}")
161
+ raise HTTPException(
162
+ status_code=status.HTTP_401_UNAUTHORIZED,
163
+ detail="Invalid user information"
164
+ )
165
+
166
+ # For now, assign basic user role
167
+ # In a real implementation, you would check Clerk metadata or roles
168
+ user_role = UserRole.USER
169
+
170
+ # Check if user is admin (placeholder logic)
171
+ # This would be based on Clerk metadata or roles
172
+
173
+ # Extract email from email_addresses array (Clerk standard structure)
174
+ user_email = actual_user_info.get("email")
175
+
176
+ # Look for email in email_addresses array if not found directly
177
+ email_addresses = actual_user_info.get("email_addresses", [])
178
+ if not user_email and email_addresses:
179
+ # Get primary email or first email
180
+ primary_email_id = actual_user_info.get("primary_email_address_id")
181
+ if primary_email_id:
182
+ primary_email = next((e for e in email_addresses if e.get("id") == primary_email_id), None)
183
+ if primary_email:
184
+ user_email = primary_email.get("email_address")
185
+
186
+ # Fallback to first email if no primary found
187
+ if not user_email and email_addresses:
188
+ first_email = email_addresses[0]
189
+ user_email = first_email.get("email_address")
190
+
191
+ if user_email and user_email.endswith("@admin.com"):
192
+ user_role = UserRole.ADMIN
193
+
194
+ permissions = UserPermissions.from_user_role(
195
+ user_id=actual_user_info["id"],
196
+ role=user_role
197
+ )
198
+
199
+ logger.info(f"Permissions retrieved for user {actual_user_info['id']}: {user_role}")
200
+ return permissions
201
+
202
+ except Exception as e:
203
+ logger.error(f"Failed to get user permissions: {e}")
204
+ raise HTTPException(
205
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
206
+ detail="Failed to retrieve user permissions"
207
+ )
208
+
209
+
210
+ @router.get("/status")
211
+ async def get_auth_status(
212
+ user_info: Dict[str, Any] = Depends(get_optional_user),
213
+ request_context: Dict[str, Any] = Depends(get_request_context)
214
+ ) -> Dict[str, Any]:
215
+ """
216
+ Get authentication status for current request.
217
+
218
+ This endpoint works with or without authentication to show auth status.
219
+
220
+ Returns:
221
+ Dict containing authentication status and user info if authenticated
222
+ """
223
+ try:
224
+ # Debug logging to understand the structure
225
+ logger.info(f"Auth status check - user_info type: {type(user_info)}, content: {user_info}")
226
+ logger.info(f"Auth status check - request_context: {request_context}")
227
+
228
+ # Extract actual user data from nested structure
229
+ if isinstance(user_info, dict) and 'user_info' in user_info:
230
+ # Nested structure from get_current_user
231
+ actual_user_info = user_info['user_info']
232
+ elif user_info:
233
+ # Direct user_info from get_verified_user
234
+ actual_user_info = user_info
235
+ else:
236
+ actual_user_info = {}
237
+
238
+ if user_info and actual_user_info.get("id"):
239
+ # Extract email from email_addresses array (Clerk standard structure)
240
+ email = actual_user_info.get("email")
241
+ email_verified = actual_user_info.get("email_verified", False)
242
+
243
+ # Look for email in email_addresses array if not found directly
244
+ email_addresses = actual_user_info.get("email_addresses", [])
245
+ if not email and email_addresses:
246
+ # Get primary email or first email
247
+ primary_email_id = actual_user_info.get("primary_email_address_id")
248
+ if primary_email_id:
249
+ primary_email = next((e for e in email_addresses if e.get("id") == primary_email_id), None)
250
+ if primary_email:
251
+ email = primary_email.get("email_address")
252
+ # Check verification status from the email object
253
+ verification = primary_email.get("verification", {})
254
+ email_verified = verification.get("status") == "verified"
255
+
256
+ # Fallback to first email if no primary found
257
+ if not email and email_addresses:
258
+ first_email = email_addresses[0]
259
+ email = first_email.get("email_address")
260
+ verification = first_email.get("verification", {})
261
+ email_verified = verification.get("status") == "verified"
262
+
263
+ return {
264
+ "authenticated": True,
265
+ "user_id": actual_user_info.get("id"),
266
+ "email": email,
267
+ "email_verified": email_verified,
268
+ "username": actual_user_info.get("username"),
269
+ "request_context": {
270
+ "path": request_context.get("path", "unknown"),
271
+ "method": request_context.get("method", "unknown"),
272
+ "client_ip": request_context.get("client_ip", "unknown")
273
+ }
274
+ }
275
+ else:
276
+ return {
277
+ "authenticated": False,
278
+ "message": "No authentication provided",
279
+ "request_context": {
280
+ "path": request_context.get("path", "unknown"),
281
+ "method": request_context.get("method", "unknown"),
282
+ "client_ip": request_context.get("client_ip", "unknown")
283
+ }
284
+ }
285
+
286
+ except Exception as e:
287
+ logger.error(f"Failed to get auth status: {e}")
288
+ return {
289
+ "authenticated": False,
290
+ "error": f"Failed to determine authentication status: {str(e)}",
291
+ "request_context": {
292
+ "path": request_context.get("path", "unknown") if request_context else "unknown",
293
+ "method": request_context.get("method", "unknown") if request_context else "unknown",
294
+ "client_ip": request_context.get("client_ip", "unknown") if request_context else "unknown"
295
+ }
296
+ }
297
+
298
+
299
+ @router.post("/verify")
300
+ async def verify_token(
301
+ user_info: Dict[str, Any] = Depends(get_verified_user)
302
+ ) -> Dict[str, Any]:
303
+ """
304
+ Verify authentication token and return user information.
305
+
306
+ This endpoint requires a verified user (email verified).
307
+
308
+ Returns:
309
+ Dict containing verification status and user information
310
+ """
311
+ try:
312
+ return {
313
+ "verified": True,
314
+ "user_id": user_info["id"],
315
+ "email": user_info.get("email"),
316
+ "email_verified": user_info.get("email_verified", False),
317
+ "message": "Token verified successfully"
318
+ }
319
+
320
+ except Exception as e:
321
+ logger.error(f"Token verification failed: {e}")
322
+ raise HTTPException(
323
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
324
+ detail="Token verification failed"
325
+ )
326
+
327
+
328
+ @router.get("/health")
329
+ async def auth_health_check() -> Dict[str, Any]:
330
+ """
331
+ Check authentication service health.
332
+
333
+ Returns:
334
+ Dict containing authentication service health status
335
+ """
336
+ try:
337
+ # Check Clerk manager health
338
+ clerk_health = clerk_manager.health_check()
339
+
340
+ return {
341
+ "status": "healthy" if clerk_health["status"] == "healthy" else "unhealthy",
342
+ "clerk": clerk_health,
343
+ "message": "Authentication service health check completed"
344
+ }
345
+
346
+ except Exception as e:
347
+ logger.error(f"Auth health check failed: {e}")
348
+ return {
349
+ "status": "unhealthy",
350
+ "error": str(e),
351
+ "message": "Authentication service health check failed"
352
+ }
353
+
354
+
355
+ @router.get("/test-protected")
356
+ async def test_protected_endpoint(
357
+ user_id: str = Depends(get_authenticated_user_id)
358
+ ) -> Dict[str, Any]:
359
+ """
360
+ Test endpoint that requires authentication.
361
+
362
+ Returns:
363
+ Dict containing success message and user ID
364
+ """
365
+ logger.info(f"Protected endpoint accessed by user {user_id}")
366
+
367
+ return {
368
+ "message": "Successfully accessed protected endpoint",
369
+ "user_id": user_id,
370
+ "timestamp": datetime.utcnow().isoformat() + "Z"
371
+ }
372
+
373
+
374
+ @router.get("/test-verified")
375
+ async def test_verified_endpoint(
376
+ user_info: Dict[str, Any] = Depends(get_verified_user)
377
+ ) -> Dict[str, Any]:
378
+ """
379
+ Test endpoint that requires verified user.
380
+
381
+ Returns:
382
+ Dict containing success message and user information
383
+ """
384
+ logger.info(f"Verified endpoint accessed by user {user_info['id']}")
385
+
386
+ return {
387
+ "message": "Successfully accessed verified user endpoint",
388
+ "user_id": user_info["id"],
389
+ "email": user_info.get("email"),
390
+ "email_verified": user_info.get("email_verified", False),
391
+ "timestamp": datetime.utcnow().isoformat() + "Z"
392
+ }
src/app/api/v1/files.py ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ File management API endpoints.
3
+
4
+ This module implements REST API endpoints for file operations including
5
+ upload, download, metadata retrieval, and file management.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional, List, Dict, Any
10
+ from pathlib import Path
11
+
12
+ from fastapi import (
13
+ APIRouter, Depends, HTTPException, status, UploadFile, File, Form,
14
+ Request, Response, Query
15
+ )
16
+ from fastapi.responses import StreamingResponse, FileResponse
17
+ from redis.asyncio import Redis
18
+ import aiofiles
19
+
20
+ from ...core.redis import get_redis
21
+ from ...core.auth import verify_clerk_token
22
+ from ...models.file import (
23
+ FileUploadRequest, FileUploadResponse, FileMetadataResponse,
24
+ FileListResponse, FileStatsResponse, FileCleanupRequest,
25
+ FileCleanupResponse, FileType, FileBatchUploadRequest,
26
+ FileBatchUploadResponse, FileSearchRequest
27
+ )
28
+ from ...models.common import PaginatedResponse
29
+ from ...services.file_service import FileService
30
+ from ...api.dependencies import get_current_user, get_file_service
31
+ from ...utils.file_utils import FileMetadata
32
+
33
+ logger = logging.getLogger(__name__)
34
+ router = APIRouter(prefix="/files", tags=["files"])
35
+
36
+
37
+ @router.post("/upload", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED)
38
+ async def upload_file(
39
+ file: UploadFile = File(..., description="File to upload"),
40
+ file_type: Optional[FileType] = Form(None, description="File type category"),
41
+ subdirectory: Optional[str] = Form(None, description="Optional subdirectory"),
42
+ description: Optional[str] = Form(None, description="File description"),
43
+ current_user: Dict[str, Any] = Depends(get_current_user),
44
+ file_service: FileService = Depends(get_file_service)
45
+ ) -> FileUploadResponse:
46
+ """
47
+ Upload a single file.
48
+
49
+ This endpoint accepts a file upload with optional metadata and stores it
50
+ securely with proper validation and access controls.
51
+
52
+ Args:
53
+ file: File to upload
54
+ file_type: Optional file type category
55
+ subdirectory: Optional subdirectory for organization
56
+ description: Optional file description
57
+ current_user: Authenticated user information
58
+ file_service: File service dependency
59
+
60
+ Returns:
61
+ FileUploadResponse with upload details
62
+
63
+ Raises:
64
+ HTTPException: If upload fails or validation errors occur
65
+ """
66
+ try:
67
+ logger.info(
68
+ "Starting file upload",
69
+ filename=file.filename,
70
+ content_type=file.content_type,
71
+ user_id=current_user["user_info"]["id"],
72
+ file_type=file_type
73
+ )
74
+
75
+ # Upload file
76
+ file_metadata = await file_service.upload_file(
77
+ file=file,
78
+ user_id=current_user["user_info"]["id"],
79
+ file_type=file_type.value if file_type else None,
80
+ subdirectory=subdirectory
81
+ )
82
+
83
+ # Generate download URL
84
+ download_url = await file_service.generate_download_url(
85
+ file_id=Path(file_metadata.filename).stem,
86
+ user_id=current_user["user_info"]["id"]
87
+ )
88
+
89
+ logger.info(
90
+ "File uploaded successfully",
91
+ file_id=Path(file_metadata.filename).stem,
92
+ filename=file_metadata.filename,
93
+ file_size=file_metadata.file_size,
94
+ user_id=current_user["user_info"]["id"]
95
+ )
96
+
97
+ return FileUploadResponse(
98
+ file_id=Path(file_metadata.filename).stem,
99
+ filename=file_metadata.filename,
100
+ file_type=FileType(file_metadata.file_type),
101
+ file_size=file_metadata.file_size,
102
+ download_url=download_url or f"/api/v1/files/{Path(file_metadata.filename).stem}/download",
103
+ created_at=file_metadata.created_at
104
+ )
105
+
106
+ except HTTPException:
107
+ raise
108
+ except Exception as e:
109
+ logger.error(
110
+ "Failed to upload file",
111
+ filename=file.filename,
112
+ user_id=current_user["user_info"]["id"],
113
+ error=str(e),
114
+ exc_info=True
115
+ )
116
+ raise HTTPException(
117
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
118
+ detail=f"Failed to upload file: {str(e)}"
119
+ )
120
+
121
+
122
+ @router.post("/batch-upload", response_model=FileBatchUploadResponse)
123
+ async def batch_upload_files(
124
+ files: List[UploadFile] = File(..., description="Files to upload"),
125
+ file_type: Optional[FileType] = Form(None, description="File type for all files"),
126
+ subdirectory: Optional[str] = Form(None, description="Subdirectory for all files"),
127
+ description: Optional[str] = Form(None, description="Description for all files"),
128
+ current_user: Dict[str, Any] = Depends(get_current_user),
129
+ file_service: FileService = Depends(get_file_service)
130
+ ) -> FileBatchUploadResponse:
131
+ """
132
+ Upload multiple files in batch.
133
+
134
+ Args:
135
+ files: List of files to upload
136
+ file_type: Optional file type for all files
137
+ subdirectory: Optional subdirectory for all files
138
+ description: Optional description for all files
139
+ current_user: Authenticated user information
140
+ file_service: File service dependency
141
+
142
+ Returns:
143
+ FileBatchUploadResponse with batch upload results
144
+ """
145
+ try:
146
+ if len(files) > 50:
147
+ raise HTTPException(
148
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
149
+ detail="Maximum 50 files allowed per batch"
150
+ )
151
+
152
+ successful_uploads = []
153
+ failed_uploads = []
154
+
155
+ logger.info(
156
+ "Starting batch file upload",
157
+ file_count=len(files),
158
+ user_id=current_user["user_info"]["id"]
159
+ )
160
+
161
+ for file in files:
162
+ try:
163
+ # Upload individual file
164
+ file_metadata = await file_service.upload_file(
165
+ file=file,
166
+ user_id=current_user["user_info"]["id"],
167
+ file_type=file_type.value if file_type else None,
168
+ subdirectory=subdirectory
169
+ )
170
+
171
+ # Generate download URL
172
+ download_url = await file_service.generate_download_url(
173
+ file_id=Path(file_metadata.filename).stem,
174
+ user_id=current_user["user_info"]["id"]
175
+ )
176
+
177
+ successful_uploads.append(FileUploadResponse(
178
+ file_id=Path(file_metadata.filename).stem,
179
+ filename=file_metadata.filename,
180
+ file_type=FileType(file_metadata.file_type),
181
+ file_size=file_metadata.file_size,
182
+ download_url=download_url or f"/api/v1/files/{Path(file_metadata.filename).stem}/download",
183
+ created_at=file_metadata.created_at
184
+ ))
185
+
186
+ except Exception as e:
187
+ failed_uploads.append({
188
+ "filename": file.filename,
189
+ "error": str(e),
190
+ "error_type": type(e).__name__
191
+ })
192
+
193
+ logger.info(
194
+ "Batch upload completed",
195
+ total_files=len(files),
196
+ successful=len(successful_uploads),
197
+ failed=len(failed_uploads),
198
+ user_id=current_user["user_info"]["id"]
199
+ )
200
+
201
+ return FileBatchUploadResponse(
202
+ successful_uploads=successful_uploads,
203
+ failed_uploads=failed_uploads,
204
+ total_files=len(files),
205
+ success_count=len(successful_uploads),
206
+ failure_count=len(failed_uploads)
207
+ )
208
+
209
+ except HTTPException:
210
+ raise
211
+ except Exception as e:
212
+ logger.error(f"Batch upload failed: {e}", exc_info=True)
213
+ raise HTTPException(
214
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
215
+ detail=f"Batch upload failed: {str(e)}"
216
+ )
217
+
218
+
219
+ @router.get("/{file_id}/download")
220
+ async def download_file(
221
+ file_id: str,
222
+ request: Request,
223
+ inline: bool = Query(False, description="Serve file inline instead of attachment"),
224
+ current_user: Dict[str, Any] = Depends(get_current_user),
225
+ file_service: FileService = Depends(get_file_service)
226
+ ) -> StreamingResponse:
227
+ """
228
+ Download a file by ID with range request support and caching.
229
+
230
+ Args:
231
+ file_id: File identifier
232
+ request: FastAPI request object
233
+ inline: Whether to serve inline or as attachment
234
+ current_user: Authenticated user information
235
+ file_service: File service dependency
236
+
237
+ Returns:
238
+ StreamingResponse with file content
239
+
240
+ Raises:
241
+ HTTPException: If file not found or access denied
242
+ """
243
+ try:
244
+ # Get file metadata
245
+ file_metadata = await file_service.get_file_metadata(
246
+ file_id=file_id,
247
+ user_id=current_user["user_info"]["id"]
248
+ )
249
+
250
+ if not file_metadata:
251
+ raise HTTPException(
252
+ status_code=status.HTTP_404_NOT_FOUND,
253
+ detail="File not found"
254
+ )
255
+
256
+ logger.info(
257
+ "Serving file download",
258
+ file_id=file_id,
259
+ filename=file_metadata.filename,
260
+ user_id=current_user["user_info"]["id"],
261
+ inline=inline
262
+ )
263
+
264
+ # Track file access
265
+ from ...utils.file_cache import track_file_access
266
+ await track_file_access(
267
+ file_id=file_id,
268
+ user_id=current_user["user_info"]["id"],
269
+ access_type="download"
270
+ )
271
+
272
+ # Use enhanced file serving with user context
273
+ from ...utils.file_serving import serve_file_secure
274
+
275
+ return await serve_file_secure(
276
+ file_path=file_metadata.file_path,
277
+ file_type=file_metadata.file_type,
278
+ original_filename=file_metadata.original_filename,
279
+ request=request,
280
+ inline=inline,
281
+ enable_caching=True,
282
+ user_id=current_user["user_info"]["id"]
283
+ )
284
+
285
+ except HTTPException:
286
+ raise
287
+ except Exception as e:
288
+ logger.error(f"Failed to download file: {e}", exc_info=True)
289
+ raise HTTPException(
290
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
291
+ detail=f"Failed to download file: {str(e)}"
292
+ )
293
+
294
+
295
+ @router.get("/{file_id}/metadata", response_model=FileMetadataResponse)
296
+ async def get_file_metadata(
297
+ file_id: str,
298
+ current_user: Dict[str, Any] = Depends(get_current_user),
299
+ file_service: FileService = Depends(get_file_service)
300
+ ) -> FileMetadataResponse:
301
+ """
302
+ Get file metadata by ID.
303
+
304
+ Args:
305
+ file_id: File identifier
306
+ current_user: Authenticated user information
307
+ file_service: File service dependency
308
+
309
+ Returns:
310
+ FileMetadataResponse with file metadata
311
+
312
+ Raises:
313
+ HTTPException: If file not found or access denied
314
+ """
315
+ try:
316
+ file_metadata = await file_service.get_file_metadata(
317
+ file_id=file_id,
318
+ user_id=current_user["user_info"]["id"]
319
+ )
320
+
321
+ if not file_metadata:
322
+ raise HTTPException(
323
+ status_code=status.HTTP_404_NOT_FOUND,
324
+ detail="File not found"
325
+ )
326
+
327
+ # Generate download URL
328
+ download_url = await file_service.generate_download_url(
329
+ file_id=file_id,
330
+ user_id=current_user["user_info"]["id"]
331
+ )
332
+
333
+ return FileMetadataResponse(
334
+ id=file_id,
335
+ filename=file_metadata.filename,
336
+ original_filename=file_metadata.original_filename,
337
+ file_type=FileType(file_metadata.file_type),
338
+ mime_type=file_metadata.mime_type,
339
+ file_size=file_metadata.file_size,
340
+ checksum=file_metadata.checksum,
341
+ created_at=file_metadata.created_at,
342
+ download_url=download_url,
343
+ metadata=file_metadata.metadata
344
+ )
345
+
346
+ except HTTPException:
347
+ raise
348
+ except Exception as e:
349
+ logger.error(f"Failed to get file metadata: {e}", exc_info=True)
350
+ raise HTTPException(
351
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
352
+ detail=f"Failed to get file metadata: {str(e)}"
353
+ )
354
+
355
+
356
+ @router.get("", response_model=FileListResponse)
357
+ async def list_files(
358
+ file_type: Optional[FileType] = Query(None, description="Filter by file type"),
359
+ page: int = Query(1, ge=1, description="Page number"),
360
+ items_per_page: int = Query(20, ge=1, le=100, description="Items per page"),
361
+ current_user: Dict[str, Any] = Depends(get_current_user),
362
+ file_service: FileService = Depends(get_file_service)
363
+ ) -> FileListResponse:
364
+ """
365
+ List user's files with pagination and filtering.
366
+
367
+ Args:
368
+ file_type: Optional file type filter
369
+ page: Page number
370
+ items_per_page: Items per page
371
+ current_user: Authenticated user information
372
+ file_service: File service dependency
373
+
374
+ Returns:
375
+ FileListResponse with paginated file list
376
+ """
377
+ try:
378
+ # Get paginated files
379
+ paginated_response = await file_service.get_user_files(
380
+ user_id=current_user["user_info"]["id"],
381
+ file_type=file_type.value if file_type else None,
382
+ page=page,
383
+ items_per_page=items_per_page
384
+ )
385
+
386
+ # Convert to response format
387
+ files = []
388
+ for file_metadata in paginated_response.data:
389
+ download_url = await file_service.generate_download_url(
390
+ file_id=Path(file_metadata.filename).stem,
391
+ user_id=current_user["user_info"]["id"]
392
+ )
393
+
394
+ files.append(FileMetadataResponse(
395
+ id=Path(file_metadata.filename).stem,
396
+ filename=file_metadata.filename,
397
+ original_filename=file_metadata.original_filename,
398
+ file_type=FileType(file_metadata.file_type),
399
+ mime_type=file_metadata.mime_type,
400
+ file_size=file_metadata.file_size,
401
+ checksum=file_metadata.checksum,
402
+ created_at=file_metadata.created_at,
403
+ download_url=download_url,
404
+ metadata=file_metadata.metadata
405
+ ))
406
+
407
+ return FileListResponse(
408
+ files=files,
409
+ total_count=paginated_response.total_count,
410
+ page=page,
411
+ items_per_page=items_per_page,
412
+ has_next=page * items_per_page < paginated_response.total_count,
413
+ has_previous=page > 1
414
+ )
415
+
416
+ except Exception as e:
417
+ logger.error(f"Failed to list files: {e}", exc_info=True)
418
+ raise HTTPException(
419
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
420
+ detail=f"Failed to list files: {str(e)}"
421
+ )
422
+
423
+
424
+ @router.delete("/{file_id}")
425
+ async def delete_file(
426
+ file_id: str,
427
+ current_user: Dict[str, Any] = Depends(get_current_user),
428
+ file_service: FileService = Depends(get_file_service)
429
+ ) -> Dict[str, Any]:
430
+ """
431
+ Delete a file by ID.
432
+
433
+ Args:
434
+ file_id: File identifier
435
+ current_user: Authenticated user information
436
+ file_service: File service dependency
437
+
438
+ Returns:
439
+ Success message
440
+
441
+ Raises:
442
+ HTTPException: If file not found or deletion fails
443
+ """
444
+ try:
445
+ success = await file_service.delete_file(
446
+ file_id=file_id,
447
+ user_id=current_user["user_info"]["id"]
448
+ )
449
+
450
+ if not success:
451
+ raise HTTPException(
452
+ status_code=status.HTTP_404_NOT_FOUND,
453
+ detail="File not found or already deleted"
454
+ )
455
+
456
+ logger.info(
457
+ "File deleted successfully",
458
+ file_id=file_id,
459
+ user_id=current_user["user_info"]["id"]
460
+ )
461
+
462
+ return {"message": "File deleted successfully", "file_id": file_id}
463
+
464
+ except HTTPException:
465
+ raise
466
+ except Exception as e:
467
+ logger.error(f"Failed to delete file: {e}", exc_info=True)
468
+ raise HTTPException(
469
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
470
+ detail=f"Failed to delete file: {str(e)}"
471
+ )
472
+
473
+
474
+ @router.get("/stats", response_model=FileStatsResponse)
475
+ async def get_file_stats(
476
+ current_user: Dict[str, Any] = Depends(get_current_user),
477
+ file_service: FileService = Depends(get_file_service)
478
+ ) -> FileStatsResponse:
479
+ """
480
+ Get file statistics for the current user.
481
+
482
+ Args:
483
+ current_user: Authenticated user information
484
+ file_service: File service dependency
485
+
486
+ Returns:
487
+ FileStatsResponse with user's file statistics
488
+ """
489
+ try:
490
+ stats = await file_service.get_file_stats(
491
+ user_id=current_user["user_info"]["id"]
492
+ )
493
+
494
+ return FileStatsResponse(
495
+ total_files=stats.get("total_files", 0),
496
+ total_size=stats.get("total_size", 0),
497
+ by_type=stats.get("by_type", {}),
498
+ recent_uploads=stats.get("recent_uploads", 0)
499
+ )
500
+
501
+ except Exception as e:
502
+ logger.error(f"Failed to get file stats: {e}", exc_info=True)
503
+ raise HTTPException(
504
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
505
+ detail=f"Failed to get file stats: {str(e)}"
506
+ )
507
+
508
+
509
+ @router.post("/cleanup", response_model=FileCleanupResponse)
510
+ async def cleanup_files(
511
+ request: FileCleanupRequest,
512
+ current_user: Dict[str, Any] = Depends(get_current_user),
513
+ file_service: FileService = Depends(get_file_service)
514
+ ) -> FileCleanupResponse:
515
+ """
516
+ Clean up old files (admin only).
517
+
518
+ Args:
519
+ request: Cleanup request parameters
520
+ current_user: Authenticated user information
521
+ file_service: File service dependency
522
+
523
+ Returns:
524
+ FileCleanupResponse with cleanup results
525
+
526
+ Raises:
527
+ HTTPException: If user is not admin or cleanup fails
528
+ """
529
+ try:
530
+ # Check if user is admin (you may need to implement admin role checking)
531
+ user_roles = current_user.get("user_info", {}).get("roles", [])
532
+ if "admin" not in user_roles:
533
+ raise HTTPException(
534
+ status_code=status.HTTP_403_FORBIDDEN,
535
+ detail="Admin access required"
536
+ )
537
+
538
+ if request.dry_run:
539
+ # For dry run, just return what would be cleaned
540
+ return FileCleanupResponse(
541
+ files_cleaned=0,
542
+ metadata_cleaned=0,
543
+ retention_days=request.retention_days,
544
+ dry_run=True
545
+ )
546
+
547
+ # Perform cleanup
548
+ cleanup_stats = await file_service.cleanup_expired_files(
549
+ retention_days=request.retention_days
550
+ )
551
+
552
+ logger.info(
553
+ "File cleanup completed",
554
+ **cleanup_stats,
555
+ user_id=current_user["user_info"]["id"]
556
+ )
557
+
558
+ return FileCleanupResponse(
559
+ files_cleaned=cleanup_stats.get("files_cleaned", 0),
560
+ metadata_cleaned=cleanup_stats.get("metadata_cleaned", 0),
561
+ retention_days=request.retention_days,
562
+ dry_run=False
563
+ )
564
+
565
+ except HTTPException:
566
+ raise
567
+ except Exception as e:
568
+ logger.error(f"Failed to cleanup files: {e}", exc_info=True)
569
+ raise HTTPException(
570
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
571
+ detail=f"Failed to cleanup files: {str(e)}"
572
+ )
573
+
574
+
575
+ @router.get("/{file_id}/thumbnail")
576
+ async def get_file_thumbnail(
577
+ file_id: str,
578
+ size: str = Query("medium", pattern="^(small|medium|large)$", description="Thumbnail size"),
579
+ request: Request = None,
580
+ current_user: Dict[str, Any] = Depends(get_current_user),
581
+ file_service: FileService = Depends(get_file_service)
582
+ ) -> StreamingResponse:
583
+ """
584
+ Get thumbnail for image or video file.
585
+
586
+ Args:
587
+ file_id: File identifier
588
+ size: Thumbnail size (small, medium, large)
589
+ request: FastAPI request object
590
+ current_user: Authenticated user information
591
+ file_service: File service dependency
592
+
593
+ Returns:
594
+ StreamingResponse with thumbnail image
595
+
596
+ Raises:
597
+ HTTPException: If file not found or thumbnail not available
598
+ """
599
+ try:
600
+ # Get file metadata
601
+ file_metadata = await file_service.get_file_metadata(
602
+ file_id=file_id,
603
+ user_id=current_user["user_info"]["id"]
604
+ )
605
+
606
+ if not file_metadata:
607
+ raise HTTPException(
608
+ status_code=status.HTTP_404_NOT_FOUND,
609
+ detail="File not found"
610
+ )
611
+
612
+ # Check if file type supports thumbnails
613
+ if file_metadata.file_type not in ['image', 'video']:
614
+ raise HTTPException(
615
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
616
+ detail="Thumbnails not available for this file type"
617
+ )
618
+
619
+ # For now, return a placeholder response
620
+ # In a real implementation, you would generate/serve actual thumbnails
621
+ raise HTTPException(
622
+ status_code=status.HTTP_501_NOT_IMPLEMENTED,
623
+ detail="Thumbnail generation not yet implemented"
624
+ )
625
+
626
+ except HTTPException:
627
+ raise
628
+ except Exception as e:
629
+ logger.error(f"Failed to get thumbnail: {e}", exc_info=True)
630
+ raise HTTPException(
631
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
632
+ detail=f"Failed to get thumbnail: {str(e)}"
633
+ )
634
+
635
+
636
+ @router.get("/{file_id}/stream")
637
+ async def stream_file(
638
+ file_id: str,
639
+ quality: str = Query("auto", description="Stream quality"),
640
+ request: Request = None,
641
+ current_user: Dict[str, Any] = Depends(get_current_user),
642
+ file_service: FileService = Depends(get_file_service)
643
+ ) -> StreamingResponse:
644
+ """
645
+ Stream video or audio file with adaptive quality.
646
+
647
+ Args:
648
+ file_id: File identifier
649
+ quality: Stream quality setting
650
+ request: FastAPI request object
651
+ current_user: Authenticated user information
652
+ file_service: File service dependency
653
+
654
+ Returns:
655
+ StreamingResponse with media stream
656
+
657
+ Raises:
658
+ HTTPException: If file not found or streaming not supported
659
+ """
660
+ try:
661
+ # Get file metadata
662
+ file_metadata = await file_service.get_file_metadata(
663
+ file_id=file_id,
664
+ user_id=current_user["user_info"]["id"]
665
+ )
666
+
667
+ if not file_metadata:
668
+ raise HTTPException(
669
+ status_code=status.HTTP_404_NOT_FOUND,
670
+ detail="File not found"
671
+ )
672
+
673
+ # Check if file type supports streaming
674
+ if file_metadata.file_type not in ['video', 'audio']:
675
+ raise HTTPException(
676
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
677
+ detail="Streaming not available for this file type"
678
+ )
679
+
680
+ logger.info(
681
+ "Serving file stream",
682
+ file_id=file_id,
683
+ filename=file_metadata.filename,
684
+ quality=quality,
685
+ user_id=current_user["user_info"]["id"]
686
+ )
687
+
688
+ # Track file access
689
+ from ...utils.file_cache import track_file_access
690
+ await track_file_access(
691
+ file_id=file_id,
692
+ user_id=current_user["user_info"]["id"],
693
+ access_type="stream"
694
+ )
695
+
696
+ # Use enhanced file serving with inline=True for streaming
697
+ from ...utils.file_serving import serve_file_secure
698
+
699
+ return await serve_file_secure(
700
+ file_path=file_metadata.file_path,
701
+ file_type=file_metadata.file_type,
702
+ original_filename=file_metadata.original_filename,
703
+ request=request,
704
+ inline=True, # Serve inline for streaming
705
+ enable_caching=True
706
+ )
707
+
708
+ except HTTPException:
709
+ raise
710
+ except Exception as e:
711
+ logger.error(f"Failed to stream file: {e}", exc_info=True)
712
+ raise HTTPException(
713
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
714
+ detail=f"Failed to stream file: {str(e)}"
715
+ )
716
+
717
+
718
+ @router.get("/{file_id}/info")
719
+ async def get_file_info(
720
+ file_id: str,
721
+ current_user: Dict[str, Any] = Depends(get_current_user),
722
+ file_service: FileService = Depends(get_file_service)
723
+ ) -> Dict[str, Any]:
724
+ """
725
+ Get comprehensive file information including URLs.
726
+
727
+ Args:
728
+ file_id: File identifier
729
+ current_user: Authenticated user information
730
+ file_service: File service dependency
731
+
732
+ Returns:
733
+ Dictionary with file information and access URLs
734
+
735
+ Raises:
736
+ HTTPException: If file not found or access denied
737
+ """
738
+ try:
739
+ file_metadata = await file_service.get_file_metadata(
740
+ file_id=file_id,
741
+ user_id=current_user["user_info"]["id"]
742
+ )
743
+
744
+ if not file_metadata:
745
+ raise HTTPException(
746
+ status_code=status.HTTP_404_NOT_FOUND,
747
+ detail="File not found"
748
+ )
749
+
750
+ # Generate various URLs
751
+ from ...utils.file_serving import (
752
+ generate_secure_file_url,
753
+ generate_thumbnail_url,
754
+ generate_streaming_url
755
+ )
756
+
757
+ urls = {
758
+ "download": generate_secure_file_url(file_id, file_metadata.file_type),
759
+ "download_inline": generate_secure_file_url(file_id, file_metadata.file_type, inline=True),
760
+ }
761
+
762
+ # Add type-specific URLs
763
+ if file_metadata.file_type in ['image', 'video']:
764
+ urls["thumbnail_small"] = generate_thumbnail_url(file_id, "small")
765
+ urls["thumbnail_medium"] = generate_thumbnail_url(file_id, "medium")
766
+ urls["thumbnail_large"] = generate_thumbnail_url(file_id, "large")
767
+
768
+ if file_metadata.file_type in ['video', 'audio']:
769
+ urls["stream"] = generate_streaming_url(file_id)
770
+
771
+ return {
772
+ "file_id": file_id,
773
+ "filename": file_metadata.filename,
774
+ "original_filename": file_metadata.original_filename,
775
+ "file_type": file_metadata.file_type,
776
+ "mime_type": file_metadata.mime_type,
777
+ "file_size": file_metadata.file_size,
778
+ "created_at": file_metadata.created_at.isoformat(),
779
+ "metadata": file_metadata.metadata,
780
+ "urls": urls
781
+ }
782
+
783
+ except HTTPException:
784
+ raise
785
+ except Exception as e:
786
+ logger.error(f"Failed to get file info: {e}", exc_info=True)
787
+ raise HTTPException(
788
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
789
+ detail=f"Failed to get file info: {str(e)}"
790
+ )
791
+
792
+
793
+ @router.get("/{file_id}/analytics")
794
+ async def get_file_analytics(
795
+ file_id: str,
796
+ current_user: Dict[str, Any] = Depends(get_current_user),
797
+ file_service: FileService = Depends(get_file_service)
798
+ ) -> Dict[str, Any]:
799
+ """
800
+ Get file access analytics and statistics.
801
+
802
+ Args:
803
+ file_id: File identifier
804
+ current_user: Authenticated user information
805
+ file_service: File service dependency
806
+
807
+ Returns:
808
+ Dictionary with file analytics
809
+
810
+ Raises:
811
+ HTTPException: If file not found or access denied
812
+ """
813
+ try:
814
+ # Verify file ownership
815
+ file_metadata = await file_service.get_file_metadata(
816
+ file_id=file_id,
817
+ user_id=current_user["user_info"]["id"]
818
+ )
819
+
820
+ if not file_metadata:
821
+ raise HTTPException(
822
+ status_code=status.HTTP_404_NOT_FOUND,
823
+ detail="File not found"
824
+ )
825
+
826
+ # Get access statistics
827
+ from ...utils.file_cache import get_file_access_statistics
828
+ access_stats = await get_file_access_statistics(file_id)
829
+
830
+ return {
831
+ "file_id": file_id,
832
+ "filename": file_metadata.filename,
833
+ "access_statistics": access_stats,
834
+ "file_size": file_metadata.file_size,
835
+ "created_at": file_metadata.created_at.isoformat()
836
+ }
837
+
838
+ except HTTPException:
839
+ raise
840
+ except Exception as e:
841
+ logger.error(f"Failed to get file analytics: {e}", exc_info=True)
842
+ raise HTTPException(
843
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
844
+ detail=f"Failed to get file analytics: {str(e)}"
845
+ )
846
+
847
+
848
+ @router.get("/{file_id}/secure")
849
+ async def secure_file_access(
850
+ file_id: str,
851
+ request: Request,
852
+ user_id: str = Query(..., description="User ID from signed URL"),
853
+ expires: str = Query(..., description="Expiration timestamp"),
854
+ signature: str = Query(..., description="URL signature"),
855
+ file_type: Optional[str] = Query(None, description="File type"),
856
+ inline: Optional[str] = Query("false", description="Serve inline"),
857
+ size: Optional[str] = Query(None, description="Thumbnail size"),
858
+ quality: Optional[str] = Query(None, description="Stream quality"),
859
+ file_service: FileService = Depends(get_file_service)
860
+ ) -> StreamingResponse:
861
+ """
862
+ Secure file access endpoint using signed URLs.
863
+
864
+ This endpoint provides secure file access without requiring authentication
865
+ headers, using signed URLs with expiration and integrity verification.
866
+
867
+ Args:
868
+ file_id: File identifier
869
+ request: FastAPI request object
870
+ user_id: User ID from signed URL
871
+ expires: Expiration timestamp
872
+ signature: URL signature for verification
873
+ file_type: Optional file type
874
+ inline: Whether to serve inline
875
+ size: Thumbnail size (for thumbnails)
876
+ quality: Stream quality (for streaming)
877
+ file_service: File service dependency
878
+
879
+ Returns:
880
+ StreamingResponse with file content
881
+
882
+ Raises:
883
+ HTTPException: If URL is invalid, expired, or file not found
884
+ """
885
+ try:
886
+ # Verify signed URL
887
+ from ...utils.secure_url_generator import verify_url_signature
888
+
889
+ params = {
890
+ 'user_id': user_id,
891
+ 'expires': expires,
892
+ 'signature': signature
893
+ }
894
+
895
+ if file_type:
896
+ params['file_type'] = file_type
897
+ if inline:
898
+ params['inline'] = inline
899
+ if size:
900
+ params['size'] = size
901
+ if quality:
902
+ params['quality'] = quality
903
+
904
+ verification_result = verify_url_signature(file_id, params)
905
+
906
+ if not verification_result['valid']:
907
+ logger.warning(
908
+ "Invalid signed URL access attempt",
909
+ file_id=file_id,
910
+ user_id=user_id,
911
+ error=verification_result['error']
912
+ )
913
+ raise HTTPException(
914
+ status_code=status.HTTP_403_FORBIDDEN,
915
+ detail=f"Invalid URL: {verification_result['error']}"
916
+ )
917
+
918
+ # Get file metadata
919
+ file_metadata = await file_service.get_file_metadata(
920
+ file_id=file_id,
921
+ user_id=verification_result['user_id']
922
+ )
923
+
924
+ if not file_metadata:
925
+ raise HTTPException(
926
+ status_code=status.HTTP_404_NOT_FOUND,
927
+ detail="File not found"
928
+ )
929
+
930
+ logger.info(
931
+ "Serving secure file access",
932
+ file_id=file_id,
933
+ user_id=verification_result['user_id'],
934
+ file_type=verification_result.get('file_type'),
935
+ inline=verification_result['inline']
936
+ )
937
+
938
+ # Track file access
939
+ from ...utils.file_cache import track_file_access
940
+ access_type = "download"
941
+ if verification_result.get('file_type') == "thumbnail":
942
+ access_type = "thumbnail"
943
+ elif verification_result.get('file_type') == "stream":
944
+ access_type = "stream"
945
+
946
+ await track_file_access(
947
+ file_id=file_id,
948
+ user_id=verification_result['user_id'],
949
+ access_type=access_type
950
+ )
951
+
952
+ # Serve file using enhanced file serving
953
+ from ...utils.file_serving import serve_file_secure
954
+
955
+ return await serve_file_secure(
956
+ file_path=file_metadata.file_path,
957
+ file_type=file_metadata.file_type,
958
+ original_filename=file_metadata.original_filename,
959
+ request=request,
960
+ inline=verification_result['inline'],
961
+ enable_caching=True,
962
+ user_id=verification_result['user_id']
963
+ )
964
+
965
+ except HTTPException:
966
+ raise
967
+ except Exception as e:
968
+ logger.error(
969
+ "Failed to serve secure file access",
970
+ file_id=file_id,
971
+ user_id=user_id,
972
+ error=str(e),
973
+ exc_info=True
974
+ )
975
+ raise HTTPException(
976
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
977
+ detail=f"Failed to serve file: {str(e)}"
978
+ )
src/app/api/v1/jobs.py ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Job management API endpoints.
3
+
4
+ This module implements REST API endpoints for job management operations,
5
+ including job listing, cancellation, deletion, and log retrieval.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional, Dict, Any, List
10
+ from datetime import datetime
11
+
12
+ from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
13
+ from redis.asyncio import Redis
14
+
15
+ from ...core.redis import get_redis, RedisKeyManager, redis_json_get, redis_json_set
16
+ from ...core.cache import cache_response, CacheConfig
17
+ from ...core.performance import performance_monitor
18
+ from ...models.job import (
19
+ JobListResponse, JobResponse, JobStatus, Job,
20
+ JobStatusResponse, JobType, JobPriority
21
+ )
22
+ from ...services.job_service import JobService
23
+ from ...api.dependencies import (
24
+ get_current_user, get_job_service, get_pagination_params,
25
+ get_job_filters, PaginationParams, JobFilters, validate_job_ownership
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+ router = APIRouter(prefix="/jobs", tags=["jobs"])
30
+
31
+
32
+ @router.get("", response_model=JobListResponse)
33
+ async def list_jobs(
34
+ request: Request,
35
+ current_user: Dict[str, Any] = Depends(get_current_user),
36
+ pagination: PaginationParams = Depends(get_pagination_params),
37
+ filters: JobFilters = Depends(get_job_filters),
38
+ job_service: JobService = Depends(get_job_service)
39
+ ) -> JobListResponse:
40
+ """
41
+ List jobs for the current user with pagination and filtering.
42
+
43
+ This endpoint returns a paginated list of jobs belonging to the current user,
44
+ with optional filtering by status, type, priority, and creation date.
45
+
46
+ Args:
47
+ current_user: Authenticated user information
48
+ pagination: Pagination parameters
49
+ filters: Job filtering parameters
50
+ job_service: Job service dependency
51
+
52
+ Returns:
53
+ JobListResponse with paginated job list
54
+
55
+ Raises:
56
+ HTTPException: If job retrieval fails
57
+ """
58
+ try:
59
+ user_id = current_user["user_info"]["id"]
60
+
61
+ logger.info(
62
+ "Listing jobs for user",
63
+ user_id=user_id,
64
+ page=pagination.page,
65
+ items_per_page=pagination.items_per_page,
66
+ filters=filters.to_dict()
67
+ )
68
+
69
+ # Get jobs from service
70
+ jobs_result = await job_service.get_jobs_paginated(
71
+ user_id=user_id,
72
+ limit=pagination.limit,
73
+ offset=pagination.offset,
74
+ filters=filters.to_dict()
75
+ )
76
+
77
+ # Convert jobs to response format
78
+ job_responses = []
79
+ for job in jobs_result["jobs"]:
80
+ job_responses.append(JobResponse(
81
+ job_id=job.id,
82
+ status=job.status,
83
+ progress=job.progress,
84
+ created_at=job.created_at,
85
+ estimated_completion=job.progress.estimated_completion
86
+ ))
87
+
88
+ # Calculate pagination info
89
+ total_count = jobs_result["total_count"]
90
+ has_next = (pagination.offset + pagination.items_per_page) < total_count
91
+ has_previous = pagination.page > 1
92
+
93
+ logger.info(
94
+ "Retrieved jobs for user",
95
+ user_id=user_id,
96
+ job_count=len(job_responses),
97
+ total_count=total_count
98
+ )
99
+
100
+ return JobListResponse(
101
+ jobs=job_responses,
102
+ total_count=total_count,
103
+ page=pagination.page,
104
+ items_per_page=pagination.items_per_page,
105
+ has_next=has_next,
106
+ has_previous=has_previous
107
+ )
108
+
109
+ except Exception as e:
110
+ logger.error(
111
+ "Failed to list jobs",
112
+ user_id=current_user["user_info"]["id"],
113
+ error=str(e),
114
+ exc_info=True
115
+ )
116
+ raise HTTPException(
117
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
118
+ detail=f"Failed to retrieve jobs: {str(e)}"
119
+ )
120
+
121
+
122
+ @router.post("/{job_id}/cancel", response_model=Dict[str, Any])
123
+ async def cancel_job(
124
+ job_id: str,
125
+ current_user: Dict[str, Any] = Depends(get_current_user),
126
+ job_service: JobService = Depends(get_job_service),
127
+ redis_client: Redis = Depends(get_redis)
128
+ ) -> Dict[str, Any]:
129
+ """
130
+ Cancel a specific job.
131
+
132
+ This endpoint attempts to cancel a job if it's in a cancellable state
133
+ (queued or processing). Completed or already cancelled jobs cannot be cancelled.
134
+
135
+ Args:
136
+ job_id: Unique job identifier
137
+ current_user: Authenticated user information
138
+ job_service: Job service dependency
139
+ redis_client: Redis client dependency
140
+
141
+ Returns:
142
+ Dict with cancellation status and message
143
+
144
+ Raises:
145
+ HTTPException: If job not found, access denied, or cancellation fails
146
+ """
147
+ try:
148
+ # Get job from Redis
149
+ job_key = RedisKeyManager.job_key(job_id)
150
+ job_data = await redis_json_get(redis_client, job_key)
151
+
152
+ if not job_data:
153
+ raise HTTPException(
154
+ status_code=status.HTTP_404_NOT_FOUND,
155
+ detail=f"Job {job_id} not found"
156
+ )
157
+
158
+ job = Job(**job_data)
159
+
160
+ # Verify user owns this job
161
+ validate_job_ownership(job.user_id, current_user)
162
+
163
+ # Check if job can be cancelled
164
+ if not job.can_be_cancelled:
165
+ raise HTTPException(
166
+ status_code=status.HTTP_409_CONFLICT,
167
+ detail=f"Job cannot be cancelled. Current status: {job.status}"
168
+ )
169
+
170
+ logger.info(
171
+ "Cancelling job",
172
+ job_id=job_id,
173
+ current_status=job.status,
174
+ user_id=current_user["user_info"]["id"]
175
+ )
176
+
177
+ # Cancel job using service
178
+ success = await job_service.cancel_job(job_id)
179
+
180
+ if not success:
181
+ raise HTTPException(
182
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
183
+ detail="Failed to cancel job"
184
+ )
185
+
186
+ logger.info(
187
+ "Job cancelled successfully",
188
+ job_id=job_id,
189
+ user_id=current_user["user_info"]["id"]
190
+ )
191
+
192
+ return {
193
+ "success": True,
194
+ "message": f"Job {job_id} has been cancelled",
195
+ "job_id": job_id,
196
+ "previous_status": job.status,
197
+ "new_status": "cancelled",
198
+ "cancelled_at": datetime.utcnow().isoformat()
199
+ }
200
+
201
+ except HTTPException:
202
+ raise
203
+ except Exception as e:
204
+ logger.error(
205
+ "Failed to cancel job",
206
+ job_id=job_id,
207
+ user_id=current_user["user_info"]["id"],
208
+ error=str(e),
209
+ exc_info=True
210
+ )
211
+ raise HTTPException(
212
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
213
+ detail=f"Failed to cancel job: {str(e)}"
214
+ )
215
+
216
+
217
+ @router.delete("/{job_id}", response_model=Dict[str, Any])
218
+ async def delete_job(
219
+ job_id: str,
220
+ current_user: Dict[str, Any] = Depends(get_current_user),
221
+ job_service: JobService = Depends(get_job_service),
222
+ redis_client: Redis = Depends(get_redis)
223
+ ) -> Dict[str, Any]:
224
+ """
225
+ Delete a specific job (soft delete).
226
+
227
+ This endpoint performs a soft delete on a job, marking it as deleted
228
+ but preserving the data for audit purposes. The job will no longer
229
+ appear in normal job listings.
230
+
231
+ Args:
232
+ job_id: Unique job identifier
233
+ current_user: Authenticated user information
234
+ job_service: Job service dependency
235
+ redis_client: Redis client dependency
236
+
237
+ Returns:
238
+ Dict with deletion status and message
239
+
240
+ Raises:
241
+ HTTPException: If job not found, access denied, or deletion fails
242
+ """
243
+ try:
244
+ # Get job from Redis
245
+ job_key = RedisKeyManager.job_key(job_id)
246
+ job_data = await redis_json_get(redis_client, job_key)
247
+
248
+ if not job_data:
249
+ raise HTTPException(
250
+ status_code=status.HTTP_404_NOT_FOUND,
251
+ detail=f"Job {job_id} not found"
252
+ )
253
+
254
+ job = Job(**job_data)
255
+
256
+ # Verify user owns this job
257
+ validate_job_ownership(job.user_id, current_user)
258
+
259
+ # Check if job is already deleted
260
+ if job.is_deleted:
261
+ raise HTTPException(
262
+ status_code=status.HTTP_409_CONFLICT,
263
+ detail="Job is already deleted"
264
+ )
265
+
266
+ logger.info(
267
+ "Deleting job",
268
+ job_id=job_id,
269
+ status=job.status,
270
+ user_id=current_user["user_info"]["id"]
271
+ )
272
+
273
+ # Cancel job if it's still active
274
+ if job.can_be_cancelled:
275
+ await job_service.cancel_job(job_id)
276
+
277
+ # Perform soft delete
278
+ success = await job_service.soft_delete_job(job_id)
279
+
280
+ if not success:
281
+ raise HTTPException(
282
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
283
+ detail="Failed to delete job"
284
+ )
285
+
286
+ # Remove from user's job index
287
+ user_jobs_key = RedisKeyManager.user_jobs_key(current_user["user_info"]["id"])
288
+ await redis_client.srem(user_jobs_key, job_id)
289
+
290
+ # Clean up related data (video metadata, cache entries)
291
+ await job_service.cleanup_job_data(job_id)
292
+
293
+ logger.info(
294
+ "Job deleted successfully",
295
+ job_id=job_id,
296
+ user_id=current_user["user_info"]["id"]
297
+ )
298
+
299
+ return {
300
+ "success": True,
301
+ "message": f"Job {job_id} has been deleted",
302
+ "job_id": job_id,
303
+ "deleted_at": datetime.utcnow().isoformat()
304
+ }
305
+
306
+ except HTTPException:
307
+ raise
308
+ except Exception as e:
309
+ logger.error(
310
+ "Failed to delete job",
311
+ job_id=job_id,
312
+ user_id=current_user["user_info"]["id"],
313
+ error=str(e),
314
+ exc_info=True
315
+ )
316
+ raise HTTPException(
317
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
318
+ detail=f"Failed to delete job: {str(e)}"
319
+ )
320
+
321
+
322
+ @router.get("/{job_id}/logs", response_model=Dict[str, Any])
323
+ async def get_job_logs(
324
+ job_id: str,
325
+ current_user: Dict[str, Any] = Depends(get_current_user),
326
+ limit: int = Query(100, ge=1, le=1000, description="Maximum number of log entries"),
327
+ offset: int = Query(0, ge=0, description="Number of log entries to skip"),
328
+ level: Optional[str] = Query(None, description="Filter by log level (DEBUG, INFO, WARNING, ERROR)"),
329
+ redis_client: Redis = Depends(get_redis)
330
+ ) -> Dict[str, Any]:
331
+ """
332
+ Get processing logs for a specific job.
333
+
334
+ This endpoint returns the processing logs for a job, which can be useful
335
+ for debugging failed jobs or monitoring progress in detail.
336
+
337
+ Args:
338
+ job_id: Unique job identifier
339
+ current_user: Authenticated user information
340
+ limit: Maximum number of log entries to return
341
+ offset: Number of log entries to skip
342
+ level: Filter logs by level
343
+ redis_client: Redis client dependency
344
+
345
+ Returns:
346
+ Dict with job logs and metadata
347
+
348
+ Raises:
349
+ HTTPException: If job not found or access denied
350
+ """
351
+ try:
352
+ # Get job from Redis
353
+ job_key = RedisKeyManager.job_key(job_id)
354
+ job_data = await redis_json_get(redis_client, job_key)
355
+
356
+ if not job_data:
357
+ raise HTTPException(
358
+ status_code=status.HTTP_404_NOT_FOUND,
359
+ detail=f"Job {job_id} not found"
360
+ )
361
+
362
+ job = Job(**job_data)
363
+
364
+ # Verify user owns this job
365
+ validate_job_ownership(job.user_id, current_user)
366
+
367
+ logger.info(
368
+ "Retrieving job logs",
369
+ job_id=job_id,
370
+ limit=limit,
371
+ offset=offset,
372
+ level=level,
373
+ user_id=current_user["user_info"]["id"]
374
+ )
375
+
376
+ # Get logs from Redis (stored as a list)
377
+ logs_key = f"job_logs:{job_id}"
378
+
379
+ # Get total log count
380
+ total_logs = await redis_client.llen(logs_key)
381
+
382
+ # Get log entries with pagination
383
+ log_entries = []
384
+ if total_logs > 0:
385
+ # Redis lists are 0-indexed, get range with offset and limit
386
+ end_index = offset + limit - 1
387
+ if end_index >= total_logs:
388
+ end_index = total_logs - 1
389
+
390
+ if offset < total_logs:
391
+ raw_logs = await redis_client.lrange(logs_key, offset, end_index)
392
+
393
+ # Parse and filter logs
394
+ for log_entry in raw_logs:
395
+ try:
396
+ import json
397
+ log_data = json.loads(log_entry)
398
+
399
+ # Filter by level if specified
400
+ if level and log_data.get("level", "").upper() != level.upper():
401
+ continue
402
+
403
+ log_entries.append(log_data)
404
+
405
+ except json.JSONDecodeError:
406
+ # Handle plain text logs
407
+ log_entries.append({
408
+ "timestamp": datetime.utcnow().isoformat(),
409
+ "level": "INFO",
410
+ "message": log_entry,
411
+ "source": "system"
412
+ })
413
+
414
+ # Calculate pagination info
415
+ has_more = (offset + limit) < total_logs
416
+
417
+ logger.info(
418
+ "Retrieved job logs",
419
+ job_id=job_id,
420
+ log_count=len(log_entries),
421
+ total_logs=total_logs,
422
+ user_id=current_user["user_info"]["id"]
423
+ )
424
+
425
+ return {
426
+ "job_id": job_id,
427
+ "logs": log_entries,
428
+ "pagination": {
429
+ "offset": offset,
430
+ "limit": limit,
431
+ "total_count": total_logs,
432
+ "returned_count": len(log_entries),
433
+ "has_more": has_more
434
+ },
435
+ "filters": {
436
+ "level": level
437
+ },
438
+ "job_info": {
439
+ "status": job.status,
440
+ "created_at": job.created_at.isoformat(),
441
+ "updated_at": job.updated_at.isoformat(),
442
+ "progress": job.progress.percentage
443
+ }
444
+ }
445
+
446
+ except HTTPException:
447
+ raise
448
+ except Exception as e:
449
+ logger.error(
450
+ "Failed to get job logs",
451
+ job_id=job_id,
452
+ user_id=current_user["user_info"]["id"],
453
+ error=str(e),
454
+ exc_info=True
455
+ )
456
+ raise HTTPException(
457
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
458
+ detail=f"Failed to retrieve job logs: {str(e)}"
459
+ )
460
+
461
+
462
+ @router.get("/{job_id}", response_model=JobStatusResponse)
463
+ async def get_job_details(
464
+ job_id: str,
465
+ current_user: Dict[str, Any] = Depends(get_current_user),
466
+ redis_client: Redis = Depends(get_redis)
467
+ ) -> JobStatusResponse:
468
+ """
469
+ Get detailed information about a specific job.
470
+
471
+ This endpoint returns comprehensive job information including status,
472
+ progress, configuration, metrics, and error details if applicable.
473
+
474
+ Args:
475
+ job_id: Unique job identifier
476
+ current_user: Authenticated user information
477
+ redis_client: Redis client dependency
478
+
479
+ Returns:
480
+ JobStatusResponse with detailed job information
481
+
482
+ Raises:
483
+ HTTPException: If job not found or access denied
484
+ """
485
+ try:
486
+ # Get job from Redis
487
+ job_key = RedisKeyManager.job_key(job_id)
488
+ job_data = await redis_json_get(redis_client, job_key)
489
+
490
+ if not job_data:
491
+ raise HTTPException(
492
+ status_code=status.HTTP_404_NOT_FOUND,
493
+ detail=f"Job {job_id} not found"
494
+ )
495
+
496
+ job = Job(**job_data)
497
+
498
+ # Verify user owns this job
499
+ validate_job_ownership(job.user_id, current_user)
500
+
501
+ logger.info(
502
+ "Retrieved job details",
503
+ job_id=job_id,
504
+ status=job.status,
505
+ user_id=current_user["user_info"]["id"]
506
+ )
507
+
508
+ return JobStatusResponse(
509
+ job_id=job.id,
510
+ status=job.status,
511
+ progress=job.progress,
512
+ error=job.error,
513
+ metrics=job.metrics,
514
+ created_at=job.created_at,
515
+ updated_at=job.updated_at,
516
+ completed_at=job.completed_at
517
+ )
518
+
519
+ except HTTPException:
520
+ raise
521
+ except Exception as e:
522
+ logger.error(
523
+ "Failed to get job details",
524
+ job_id=job_id,
525
+ user_id=current_user["user_info"]["id"],
526
+ error=str(e),
527
+ exc_info=True
528
+ )
529
+ raise HTTPException(
530
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
531
+ detail=f"Failed to retrieve job details: {str(e)}"
532
+ )