Files
soroush.asadi 90ac0b81d1 feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file,
render, notification, gateway (Go) services with vendored deps, plus DB
migrations, event/API contracts, and an init-db script.

Wire the Next.js frontend to the gateway: server-side JWT auth routes
(login/register/refresh/logout/me), gateway fetch helper, and session/
cookie/jwt helpers under src/lib.

Containerize the stack via docker-compose.v2.yml and per-service
Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and
MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via
next/font/local to avoid Google Fonts (geo-blocked).

Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:29:31 +03:30

220 lines
9.1 KiB
YAML

# =====================================================================
# Render Events — published by Render Orchestrator
# Routing: flatrender.events (topic) — key: render.*.v1
# =====================================================================
$schema: "http://json-schema.org/draft-07/schema#"
title: Render Event Schemas
type: object
definitions:
EventEnvelope:
type: object
required: [event_id, event_type, event_time, tenant_id, data]
properties:
event_id: { type: string, format: uuid }
event_type: { type: string }
event_time: { type: string, format: date-time }
tenant_id: { type: string, format: uuid }
user_id: { type: string, format: uuid }
trace_id: { type: string, format: uuid }
correlation_id: { type: string, format: uuid }
producer: { type: string, const: render-orchestrator }
data: { type: object }
events:
# -------------------------------------------------------------------
# render.job.queued.v1
# Emitted when a job is accepted into the queue.
# Consumed by: notification (none yet), analytics
# -------------------------------------------------------------------
render.job.queued.v1:
routing_key: render.job.queued.v1
description: New render job has been accepted into a priority queue.
payload:
type: object
required: [render_job_id, saved_project_id, priority_queue, price_type, region]
properties:
render_job_id: { type: string, format: uuid }
saved_project_id: { type: string, format: uuid }
original_project_id: { type: string, format: uuid }
priority_queue:
type: string
enum: [snapshot, vip, paid, preview, mockup, voiceover]
priority_score: { type: integer, minimum: 0, maximum: 100 }
price_type:
type: string
enum: [Free, Preview, Cash, Plan, Snapshot, Reseller]
paid_price_minor: { type: integer, format: int64 }
region: { type: string }
duration_sec: { type: number }
resolution: { type: string }
is_60_fps: { type: boolean }
has_music: { type: boolean }
has_sfx: { type: boolean }
has_voiceover: { type: boolean }
# -------------------------------------------------------------------
# render.job.started.v1
# Emitted once nodes are assigned and rendering begins.
# -------------------------------------------------------------------
render.job.started.v1:
routing_key: render.job.started.v1
description: Render job has begun processing on at least one node.
payload:
type: object
required: [render_job_id, started_at]
properties:
render_job_id: { type: string, format: uuid }
started_at: { type: string, format: date-time }
node_ids:
type: array
items: { type: string, format: uuid }
region: { type: string }
total_frames: { type: integer }
# -------------------------------------------------------------------
# render.job.progress.v1 (high-frequency, optional pub/sub)
# NOT broadcast on the main events exchange — only on per-job
# ephemeral fanout for WebSocket fan-out. Schema documented here.
# -------------------------------------------------------------------
render.job.progress.v1:
routing_key: render.job.progress.{job_id}
description: Progress tick for live UI update.
exchange: render.progress # separate dedicated exchange (auto-delete fanout)
payload:
type: object
required: [render_job_id, step, progress]
properties:
render_job_id: { type: string, format: uuid }
step:
type: string
enum: [Queued, Preparing, TemplateCache, JsxGen, Music,
Rendering, Validating, Repairing, Optimisation, Video,
Mixing, Final, Uploading, Done, Failed, Cancelled]
progress: { type: integer, minimum: 0, maximum: 100 }
current_frame: { type: integer, nullable: true }
total_frames: { type: integer, nullable: true }
eta_seconds: { type: integer, nullable: true }
preview_b64:
type: string
nullable: true
description: Last rendered frame thumbnail (small, ~5-15 KB)
message: { type: string, nullable: true }
# -------------------------------------------------------------------
# render.job.completed.v1
# Consumed by: notification, studio (mark project), file (cleanup),
# tenant (usage metering), webhook dispatcher
# -------------------------------------------------------------------
render.job.completed.v1:
routing_key: render.job.completed.v1
description: Render job successfully produced an export.
payload:
type: object
required: [render_job_id, export_id, output_url, duration_sec, size_bytes]
properties:
render_job_id: { type: string, format: uuid }
saved_project_id: { type: string, format: uuid }
original_project_id: { type: string, format: uuid }
export_id: { type: string, format: uuid }
output_url: { type: string }
thumbnail_url: { type: string, nullable: true }
duration_sec: { type: number }
size_bytes: { type: integer, format: int64 }
resolution: { type: string }
width: { type: integer }
height: { type: integer }
render_compute_seconds: { type: integer, description: "Sum of node-seconds spent" }
node_ids_used:
type: array
items: { type: string, format: uuid }
price_type: { type: string }
paid_price_minor: { type: integer, format: int64 }
# -------------------------------------------------------------------
# render.job.failed.v1
# Consumed by: notification, identity (refund), tenant (metering)
# -------------------------------------------------------------------
render.job.failed.v1:
routing_key: render.job.failed.v1
description: Render job failed permanently (after retries exhausted).
payload:
type: object
required: [render_job_id, failed_at_step, error_message]
properties:
render_job_id: { type: string, format: uuid }
saved_project_id: { type: string, format: uuid }
failed_at_step:
type: string
enum: [Queued, Preparing, TemplateCache, JsxGen, Music,
Rendering, Validating, Repairing, Optimisation, Video,
Mixing, Final, Uploading, Done, Failed, Cancelled]
error_message: { type: string }
error_code: { type: string, nullable: true }
retry_count: { type: integer }
refund_required: { type: boolean, default: true }
price_type: { type: string }
paid_price_minor: { type: integer, format: int64 }
node_ids_attempted:
type: array
items: { type: string, format: uuid }
# -------------------------------------------------------------------
# render.job.cancelled.v1
# User cancelled via UI before completion.
# -------------------------------------------------------------------
render.job.cancelled.v1:
routing_key: render.job.cancelled.v1
description: Render job was cancelled by user.
payload:
type: object
required: [render_job_id, cancelled_by_user_id]
properties:
render_job_id: { type: string, format: uuid }
cancelled_by_user_id: { type: string, format: uuid }
cancelled_at_step: { type: string }
progress_when_cancelled: { type: integer }
refund_required: { type: boolean, default: true }
# -------------------------------------------------------------------
# render.snapshot.requested.v1
# User asked for a single-frame snapshot of a scene.
# -------------------------------------------------------------------
render.snapshot.requested.v1:
routing_key: render.snapshot.requested.v1
description: Scene snapshot was queued.
payload:
type: object
required: [snapshot_id, saved_project_id, scene_key, frame_number]
properties:
snapshot_id: { type: string, format: uuid }
saved_project_id: { type: string, format: uuid }
scene_key: { type: string }
frame_number: { type: integer }
cached:
type: boolean
description: True if served from cache (no render needed)
# -------------------------------------------------------------------
# render.snapshot.ready.v1
# Single-frame snapshot finished.
# -------------------------------------------------------------------
render.snapshot.ready.v1:
routing_key: render.snapshot.ready.v1
description: Snapshot image is ready.
payload:
type: object
required: [snapshot_id, image_url]
properties:
snapshot_id: { type: string, format: uuid }
saved_project_id: { type: string, format: uuid }
scene_key: { type: string }
frame_number: { type: integer }
image_url: { type: string }
thumbnail_url: { type: string, nullable: true }
width: { type: integer }
height: { type: integer }
size_bytes: { type: integer }
duration_ms: { type: integer }
expires_at: { type: string, format: date-time }