Commit Graph

59 Commits

Author SHA1 Message Date
soroush.asadi 4f04f6bf75 feat(render+templates): Remotion engine, 16 branded templates (incl. 3D), seconds pricing, coming-soon
CI/CD / CI · Web (tsc) (push) Successful in 1m21s
CI/CD / Deploy · full stack (push) Failing after 20s
Render engine
- Add Remotion (code-based) as a 2nd render engine alongside After Effects.
  node-agent dispatches on Job.Engine; RunRemotion maps bindings -> --props,
  renders native then ffmpeg-scales to the quality tier (aspect-preserving).
- content.projects.render_engine + render_remotion_comp (migration 32);
  render-svc claim resolves engine and routes (skips .aep for Remotion).
- Admin TemplatesAdmin gains an engine picker + Remotion composition id field.

Template pack (services/remotion)
- 16 branded, Persian (Vazirmatn), color- and text-editable templates, each in
  3 aspects (16:9 / 1:1 / 9:16): LogoMotion, Opener, InstaPromo, YouTubeIntro,
  Slideshow, HappyBirthday, SalePromo, QuoteCard, EventInvite, Countdown,
  GlitterReveal (editable logo image), NowruzGreeting (animated characters),
  and 4 cinematic 3D templates via @remotion/three (Hero3D, Nowruz3D,
  Birthday3D, Promo3D) with reflections + bloom/DOF/vignette.
- scripts/seed_remotion_templates.py seeds containers/projects/scenes/colors.

Pricing
- Rewrite /pricing to the seconds-based model (charge = length x resolution),
  data-driven from /v1/plans, Toman, broker checkout.

Coming-soon
- Persian experimental-build overlay on all pages (launch date + countdown).

Fixes
- middleware matcher bypasses all static asset paths; catalog mapping passes
  cover image + preview video so real thumbnails render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:52:52 +03:30
soroush.asadi 6d79ddb8d1 feat(render): real progress %, ETA, and frequent preview during AE renders
The render page already displayed progress/ETA/preview — but the node agent never
fed real data: aeRender used fake +5%/10s increments, discarded aerender stdout,
and pushed a preview only every 30s. (Plus the deployed agent predated even the
progress-reporting wiring.)

node-agent (aeRender):
- Capture aerender stdout; parse "(N):" current frame + "N frames"/"to N" total.
- Real percentage when total is known (5–90%, headroom for transcode/upload),
  else a smooth time-asymptotic estimate that never sticks — message shows the
  live frame number either way.
- Push a preview frame ~every 8s (was 30s) so the box fills in quickly.

render-svc:
- GET /v1/renders/:id/progress now computes eta_seconds from started_at + progress
  (linear extrapolation) instead of returning null.

frontend:
- Thread eta_seconds → status route → render page; page prefers the server ETA and
  falls back to the client-observed rate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 01:18:54 +03:30
soroush.asadi 23d1fd8fb1 fix(payment): default broker host port to 1607 (8090 was allocated on the server)
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Successful in 3m6s
2026-06-16 07:28:05 +03:30
soroush.asadi ec51e87d2d feat(payment): standalone ZarinPal broker on pay.flatrender.ir
A generic multi-client payment gateway so FlatRender, meezi.ir and
bargevasat.ir can all pay through ZarinPal's single verified callback
domain (pay.flatrender.ir).

New Go service services/payment (clones the notification skeleton +
vendored deps):
- migration 31_payment_broker.sql — `payment` schema: client_apps,
  transactions, webhook_deliveries.
- ZarinPal v4 client ported from the proven identity PaymentService
  (request.json -> StartPay -> verify.json; codes 100/101).
- client API: POST /v1/pay/request + /v1/pay/inquiry, authed by
  X-Api-Key + HMAC body signature; GET /callback/zarinpal (the single
  verified endpoint) verifies, then 302s the user back to the site's
  return_url (signed) and fires a signed, retried webhook.
- per-client ZarinPal merchant override (default = shared merchant);
  amount stored canonically in Rial, unit to ZarinPal env-configurable.
- admin API /v1/admin/* (FlatRender admin JWT): client-app CRUD +
  key issue/rotate + transactions list.

Deploy wiring: payment-svc in docker-compose.v2.yml (host port 1607),
pay.flatrender.ir server block in mirror-nginx conf, ENV_FILE +
README updates (cert SAN + manual migration note).

Admin UI: src/components/admin/PaymentsAdmin.tsx (client apps with
one-time key reveal + rotate, transactions table) + /admin/payments
page + nav link + fa/en strings; pay-admin proxy route to payment-svc.

Docs/SDK: deploy/PAYMENTS.md (integration contract) + deploy/sdk/flatpay.js
(zero-dep Node client + webhook verifier) for meezi/any site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:59:54 +03:30
soroush.asadi 896ce3dfa9 feat(render): plan-gate quality tiers — free=360p watermarked, paid=all
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Successful in 3m8s
Monetization gate for the template render flow:
- render-quality.ts: single source of truth (free -> 360p only +
  watermark; pro/business -> 540p..4K, no watermark).
- /api/render: server-authoritative gate — rejects >360p for free
  users with 403 quality_locked; passes a watermark flag through
  createRenderJob -> /v1/renders (render-svc passthrough, wired later).
- /api/render/limits: GET endpoint exposing the user's allowed tiers
  and watermark state to the studio.
- render page: locks higher tiers for free users (dashed + lock badge,
  click routes to /pricing), clamps the selected resolution down,
  shows the "free = 360p + watermark, upgrade" notice, and handles the
  403 quality_locked response.

AI-video "no free preview" rule is a future hook (no AI gen yet).
Watermark rendering (ffmpeg drawtext on the node) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:17:25 +03:30
soroush.asadi 61ba526122 feat(admin): render-engine kill switch (block renders + show message)
Lets an admin disable rendering when no render node is available — users can't
start new renders and see a localized "service unavailable until <date>" message.

- Admin → فارم رندر → موتور رندر (RenderEngineAdmin): on/off toggle + fa/en message
  + optional Jalali "until" date; saved as one `render_service` Website Setting
  (jsonb) via /v1/settings — no backend change, no migration.
- lib/render-service.ts: fetchRenderServiceStatus (fail-open) + renderServiceMessage
  (locale + appends the date).
- Enforcement: POST /api/render returns 503 {code:render_disabled, messages} when off;
  studio render page reads GET /api/render/service on mount → disables "شروع رندر"
  and shows the banner, and handles the 503 on click.
- i18n: appAdminLayout.renderEngine (fa+en, parity 1045/1045). tsc + next build clean.
  Verified: disabled setting → /api/render/service returns enabled:false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:47:42 +03:30
soroush.asadi b3637cf839 feat(home): admin-managed homepage section manager (toggle/reorder/edit)
The homepage is now driven by a `home_layout` Website Setting (jsonb) instead of a
hardcoded section stack — zero backend changes, no migration.

- lib/home-layout.ts: section catalog + saved-layout merge + locale-aware config
  reader (`<field>_fa`/`<field>_en`) + public fetchHomeLayout() (falls back to
  defaults when unset/unreachable).
- app/[locale]/page.tsx: renders ordered, enabled sections from the layout, passing
  per-section content overrides.
- sections (Hero/Products/Templates/HowItWorks/Pricing/Testimonials/FAQ): accept an
  optional `config` prop overriding heading/subtitle/CTA, locale-aware, default-safe.
- new HomeSlides + HomeEvents sections render the previously-orphaned admin Slides
  (/v1/slides) and Home Events (/v1/home-events) data.
- admin: HomeSectionsManager (toggle on/off, ↑/↓ reorder, per-section FA/EN content
  editor) at /admin/home, saved via the existing /v1/settings upsert; nav item + i18n.

Verified: a saved layout overrides Hero/Pricing headings and reorders sections;
removing it reverts to the default homepage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:21:44 +03:30
soroush.asadi c92de06c28 feat(content): public Blog + Learn sections and static CMS pages (full-stack)
Adds the missing public-facing content pages and their admin authoring, all
powered by the existing content-svc Blog entity discriminated by `kind`.

Backend (content-svc):
- BlogKind enum += Learn, Page (reuses Blog CRUD/SEO/slug/publish for all three).
- SQL migration services/content/migrations/001_blog_kind_learn_page.sql
  (ALTER TYPE content.blog_kind ADD VALUE 'Learn','Page').

Frontend (public, Next.js):
- lib/content-api.ts: fetchArticles(kind) / fetchArticle(slug) / fetchPage(slug)
  with safe empty/null fallbacks.
- components/content: article-ui (card/list/detail + RTL prose), CmsPageContent,
  CmsRoute (admin-authored page or localized built-in fallback copy).
- Routes: /blog, /blog/[slug], /learn, /learn/[slug] and static pages
  /about /contact /careers /privacy /terms /cookies /help.
- Navbar "tutorials" → /learn; all footer links now resolve.

Admin:
- AdminResource: new `fixedValues` option (injects kind on create/update).
- learnConfig (kind=Learn) + pagesConfig (kind=Page) reuse the /v1/blogs endpoint;
  /admin/learn + /admin/pages routes + nav items.

i18n: blog, learn and 7 *Page namespaces added to both fa.json and en.json
(verified key parity); admin nav labels learn/pages. Frontend tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 22:43:25 +03:30
soroush.asadi ab568c0663 feat(presets): admin preset stories (premade example videos) end-to-end
Build backend images / build content-svc (push) Failing after 31s
Build backend images / build file-svc (push) Failing after 31s
Build backend images / build gateway (push) Failing after 31s
Build backend images / build identity-svc (push) Failing after 30s
Build backend images / build notification-svc (push) Failing after 30s
Build backend images / build render-svc (push) Failing after 31s
Build backend images / build studio-svc (push) Failing after 31s
Epic A — admins author premade example videos per template; users pick one
on the template detail page to start a pre-filled project.

Backend (content-svc):
- PresetStory DTOs + PresetStoryService (admin CRUD + public published-only
  filter via role check + soft-delete) + PresetStoriesController (/v1/preset-stories)
- DI registration; gateway route /v1/preset-stories (optionalAuth, public read)

Frontend:
- ProjectPresetStories admin authoring UI (name/description/demo upload/published/
  sort + scene picker with order+duration + advanced scenes_spa); «ویدیوهای نمونه»
  button + modal in ProjectsAdmin
- TemplateDetailExamples renders real published stories (image/video preview,
  hover → "use this example" → creates a pre-bound project), falls back to
  placeholders when none; selected aspect's variant id keys the fetch
- public /api/preset-stories route; preset_story_id plumbed through
  createProjectFromTemplate + projects POST route; usePreset i18n (fa+en)

Verified: full CRUD via gateway (public hides unpublished); creating a project
with presetStoryId persists selected_preset_story_id on the saved project.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 05:24:14 +03:30
soroush.asadi fca6bcac53 feat(#41): admin/renders pagination + user name link + output + project name
Build backend images / build content-svc (push) Failing after 1m1s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 49s
Build backend images / build identity-svc (push) Failing after 50s
Build backend images / build notification-svc (push) Failing after 49s
Build backend images / build render-svc (push) Failing after 1m2s
Build backend images / build studio-svc (push) Failing after 47s
render-svc admin-renders enriches jobs with user_name/email (cross-schema lookup to
identity.users). Page adds prev/next pagination (page_size 30). Table adds User column
(name → /admin/users?q=email) and Output column (export → /admin/exports), and shows
project_name. Verified: 21 jobs, paginated, names resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 05:24:47 +03:30
soroush.asadi a69bc62724 feat(studio B1): persist input edits to content elements (render-binding foundation)
Build backend images / build content-svc (push) Failing after 1m3s
Build backend images / build file-svc (push) Failing after 1m5s
Build backend images / build gateway (push) Failing after 1m0s
Build backend images / build identity-svc (push) Failing after 1m8s
Build backend images / build notification-svc (push) Failing after 57s
Build backend images / build render-svc (push) Failing after 1m6s
Build backend images / build studio-svc (push) Failing after 1m3s
Studio edits previously went only to edit_state; the render binds saved_scene_contents,
so edits never reached the MP4. Add studio-svc PATCH /v1/saved-projects/{id}/contents
(update saved_scene_contents.value/value_file_id by content key, ExecuteSqlInterpolated
for null-safe params) + Next /api/projects/[id]/contents route + persistence hook pushes
edited values (from bridged c-<key> layers) alongside the scene_data save. Verified
text persists incl. UTF-8 Persian (9 chars/17 bytes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:53:17 +03:30
soroush.asadi d7a74daa96 feat(render): 5 quality tiers (360p–4K) + ETA on render page; 24h session
- Render page: resolution picker now 360p/540p/720p/1080p/4K; live ETA
  ('تقریباً … باقی مانده') computed from progress rate; preview+progress bar already wired.
- render-schemas: resolution enum + RESOLUTION_DIMENSIONS add 360p/540p.
- render-jobs.mapQuality: 5-tier → render_quality (Low/Medium/High/Full).
- Session: Jwt__AccessTokenMinutes=1440 (24h) via compose so logins persist
  (refresh middleware + 30d refresh token back it up).

(Real per-tier output height still pending: render-svc r_height is hardcoded 1080 →
node ffmpeg scale — next.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:04:32 +03:30
soroush.asadi 2a6bbcd408 fix(render-page): register completion without requiring a download URL
Build backend images / build content-svc (push) Failing after 56s
Build backend images / build file-svc (push) Failing after 29s
Build backend images / build gateway (push) Failing after 41s
Build backend images / build identity-svc (push) Failing after 5m32s
Build backend images / build notification-svc (push) Failing after 1m18s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 1m5s
The full-screen render page only transitioned to "completed" when status was
completed AND an outputUrl existed, so dev renders (which produce no export file)
polled forever at 100%. Now completion is driven by status alone; the download/
share buttons render only when a URL is present, otherwise a "dev render, no file"
note is shown. Same guard helps real renders whose export URL resolves a beat late.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:13:26 +03:30
soroush.asadi 81912cac66 feat(render): full-screen render page, one-active-render limit, app-wide progress
Build backend images / build content-svc (push) Failing after 14s
Build backend images / build file-svc (push) Failing after 1m28s
Build backend images / build gateway (push) Failing after 1m43s
Build backend images / build identity-svc (push) Failing after 3m0s
Build backend images / build notification-svc (push) Failing after 51s
Build backend images / build render-svc (push) Failing after 1m3s
Build backend images / build studio-svc (push) Failing after 1m1s
Concurrent-render ceiling (a user runs 1 render at a time unless granted more):
- Identity: TokenService emits max_renders claim from User.ParallelRenderingCeiling
- Identity: admin POST /v1/users/{id}/render-slots (AdminService.SetRenderSlotsAsync,
  clamped 1..50) — gamification or admin raises a user's ceiling
- render-svc: middleware reads max_renders (default 1); CreateJob rejects with 409
  active_render_limit when active jobs >= ceiling
- render-svc: db.CountActiveJobs + ListActiveJobs; GET /v1/renders/active returns
  in-flight renders + can_start_new

Full-screen render page (replaces the modal):
- /studio/render/[projectId]: config (resolution/fps) → live preview + progress →
  download; resumes this project's in-flight render on mount; blocks when another
  render is active; reads ?preset=
- StudioTopBar export menu now navigates to the page; RenderModal deleted (dead)

App-wide minimal progress:
- GlobalRenderProgress pill mounted in the locale layout for authed users; polls
  /api/render/active every 4s, shows thumbnail + step + % on every page, click →
  the render page; hidden on the render page and when idle

Admin: UserActions gains a "concurrent render slots" control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:48:05 +03:30
soroush.asadi 2918b7acbf feat(admin/media): folders in the media library
- admin-files: fetchFolders / createFolder / deleteFolder + FolderItem; fetchFiles
  takes a folderId filter
- admin files upload route forwards target_folder_id so uploads land in the open folder
- FileManager: breadcrumb navigation, folder cards (open / delete), "+ new folder",
  folder-scoped file listing + upload. Folders hidden while searching (search spans all)

Uses the file-svc folder API (GET/POST/DELETE /v1/folders, folder_id list filter)
that already existed but had no UI. "Pick from library" was already wired via
FilePicker in FileUploadField.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:34:56 +03:30
soroush.asadi 1142c38c62 feat(editor+trimmer): save output to cloud account via V2 File service
- New /api/files/upload: generic user-scoped Browser→Next→MinIO upload
  (presign → PUT → confirm), 200MB cap, image+video only, returns public URL
- image-editor-export: stageToBlob() + saveStageToCloud(); "Save to my account"
  button in the Image Editor export popover
- Trimmer: "Save to my account" button uploads the trimmed clip blob
- i18n: saveToCloud/savingToCloud/savedToCloud/saveToCloudFailed in fa+en
  (parity 1002/1002)

Connects the two client-side editors to V2 storage — output now lands in the
user's account instead of only a local download.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:29:03 +03:30
soroush.asadi 8ab86a5cc6 feat(templates): aspect-ratio picker drives which variant is built
The detail page now loads a template's real published aspect variants (16:9/1:1/9:16)
from the content container and the preview chips select among them. Build now copies
the SELECTED variant's scene graph (passes that variant's content project UUID), not a
default. Selection is lifted to TemplateDetailContent and shared by the preview picker
and the build button; the preview box reflects the chosen aspect.

Verified on insta-promo (16:9 + a duplicated 1:1 variant): both chips render, and
building 1:1 copies the 1:1 project's scenes (1 scene, 6 fields).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:08:11 +03:30
soroush.asadi 0ca11f19dd feat(studio): copy template scene graph into editable project (use-template works)
Build backend images / build content-svc (push) Failing after 53s
Build backend images / build file-svc (push) Failing after 51s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 45s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 53s
Build now created an EMPTY project: (1) the studio binds camelCase but the frontend
sent snake_case → original_project_id dropped to Guid.Empty; (2) CreateProjectAsync
never copied scenes. Now:
- saved-projects.ts sends camelCase (originalProjectId/copyDefaultValues).
- /api/projects resolves the container slug → first published variant content project.
- StudioService.CreateProjectAsync deep-copies the content scene graph (scenes +
  content elements + scene colours + shared colours) into the new saved project via
  one atomic cross-schema SQL copy (enum cols cast to text; temp scene-id map).

Verified: insta-promo → 1 scene, 6 content fields, 4 shared colours, loadable by the
studio editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:27:25 +03:30
soroush.asadi baf6e40dde fix(templates): wire template detail page to real content service
/templates/[id] only searched the hardcoded demo catalog, so real published
containers (e.g. insta-promo) 404'd even though the browser listed and linked them.
Now resolveTemplate() fetches the container by slug via fetchProject(), falling back
to the demo catalog, else notFound(). Page + generateMetadata made async (await params).

Also fix TemplateDetailBreadcrumb: it called server-only getTranslations while
rendered inside the client TemplateDetailContent tree (500 at request time) — switched
to the useTranslations hook. Was latent because demo pages were static-prerendered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 06:45:02 +03:30
soroush.asadi d4fee8d1d7 feat(profile): role-aware nav + avatar menu + full editable profile
Build backend images / build content-svc (push) Failing after 1m59s
Build backend images / build file-svc (push) Failing after 3m18s
Build backend images / build gateway (push) Failing after 3m28s
Build backend images / build identity-svc (push) Failing after 2m1s
Build backend images / build notification-svc (push) Failing after 4m45s
Build backend images / build render-svc (push) Failing after 5m18s
Build backend images / build studio-svc (push) Failing after 2m12s
Navigation:
- UserMenu (avatar + role-aware dropdown: Dashboard, Admin Panel for admins,
  Profile, Sign out) replaces Sign In/Try Free when logged in (desktop + mobile).
- Real avatars in dashboard sidebar + a new admin-shell profile section.
- Shared Avatar primitive (image with initials fallback). SiteChrome excludes /admin.

Profile (data-collection surface for future AI video generation):
- SettingsProfile rebuilt: avatar upload + slogan, about, company, website,
  country, national code, birthdate, gender. No resume builder (per scope change).
- /api/profile forwards all fields; new user-scoped /api/profile/upload (avatar →
  MinIO via file-svc, sets avatar). Identity UpdateUserRequest/UserResponse widened
  (country/national/method); no DB migration (columns already exist).
- fa+en strings; verified GET/PATCH round-trip + logged-in SSR render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:34:25 +03:30
soroush.asadi 0a7dd9b84c feat(nodes): live CPU/RAM/disk monitoring in the node list
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 54s
Build backend images / build notification-svc (push) Failing after 53s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 51s
- node-agent: internal/metrics — read CPU% (GetSystemTimes), RAM (GlobalMemoryStatusEx),
  disk used%/total (GetDiskFreeSpaceEx) via stdlib kernel32 (no external dep; windows
  build + non-windows stub). Heartbeat now reports cpu_pct/ram_available_mb/disk_used_pct/
  disk_total_gb + ae_running.
- render-svc: heartbeat persists last_disk_pct + disk_total_gb (migration 29); RenderNode
  model + node SELECT/scan carry them.
- admin: rewrite NodesTable to the real RenderNode shape (fixes a pre-existing items/V2Node
  mismatch that left the list empty) + a CPU/RAM/disk bars column + stale-heartbeat flag.
- assets-bundle ingestion: ProjectMediaBundle (jszip) auto-maps project.zip → project/scene
  image/demo/colour + music; PatchProject gains image/full_demo/shared_colors_svg.
- scan: RGBA (4-number) colours recognised + frshare single-int controls detected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:01:18 +03:30
soroush.asadi 1ff6e494c0 @
Build backend images / build content-svc (push) Failing after 19s
Build backend images / build file-svc (push) Failing after 1m53s
Build backend images / build gateway (push) Failing after 16s
Build backend images / build identity-svc (push) Failing after 7m1s
Build backend images / build notification-svc (push) Failing after 7m24s
Build backend images / build render-svc (push) Failing after 3m12s
Build backend images / build studio-svc (push) Failing after 43s
feat: AE template scanner + scene editor + AEP bundle pipeline

Scene editor (admin): per-project Scenes / Shared Colors / Color Presets
manager (ProjectScenes) reachable from each project.

AEP bundle pipeline: upload .aep or .zip → stored once per template at
templates/{project_id}/(bundle.zip|template.aep); render claim probes and
returns is_bundle+md5; node-agent extracts the bundle, locates the .aep
(zip-slip guarded), and caches by md5 so repeated renders extract once.

AE template scanner ("read scenes/colours/configs from the AEP"):
- content-svc importer: POST /v1/projects/{id}/scan/{preview,apply} —
  review-diff-then-merge into scenes/elements/colours (manual edits kept).
- render-svc Go quick-scan: stdlib RIFX parser extracts comp names+durations
  (no AE) → POST /v1/template-scans/{id}/quick.
- render-svc AE scan jobs + node-agent runner: queue → node runs scan.jsx
  (reverse of legacy JSXGenerator conventions: frfinal/frshare/frl_/frd_) →
  posts ScanResult back. Migration 26_render_scan_jobs.
- admin UI: "اسکن از افترافکت" with quick/full engines + diff-review modal.

Verified: importer preview/apply, Go quick-scan end-to-end (synthetic .aep →
scene imported), bundle extract unit tests, RIFX parser unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-06-04 10:39:45 +03:30
soroush.asadi 264fccf21f i18n(fa): brand فلت‌رندر across all strings + locale-aware page title
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:32:27 +03:30
soroush.asadi 1cd1e504d9 feat(dashboard): "My Renders" page for users
- /dashboard/renders: user's own render jobs (live status + progress bar + cancel)
  and finished exports (thumbnail + size/duration + download); bilingual fa/en
- server lib my-renders.ts (user-scoped /v1/renders + /v1/exports via session JWT)
- user action routes: POST /api/renders/[id]/cancel, GET /api/exports/[id]/download
  (presigned URL)
- dashboard sidebar: "رندرهای من / My Renders" nav item

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:22:38 +03:30
soroush.asadi b270ef438d fix(admin): sidebar invisible (CSS specificity) + /admin lands on dashboard
- AdminShell: the rtl:/ltr: translate variants ([dir] selector) out-specified
  lg:translate-x-0, so the sidebar stayed off-screen on desktop and the mobile
  drawer couldn't open. Pin physically right + plain translate-x-full/0; content
  uses lg:mr-60.
- /admin now redirects to /admin/stats (overview) instead of /admin/nodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 08:20:46 +03:30
soroush.asadi 43780f94f6 feat(admin): grouped sidebar shell (replace cramped 27-link top bar)
- AdminShell: fixed RTL sidebar with grouped nav (نمای کلی / محتوا / رشد و ارتباطات
  / کاربران و مالی / فارم رندر / سیستم), active-link highlighting via usePathname,
  sticky header showing the current section, mobile drawer with hamburger + overlay
- layout: build the grouped nav and render via AdminShell

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:56:54 +03:30
soroush.asadi ebf0e11f22 fix(render+admin): render queue shows ALL users' jobs
Build backend images / build content-svc (push) Failing after 53s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 58s
Build backend images / build identity-svc (push) Failing after 1m0s
Build backend images / build notification-svc (push) Failing after 49s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 59s
The admin render queue called the user-scoped /v1/renders (so it only showed the
admin's own jobs) and parsed items/total instead of data/meta (→ always empty).
- render-svc: GET /v1/admin-renders (admin) → ListAllJobs across users, optional
  ?status= filter; gateway-wired
- admin renders page now fetches /v1/admin-renders and reads data/meta correctly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:35:17 +03:30
soroush.asadi 928956689b feat(render+admin): exports management (all users' rendered videos)
Build backend images / build content-svc (push) Failing after 54s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 52s
Build backend images / build identity-svc (push) Failing after 55s
Build backend images / build notification-svc (push) Failing after 58s
Build backend images / build render-svc (push) Failing after 48s
Build backend images / build studio-svc (push) Failing after 1m0s
- render-svc: admin-scoped store (ListAllExports / GetExportByIDAny /
  SoftDeleteExportAny) + GET/DELETE/download-url under /v1/admin-exports
  (admin-gated; separate prefix so it routes to render, not identity's /admin)
- gateway: /v1/admin-exports/* → render
- admin /admin/exports: paginated table of every rendered export with thumbnail,
  type/quality, size, duration, dimensions, produce + expiry dates; download
  (presigned URL) and delete

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:04:06 +03:30
soroush.asadi 7f2f65dd8a feat(render+node-agent+admin): install fonts on all render nodes + verify
Build backend images / build content-svc (push) Failing after 53s
Build backend images / build file-svc (push) Failing after 47s
Build backend images / build gateway (push) Failing after 52s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 55s
Build backend images / build render-svc (push) Failing after 59s
Build backend images / build studio-svc (push) Failing after 48s
Push a font once → every node installs it → admin sees per-node status.

- render-svc: font_requests + node_fonts tables (mig 25); admin GET/POST/DELETE
  /v1/node-fonts (with per-node status matrix); internal (HMAC) GET pending +
  POST status for node-agents
- node-agent: fontSyncLoop polls pending fonts every 60s, downloads, installs
  (Windows Fonts dir + registry / macOS / linux fc-cache), reports Installed/Failed
- gateway: /v1/node-fonts/* → render
- admin /admin/node-fonts: upload a .ttf/.otf → install on all nodes; per-node
  Installed/Pending/Failed badges + counts + delete

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:33:48 +03:30
soroush.asadi 7f7feabb85 feat(render+admin): stop a render job (admin, any owner)
Build backend images / build content-svc (push) Failing after 1m0s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 1m20s
Build backend images / build notification-svc (push) Failing after 1m13s
Build backend images / build render-svc (push) Failing after 1m5s
Build backend images / build studio-svc (push) Failing after 1m0s
The render-queue cancel button used the owner-scoped /cancel (WHERE user_id=…),
so an admin couldn't stop another user's job. Added:
- render-svc: POST /v1/renders/:job_id/stop (admin-gated) → store.StopJob cancels
  any in-progress job regardless of owner and frees the assigned node
- admin: render-queue button now "توقف" → /api/admin/renders/{id}/stop (with confirm)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 01:39:33 +03:30
soroush.asadi c076345ceb feat(render+admin): delete render node
Build backend images / build content-svc (push) Failing after 1m7s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 55s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 1m7s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 59s
- render-svc: DELETE /v1/nodes/:node_id (store.DeleteNode + handler + route)
- admin: حذف button per node row + DELETE /api/admin/nodes/[nodeId] proxy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 01:21:13 +03:30
soroush.asadi ffc0c5d6d5 feat(admin): add-node form + After Effects version dropdown
- Nodes page: "+ افزودن نود" opens a full-screen form (name, region, IP, worker
  port, AE version, node kind, RAM, CPU, priority) → POST /v1/nodes
- current_ae_version is now a dropdown (2025…2020, matching the ae_version DB
  enum) instead of free text; node_kind is a dropdown (Shared/Dedicated/Spot)
- new POST /api/admin/nodes proxy route (forwards body; admin-gated). The backend
  POST /v1/nodes existed but had no UI — you couldn't define nodes before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 01:00:14 +03:30
soroush.asadi 7fe5f8a563 feat(admin): standalone Projects page + per-project asset manager
Build backend images / build content-svc (push) Failing after 1m36s
Build backend images / build file-svc (push) Failing after 1m28s
Build backend images / build gateway (push) Failing after 2m11s
Build backend images / build identity-svc (push) Failing after 2m11s
Build backend images / build notification-svc (push) Failing after 3m46s
Build backend images / build render-svc (push) Failing after 55s
Build backend images / build studio-svc (push) Failing after 1m2s
- content-svc: GET /v1/projects (browse/search all projects across containers,
  paginated, admin) returning template name/slug + AE status; project_assets
  table (mig 23) + entity; GET/POST/DELETE /v1/projects/{id}/assets
- /admin/projects: searchable, paginated list of every renderable project with
  thumbnail, template, aspect/resolution, AE-file + publish status
- ProjectAssets component: list/upload/delete named footage/image/audio/font
  files per project (reused in the projects page; AE file upload alongside)
- nav + fa/en "Projects" label

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:39:33 +03:30
soroush.asadi 675b60d858 feat(auth+admin): Sign in with Google (OAuth) + Integrations config panel
Build backend images / build content-svc (push) Failing after 1m2s
Build backend images / build file-svc (push) Failing after 3m11s
Build backend images / build gateway (push) Failing after 5m39s
Build backend images / build identity-svc (push) Failing after 38s
Build backend images / build notification-svc (push) Failing after 2m0s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 58s
Backend (identity-svc):
- oauth_config table (mig 22) + OAuthConfig entity
- OAuthService: admin config CRUD + Google authorization-code flow (build consent
  URL, exchange code, fetch userinfo, find/create RegisterMode.Google user, issue
  session via AuthService.IssueOAuthSessionAsync)
- AuthController: GET /v1/auth/google/{start,callback} (public); tokens handed to
  frontend via URL fragment
- AdminController: GET/PUT /v1/admin/oauth/{provider} (admin, secret masked)

Frontend:
- "ورود با گوگل" button on /auth → identity start endpoint
- /auth/callback reads fragment tokens → /api/auth/oauth-session sets httpOnly cookies
- /admin/integrations: Google client_id/secret/redirect_uri + enable, with setup guide
- nav + fa/en labels

Client ID/Secret are configured entirely in the admin panel — no redeploy needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:08:21 +03:30
soroush.asadi 88a44b1349 feat(admin): full Persian (fa) localization + RTL polish
- admin-resources: all config titles/descriptions/columns/fields → Persian
- AdminResource: generic UI strings (new/edit/delete/save/cancel/loading/empty/
  actions/confirm) → Persian; text-left/right → logical text-start/end
- TemplatesAdmin: all labels, table, modal, statuses, errors → Persian
- FileUploadField, WebsiteSettingsAdmin, FileManager → Persian
- layout: ms-auto logical prop (NodesTable/RenderQueue/AiContentStudio already
  i18n with Persian values)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:47:35 +03:30
soroush.asadi 151970accd feat(admin): plan statistics + node restart/close-ae actions
Build backend images / build content-svc (push) Failing after 1m22s
Build backend images / build file-svc (push) Failing after 3m8s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 1m25s
Build backend images / build render-svc (push) Failing after 2m5s
Build backend images / build studio-svc (push) Failing after 3m59s
Final legacy-admin items:
- identity GET /v1/admin/plan-statistics (active/total users + revenue per plan
  from user_plans); surfaced as a breakdown table in /admin/stats
- NodesTable: wire Restart + Close-AE actions (backend already supported them) via
  new proxy routes; was only drain/release before

Full DivineGateWeb legacy-admin parity achieved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:02:03 +03:30
soroush.asadi 3091911260 feat(admin): affiliate/personal discounts, user-videos, internal routes, authz
Build backend images / build content-svc (push) Failing after 1s
Build backend images / build file-svc (push) Failing after 1s
Build backend images / build gateway (push) Failing after 0s
Build backend images / build identity-svc (push) Failing after 0s
Build backend images / build notification-svc (push) Failing after 1s
Build backend images / build render-svc (push) Failing after 1s
Build backend images / build studio-svc (push) Failing after 1s
Closes the remaining legacy-admin gaps:
- Users «مدیریت» modal: create personal discount or affiliate code (owner_user_id +
  owner_profit_percentage on existing /v1/discounts), and view the user's saved
  projects ("videos") via new admin GET /v1/saved-projects/by-user/{id} (studio)
- Internal routes admin (/admin/routes): CRUD on content.internal_routes
  (RoutesController + CmsService + gateway /v1/routes/*)
- Security: lock identity UsersController Search + Ban to [Authorize(Roles="Admin")]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:42:01 +03:30
soroush.asadi 0b538e1b1e feat(content+admin): home-events CRUD + comments moderation
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 0s
Build backend images / build identity-svc (push) Failing after 0s
Build backend images / build notification-svc (push) Failing after 1s
Build backend images / build render-svc (push) Failing after 1s
Build backend images / build studio-svc (push) Failing after 0s
- content-svc: home-events gains Create/Update/Delete + includeInactive list
  (POST/PUT/DELETE /v1/home-events, admin-gated; dates coerced to UTC)
- admin /admin/home-events: full CRUD for homepage hero event banners
- admin /admin/comments: list + approve/unapprove + delete (moderation)
- AdminResource: optional listQuery to fetch inactive rows for admin views

Fills the remaining legacy-admin gaps (home events, comments).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:24:56 +03:30
soroush.asadi 3acd366fda feat(admin): music library admin + fix CRM analytics UTC
Build backend images / build content-svc (push) Failing after 1m7s
Build backend images / build file-svc (push) Failing after 50s
Build backend images / build gateway (push) Failing after 59s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 1m0s
Build backend images / build render-svc (push) Failing after 1m0s
Build backend images / build studio-svc (push) Failing after 56s
- /admin/music: list / upload / delete studio audio tracks (content-svc
  GET/POST/DELETE /v1/music) — fills the legacy music-library gap
- fix: CRM analytics coerced query-bound dates to UTC (Npgsql timestamptz
  rejects Kind=Unspecified) — endpoint was returning 400

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:17:14 +03:30
soroush.asadi 2c961b123b feat(content+admin): content ranking + statistics dashboard
Build backend images / build content-svc (push) Failing after 16s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 17s
Build backend images / build identity-svc (push) Failing after 2m12s
Build backend images / build notification-svc (push) Failing after 3m15s
Build backend images / build render-svc (push) Failing after 51s
Build backend images / build studio-svc (push) Failing after 56s
- content-svc: template list gains popularity/rating sort modes (use_count_desc,
  popular, rating_desc); new PATCH /v1/templates/{id}/sort to set manual sort
  weight (feature/pin) without a full edit
- admin /admin/ranking: templates ordered by popularity with views/uses/rating
  and inline manual-sort editor
- admin /admin/stats: overview dashboard (users, revenue, paying customers,
  conversion, templates/categories/campaigns/blogs counts) aggregated from
  existing identity + content endpoints
- nav: Dashboard + Ranking links

Completes the epic: SMS/Email/Templates → Marketing → CRM → Ranking + Stats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:11:18 +03:30
soroush.asadi 62a5121ffe feat(identity+admin): CRM analytics + customer notes + user power-actions
Build backend images / build content-svc (push) Failing after 56s
Build backend images / build file-svc (push) Failing after 54s
Build backend images / build gateway (push) Failing after 1m1s
Build backend images / build identity-svc (push) Failing after 55s
Build backend images / build notification-svc (push) Failing after 54s
Build backend images / build render-svc (push) Failing after 52s
Build backend images / build studio-svc (push) Failing after 1m2s
Modeled on the legacy DivineGateWeb admin (CRM + Security/* actions):
- identity-svc AdminService + AdminController (admin-gated):
  - GET /v1/admin/crm/analytics — signups/buyers/conversion/revenue + daily series
    (from identity.users + identity.payments)
  - GET/PUT /v1/users/{id}/crm — tags / note / pipeline status (user_crm table, mig 20)
  - power-actions: POST /v1/users/{id}/{balance,password,charge,moderator,grant-plan}
- admin UI: /admin/crm dashboard (funnel cards + daily signup/revenue bars);
  per-user "مدیریت" modal in Users (balance, render charge, plan days, password,
  moderator, CRM notes)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:59:07 +03:30
soroush.asadi 6dbb14d146 feat(notifications+admin): marketing campaigns
Build backend images / build content-svc (push) Failing after 14s
Build backend images / build file-svc (push) Failing after 22s
Build backend images / build gateway (push) Failing after 1m21s
Build backend images / build identity-svc (push) Failing after 1m43s
Build backend images / build notification-svc (push) Failing after 1m6s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 1m5s
- campaigns table (migration 19) + CRUD + send endpoint in notification-svc
- audience resolution reads cross-schema from identity.users (all / verified /
  with_plan); send dispatches via the SMS or Email channel and logs deliveries
- endpoints: GET/POST /v1/campaigns, POST /v1/campaigns/:id/send, DELETE
- gateway route /v1/campaigns/* → notification
- /admin/marketing: create campaign (channel, audience, template/subject/body),
  list with status + sent counts, send, delete

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:17:19 +03:30
soroush.asadi 507ac7e6a4 feat(notifications+admin): SMS (Kavenegar) + Email (SMTP) channels & templates
Build backend images / build content-svc (push) Failing after 56s
Build backend images / build file-svc (push) Failing after 47s
Build backend images / build gateway (push) Failing after 1m0s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 11s
Build backend images / build render-svc (push) Failing after 4m5s
Build backend images / build studio-svc (push) Failing after 56s
Backend (notification-svc):
- channel_config table (per-tenant Kavenegar + SMTP settings) + migration 18
- sender pkg: Kavenegar SMS client + SMTP mailer (STARTTLS / implicit TLS), stdlib only
- endpoints: GET/PUT /v1/channels[/:channel], POST /v1/sms/send, POST /v1/email/send
  (template + {{var}} rendering); deliveries logged
- seeded 3 Persian email templates: welcome / account_verification / promotion
- gateway routes /v1/{channels,sms,email}/* → notification

Admin UI:
- /admin/messaging: SMS + Email provider config cards, test-send, email template editor
- nav link + fa/en labels

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:32:54 +03:30
soroush.asadi 9a1d60e9d0 feat(admin): Discounts and Website Settings sections
- /admin/discounts: list + create discount codes (kind, value, max uses, expiry)
  via /v1/discounts (backend has no edit/delete API yet)
- /admin/settings: key/value site settings with upsert + secret flag. The value
  column is jsonb, so values are JSON-encoded on save / decoded for display
- nav links + fa/en labels

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:20:07 +03:30
soroush.asadi 163f0c9ec3 feat(admin): media library + upload component (replace URL fields)
- /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>
2026-06-02 14:55:52 +03:30
soroush.asadi cf5dd4f195 feat(admin): category SEO fields, Templates admin, safe project PATCH
Build backend images / build content-svc (push) Failing after 21s
Build backend images / build file-svc (push) Failing after 3m49s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 1m1s
Build backend images / build notification-svc (push) Failing after 1m2s
Build backend images / build render-svc (push) Failing after 1m0s
Build backend images / build studio-svc (push) Failing after 58s
- categories/tags admin forms: add meta title/description/keywords, bot-follow,
  sort, is_active (backend already supported these)
- new Templates admin (/admin/templates): container CRUD with description,
  keywords, publishing, premium, primary mode, category/tag assignment, plus
  editable per-variant aspect & resolution
- content-svc: PATCH /v1/projects/{id} partial update so aspect/resolution edits
  never wipe render/colour data (SharedColorsSvg, RenderAepComp, Folder)
- admin resource proxy: add PATCH passthrough

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 14:26:44 +03:30
soroush.asadi 3fc7bf2b97 feat: AI SEO generator, full admin panel, i18n sweep, new logo + auth/RTL fixes
Build backend images / build content-svc (push) Failing after 3m39s
Build backend images / build file-svc (push) Failing after 52s
Build backend images / build gateway (push) Failing after 58s
Build backend images / build identity-svc (push) Failing after 1m21s
Build backend images / build notification-svc (push) Failing after 1m0s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 55s
AI SEO content generator
- content-svc: per-tenant OpenAI config (ai_settings) + /v1/ai endpoints
  (settings GET/PUT, seo-post) with SEO-expert prompt → structured article
- admin UI to configure token/base-url/model and generate + save as blog
- configurable base URL for restricted networks

Full data-driven admin panel
- generic /api/admin/resource proxy + reusable AdminResource component
- categories/tags/fonts/blogs (CRUD), users (list + ban), plans/slides
- AI content section; nav + i18n

i18n localization sweep
- localized 116 user-facing + studio/editor components to next-intl (fa+en)
  under the auto.* namespace; merge tooling in scripts/merge-i18n.js

Branding + assets
- Monoline F logo (LogoMark + favicon)
- offline SVG placeholder generator (/api/placeholder), dropped picsum.photos

Fixes
- JWT issuer mismatch on content/studio (flatrender → flatrender-identity)
- missing role claim → [Authorize(Roles="Admin")] now works (RBAC)
- Secure cookies broke HTTP sessions → gated behind AUTH_COOKIE_SECURE
- Radix RTL via DirectionProvider (right-aligned menus in fa)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 09:35:14 +03:30
soroush.asadi bcc69f0a2e feat: complete node-agent pipeline, TLS proxy, billing cancel, password reset
Node-agent — full render pipeline (items 1-3):
- render-svc: ClaimedJob now includes aep_download_url (presigned MinIO GET,
  2h TTL, path=templates/{original_project_id}/template.aep)
- render-svc: POST /v1/internal/render/jobs/:id/output-upload-url
  allocates Export row + returns presigned MinIO PUT URL + export_id
- render-svc: db.CreateExportForJob() inserts export row with 30-day retention
- render-svc: InternalHandler now owns minio client (templatesBucket + exportsBucket)
  MINIO_TEMPLATES_BUCKET env var (default flatrender-templates)
- node-agent: runner/download.go — DownloadFile() + UploadFile() (stdlib only)
- node-agent: client.GetOutputUploadURL() + ClaimedJob.AEPDownloadURL field
- node-agent: runJob() full flow: download AEP → render → get upload URL →
  PUT output to MinIO → Complete(export_id)
  All steps are non-fatal with fallback (AEP miss → mock, upload fail → no export)

TLS reverse proxy (item 15):
- Caddyfile: three virtual hosts (DOMAIN, API_DOMAIN, STORAGE_DOMAIN)
  auto-TLS via Let's Encrypt; security headers; 512MB upload limit on API
- docker-compose.v2.yml: caddy:2-alpine service, ports 80/443/443udp,
  caddy_data + caddy_config volumes; env vars DOMAIN/API_DOMAIN/STORAGE_DOMAIN/ACME_EMAIL
- .env.v2.example: new Caddy + MINIO_TEMPLATES_BUCKET entries

Billing portal (item 5):
- Identity: POST /v1/users/me/plan/cancel — sets cancelled_at, auto_renew=false
  (access continues to expiry); 404 when no active plan
- POST /api/billing/cancel — frontend proxy, validates auth
- GET /api/billing/portal — redirects to /dashboard/settings?tab=billing
- SettingsBilling: "Cancel plan" button with confirm dialog + optimistic UI,
  "Change plan" button; becomes "use client" component

Password reset UI (item 7):
- POST /api/auth/password-reset — proxies /v1/auth/password/reset/request
  (always 200, anti-enumeration)
- POST /api/auth/password-reset-confirm — proxies /v1/auth/password/reset/confirm
- AuthPageContent: "Forgot password?" link on sign-in tab opens 2-step reset flow
  (email → OTP+new-password) without leaving the auth page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 16:41:13 +03:30
soroush.asadi 12773e125a feat: token auto-refresh, studio→render wiring, admin panel (nodes + render queue)
Token auto-refresh (middleware):
- Proactively refresh fr_access when < 120s remain — no more silent 15-min kick
- Inlines /v1/auth/refresh call in middleware, stamps new cookies on response
- /admin/* protected: is_admin JWT claim required, else redirect /dashboard
- apiFetch() (src/lib/api/fetch.ts): client-side 401 → auto-refresh → retry;
  de-duplicates concurrent refresh calls; redirects to /auth on failure

Studio → Render V2 wiring:
- scenes[] no longer sent to POST /api/render (V2 render-svc fetches project
  from Studio service via saved_project_id directly)
- renderRequestSchema.scenes is now optional
- RenderModal uses apiFetch for auto-refresh on 401 during polling

Admin panel (/admin/*):
- Admin layout: server-side is_admin guard + top nav (Nodes, Render Queue)
- /admin/nodes: lists all nodes from GET /v1/nodes with status badges,
  heartbeat age, slot usage, tags; Drain (PATCH status=Draining) + Release actions
- /admin/renders: render job table with step filter tabs; progress bars,
  error messages, Retry + Cancel per-row actions; polls GET /v1/renders
- API proxy routes: /api/admin/nodes/:id/drain|release,
  /api/admin/renders/:id/retry|cancel — all validate is_admin in JWT before proxying

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:42:30 +03:30
soroush.asadi d7743a6fbe feat: live render preview — node agent pushes PNG frames, frontend displays them in real time
render-svc:
- db.UpdateJobPreview(): writes base64 PNG to render_jobs.image_preview_b64
  (only on active jobs; Done/Failed/Cancelled rows ignored)
- POST /v1/internal/render/jobs/:job_id/preview — node agent endpoint
- Route registered under /v1/internal (nodeAuth)

node-agent:
- runner.PreviewFn callback type alongside ProgressFn
- runner.preview.go: GeneratePreviewB64(percent, quality, resolution)
  — pure stdlib (image/png + encoding/base64), no external deps
  — 320×180 dark frame with animated progress bar + colored indicator dots
- mock render: pushes a preview frame at every step (5→95%)
- real AE render: pushes a preview frame every 30s
- client.UpdatePreview(): POST /v1/internal/render/jobs/:job_id/preview
- main.go: onPreview callback wires client.UpdatePreview() into runner.Run()

frontend:
- render-jobs.ts: RenderJobRow.preview_b64 field; read from progress endpoint
- status/route.ts: previewB64 included in JSON response
- RenderModal: aspect-ratio preview pane during polling — shows spinner until
  first frame arrives, then live-updates with each poll (every 3s);
  step label overlaid as badge bottom-right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 09:42:03 +03:30