VOD Docs

Data model

Every PostgreSQL table, its key columns and how the tables relate.

Six tables, defined as SQLAlchemy models in backend/app/models/. Primary keys are UUID strings generated in Python.

Architecture diagram · placeholder
Entity-relationship diagram: users, videos, refresh_tokens, email_verification_tokens, password_reset_tokens, tus_uploads

Crow's-foot ERD with foreign keys and ON DELETE behaviour.

users

ColumnTypeNotes
idstring (UUID)Primary key
emailstringUnique, indexed
usernamestringUnique, indexed
hashed_passwordstringbcrypt
roleenum USER / ADMINDatabase default USER, indexed
is_activebooleanDefault true; inactive users cannot sign in or refresh
is_verifiedbooleanDefault false; set by email verification
created_at, updated_attimestamps

videos

The central table. It holds editorial metadata, the processing state machine and storage paths.

Prop

Type

Two columns describe two different things and are easy to confuse:

  • status is editorial: has an admin published it?
  • processing_status is technical: how far has the pipeline got?

A video can be published while still transcoding; the player shows "still processing" until manifest_url is set.

processing_status values

State diagram · placeholder
processing_status state machine: uploading → queued → preparing → … → completed, with failed reachable from every active state

Include the re-queue edge (failed → queued) used for manual retries.

ValueProgress shownSet by
uploading5%Model default
queued15%Upload handlers
preparing25%prepare_video
generating_storyboard35%generate_storyboard
transcoding50%transcode_quality
aggregating60%on_transcode_complete
segmenting70%segment_videos
creating_manifest80%create_manifest
uploading_to_storage90%upload_to_minio
finalizing95%finalize_processing
completed100%finalize_processing
failed0%Any stage, or the pipeline's failure handler

The progress numbers are fixed per stage (utils/video_helpers.py), not measured.

refresh_tokens

ColumnNotes
user_id→ users.id, ON DELETE CASCADE
token_hashSHA-256 of the refresh JWT, indexed
expires_at
is_revokedSet by logout and password reset

email_verification_tokens and password_reset_tokens

Both have the same shape: user_id (cascade delete), token, expires_at, used, created_at. Verification tokens are UUIDs valid for 24 hours; reset tokens are 6-digit codes valid for 1 hour.

tus_uploads

One row per resumable upload session.

ColumnNotes
upload_idPrimary key; the tus upload ID assigned in the pre-create hook
user_id→ users.id
video_id→ videos.id, set when the upload finishes
object_keyWhere the file landed in MinIO
declared_sizeBytes, from the client
statuscreated, completed or terminated
created_at, completed_at

Migrations

Alembic migrations live in backend/alembic/versions/ as a single linear chain. They are run manually (alembic upgrade head). At startup the API also calls create_all(), which creates any table that does not exist yet but never alters existing ones.

Design notes

  • Status columns are strings, not database enums. Adding a pipeline stage needs no migration; the allowed values are enforced in Python (ProcessingStatus).
  • Soft delete keeps rows (and their files) when an admin deletes a video; queries filter on deleted_at IS NULL.
  • Paths, not URLs, are stored for every file, so the public host can change without touching data.

On this page