90ac0b81d1
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>
1415 lines
44 KiB
YAML
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 }
|