Leon SerfatyandClaude Opus 5 3e9ba07175 feat: Cloudflare Turnstile on auth, CSP fixes, admin/SEO/analytics additions
Turnstile bot protection (sign-in, sign-up, password-reset):
- Register Better Auth's captcha plugin with the cloudflare-turnstile
  provider; endpoints listed explicitly rather than relying on defaults.
  /reset-password is intentionally excluded — it is reached only via a
  single-use emailed token.
- Add an explicit-render Turnstile widget component. Tokens are single-use,
  so each form resets the challenge after a failed submit; submit stays
  disabled until a token is held.
- Read the site key server-side and pass it down as a prop, so rotating it
  does not require a rebuild.
- Fail fast in production when TURNSTILE_SECRET_KEY is missing, and when a
  secret is set without a site key (that combination would demand a token
  no form can produce, locking every user out).
- Pass a throwaway secret during `next build` in the Dockerfile, mirroring
  the existing BETTER_AUTH_SECRET treatment, so image builds don't need it.

CSP fixes in middleware (these blocked Turnstile entirely):
- Add frame-src for challenges.cloudflare.com. Without it the widget's
  iframe fell back to default-src 'self' and was blocked outright.
- Allow 'unsafe-eval' and websockets in DEVELOPMENT only. `next dev`
  compiles with eval(), so the strict policy threw EvalError and killed
  hydration — no client JS ran at all, which also meant form submit
  handlers never fired. Production policy is unchanged and still strict.

Also included (concurrent work in the tree):
- Admin organizations pages and lib/admin/orgs.
- Episode moderation migration, SEO metadata (sitemap, robots, JSON-LD,
  OG/Twitter images, manifest), Umami analytics, not-found page.

Local dev database: docker-compose.dev.yml provisions Postgres 18 on port
5443 (5432-5442 are in use by other local projects).

Note: `npx tsc --noEmit` currently fails in app/(app)/team/page.tsx — an
`invitations` prop the component does not accept. This predates the commit
and will fail `next build` until fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:10:55 -04:00

🎙️ Podcast Distribution AI

An all-in-one AI platform that takes you from a topic idea to a finished, publishable podcast episode in minutes — it writes the script (GPT-4), records realistic multi-voice audio (ElevenLabs), and designs the cover art (DALL·E), then lets you fine-tune, download, and repurpose the result.

Stack

Concern Choice
Framework Next.js (App Router) + TypeScript
UI Tailwind CSS + shadcn/ui + Recharts
Database PostgreSQL + Prisma
Auth Better Auth (email/password + Google, admin + organization plugins)
Queue pg-boss (Postgres-backed, no Redis) + a separate worker process
Storage Local disk on a persistent volume (swappable StorageProvider → S3/R2)
AI OpenAI (GPT-4 + DALL·E), ElevenLabs (TTS + dialogue)
Billing Stripe and PayPal → one unified Subscription model
Email Resend
Deploy Dokploy (Docker Compose + Traefik) — see deploy/README.md

Architecture

Browser ──► Next.js web (PM2)  ──enqueue──►  Postgres (app data + pgboss queue)
                │  SSE status                      ▲ claim job (SKIP LOCKED)
                ▼                                   │
          /api/assets (private mp3)          Worker (PM2, ffmpeg)
                                              script → segment → ElevenLabs
                                              → ffmpeg stitch → DALL·E → save → meter
                                                       │
                                                       ▼
                                              Local disk  storage/{mp3,art}

Local development

npm install
cp .env.example .env        # fill DATABASE_URL, BETTER_AUTH_SECRET, OPENAI_API_KEY, ELEVENLABS_API_KEY…
npx prisma migrate dev      # create tables
npm run db:seed             # seed the plan catalog

# Two processes:
npm run dev                 # web (http://localhost:3000)
npm run worker:dev          # generation worker (needs ffmpeg on PATH)

ffmpeg must be installed and on PATH for audio stitching (brew install ffmpeg / apt install ffmpeg / choco install ffmpeg).

Make yourself an admin after signing up:

npm run make-admin you@email.com   # then visit /admin

Key directories

app/(marketing)   public landing + pricing
app/(auth)        sign-in / sign-up / password reset
app/(app)         user dashboard (episodes, wizard, usage, billing, team, api-keys, settings)
app/(admin)       admin dashboard (users, subscriptions, AI cost, moderation, health, flags, audit)
app/api           auth, webhooks/{stripe,paypal}, episodes/[id]/stream (SSE), assets, v1 (API)
lib/ai            provider abstraction + pipeline (generate-episode, segment, stitch, repurpose)
lib/billing       plans, stripe, paypal, catalog, unified subscription writer, webhooks
lib/queue         pg-boss client + job definitions
lib/storage       StorageProvider interface + local-disk impl
worker/           pg-boss consumer (runs the generation pipeline)

How generation works

  1. The 3-step wizard creates an Episode (QUEUED) + speaker config, then enqueues a pg-boss job (after enforceLimit checks the plan).
  2. The worker runs the pipeline, updating Episode.status at each stage.
  3. The episode page subscribes to /api/episodes/[id]/stream (SSE) and shows a live stepper, then renders the script editor, audio player, and cover art when READY.

Long scripts are chunked to stay under ElevenLabs' ~2k-char dialogue limit, synthesized per segment, then concatenated and loudness-normalized with ffmpeg.

Deployment

Containerized for Dokploy (Docker Compose: web + worker, Traefik for domain/TLS/SSE, a persistent storage volume). See deploy/README.md for the full runbook (env, domain, migrations-on-boot, webhooks, backups).

S
Description
No description provided
Readme
786 KiB
Languages
TypeScript 98.7%
JavaScript 0.5%
Dockerfile 0.5%
CSS 0.3%