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>
/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>
Root cause of 'stuck on AE': heavy expression-driven projects take >10min for AE
to open, exceeding the scan timeout → job dies → admin UI stuck 'scanning'.
Fix: extend the stdlib .aep RIFX parser to collect every Utf8 name (ParseNames),
since FIX media placeholders are renamed footage ITEMS (frl_c1m1), not layers, and
text are layer names (frl_c1t1) — both are Utf8 chunks. QuickScan now branches on
?mode= (or auto-detects frl_ names) and scaffolds FIX scenes/elements + frd_*color
slots directly from the binary. Verified on the real final.aep that timed out in AE:
1 scene, 6 elements, 4 colors in 0.5s vs 10-min AE timeout.
Admin 'Quick scan (no AE)' is now the recommended path and passes the project mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PrepareFreshAE = taskkill AfterFX/aerender/AfterFXLib/dynamiclinkmanager/QT32
+ 2s settle + clear crash markers, then launch. A hung/zombie AE from a prior
job would otherwise block or corrupt the new run. RunScan now calls it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SCRPriorState.json alone didn't suppress it — AE's per-session GUID under
HKCU\Software\Adobe\After Effects\AppStates persists after a kill/crash and
trips Safe Mode. ClearAECrashState now reg-deletes AppStates too (reg.exe, no dep).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
afterfx -r alone leaves AE on its empty Home/Start screen, which blocks the
script from running (AE sits idle on Untitled Project until the scan times out).
Now launch 'afterfx <aep> -r scan.jsx' so the project opens directly; scan.jsx
uses the already-open project and only app.open()s as a fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The disk-column append only hit GetNodeByID (whitespace differed); ListNodes
lacked last_disk_pct/disk_total_gb, so the node list 500'd and rendered empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AepImportService: the global Scene HasQueryFilter(DeletedAt==null) was hiding
soft-deleted rows, so the revive never matched and the importer re-inserted →
scenes_project_id_key violation. Add .IgnoreQueryFilters() to the load. (apply
now revives + returns 200, verified.)
- node-agent: ClearAECrashState() deletes AE's SCRPriorState.json before each
launch so the 'Crash Repair Options' dialog can't hang a headless scan/render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- nav/footer/admin brand → فلترندر in fa (FlatRender in en)
- aspect-ratio 'All Sizes' option now uses t('allSizes') (همه اندازهها)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Category badges now show the live template count per category computed from the
catalog (0 → no badge), instead of hardcoded demo numbers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The public templates page and homepage gallery fell back to hardcoded demo
templates (VIDEO_TEMPLATES_CATALOG / TEMPLATES) whenever the admin list was
empty — so dummy templates showed even though the DB had none. Now both render
only real admin-sourced templates (empty when there are none). Categories are
untouched (kept as-is).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- slug fields auto-fill from the name (slugify keeps Persian + latin letters,
spaces → "-") until the slug is edited by hand; applies to all data-driven
forms (categories/tags/blogs/…) and the Templates form
- Projects page (/admin/projects) gains "+ پروژه جدید": pick a template
(container) + name/aspect/resolution/size/duration/fps/mode → POST /v1/projects.
Previously a project could only be added while editing a template.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- routing: localeDetection:false — a non-prefixed URL always serves fa (default);
English only via explicit /en/ prefix. Browser Accept-Language no longer
redirects fa pages to /en on every click.
- AdminShell + DashboardSidebarNav: use next-intl Link + usePathname (from
@/i18n/navigation) instead of plain next/link, so links preserve the current
locale and active-state matches the prefix-stripped path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /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>
- 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>
- 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>
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>
Per-node "جزئیات" button opens a modal with live health (status/CPU/RAM/AE/cache/
last heartbeat), a 24h CPU history mini-chart, and the recent crash log (signal,
auto-recovered, last frame, error log, log-file link). Uses existing render-svc
GET /v1/nodes/:id/health, /health/history, /crashes endpoints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- AdminResource: client-side search box (matches across all fields) + 25/page
pagination with prev/next and a filtered-count footer
- bumped pageSize on server-paged configs (users/blogs/comments/discounts/music/
fonts/tags) so search/paginate covers the full set
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The nested positional record ChargeReq(Guid UserId) failed System.Text.Json
binding under the snake_case policy (400). Use a plain class with a settable
property. Verified: consume decrements + blocks at 0, refund restores, bad
service token → 401.
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>
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>
Per-file checkboxes + "حذف موارد انتخابشده (N)" bar that deletes all selected
files in parallel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FileManager: type tabs (همه/تصاویر/ویدیوها/صدا/پروژههای AE و سایر) + name
search (uses file_type + search params the file svc already supports; type
values capitalized to match the enum), video thumbnails via <video>, AE/zip
shown under "AE و سایر"; delete + copy-URL retained
- FilePicker: reusable modal to re-choose an existing file from the library
(search + filter + click to pick)
- FileUploadField: new "از کتابخانه" button on every upload field → pick from
library instead of re-uploading; picker auto-filters by the field's accept
- shared src/lib/admin-files.ts helpers (fileUrl/isImage/isVideo/fetchFiles)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- AdminResource + TemplatesAdmin modals are now large full-height panels
(max-w-5xl, sticky header/footer, scrolling body, 2-column field grid;
textarea/richtext span full width)
- RichTextField: dependency-free contentEditable WYSIWYG (bold/italic/underline,
H2/H3/¶, lists, link, clear) emitting HTML, dir="auto" for fa/en
- blog content + category description now use the rich editor
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
The admin could edit a container but not manage its renderable projects or attach
AE files. Now, inside the template editor:
- add a new project/variant under the container (name, WxH, aspect, resolution,
duration, fps, choose-mode) → POST /v1/projects (maps via container_id)
- upload the After Effects file (.aep/.zip) per project → new PATCH
/v1/projects/{id}/aep (sets AepFileUrl/Minio/Md5/Size + RenderAepComp), with an
"AE ✓ / بدون فایل" status badge
- set the render composition name; delete a variant
- ProjectResponse now surfaces aep_file_url / aep_file_size_bytes / render_aep_comp
Additive only — the existing aspect/resolution variant editing is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AdminThumb detects raw <svg> markup and renders it inline (object-contain);
categories list shows an Icon column. Admin-entered, authenticated input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EF Core can't translate a conditional Count(predicate) inside a grouped Select;
fetch flat rows then group/aggregate in memory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- /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>