No description
- TypeScript 96.6%
- Dockerfile 1.9%
- CSS 0.7%
- JavaScript 0.4%
- Shell 0.4%
| app | ||
| components | ||
| config | ||
| lib | ||
| prisma | ||
| public | ||
| services | ||
| .env.example | ||
| .gitignore | ||
| ai.md | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| next-env.d.ts | ||
| next.config.js | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| start.md | ||
| tailwind.config.ts | ||
| tsconfig.json | ||
AI Photo Style Generator (MVP)
Upload a photo, pick a style, pay via Stripe, get an AI-generated version back via Nano Banana Pro (Gemini image generation).
Stack
- Next.js 15 (App Router) + TypeScript + Tailwind
- PostgreSQL + Prisma
- MinIO (S3-compatible storage)
- Stripe Checkout + Webhooks
- Google Gemini API (Nano Banana Pro) for image generation
- Docker Compose
Setup
-
Copy
.env.exampleto.envand fill in:STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET- from your Stripe test dashboard.GEMINI_API_KEY- from Google AI Studio.NEXT_PUBLIC_BASE_URL- public URL of the app (used for Stripe redirect URLs).MINIO_PUBLIC_ENDPOINT- URL where the browser can reach MinIO (e.g.http://localhost:9000for local dev, or your domain in production).- Adjust
MINIO_ACCESS_KEY/MINIO_SECRET_KEYif you don't want the defaults.
-
Run everything:
docker compose up -d --build
The app runs migrations automatically on startup (docker-entrypoint.sh).
Stripe webhook (local testing)
Use the Stripe CLI to forward events to your local instance:
stripe listen --forward-to localhost:3000/api/stripe/webhook
Copy the printed whsec_... value into STRIPE_WEBHOOK_SECRET in .env.
Local development (without Docker)
Requires a local Postgres and MinIO (or point .env at remote instances).
npm install
npm run prisma:migrate:dev
npm run dev
Flow
/- landing page, CTA to/generate./generate- upload photo, pick one of 5 styles, optional prompt ->POST /api/upload(validates file, stores in MinIO, createsGenerationrow with statuspending) ->POST /api/checkout(creates Stripe Checkout Session,Paymentrow) -> redirect to Stripe.- After payment, Stripe redirects to
/processing?generationId=...and sendscheckout.session.completedto/api/stripe/webhook, which marks the payment as paid and triggersservices/imageGeneration.ts(status:pending->processing->completed/failed). /processingpollsGET /api/generations/:idevery ~2.5s, redirects to/result/:idoncecompleted./result/:idshows the generated image(s) with signed download links and a "Generate Another" button.
Notes / limitations (MVP)
- No user accounts - generations are addressed by UUID only.
- Rate limiting (
lib/rateLimit.ts) is in-memory, per-instance only - fine for a single container, won't work correctly if you scaleappto >1 replica. - Image generation runs in the webhook handler without awaiting (fire and
forget) so Stripe gets a fast response. If the container restarts mid-
generation, that generation stays
processingforever - acceptable for MVP, would need a real job queue for production. - Original uploads and results are not auto-deleted (the 24h/30-day retention mentioned in the spec would need a cron/cleanup job).