Leon SerfatyandClaude Opus 5 68c0b1306d fix(csp): blank page on statically prerendered routes
The nonce-based script-src is incompatible with Next's static prerendering.
The nonce is minted per request in middleware, but statically generated HTML
was produced at build time with no nonce on its inline scripts, so on "/" and
the marketing routes every inline hydration script was blocked: the page
painted from SSR HTML, React failed to hydrate, and the tree unmounted to a
blank screen a moment later. Dynamic routes (/sign-in, /dashboard) render per
request, receive the nonce, and worked — which is why this was missed.

Browsers that honour a nonce ignore 'unsafe-inline', so the two cannot be
combined. Drop the nonce and allow inline scripts; the policy still restricts
which origins may serve scripts, and frame-src/connect-src stay strict.
Restoring nonces would require every route to render dynamically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 12:16:09 -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%