video-accessibility/backend/app/models/vtt_version.py
Vadym Samoilenko bab30e1508 feat: VTT version control — snapshots, diff, restore
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>
2026-04-29 11:46:21 +01:00

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