Add a comprehensive video review feature to the Final Review page that allows
reviewers to watch videos with caption overlays and add timestamped notes.
Backend:
- New ReviewNote model for MongoDB with job_id, asset_key, timestamp, content
- CRUD API endpoints at /jobs/{job_id}/review-notes
- Owner-only edit/delete permissions (admins can bypass)
- Database indexes for efficient querying
Frontend:
- VideoReviewPlayer component with video player and caption overlay
- NotesSidebar for viewing/adding notes with auto-highlight when video reaches timestamp
- SyncedCaptionList with auto-scroll and click-to-seek
- AssetTabs for switching between languages and accessible videos
- React Query hooks with 30s polling for collaborative updates
Features:
- Notes persist to database and are shared across all reviewers
- Notes highlight for 5 seconds when video playback reaches their timestamp
- Click note to seek video to that position
- Pause video to add note at current timestamp
- Accessible videos use retimed captions when available
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
|
|
from ..core.logging import get_logger
|
|
from .config import settings
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
class MongoDB:
|
|
client: AsyncIOMotorClient = None
|
|
database: AsyncIOMotorDatabase = None
|
|
|
|
|
|
mongodb = MongoDB()
|
|
|
|
|
|
async def connect_to_mongo():
|
|
logger.info("Connecting to MongoDB...")
|
|
mongodb.client = AsyncIOMotorClient(settings.mongodb_uri)
|
|
mongodb.database = mongodb.client[settings.mongodb_db]
|
|
|
|
# Test connection
|
|
try:
|
|
await mongodb.client.admin.command('ping')
|
|
logger.info("Successfully connected to MongoDB")
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect to MongoDB: {e}")
|
|
raise
|
|
|
|
|
|
async def close_mongo_connection():
|
|
logger.info("Closing MongoDB connection...")
|
|
if mongodb.client:
|
|
mongodb.client.close()
|
|
|
|
|
|
async def get_database() -> AsyncIOMotorDatabase:
|
|
return mongodb.database
|
|
|
|
|
|
async def create_indexes():
|
|
"""Create database indexes as specified in the development plan"""
|
|
db = mongodb.database
|
|
|
|
# Jobs collection indexes
|
|
await db.jobs.create_index([("status", 1), ("created_at", -1)])
|
|
await db.jobs.create_index([("client_id", 1)])
|
|
|
|
# Users collection indexes
|
|
await db.users.create_index([("email", 1)], unique=True)
|
|
|
|
# Audit logs collection indexes - comprehensive indexing for audit queries
|
|
await db.audit_logs.create_index([("timestamp", -1)]) # Primary sort field
|
|
await db.audit_logs.create_index([("action", 1), ("timestamp", -1)]) # Filter by action
|
|
await db.audit_logs.create_index([("user_id", 1), ("timestamp", -1)]) # User activity
|
|
await db.audit_logs.create_index([("severity", 1), ("timestamp", -1)]) # Security events
|
|
await db.audit_logs.create_index([("resource_type", 1), ("resource_id", 1)]) # Resource tracking
|
|
await db.audit_logs.create_index([("ip_address", 1), ("timestamp", -1)]) # IP-based analysis
|
|
await db.audit_logs.create_index([("success", 1), ("timestamp", -1)]) # Failed operations
|
|
|
|
# Text search index for description and details
|
|
await db.audit_logs.create_index([
|
|
("description", "text"),
|
|
("details", "text"),
|
|
("error_message", "text")
|
|
])
|
|
|
|
# Review notes collection indexes
|
|
await db.review_notes.create_index([("job_id", 1), ("asset_key", 1)])
|
|
await db.review_notes.create_index([("job_id", 1), ("asset_key", 1), ("timestamp_seconds", 1)])
|
|
await db.review_notes.create_index([("user_id", 1)])
|
|
|
|
logger.info("Database indexes created successfully")
|