Commit Graph

177 Commits

Author SHA1 Message Date
soroush.asadi cd95ca2c6f fix(gateway/services): admin node/render pages 500 — redirect loop + is_admin claim
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 55s
Build backend images / build identity-svc (push) Failing after 48s
Build backend images / build notification-svc (push) Failing after 55s
Build backend images / build render-svc (push) Failing after 57s
Build backend images / build studio-svc (push) Failing after 44s
- gateway proxy: trim trailing slash before forwarding upstream. gin's
  RedirectTrailingSlash adds /nodes → /nodes/ while render-svc redirects
  /nodes/ → /nodes, forming an infinite redirect loop (admin pages 500'd)
- accept is_admin as bool OR string "true" in render/file/notification/gateway
  auth middleware (identity emits it as a string) — admin endpoints were 403'ing

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 11:26:44 +03:30
soroush.asadi 1aacf8bd5d fix(i18n): working locale switcher + RTL category sidebar
- add src/i18n/navigation.ts (next-intl createNavigation) and rewire
  LanguageSwitcher to router.replace(pathname, { locale }) — the manual
  next/navigation path-munging didn't switch locale reliably
- VideoTemplatesCategorySidebar: text-left → text-start, ml-auto → ms-auto
  so labels/badges align correctly under dir="rtl"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 09:59:07 +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
soroush.asadi ee421ccc68 feat(render-svc+node-agent): add job-claim endpoint and build node-agent skeleton
render-svc:
- db: ClaimJob() — atomic SELECT FOR UPDATE SKIP LOCKED; transitions job to
  Preparing, marks node Busy in a single transaction
- models: ClaimJobRequest + ClaimedJob types
- handlers/internal: POST /v1/internal/render/jobs/claim — 200 with job or 204 when queue empty
- main: register the claim route under /v1/internal (nodeAuth)

services/node-agent/ (new Go module github.com/flatrender/node-agent):
- internal/config: env-var based config (NODE_ID required, sensible defaults)
- internal/client: typed orchestrator HTTP client (Online, Heartbeat, ClaimJob,
  Complete, Fail, ReportCrash) — X-Node-Signature auth
- internal/runner: AE render via aerender.exe or mock (for dev without AE)
- cmd/agent/main: register online → heartbeat loop (5s) + poll loop (3s) →
  claim job → run render → report complete/fail; health endpoint on :7777
- Dockerfile: cross-compiles to Windows amd64 static binary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 09:28:31 +03:30
soroush.asadi 541e935418 chore: remove dead Supabase/Stripe env vars from docker-compose and env example
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>
2026-06-01 09:15:02 +03:30
soroush.asadi 1b9a92a790 chore: drop legacy nexrender render-worker; remove @supabase, stripe, @nexrender/core, tsx packages
V2 render orchestrator (render-svc) + future node-agent Go binary replace
the entire server/ directory. All dead dependencies uninstalled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 09:00:04 +03:30
soroush.asadi a076c4911f feat(frontend): migrate render jobs off Supabase to V2 render orchestrator; drop all Supabase lib files
- render-jobs.ts: replace Supabase client with V2 gateway calls
  POST /v1/renders (saved_project_id + quality + resolution + frame_rate)
  GET  /v1/renders/:id/progress for status polling
  GET  /v1/renders/:id + /v1/exports/:id/download-url for completed output URL
  triggerRenderWorker is now a no-op (V2 dispatches internally)
- render/route.ts: add getAccessToken() guard, pass token to createRenderJob
- render/[jobId]/status/route.ts: add getAccessToken() guard, pass token to getRenderJob
- Delete src/lib/supabase/, src/lib/supabase.ts — no remaining consumers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 08:41:58 +03:30
soroush.asadi 9555044485 feat(frontend): route checkout through V2 Identity plan-purchase; drop dead Stripe webhook
/api/checkout now resolves the requested plan (pro|business × monthly|annual)
to a plan GUID via gateway /v1/plans (codes follow pro_monthly / business_annual)
and POSTs /v1/users/me/plan/purchase. The payments service owns the gateway
(ZarinPal/Stripe) and returns redirect_url, which we hand back unchanged.

Removes the orphaned Stripe→Supabase webhook + lib/stripe.ts client: profile
plan reads come from Identity now, so the Supabase profiles upsert loop is dead.
V2 payments has its own gateway callback (skip-auth, payment-callback route).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 12:46:01 +03:30
soroush.asadi 903306c7cf feat(frontend): settings page reads user from V2 Identity; drop dead OAuth callback
- dashboard/settings/page.tsx now resolves the current user via getCurrentUser()
  (Identity JWT cookie) instead of the Supabase server client; display name comes
  from Identity's full_name.
- Remove src/app/auth/callback/route.ts — the Supabase OAuth code-exchange
  callback is unreferenced now that auth runs entirely on Identity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 06:10:21 +03:30
soroush.asadi 2adaf57f10 feat(frontend): move settings profile + password off Supabase to V2 Identity
Adds two authenticated gateway proxies and rewires the settings UI to them:
- POST /api/auth/password → Identity /v1/auth/password/change (server-side
  re-validates current password; drops the client-side re-auth round-trip)
- PATCH /api/profile → Identity PATCH /v1/users/me (full_name)

SettingsProfile and SettingsSecurity now fetch these routes instead of the
Supabase browser client.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 06:06:47 +03:30
soroush.asadi 6d2a296c38 feat(frontend): read user profile + plan from V2 Identity instead of Supabase
getUserProfile now calls the gateway /v1/users/me and /v1/users/me/plan with
the access-token cookie, mapping plan_code → PlanId. Falls back to a free-plan
profile when signed out or Identity is unreachable. Stripe ids drop to null
(V2 billing runs through the payments service). Signature unchanged so the
dashboard plan badge + settings call sites are untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 06:02:17 +03:30
soroush.asadi 14cdb772b4 feat(frontend): migrate dashboard projects flow off Supabase to V2 Studio
User-saved projects now read/write through the gateway /v1/saved-projects
(studio schema) using the Identity access-token cookie, replacing the
Supabase `projects` table. Adds src/lib/api/saved-projects.ts client that
maps studio snake_case DTOs into the existing DashboardProject shape.

- DashboardProjectsContent: lists via studio service, degrades gracefully
- /api/projects GET: studio list; POST: copy-from-template create
  (studio requires original_project_id; falls back to scene_data.templateId)
- /api/projects/[projectId] GET/PATCH: proxy to studio with JWT

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 05:56:25 +03:30
soroush.asadi f366d73697 chore: harden frontend npm ci with retry loop for flaky Nexus proxy
The Nexus npm-group proxy intermittently returns 500s / corrupted
tarballs while back-filling its cache. Wrap npm ci in a 5-attempt retry
loop with raised fetch-retry budget so successive passes converge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 05:45:10 +03:30
soroush.asadi e8c1587695 fix: satisfy next build ESLint in admin-api (unused param)
next build runs ESLint (stricter than tsc --noEmit), which failed on the
unused `type` param in fetchCategories with no-unused-vars, breaking the
frontend production build. Reference it via `void type;` while keeping
the signature for API compatibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 05:45:10 +03:30
soroush.asadi 7f03ad1d03 chore: add demo content seed for V2 content service
Idempotent SQL seed (deterministic UUIDs, ON CONFLICT DO NOTHING) that
inserts 4 categories and 8 published template containers linked to them,
so the public site shows real data through the gateway /v1/* routes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 05:22:47 +03:30
soroush.asadi fa013b2305 feat: repoint admin-api content reads to V2 gateway
src/lib/admin-api.ts now reads categories/templates/projects from the
V2 content service via the gateway (/v1/*) instead of the legacy
admin-api (:5000). Maps V2 snake_case DTOs (CategoryResponse tree,
PagedResponse<ContainerSummaryResponse>) into the existing camelCase
AdminCategory/AdminProject shapes, so call sites are unchanged and the
hardcoded fallback still applies when the gateway is unreachable.

V2 containers are video templates (primary_mode is a render mode, not a
type), so all map to type "video". Query params use camelCase
(pageSize/isPublished) since .NET binds query strings by property name,
not the snake_case used in response bodies.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 05:15:45 +03:30
soroush.asadi 8b86f17645 fix: make plans list public, fix frontend healthcheck IPv6
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>
2026-05-30 00:24:53 +03:30
soroush.asadi 90ac0b81d1 feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file,
render, notification, gateway (Go) services with vendored deps, plus DB
migrations, event/API contracts, and an init-db script.

Wire the Next.js frontend to the gateway: server-side JWT auth routes
(login/register/refresh/logout/me), gateway fetch helper, and session/
cookie/jwt helpers under src/lib.

Containerize the stack via docker-compose.v2.yml and per-service
Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and
MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via
next/font/local to avoid Google Fonts (geo-blocked).

Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:29:31 +03:30
Soroush.Asadi 53ea78a00d chore: dockerize Next.js app (multi-stage build, standalone output)
- Dockerfile: 3-stage build (deps → builder → runner) using node:20-alpine
- .dockerignore: excludes node_modules, .next, secrets, graphify-out
- docker-compose.yml: runtime secrets via env, build-time NEXT_PUBLIC_* via args
- Render worker service pre-wired (commented out, opt-in)
2026-05-27 10:17:07 +03:30
Soroush.Asadi 36e264f3e3 feat: admin API integration, LogoMark, settings page, i18n, RTL font, docs
- Wire admin API into homepage + templates page (ISR 60s, null fallback)
- Add src/lib/admin-api.ts with safeFetch helper
- Add adminProjectToTemplateItem + adminProjectToCatalogTemplate mappers
- Add LogoMark SVG component, replace Sparkles icon in Navbar/Footer/Sidebar
- Add public/favicon.svg (SVG brand mark)
- Rewrite opengraph-image.tsx with FlatRender branding
- Add RTL/Persian font cascade: unlayered [dir=rtl] block forces Vazirmatn
- Dashboard Settings page: Profile, Security, Billing, Notifications sections
- Add src/lib/supabase/client.ts browser client
- Admin API: GET /me, PATCH /profile, POST /change-password endpoints
- Admin API DTOs: AdminUserDto, UpdateProfileRequest, ChangePasswordRequest
- Admin UI Settings page with TanStack Query + mutations
- Add CLAUDE.md + README.md to both repos for new-machine onboarding
- Update PROJECT_MEMORY.md with session log
- Add appsettings.Development.json.example template
2026-05-27 09:06:51 +03:30
Soroush.Asadi 4875e468fe i18n: wire useTranslations for video-maker, image-maker, and pricing heading
- Add videoMaker + imageMaker namespace to en.json and fa.json
  (hero, features x5, use-cases x4, CTA per section)
- Pricing.tsx: replace hardcoded heading with t('pricing.heading')
- VideoMakerHero/Features/UseCases/Cta: full useTranslations wiring
- ImageMakerHero/Features/UseCases/Cta: full useTranslations wiring
- Features/UseCases arrays moved inside components; icons kept as
  module-level constants to avoid per-render allocation
2026-05-25 07:40:26 +03:30
Soroush.Asadi 39a86b93d4 fix: konva Image alt prop + useMemo dep array corrections 2026-05-25 07:29:40 +03:30
Soroush.Asadi c61f587767 feat: full studio build -- light theme, canvas thumbnails, i18n (fa/en) 2026-05-24 17:37:31 +03:30
Soroush.Asadi d962483359 Initial commit from Create Next App 2026-05-21 12:17:29 +03:30