Files
flatrender/backend/contracts/rest/studio.openapi.yaml
T
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

662 lines
21 KiB
YAML

openapi: 3.0.3
info:
title: FlatRender Studio Service (internal)
version: 1.0.0
description: |
User's saved projects (the editor's state). Includes voiceover +
audio mix settings.
servers:
- url: http://studio-svc.internal/v1
security:
- BearerAuth: []
- ServiceToken: []
tags:
- name: SavedProjects
- name: SavedScenes
- name: Audio
- name: Internal
paths:
/saved-projects:
get:
tags: [SavedProjects]
summary: List user's saved projects
parameters:
- { name: q, in: query, schema: { type: string } }
- { name: type, in: query, schema: { type: string, enum: [Draft, Active, Archived, Trash] } }
- { name: page, in: query, schema: { type: integer } }
- { name: page_size, in: query, schema: { type: integer } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/SavedProjectSummary' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
post:
tags: [SavedProjects]
summary: Create new saved project from a template
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [original_project_id]
properties:
original_project_id: { type: string, format: uuid }
name: { type: string }
preset_story_id: { type: string, format: uuid }
copy_default_values: { type: boolean, default: true }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProject' }
/saved-projects/{id}:
get:
tags: [SavedProjects]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProjectFull' }
patch:
tags: [SavedProjects]
summary: Update top-level fields (name, audio, etc.)
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProjectUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProject' }
delete:
tags: [SavedProjects]
summary: Soft-delete (move to trash)
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Trashed }
/saved-projects/{id}/restore:
post:
tags: [SavedProjects]
summary: Restore from trash
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProject' }
/saved-projects/{id}/duplicate:
post:
tags: [SavedProjects]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
content:
application/json:
schema:
type: object
properties:
new_name: { type: string }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProject' }
/saved-projects/{id}/autosave:
put:
tags: [SavedProjects]
summary: Autosave entire project graph (debounced from UI)
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProjectFull' }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
saved_at: { type: string, format: date-time }
version: { type: integer }
# ===================== AUDIO (NEW) =====================
/saved-projects/{id}/audio:
get:
tags: [Audio]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AudioSettings' }
put:
tags: [Audio]
summary: Update audio mix
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AudioSettings' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AudioSettings' }
/saved-projects/{id}/voiceover:
post:
tags: [Audio]
summary: |
Upload or record voiceover. Returns target file_id.
Use file service upload endpoints for actual binary; this
attaches an existing file to the project.
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [file_id]
properties:
file_id: { type: string, format: uuid, description: existing user_file_id }
recorded_in_browser: { type: boolean, default: false }
volume: { type: number, minimum: 0, maximum: 1, default: 1.0 }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AudioSettings' }
delete:
tags: [Audio]
summary: Remove voiceover
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Removed }
/saved-projects/{id}/music:
put:
tags: [Audio]
summary: Set music track (from library or upload)
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
music_track_id: { type: string, format: uuid }
music_file_id: { type: string, format: uuid }
volume: { type: number, minimum: 0, maximum: 1 }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AudioSettings' }
# ===================== SCENES =====================
/saved-projects/{id}/scenes:
get:
tags: [SavedScenes]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/SavedSceneFull' } }
post:
tags: [SavedScenes]
summary: Add a scene from project template
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [original_scene_id]
properties:
original_scene_id: { type: string, format: uuid }
sort: { type: integer }
scene_length_sec: { type: number }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedSceneFull' }
/saved-scenes/{scene_id}:
patch:
tags: [SavedScenes]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
scene_length_sec: { type: number }
manual_color_selection: { type: boolean }
selected_color_preset_id: { type: integer, format: int64 }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedScene' }
delete:
tags: [SavedScenes]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } }
responses:
'204': { description: Removed }
/saved-projects/{id}/scenes/reorder:
post:
tags: [SavedScenes]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [ordered_ids]
properties:
ordered_ids:
type: array
items: { type: integer, format: int64 }
responses:
'204': { description: Reordered }
/saved-scenes/{scene_id}/contents:
put:
tags: [SavedScenes]
summary: Bulk-update contents for a scene
parameters:
- { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contents:
type: array
items: { $ref: '#/components/schemas/SavedSceneContent' }
responses:
'200': { description: Updated }
/saved-scenes/{scene_id}/colors:
put:
tags: [SavedScenes]
summary: Bulk-update colors for a scene
parameters:
- { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
colors:
type: array
items: { $ref: '#/components/schemas/SavedSceneColor' }
responses:
'200': { description: Updated }
/saved-projects/{id}/shared-colors:
put:
tags: [SavedProjects]
summary: Bulk update project-level shared colors
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
colors:
type: array
items: { $ref: '#/components/schemas/SavedSharedColor' }
responses:
'200': { description: Updated }
/saved-projects/{id}/shared-layers:
put:
tags: [SavedProjects]
summary: Bulk update project-level shared layers
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
layers:
type: array
items: { $ref: '#/components/schemas/SavedSharedLayer' }
responses:
'200': { description: Updated }
# ===================== INTERNAL =====================
/internal/saved-projects/{id}/snapshot-for-render:
get:
tags: [Internal]
summary: |
Called by Render Orchestrator to get the full JSX-ready payload.
Returns everything needed to generate JSX (FIX/FLEXIBLE/Mockup/MV).
security: [ServiceToken: []]
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SavedProjectSnapshot' }
components:
securitySchemes:
BearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
ServiceToken: { type: http, scheme: bearer }
schemas:
PaginationMeta:
type: object
properties:
page: { type: integer }
page_size: { type: integer }
total: { type: integer }
has_more: { type: boolean }
SavedProjectSummary:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
image: { type: string }
type: { type: string }
original_project_id: { type: string, format: uuid }
original_project_name: { type: string }
original_container_slug: { type: string }
choose_mode: { type: string }
resolution: { type: string }
project_duration_sec: { type: number }
scene_count: { type: integer }
last_edit_date: { type: string, format: date-time }
created_at: { type: string, format: date-time }
SavedProject:
allOf:
- $ref: '#/components/schemas/SavedProjectSummary'
- type: object
properties:
frame_rate: { type: integer }
vip_factor: { type: number }
manual_color_picker: { type: boolean }
selected_preset_story_id: { type: string, format: uuid, nullable: true }
audio: { $ref: '#/components/schemas/AudioSettings' }
SavedProjectUpdate:
type: object
properties:
name: { type: string }
type: { type: string, enum: [Draft, Active, Archived, Trash] }
manual_color_picker: { type: boolean }
selected_preset_story_id: { type: string, format: uuid, nullable: true }
last_edit_step: { type: string }
SavedProjectFull:
allOf:
- $ref: '#/components/schemas/SavedProject'
- type: object
properties:
scenes: { type: array, items: { $ref: '#/components/schemas/SavedSceneFull' } }
shared_colors: { type: array, items: { $ref: '#/components/schemas/SavedSharedColor' } }
shared_color_presets: { type: array, items: { $ref: '#/components/schemas/SavedSharedColorPreset' } }
shared_layers: { type: array, items: { $ref: '#/components/schemas/SavedSharedLayer' } }
AudioSettings:
type: object
properties:
music_track_id: { type: string, format: uuid, nullable: true }
music_file_id: { type: string, format: uuid, nullable: true }
music_url: { type: string, nullable: true }
music_duration_sec: { type: number, nullable: true }
music_volume: { type: number, minimum: 0, maximum: 1 }
voiceover_file_id: { type: string, format: uuid, nullable: true }
voiceover_url: { type: string, nullable: true }
voiceover_duration_sec: { type: number, nullable: true }
voiceover_volume: { type: number, minimum: 0, maximum: 1 }
voiceover_recorded_in_browser: { type: boolean }
sfx_enabled: { type: boolean }
sfx_volume: { type: number, minimum: 0, maximum: 1 }
SavedScene:
type: object
properties:
id: { type: integer, format: int64 }
saved_project_id: { type: string, format: uuid }
original_scene_id: { type: string, format: uuid }
key: { type: string }
title: { type: string }
image: { type: string }
demo: { type: string }
scene_type: { type: string }
sort: { type: integer }
scene_length_sec: { type: number }
min_duration_sec: { type: number }
max_duration_sec: { type: number }
overlap_at_end_sec: { type: number }
manual_color_selection: { type: boolean }
SavedSceneFull:
allOf:
- $ref: '#/components/schemas/SavedScene'
- type: object
properties:
contents: { type: array, items: { $ref: '#/components/schemas/SavedSceneContent' } }
colors: { type: array, items: { $ref: '#/components/schemas/SavedSceneColor' } }
color_presets: { type: array, items: { $ref: '#/components/schemas/SavedSceneColorPreset' } }
characters: { type: array, items: { $ref: '#/components/schemas/SavedSceneCharacter' } }
SavedSceneContent:
type: object
properties:
id: { type: integer, format: int64 }
key: { type: string }
type: { type: string }
value: { type: string }
value_file_id: { type: string, format: uuid, nullable: true }
file_url_cached: { type: string, nullable: true }
inserted_file_type: { type: string, nullable: true }
font_face: { type: string, nullable: true }
font_size: { type: integer, nullable: true }
justify: { type: string }
position_in_container: { type: integer }
direction_layer_value: { type: integer }
is_text_box: { type: boolean }
ai_input_type: { type: string, nullable: true }
selected_dp: { type: integer, nullable: true }
repeater_item_key: { type: string, nullable: true }
repeater_index: { type: integer, nullable: true }
sort: { type: integer }
SavedSceneColor:
type: object
properties:
id: { type: integer, format: int64 }
element_key: { type: string }
title: { type: string }
icon: { type: string }
attr_value: { type: string }
value: { type: string }
is_selected: { type: boolean }
sort: { type: integer }
SavedSceneColorPreset:
type: object
properties:
id: { type: integer, format: int64 }
is_selected: { type: boolean }
sort: { type: integer }
items:
type: array
items:
type: object
properties:
element_key: { type: string }
value: { type: string }
sort: { type: integer }
SavedSceneCharacter:
type: object
properties:
id: { type: integer, format: int64 }
key: { type: string, format: uuid }
name: { type: string }
icon: { type: string }
controllers:
type: array
items:
type: object
properties:
name: { type: string }
key: { type: string }
value: { type: string }
sort: { type: integer }
SavedSharedColor:
type: object
properties:
id: { type: integer, format: int64 }
element_key: { type: string }
title: { type: string }
attr_value: { type: string }
value: { type: string }
is_selected: { type: boolean }
sort: { type: integer }
SavedSharedColorPreset:
type: object
properties:
id: { type: integer, format: int64 }
name: { type: string }
is_selected: { type: boolean }
sort: { type: integer }
items:
type: array
items:
type: object
properties:
element_key: { type: string }
value: { type: string }
SavedSharedLayer:
type: object
properties:
id: { type: integer, format: int64 }
key: { type: string }
title: { type: string }
type: { type: string }
value: { type: string }
value_file_id: { type: string, format: uuid, nullable: true }
file_url_cached: { type: string, nullable: true }
font_face: { type: string }
font_size: { type: integer }
justify: { type: string }
position_in_container: { type: integer }
direction_layer_value: { type: integer }
is_text_box: { type: boolean }
sort: { type: integer }
SavedProjectSnapshot:
type: object
description: |
Complete payload for JSX generation. Same shape returned for any
choose_mode; render service decides which JSX generator to use.
properties:
saved_project_id: { type: string, format: uuid }
tenant_id: { type: string, format: uuid }
user_id: { type: string, format: uuid }
original_project_id: { type: string, format: uuid }
original_project_name: { type: string }
choose_mode: { type: string }
resolution: { type: string }
frame_rate: { type: integer }
project_duration_sec: { type: number }
vip_factor: { type: number }
aep:
type: object
properties:
url: { type: string }
md5: { type: string }
size_bytes: { type: integer, format: int64 }
render_comp: { type: string }
original_width: { type: integer }
original_height: { type: integer }
audio: { $ref: '#/components/schemas/AudioSettings' }
shared_colors:
type: array
items: { $ref: '#/components/schemas/SavedSharedColor' }
shared_layers:
type: array
items: { $ref: '#/components/schemas/SavedSharedLayer' }
scenes:
type: array
items: { $ref: '#/components/schemas/SavedSceneFull' }