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

1415 lines
44 KiB
YAML

openapi: 3.0.3
info:
title: FlatRender Content Service (internal)
version: 1.0.0
description: |
Templates (project_containers + projects), scenes, content elements,
fonts, music, categories, tags, CMS (blogs, slides), SVG previews.
servers:
- url: http://content-svc.internal/v1
security:
- BearerAuth: []
- ServiceToken: []
tags:
- name: Templates # project_containers + projects (catalog view)
- name: Projects # internal project (AE) management
- name: Scenes
- name: Elements # content/color/character/shared layers
- name: Presets # preset_stories + color presets + character presets
- name: Repeaters
- name: Fonts
- name: Music
- name: Categories
- name: Tags
- name: SVGPreview
- name: Favorites
- name: CMS
- name: ProjectServers
paths:
# ===================== TEMPLATES (catalog) =====================
/templates:
get:
tags: [Templates]
summary: Browse published templates
parameters:
- { name: q, in: query, schema: { type: string } }
- { name: category_id, in: query, schema: { type: string, format: uuid } }
- { name: tag_ids, in: query, schema: { type: array, items: { type: string, format: uuid } }, style: form, explode: true }
- { name: mode, in: query, schema: { type: string } }
- { name: aspect, in: query, schema: { type: string, enum: ['16:9','9:16','1:1','4:5'] } }
- { name: is_premium, in: query, schema: { type: boolean } }
- { name: sort, in: query, schema: { type: string, enum: [newest, popular, rate, az] } }
- { name: page, in: query, schema: { type: integer, default: 1 } }
- { name: page_size, in: query, schema: { type: integer, default: 20 } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/TemplateCard' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
/templates/{slug_or_id}:
get:
tags: [Templates]
summary: Template detail (container + its projects)
parameters:
- { name: slug_or_id, in: path, required: true, schema: { type: string } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/TemplateDetail' }
'404': { description: Not found }
/templates/{container_id}/projects:
get:
tags: [Templates]
parameters:
- { name: container_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/ProjectSummary' } }
/templates/{container_id}/related:
get:
tags: [Templates]
summary: Related templates (by category/tag)
parameters:
- { name: container_id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: limit, in: query, schema: { type: integer, default: 8 } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/TemplateCard' } }
# ===================== Containers (admin CRUD) =====================
/containers:
post:
tags: [Templates]
summary: (Admin) Create container
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ContainerCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Container' }
/containers/{container_id}:
patch:
tags: [Templates]
summary: (Admin) Update container
parameters:
- { name: container_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ContainerUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Container' }
delete:
tags: [Templates]
parameters:
- { name: container_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted (soft) }
/containers/{container_id}/publish:
post:
tags: [Templates]
summary: Publish a container
parameters:
- { name: container_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Published }
/containers/{container_id}/unpublish:
post:
tags: [Templates]
parameters:
- { name: container_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
content:
application/json:
schema:
type: object
properties:
reason: { type: string }
responses:
'204': { description: Unpublished }
# ===================== Projects (admin) =====================
/projects:
post:
tags: [Projects]
summary: (Admin) Create project variant
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Project' }
/projects/{project_id}:
get:
tags: [Projects]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Project' }
patch:
tags: [Projects]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Project' }
delete:
tags: [Projects]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
/projects/{project_id}/full:
get:
tags: [Projects]
summary: Full project graph for editor (scenes + elements + presets)
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/ProjectFull' }
/projects/{project_id}/aep:
put:
tags: [Projects]
summary: (Admin) Upload AEP file (multipart) -> stored in MinIO
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file: { type: string, format: binary }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aep_file_url: { type: string }
aep_file_md5: { type: string }
aep_file_size_bytes: { type: integer, format: int64 }
/projects/{project_id}/aep/start-upload:
post:
tags: [Projects]
summary: Start multipart upload of large AEP (300MB-3GB)
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [filename, total_size_bytes]
properties:
filename: { type: string }
total_size_bytes: { type: integer, format: int64 }
responses:
'201':
content:
application/json:
schema:
type: object
properties:
upload_id: { type: string }
bucket: { type: string }
key: { type: string }
chunk_size_bytes: { type: integer }
part_urls:
type: array
items:
type: object
properties:
part_number: { type: integer }
url: { type: string }
/projects/{project_id}/aep/complete-upload:
post:
tags: [Projects]
summary: Complete multipart AEP upload (server hashes + registers)
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [upload_id, parts]
properties:
upload_id: { type: string }
parts:
type: array
items:
type: object
properties:
part_number: { type: integer }
etag: { type: string }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
aep_file_md5: { type: string }
aep_file_size_bytes: { type: integer, format: int64 }
# ===================== SCENES =====================
/projects/{project_id}/scenes:
get:
tags: [Scenes]
parameters:
- { name: project_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/Scene' } }
post:
tags: [Scenes]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Scene' }
/scenes/{scene_id}:
get:
tags: [Scenes]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SceneFull' }
patch:
tags: [Scenes]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Scene' }
delete:
tags: [Scenes]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
/scenes/{scene_id}/reorder:
post:
tags: [Scenes]
summary: Bulk reorder scenes
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ordered_ids:
type: array
items: { type: string, format: uuid }
responses:
'204': { description: Reordered }
# ===================== ELEMENTS =====================
/scenes/{scene_id}/elements:
get:
tags: [Elements]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
contents: { type: array, items: { $ref: '#/components/schemas/SceneContentElement' } }
colors: { type: array, items: { $ref: '#/components/schemas/SceneColorElement' } }
characters: { type: array, items: { $ref: '#/components/schemas/SceneCharacter' } }
repeaters: { type: array, items: { $ref: '#/components/schemas/RepeaterItem' } }
/scenes/{scene_id}/contents:
post:
tags: [Elements]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneContentElement' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SceneContentElement' }
/scene-contents/{element_id}:
patch:
tags: [Elements]
parameters:
- { name: element_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneContentElement' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SceneContentElement' }
delete:
tags: [Elements]
parameters:
- { name: element_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
/scenes/{scene_id}/colors:
post:
tags: [Elements]
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneColorElement' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SceneColorElement' }
/scene-colors/{element_id}:
patch:
tags: [Elements]
parameters:
- { name: element_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SceneColorElement' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SceneColorElement' }
delete:
tags: [Elements]
parameters:
- { name: element_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
/projects/{project_id}/shared-layers:
get:
tags: [Elements]
parameters:
- { name: project_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/SharedLayer' } }
post:
tags: [Elements]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SharedLayer' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/SharedLayer' }
/projects/{project_id}/shared-colors:
get:
tags: [Elements]
parameters:
- { name: project_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/SharedColor' } }
# ===================== PRESETS =====================
/projects/{project_id}/preset-stories:
get:
tags: [Presets]
parameters:
- { name: project_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/PresetStory' } }
post:
tags: [Presets]
parameters:
- { name: project_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/PresetStoryCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/PresetStory' }
/preset-stories/{preset_story_id}:
get:
tags: [Presets]
parameters:
- { name: preset_story_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/PresetStoryFull' }
# ===================== SVG PREVIEW (color picker) =====================
/svg-previews/generate:
post:
tags: [SVGPreview]
summary: |
Generate SVG color preview by dropping an image.
Calls AI Service. Returns generated SVG with named color zones.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [image, target]
properties:
image: { type: string, format: binary }
target_type: { type: string, enum: [project, scene] }
target_id: { type: string, format: uuid }
color_keys:
type: array
description: List of color element keys to detect
items:
type: object
properties:
element_key: { type: string }
current_color: { type: string }
responses:
'202':
description: Accepted; generation async, event content.svg_preview.generated.v1 fired
content:
application/json:
schema:
type: object
properties:
svg_preview_id: { type: string, format: uuid }
status: { type: string, enum: [Generating] }
/svg-previews/{svg_preview_id}:
get:
tags: [SVGPreview]
parameters:
- { name: svg_preview_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SvgPreview' }
/scenes/{scene_id}/svg-preview:
get:
tags: [SVGPreview]
summary: Get current SVG preview for a scene
parameters:
- { name: scene_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/SvgPreview' }
'404': { description: No preview generated yet }
# ===================== FONTS =====================
/fonts:
get:
tags: [Fonts]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Font' } }
post:
tags: [Fonts]
summary: (Admin) Upload font + register
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file: { type: string, format: binary }
name: { type: string }
family: { type: string }
direction: { type: string }
is_premium: { type: boolean }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Font' }
/fonts/{font_id}:
delete:
tags: [Fonts]
parameters:
- { name: font_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
# ===================== MUSIC =====================
/music:
get:
tags: [Music]
parameters:
- { name: q, in: query, schema: { type: string } }
- { name: genre, in: query, schema: { type: string } }
- { name: mood, in: query, schema: { type: string } }
- { name: min_duration, in: query, schema: { type: number } }
- { name: max_duration, in: query, schema: { type: number } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/MusicTrack' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
post:
tags: [Music]
summary: (Admin) Add music track
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/MusicTrack' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/MusicTrack' }
# ===================== CATEGORIES =====================
/categories:
get:
tags: [Categories]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Category' } }
post:
tags: [Categories]
summary: (Admin) Create
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Category' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Category' }
/tags:
get:
tags: [Tags]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Tag' } }
# ===================== FAVORITES =====================
/favorites/folders:
get:
tags: [Favorites]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/FavoriteFolder' } }
post:
tags: [Favorites]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
description: { type: string }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/FavoriteFolder' }
/favorites/containers:
post:
tags: [Favorites]
summary: Add template to favorites
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [container_id]
properties:
container_id: { type: string, format: uuid }
folder_id: { type: string, format: uuid }
note: { type: string }
responses:
'201': { description: Added }
delete:
tags: [Favorites]
parameters:
- { name: container_id, in: query, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Removed }
# ===================== CMS =====================
/blogs:
get:
tags: [CMS]
parameters:
- { name: q, in: query, schema: { type: string } }
- { name: page, in: query, schema: { type: integer } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Blog' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
/blogs/{slug}:
get:
tags: [CMS]
parameters:
- { name: slug, in: path, required: true, schema: { type: string } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Blog' }
/comments:
post:
tags: [CMS]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [content]
properties:
content: { type: string }
rate: { type: number }
blog_id: { type: string, format: uuid }
container_id: { type: string, format: uuid }
parent_comment_id: { type: string, format: uuid }
responses:
'201': { description: Created (pending approval) }
/slides:
get:
tags: [CMS]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Slide' } }
/home-events:
get:
tags: [CMS]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/HomeEvent' } }
/settings:
get:
tags: [CMS]
summary: Tenant website settings (public subset)
responses:
'200':
content:
application/json:
schema:
type: object
additionalProperties: true
# ===================== PROJECT SERVERS =====================
/project-servers:
get:
tags: [ProjectServers]
summary: List render servers (for node assignment)
security: [ServiceToken: []]
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/ProjectServer' } }
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 }
TemplateCard:
type: object
properties:
container_id: { type: string, format: uuid }
slug: { type: string }
name: { type: string }
image: { type: string }
demo: { type: string }
mini_demo: { type: string }
is_premium: { type: boolean }
is_mockup: { type: boolean }
primary_mode: { type: string }
rate_avg: { type: number }
rate_count: { type: integer }
use_count: { type: integer, format: int64 }
available_aspects:
type: array
items: { type: string }
TemplateDetail:
type: object
properties:
container: { $ref: '#/components/schemas/Container' }
projects: { type: array, items: { $ref: '#/components/schemas/ProjectSummary' } }
categories: { type: array, items: { $ref: '#/components/schemas/Category' } }
tags: { type: array, items: { $ref: '#/components/schemas/Tag' } }
Container:
type: object
properties:
id: { type: string, format: uuid }
slug: { type: string }
name: { type: string }
description: { type: string }
keywords: { type: string }
news_text: { type: string }
image: { type: string }
demo: { type: string }
full_demo: { type: string }
mini_demo: { type: string }
is_published: { type: boolean }
is_premium: { type: boolean }
is_mockup: { type: boolean }
primary_mode: { type: string }
rate_avg: { type: number }
rate_count: { type: integer }
use_count: { type: integer, format: int64 }
ContainerCreate:
type: object
required: [slug, name, primary_mode]
properties:
slug: { type: string }
name: { type: string }
description: { type: string }
primary_mode: { type: string }
is_premium: { type: boolean }
is_mockup: { type: boolean }
category_ids: { type: array, items: { type: string, format: uuid } }
tag_ids: { type: array, items: { type: string, format: uuid } }
ContainerUpdate:
allOf:
- $ref: '#/components/schemas/ContainerCreate'
ProjectSummary:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
aspect: { type: string }
original_width: { type: integer }
original_height: { type: integer }
resolution: { type: string }
project_duration_sec: { type: number }
choose_mode: { type: string }
image: { type: string }
Project:
allOf:
- $ref: '#/components/schemas/ProjectSummary'
- type: object
properties:
container_id: { type: string, format: uuid }
project_server_id: { type: string, format: uuid }
description: { type: string }
full_demo: { type: string }
download_link: { type: string }
aep_file_url: { type: string }
aep_file_md5: { type: string }
aep_file_size_bytes: { type: integer, format: int64 }
min_duration_sec: { type: number }
max_duration_sec: { type: number }
free_fps: { type: integer }
vip_factor: { type: number }
render_aep_comp: { type: string }
is_published: { type: boolean }
ProjectCreate:
type: object
required: [container_id, name, original_width, original_height, project_duration_sec, choose_mode]
properties:
container_id: { type: string, format: uuid }
name: { type: string }
original_width: { type: integer }
original_height: { type: integer }
aspect: { type: string }
project_duration_sec: { type: number }
min_duration_sec: { type: number }
max_duration_sec: { type: number }
choose_mode: { type: string }
resolution: { type: string }
free_fps: { type: integer }
vip_factor: { type: number }
render_aep_comp: { type: string }
project_server_id: { type: string, format: uuid }
ProjectUpdate:
allOf:
- $ref: '#/components/schemas/ProjectCreate'
ProjectFull:
type: object
properties:
project: { $ref: '#/components/schemas/Project' }
scenes: { type: array, items: { $ref: '#/components/schemas/SceneFull' } }
shared_colors: { type: array, items: { $ref: '#/components/schemas/SharedColor' } }
shared_layers: { type: array, items: { $ref: '#/components/schemas/SharedLayer' } }
character_controllers: { type: array, items: { $ref: '#/components/schemas/CharacterController' } }
character_presets: { type: array, items: { $ref: '#/components/schemas/CharacterPreset' } }
preset_stories: { type: array, items: { $ref: '#/components/schemas/PresetStory' } }
Scene:
type: object
properties:
id: { type: string, format: uuid }
project_id: { type: string, format: uuid }
key: { type: string }
title: { type: string }
localized_title: { type: object, additionalProperties: { type: string } }
scene_type: { type: string }
image: { type: string }
demo: { type: string }
snapshot_url: { type: string }
default_duration_sec: { type: number }
min_duration_sec: { type: number }
max_duration_sec: { type: number }
overlap_at_end_sec: { type: number }
manual_color_selection: { type: boolean }
sort: { type: integer }
SceneCreate:
type: object
required: [key, title]
properties:
key: { type: string }
title: { type: string }
scene_type: { type: string }
default_duration_sec: { type: number }
sort: { type: integer }
SceneUpdate:
type: object
properties:
title: { type: string }
default_duration_sec: { type: number }
sort: { type: integer }
manual_color_selection: { type: boolean }
SceneFull:
allOf:
- $ref: '#/components/schemas/Scene'
- type: object
properties:
contents: { type: array, items: { $ref: '#/components/schemas/SceneContentElement' } }
colors: { type: array, items: { $ref: '#/components/schemas/SceneColorElement' } }
color_presets: { type: array, items: { $ref: '#/components/schemas/SceneColorPreset' } }
characters: { type: array, items: { $ref: '#/components/schemas/SceneCharacter' } }
repeaters: { type: array, items: { $ref: '#/components/schemas/RepeaterItem' } }
SceneContentElement:
type: object
properties:
id: { type: string, format: uuid }
scene_id: { type: string, format: uuid }
repeater_item_id: { type: string, format: uuid, nullable: true }
key: { type: string }
title: { type: string }
localized_title: { type: object, additionalProperties: { type: string } }
hint: { type: string }
type: { type: string }
default_value: { type: string }
font_id: { type: string, format: uuid, nullable: true }
font_face: { type: string }
font_size: { type: integer }
justify: { type: string }
position_in_container: { type: integer }
is_text_box: { type: boolean }
max_size: { type: integer, nullable: true }
direction_layer_key: { type: string }
direction_layer_value: { type: integer }
video_support: { type: boolean }
mapped_list: { type: array, items: { type: object } }
ai_input_type: { type: string }
is_hidden: { type: boolean }
opacity_controller_key: { type: string }
virtual_count: { type: integer }
sort: { type: integer }
SceneColorElement:
type: object
properties:
id: { type: string, format: uuid }
scene_id: { type: string, format: uuid }
element_key: { type: string }
title: { type: string }
icon: { type: string }
attr_value: { type: string, enum: [fill, stroke, tracking, dropshadow] }
default_color: { type: string }
sort: { type: integer }
SceneColorPreset:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
sort: { type: integer }
items:
type: array
items:
type: object
properties:
element_key: { type: string }
value: { type: string }
sort: { type: integer }
SceneCharacter:
type: object
properties:
id: { type: string, format: uuid }
key: { type: string }
name: { type: string }
icon: { type: string }
controllers:
type: array
items:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
key: { type: string }
default_value: { type: string }
options:
type: array
items:
type: object
properties:
name: { type: string }
icon: { type: string }
value: { type: string }
SharedLayer:
type: object
properties:
id: { type: string, format: uuid }
project_id: { type: string, format: uuid }
key: { type: string }
title: { type: string }
type: { type: string }
default_value: { type: string }
font_face: { type: string }
font_size: { type: integer }
justify: { type: string }
position_in_container: { type: integer }
is_text_box: { type: boolean }
sort: { type: integer }
SharedColor:
type: object
properties:
id: { type: string, format: uuid }
project_id: { type: string, format: uuid }
element_key: { type: string }
title: { type: string }
icon: { type: string }
attr_value: { type: string }
default_color: { type: string }
sort: { type: integer }
RepeaterItem:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
repeat_box_key: { type: string }
repeat_item_key: { type: string }
max_repeat_count: { type: integer }
sort: { type: integer }
repeat_sort_strategy: { type: string }
CharacterController:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
key: { type: string }
sort: { type: integer }
options: { type: array, items: { type: object } }
CharacterPreset:
type: object
properties:
id: { type: string, format: uuid }
key: { type: string, format: uuid }
name: { type: string }
icon: { type: string }
sort: { type: integer }
controllers:
type: array
items:
type: object
properties:
name: { type: string }
key: { type: string }
value: { type: string }
PresetStory:
type: object
properties:
id: { type: string, format: uuid }
project_id: { type: string, format: uuid }
name: { type: string }
description: { type: string }
demo: { type: string }
music_id: { type: string, format: uuid }
sort: { type: integer }
is_published: { type: boolean }
scene_count: { type: integer }
PresetStoryCreate:
type: object
required: [name]
properties:
name: { type: string }
description: { type: string }
demo: { type: string }
music_id: { type: string, format: uuid }
scene_ids:
type: array
items: { type: string, format: uuid }
PresetStoryFull:
allOf:
- $ref: '#/components/schemas/PresetStory'
- type: object
properties:
scenes:
type: array
items:
allOf:
- $ref: '#/components/schemas/Scene'
- type: object
properties:
sort_in_story: { type: integer }
default_duration_sec: { type: number }
SvgPreview:
type: object
properties:
id: { type: string, format: uuid }
project_id: { type: string, format: uuid, nullable: true }
scene_id: { type: string, format: uuid, nullable: true }
source_image_url: { type: string }
svg_url: { type: string }
thumbnail_url: { type: string }
width: { type: integer }
height: { type: integer }
generated_by_ai: { type: boolean }
quality_score: { type: number }
color_zones:
type: array
items:
type: object
properties:
element_key: { type: string }
detected_color: { type: string }
bbox: { type: array, items: { type: number } }
created_at: { type: string, format: date-time }
Font:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
original_name: { type: string }
system_name: { type: string }
family: { type: string }
weight: { type: integer }
style: { type: string }
direction: { type: string }
file_url: { type: string }
sample_image_url: { type: string }
is_premium: { type: boolean }
installed_on_nodes: { type: boolean }
MusicTrack:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
caption: { type: string }
keywords: { type: string }
url: { type: string }
duration_sec: { type: number }
bpm: { type: integer }
genre: { type: string }
mood: { type: string }
is_premium: { type: boolean }
waveform_data: { type: object }
Category:
type: object
properties:
id: { type: string, format: uuid }
slug: { type: string }
name: { type: string }
description: { type: string }
image_url: { type: string }
icon: { type: string }
parent_id: { type: string, format: uuid, nullable: true }
sort: { type: integer }
Tag:
type: object
properties:
id: { type: string, format: uuid }
slug: { type: string }
name: { type: string }
latin_name: { type: string }
applies_to_mode: { type: string }
FavoriteFolder:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
description: { type: string }
item_count: { type: integer }
created_at: { type: string, format: date-time }
Blog:
type: object
properties:
id: { type: string, format: uuid }
slug: { type: string }
kind: { type: string }
title: { type: string }
short_description: { type: string }
content: { type: string }
image: { type: string }
cover: { type: string }
author_display_name: { type: string }
publish_date: { type: string, format: date-time }
view_count: { type: integer, format: int64 }
Slide:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
image: { type: string }
keyword: { type: string }
slide_type: { type: string }
parameter: { type: string }
sort: { type: integer }
HomeEvent:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
subtitle: { type: string }
description: { type: string }
button_text: { type: string }
button_url: { type: string }
badge: { type: string }
image: { type: string }
color: { type: string }
background_color: { type: string }
text_color: { type: string }
sort: { type: integer }
ProjectServer:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
region: { type: string }
ip: { type: string }
default_project_address: { type: string }
render_output_location: { type: string }
minio_endpoint: { type: string }
minio_bucket_templates: { type: string }
minio_bucket_outputs: { type: string }
is_active: { type: boolean }