Architecture
Mişko is a Go API with a Vue panel and a PostgreSQL database. Video analysis runs outside the API, in workers that ask for jobs and upload their results.
Technology stack
Section titled “Technology stack”| Layer | Technology |
|---|---|
| Backend | Go 1.27 · pgx · sqlc · PostgreSQL 18 |
| Frontend | Vue 3 · Vite · Vue Router · Pinia · vue-i18n |
| Security | JWT · bcrypt · role based access control |
| Storage | Google Cloud Storage (private bucket, signed URLs) |
| Worker | Python · PyAV · NumPy · SciPy |
| Infra | Docker · Docker Compose · Nginx · GitHub Actions |
Backend layers
Section titled “Backend layers”The API is a modular monolith. Every domain is its own package under
internal/, and each one is split the same way:
internal/<domain>/├── domain/ pure business rules, no database and no HTTP├── application/ use cases and the ports they need└── adapters/ ├── http/ routes, request decoding, error mapping ├── postgres/ queries generated by sqlc ├── catalog/ read-only views of the paradigm catalog └── token/ token issuing and verificationinternal/bootstrap is the composition root: it wires the concrete adapters
into the use cases and mounts every route. internal/access/domain holds the
shared permission kernel, and a use case calls actor.Require(permission)
before it touches a store.
A policy test in tests/architecture enforces the shape: domain and
application packages may not import adapters, and domain code is limited to a
small allowlist of standard library packages. A layering mistake fails the
build rather than waiting for review.
List queries
Section titled “List queries”Every list endpoint returns a paginated envelope rather than a bare array:
{ "data": [ ... ], "total": 42, "page": 1, "pageSize": 10 }page and pageSize are query parameters, and the database applies the
paging. On the panel a single DataTable component and the useDataTable
composable consume this envelope, so search, paging and sorting behave the same
way on every screen.
How analysis runs
Section titled “How analysis runs”The camera and computer vision side is not a second service with its own database. It is a worker process that authenticates against this API:
Panel ──► Go API ──► PostgreSQL (records, metrics, events) │ │ Authorization: Worker <token> ▼ Worker ──► reads the recording and writes its outputs │ to the bucket with signed URLs ▼ Cloud Storage (original video, analyzed video, trajectory)A worker claims a queued run, reads the pinned source video, and uploads an
analyzed video and a misko.trajectory.v1 trajectory. It does not send metrics.
The API verifies every uploaded object against its declared size, checksum and
content type, applies the paradigm’s quality rules, and then the Go metric
engine computes the metrics and events from the trajectory. That keeps one
definition of every measurement inside the API, whatever produced the tracking.
Heavy data stays in the bucket: the database holds records, metrics, events and references to objects, never video bytes.
Deployment
Section titled “Deployment”The stack runs with Docker Compose: PostgreSQL, the API, the jobs process that
queues analysis runs, and Nginx serving the panel. The schema and the first
administrator are installed by explicit one-time commands (bootstrap schema
and bootstrap setup), not on container start, so an existing database is
never migrated by accident. Backend, frontend and worker each have their own
path-filtered GitHub Actions pipeline.