- .gitea/workflows/ci-cd.yml: frontend tsc check → self-hosted deploy job that
builds the full compose stack and brings it up behind Caddy. Locks
COMPOSE_PROJECT_NAME=flatrender (stable volumes), backs up the DB before each
deploy, health-waits gateway+frontend, no `down -v`.
- Route all package installs through mirror.soroushasadi.com:
frontend Dockerfile npm registry → NPM_REGISTRY build arg (Nexus default);
3× NuGet.Config (content/identity/studio) → HTTPS nuget-group (were a bare IP).
- Harden host ports: ${HOST_BIND:-0.0.0.0} prefix on postgres/minio/render/gateway/
frontend so prod (HOST_BIND=127.0.0.1) keeps them off the public internet — only
Caddy 80/443 is public. Dev (unset → 0.0.0.0) unchanged.
- render-svc MINIO_USE_SSL now env-driven (MINIO_HOST_USE_SSL) for HTTPS storage domain.
- deploy/ENV_FILE.production.example (the Gitea secret template) + deploy/README.md
(one-time setup + go-live checklist).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
C2 — real-AE scene snapshots on the node:
- node-agent: runner/snapshot.go RunSnapshot (aerender -comp <key> -s f -e f
→ findRenderedOutput → ffmpeg -frames:v 1 PNG); client ClaimSnapshot /
GetSnapshotUploadURL / ReportSnapshotResult / ReportSnapshotFail; snapshotLoop +
pollSnapshotOnce mirroring the scan loop (reuses the AE-exclusive lock).
- render-svc: GetSnapshotJobMeta + UploadURL handler presigns a PUT to the
public-read user-uploads bucket at snapshots/{project}/{scene}.png and returns a
permanent public_url (not the time-limited export presign); MINIO_UPLOAD_BUCKET +
MINIO_PUBLIC_URL config + compose env + /snapshot/:id/upload-url route.
Epic B — bind edited colours into the render:
- render-svc GetRenderBindings UNIONs studio.saved_shared_colors +
saved_scene_colors (type 'color') so the node writes them before render.
- node-agent binder.go routes type:"color" bindings into the bind-spec colors[]
array that bind.jsx already applies to the frshare colour layers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-scene preview thumbnails for templates. Admin clicks "ساخت پیشنمایش
صحنهها" → one single-frame AE render per scene → content.scenes.snapshot_url
→ shown as a thumbnail in the admin scene list (and available to the studio).
- migration 30_render_snapshot_jobs.sql: render.snapshot_jobs (queued|running|
done|error, per scene, image_url).
- render-svc: db/snapshotjobs.go (EnqueueSceneSnapshots, List, Claim, SetResult
-> writes content.scenes.snapshot_url cross-schema, SetError); handlers/
snapshotjobs.go (admin POST/GET /v1/scene-snapshots/:project_id + node-facing
internal claim/result/fail); main.go routes; gateway route.
- devworker: RunSnapshots — fulfils snapshot jobs with a generated placeholder
PNG (data: URL, scene-key-tinted) so the flow is verifiable without an AE node.
Gated by RENDER_DEV_SNAPSHOTS (default off; never hijacks real render jobs).
- admin UI: ProjectScenes "generate snapshots" button (enqueue + poll + reload)
and a thumbnail (snapshot_url || image) per scene row.
Verified e2e via the dev mock: enqueue -> jobs run -> content.scenes.snapshot_url
populated -> scenes API returns it -> admin renders the thumbnail.
Remaining (C2): node-agent real-AE runner — claim snapshot, aerender -s0 -e0 ->
ffmpeg still -> upload to a PERMANENT URL (mirror file-svc, not the time-limited
export presign) -> post result. Needs a live AE node to build + verify.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render — "stuck in Queued" fix:
- Jobs were created Queued and only a Windows AE node could claim them, so in the
dev stack (no node) they queued forever.
- New devworker package: in-process mock worker drives Queued jobs through the steps
with progress + live preview frames → Done. Enabled via RENDER_DEV_WORKER (default
true in compose; set false in prod where real nodes claim jobs).
- db: DevClaimNextQueued (atomic oldest-queued → Preparing) + UpdateJobStepProgress
- Verified live: a stuck job advanced Preparing→Done in ~10s with frontend polling.
Studio — predefined template structure:
- Projects are always copied from a template; structure is fixed. Users customise
existing layers, they don't add new ones.
- New studio-config flag ALLOW_ADD_LAYERS (false): StudioToolbar (add text/image/
video/shape) returns null; SceneEditSidebar "add text layer" button hidden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Business rule: each user has a daily render limit. Admin-stop refunds the used
charge (not the user's fault); a user's own cancel does not.
- identity: ConsumeRenderChargeAsync / RefundRenderChargeAsync on DailyRemainRenderCount
with lazy daily reset (mig 24: daily_renders_reset_at). Convention: max=0 ⇒ UNLIMITED,
so existing 0/0 users keep rendering until an admin sets a real limit.
- identity InternalController (service-token): POST /v1/internal/render-charge/{consume,refund}
- render-svc: identityclient + on Create consume (block 429 when limit reached, fail-open
on identity outage); on admin Stop refund the job owner; user /cancel unchanged
- compose: IDENTITY_URL for render-svc, ServiceToken for identity-svc
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /admin/files Media Library: drag-drop multi-upload, thumbnails, copy-URL, delete
- FileUploadField replaces raw URL inputs; new "image" field type in AdminResource;
wired into category image
- upload proxy /api/admin/files/upload: browser → Next → presigned PUT (server-side,
reaches minio:9000) → confirm → returns public URL
- user-uploads bucket is public-read; public base via NEXT_PUBLIC_MINIO_URL
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend build args and runtime env no longer need NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET — all replaced by V2 gateway. .env.v2.example updated to
reflect the current V2-only config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PlansController had a class-level [Authorize] that gated the public
plans list, contradicting the gateway's optionalAuth on /plans. Mark
List/GetById [AllowAnonymous] and resolve tenant optionally so
anonymous callers receive global plans (purchase/current-plan stay
authenticated).
Frontend container stayed "unhealthy" because busybox wget resolves
localhost to IPv6 [::1] while the Next.js standalone server binds
IPv4 only. Use 127.0.0.1 in the healthcheck.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>