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):
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¶
- Events and realtime — the event log, the bus, and the WebSocket.
- Performance & scale — indexes, query limits, benchmark.
- Media pipeline — async transcode + job status.
- Security model — the threat model and auth boundary.
- Contributing — dev setup, tests, conventions.