Backend:
- VttVersion model (vtt_version.py): immutable snapshot per job/lang/kind/version
- vtt_versioning service: create_version (atomic counter + GCS snapshot),
list_versions, get_version, restore_version, diff_versions (difflib line-level)
- routes_vtt_versions.py: GET /versions, GET /versions/{v}, GET /versions/diff,
POST /versions/{v}/restore (PRODUCTION/ADMIN only, overwrites live file + audit log)
- Hook create_version into update_job_vtt_content before each live-file overwrite
- Mongo indexes: unique (job_id, lang, kind, version) + (job_id, created_at)
Frontend:
- VttVersionSummary / VttVersionFull / VttDiffResponse types
- api.ts: listVttVersions, getVttVersion, diffVttVersions, restoreVttVersion
- VersionsTab.tsx: lang/kind switcher, version list with A/B compare buttons,
inline diff viewer (color-coded +/−), content viewer, restore with confirm dialog
- JobDetail.tsx: new "VTT Versions" tab wired to VersionsTab
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
73 lines
1.6 KiB
Python
73 lines
1.6 KiB
Python
from datetime import datetime
|
|
from typing import Literal, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
VttKind = Literal["captions", "ad"]
|
|
|
|
|
|
class VttVersionActor(BaseModel):
|
|
user_id: str
|
|
user_email: str
|
|
|
|
|
|
class VttVersion(BaseModel):
|
|
id: Optional[str] = Field(None, alias="_id")
|
|
job_id: str
|
|
lang: str
|
|
kind: VttKind
|
|
version: int
|
|
content: str
|
|
gcs_uri: str
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
created_by: VttVersionActor
|
|
note: Optional[str] = None
|
|
parent_version: Optional[int] = None
|
|
cue_count: int = 0
|
|
byte_size: int = 0
|
|
|
|
class Config:
|
|
populate_by_name = True
|
|
|
|
|
|
class VttVersionSummary(BaseModel):
|
|
"""Lightweight version entry for list responses (no content)."""
|
|
id: Optional[str] = Field(None, alias="_id")
|
|
job_id: str
|
|
lang: str
|
|
kind: VttKind
|
|
version: int
|
|
gcs_uri: str
|
|
created_at: datetime
|
|
created_by: VttVersionActor
|
|
note: Optional[str] = None
|
|
parent_version: Optional[int] = None
|
|
cue_count: int = 0
|
|
byte_size: int = 0
|
|
|
|
class Config:
|
|
populate_by_name = True
|
|
|
|
|
|
class VttVersionListResponse(BaseModel):
|
|
versions: list[VttVersionSummary]
|
|
total: int
|
|
|
|
|
|
class DiffLine(BaseModel):
|
|
type: Literal["unchanged", "added", "removed"]
|
|
content: str
|
|
line_no_old: Optional[int] = None
|
|
line_no_new: Optional[int] = None
|
|
|
|
|
|
class VttDiffResponse(BaseModel):
|
|
job_id: str
|
|
lang: str
|
|
kind: VttKind
|
|
from_version: int
|
|
to_version: int
|
|
lines: list[DiffLine]
|
|
added_count: int
|
|
removed_count: int
|