Перейти к содержимому

Conventions

Это содержимое пока не доступно на вашем языке.

Item Rule
Base path /api/v1 (major in path). Ops endpoints unversioned: /health, /ready, /metrics
Self-description GET /api/v1/openapi.json, Swagger UI GET /api/v1/docs, GraphQL POST/GET /api/v1/graphql (GraphiQL off by default), WS GET /api/v1/ws, socket.io path /socket.io/ namespace /v1
JSON application/json, UTF-8, no BOM; camelCase field names; unknown fields ignored by readers
Errors application/problem+json (RFC 9457)
Partial update PATCH with application/merge-patch+json (RFC 7396); PUT replaces
Actions verbs as sub-resources: POST …/players/{playerId}/play; long actions return 202 + Location: /api/v1/jobs/{jobId}
Time RFC 3339 UTC with ms (2026-10-19T12:00:00.000Z); durations / positions in seconds (float)
Units metres, degrees, seconds, Hz; brightness 0..1; volume 0..1 linear gain with optional gainDb; colours #rrggbb
i18n text { "en": "…", "ru": "…" } (BCP 47 keys), as in .dome meta.title
  • Server-owned resources: UUIDv7, lowercase string (0192…), sortable by creation time.
  • Document-scoped ids (from .dome: displays[].id, assets[].id, playlist / cue ids): ^[A-Za-z0-9._-]{1,64}$, unique within their parent; addressed as nested path segments (/venues/{venueId}/displays/p1).
  • Aliases: default = the installation’s default venue; main = default media player. Clients that know only one dome never need a venue id.
  • Client-chosen ids allowed on PUT create (idempotent) for venues / shows imported from .dome (meta.id).
{ "type": "https://freedome.nativemind.us/problems/engine-unavailable", "title": "Visual engine unavailable",
"status": 503, "detail": "freedome-engined not reachable at unix:/run/freedome/engine.sock",
"instance": "/api/v1/venues/default/players/main/play", "code": "engine-unavailable",
"traceId": "4bf92f35…", "errors": [ { "pointer": "/position", "detail": "must be ≥ 0" } ] }

code is the stable machine key (also the last path segment of type); the same object is the data of WS error messages, the socket.io ack error, and GraphQL errors[].extensions.problem. Catalogue (extensible, in schemas/common/1.0/problem.schema.json): validation-failed 422, not-found 404, conflict 409, precondition-failed 412 (ETag), precondition-required 428, unauthenticated 401, forbidden 403 (missing scope; detail names it), rate-limited 429 (+Retry-After), payload-too-large 413, unsupported-format 415, engine-unavailable 503, engine-incompatible 503, degraded 503, job-failed (in job results), upload-checksum-mismatch 422, legacy-unsupported 501, internal 500.

  • Cursor pagination on every collection: ?limit= (default 50, max 500) &cursor= (opaque). Response { "items": [...], "nextCursor": "…" | null } + Link: <…>; rel="next". No total counts by default (?count=true where cheap).
  • Filters as plain query params named after fields (?type=video&tag=intro), ?q= full text where supported, ?sort=field,-other. GraphQL uses Relay connections (first, after, edges, pageInfo).
  • Every resource representation carries ETag (strong, derived from a revision integer also present in the body). PUT / PATCH / DELETE on venues, shows, playlists, timelines, cues, layouts, config require If-Match (428 otherwise); 412 on mismatch. Playback actions do not (last writer wins, ordered by server receive time).
  • Idempotency-Key header (UUID, 24 h retention, per principal) on all non-idempotent POSTs (create, actions, jobs, uploads creation). Replay returns the original status and body; different body with the same key → 409. WS / socket.io call messages use the envelope id as the idempotency key.
  • GET supports If-None-Match → 304. Asset / export downloads support Range (206) and ETag = sha256.
Mechanism Used by Notes
Authorization: Bearer <access JWT> users (Manager, Sphere) from POST /auth/session (local account) or an L7 token exchanged at POST /auth/token-exchange when the installation is linked to the cloud; 15 min access + rotating refresh
Authorization: Bearer fdk_<key> services, kiosks, gateways API keys, scoped, revocable, shown once
Pairing ecosystem apps (guest) POST /auth/pairings → 6-digit code shown in Manager / on the dome; operator approves → app gets a guest token; QR = freedome://pair?host=…&code=…
Anonymous GET /api/v1/info, /health, /ready configurable: an installation may allow anonymous guest (kiosk mode)
OSC / serial / raw TCP controllers no tokens; per-listener CIDR allowlist mapped to a fixed scope set

Scopes (roles = scope bundles; from flutter_freedome FreeDomeUserRole / FreeDomePermission, models/dome_models.dart:50-65):

Scope guest operator admin superadmin
info:read, events:subscribe, playback:read, content:read ✓ ✓ ✓ ✓
playback:control (play / pause / volume / brightness), apps:publish ✓ (if venue allows) ✓ ✓ ✓
content:write (assets, shows, uploads, imports) ✓ ✓ ✓
audio:control, displays:control (patterns, power) ✓ ✓ ✓
calibration:run, audio:calibrate, venues:write ✓ ✓
gateways:manage, system:read, users:manage, keys:manage ✓ ✓
system:admin (reboot, update, restore, engine restart, config) ✓

Every OpenAPI operation declares security with its scope; GraphQL fields carry @auth(scope: "…"); WS / socket.io / OSC operations are checked against the same table via parity.yaml.

RateLimit-Policy / RateLimit headers (IETF draft) on 429; defaults per principal configurable. Body limit for JSON 4 MB; media go through uploads — the current 2 MB body limit and lost extension are replaced.

8. How this repository enforces the conventions

Section titled “8. How this repository enforces the conventions”
Convention Enforced by
camelCase, unique operationId .spectral.yaml freedome-operation-id-camel-case, operation-operationId-unique
security + scope on every operation freedome-operation-security: security present (anonymous = [{}]) and x-scope = anon | any | <area>:<action>
errors are application/problem+json freedome-problem-json (every 4xx, 5xx, 4XX, 5XX, default response); Redocly operation-4xx-problem-details-rfc7807
limit / cursor on collections freedome-collection-pagination (GET with x-collection: true or a 200 body {items, nextCursor})
examples on every JSON body freedome-examples (requests and 2xx responses)
If-Match on PUT / PATCH / DELETE freedome-if-match; playback-style actions opt out with x-concurrency: none
Idempotency-Key on POST freedome-idempotency-key; idempotent-by-nature POSTs (e.g. /auth/session) opt out with x-idempotency: none
JSON Schema 2020-12, $id = published URL tools/schemas/test.mjs ($id must equal https://freedome.nativemind.us/schemas/<path>)
licence header tools/hygiene/licence-check.mjs (NativeMindNONC in the first 5 lines; schemas: top-level $comment)

Negative fixtures for each Spectral rule: tools/tests/spectral/ (make lint-tests).