Skip to content

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.

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

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 verification

internal/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.

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.

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.

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.