Skip to content

Architecture overview

A map of the codebase for people working on fxquinox itself. The guiding idea is one API, one store: every surface is a client of the same HTTP API, and every entity is a row in one table.

The universal Record store

There are no per-entity tables. Every entity — Project, Shot, Asset, Task, User, Note, Version, … — is a single Record (models/record.py):

id  entity_type  project_id  status  data (JSON)  active
thumbnail_id  created_by  created_at  updated_at

Structural fields that drive filtering (entity_type, project_id, status, active) are real, indexed columns; everything entity-specific lives in the data JSON blob. Provenance (created_by, timestamps) and the optional thumbnail_id round out every row. Adding a new entity type or field needs no migration — see Performance for how this scales and is indexed.

Backend layout (backend/src/fxq)

Area What it does
models/ The Record table, plus the schema-engine tables (EntityType, Status, Step, Priority, Workflow, WorkflowSchema).
schema/ seed.py (built-in entity types, statuses, steps, workflows) and demo.py (sample data, opt-in).
services/ Business logic. records.py (create/update emit EventLog + notifications), schema_admin.py (runtime schema edits + cascade renames), workflow.py (status-transition rules), auth.py, media.py, notifications.py, dispatch.py / webhooks.py (background event side effects).
events/ The in-process pub/sub bus (bus.py: subscribe / emit / unsubscribe).
query/ The JSON filter DSL (dsl.py) and its SQLAlchemy compiler.py — parameterised, with link-field traversal.
api/ deps.py (auth/session dependencies), app.py (the app factory), and routers/ (auth, entities, schema, media, publish, events, plus pipeline routers: farm, sitesync, sync, actions, addons, scheduler, share).
publish/ Version/Product/Representation creation, path resolution, and lineage.
media/ Storage abstraction + ffmpeg transcode.

The API base is http://localhost:8000 with no /api prefix. Every entity type shares the same generic CRUD routes (POST /entities/{Type}, GET|PATCH|DELETE /entities/{Type}/{id}) plus POST /entities/{Type}/query for the filter DSL.

Writes flow through services/records.py, which emits an EventLog row and fires notifications — so activity feeds and the realtime WebSocket are populated automatically. The same write also emits on the in-process event bus, which a background dispatcher drains to run webhook / chat side effects off the request path. Auth is enforced centrally; see the authorization model.

Frontend layout (frontend/src)

Vite + React 18 + TypeScript (strict), Tailwind with a shadcn-style component set. api.ts is the single data layer (every call carries the bearer token); TanStack Query handles caching and a global mutation cache refreshes activity and notifications after any write. Routes live in routes/, shared UI in ui/, and feature components in components/.

Request lifecycle

client → router (auth dependency) → service (records/schema_admin/…)
       → Record store (+ EventLog) → response
                         └→ notifications + WebSocket broadcast

Where to look next