Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
133
.env.example
Normal file
133
.env.example
Normal file
@@ -0,0 +1,133 @@
|
||||
# ==============================================
|
||||
# OpenRouter / DeepSeek AI Vision Keys
|
||||
# ==============================================
|
||||
# OpenRouter API Key for GPT-5.6 Luna ($0.10 / $0.60 per 1M)
|
||||
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
||||
OPENROUTER_MODEL=openai/gpt-5.6-luna
|
||||
|
||||
# DeepSeek Direct API
|
||||
DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
|
||||
# Fallback Vision Provider (OpenAI GPT-4o-mini / Gemini Flash)
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
|
||||
# ==============================================
|
||||
# Database (PostgreSQL - Neon / Supabase)
|
||||
# ==============================================
|
||||
# Local-First IndexedDB is active by default. Optional PostgreSQL for production:
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/receipt_scanner
|
||||
|
||||
# Least-privilege runtime role (recommended for production).
|
||||
# Provision it with scripts/db-permissions.sql — via `node
|
||||
# scripts/apply-db-permissions.mjs`, or automatically on a fresh volume through
|
||||
# the docker-compose init mount — then point the app's RUNTIME connection at
|
||||
# it. The role (receipt_app) has CONNECT + schema USAGE + table/sequence DML
|
||||
# only: no superuser, no CREATEDB/CREATEROLE, no DDL. Migrations and schema
|
||||
# init still require the OWNER DATABASE_URL above (they run DDL), so run those
|
||||
# with the owner URL and the app with the restricted URL.
|
||||
# APP_DATABASE_URL=postgresql://receipt_app:receipt_app_secure_password@localhost:5432/receipt_scanner
|
||||
# APP_DATABASE_PASSWORD=receipt_app_secure_password
|
||||
|
||||
# ==============================================
|
||||
# Storage (Cloudflare R2 / S3 - Optional)
|
||||
# ==============================================
|
||||
R2_ACCOUNT_ID=your_cloudflare_account_id
|
||||
R2_ACCESS_KEY_ID=your_r2_access_key
|
||||
R2_SECRET_ACCESS_KEY=your_r2_secret_key
|
||||
R2_BUCKET_NAME=receipt-images
|
||||
R2_PUBLIC_URL=https://your-bucket-url.com
|
||||
|
||||
# ==============================================
|
||||
# Payments & Webhooks (Stripe)
|
||||
# ==============================================
|
||||
STRIPE_SECRET_KEY=sk_test_...
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
STRIPE_WEEKLY_PRICE_ID=price_...
|
||||
STRIPE_ANNUAL_PRICE_ID=price_...
|
||||
STRIPE_LIFETIME_PRICE_ID=price_...
|
||||
# Prices are decided server-side from src/lib/billing/pricing.ts. When a Price
|
||||
# ID above is set, Checkout uses it; otherwise the catalog amount is charged.
|
||||
# The webhook cross-checks the paid amount against that same catalog, so a
|
||||
# Price ID must never diverge from the catalog amount for its plan.
|
||||
|
||||
# ==============================================
|
||||
# Authentication — Google Sign-In (optional)
|
||||
# ==============================================
|
||||
# Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID
|
||||
# (type "Web application"). Authorised redirect URI must be exactly:
|
||||
# <NEXT_PUBLIC_APP_URL>/api/auth/google/callback
|
||||
# The Google button only renders when both values are present.
|
||||
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=your_client_secret
|
||||
|
||||
# ==============================================
|
||||
# Authentication — SMTP for confirmation links
|
||||
# ==============================================
|
||||
# Required in production: signup fails loudly without it, because an account
|
||||
# that can never be confirmed must not be created. In development, missing SMTP
|
||||
# makes the confirmation link appear in the server log and in the UI instead.
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=no-reply@yourdomain.com
|
||||
SMTP_PASSWORD=your_smtp_password
|
||||
# true for implicit TLS on port 465; false for STARTTLS on 587.
|
||||
SMTP_SECURE=false
|
||||
MAIL_FROM=ScanReceipts <no-reply@yourdomain.com>
|
||||
|
||||
# ==============================================
|
||||
# Discord Sales Notification Bot
|
||||
# ==============================================
|
||||
DISCORD_SALES_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
||||
|
||||
# ==============================================
|
||||
# App Settings
|
||||
# ==============================================
|
||||
# Inlined into the client bundle at build time and baked into the Docker image.
|
||||
# Pass it as --build-arg NEXT_PUBLIC_APP_URL (docker-compose forwards it via
|
||||
# build.args) so sitemap/robots/canonicals are built against the real domain.
|
||||
# PRODUCTION MUST BE AN https:// URL: the app is HTTPS-only (HSTS via
|
||||
# Strict-Transport-Security), and an http:// value would make browsers refuse
|
||||
# the upgrade promise baked into every response. http://localhost:3000 is fine
|
||||
# for local development only.
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
# ==============================================
|
||||
# Cookie domain — share the session across subdomains
|
||||
# ==============================================
|
||||
# When the dashboard is served on app.<domain> and the admin on admin.<domain>
|
||||
# (both rewritten by src/middleware.ts), the session, guest, CSRF and OAuth
|
||||
# cookies must be scoped to the parent domain so one login works everywhere.
|
||||
# Leave EMPTY in local development (localhost cookies stay host-only).
|
||||
# Example for production:
|
||||
# COOKIE_DOMAIN=.scan-receipts.app
|
||||
|
||||
# ==============================================
|
||||
# CORS — cross-origin API access
|
||||
# ==============================================
|
||||
# Comma-separated allowlist of origins (scheme://host[:port]) allowed to call
|
||||
# the API with credentials. The app's own origin (NEXT_PUBLIC_APP_URL) is
|
||||
# always allowed and must NOT be repeated here. Never use "*": the app sends
|
||||
# cookies (guest sessions, admin subdomain) and a wildcard would be rejected by
|
||||
# browsers and is a CSRF risk. Include the admin subdomain origin
|
||||
# (e.g. https://admin.example.com) if it should call the API cross-origin.
|
||||
# Leave empty for same-origin-only access.
|
||||
CORS_ORIGINS=
|
||||
|
||||
# ==============================================
|
||||
# Umami Analytics (self-hosted, optional)
|
||||
# ==============================================
|
||||
# Inlined into the client bundle at build time (see Dockerfile ARG/ENV) and
|
||||
# also read by next.config.ts to widen the CSP to the script's origin. The
|
||||
# tracking script only renders when BOTH values are set (src/app/(app)/layout.tsx
|
||||
# and src/app/(marketing)/[locale]/layout.tsx). Leave empty to disable.
|
||||
# NEXT_PUBLIC_UMAMI_SRC=https://analytics.yourdomain.com/script.js
|
||||
# NEXT_PUBLIC_UMAMI_ID=your-website-id
|
||||
|
||||
# ==============================================
|
||||
# Admin Dashboard Access
|
||||
# ==============================================
|
||||
# Comma-separated list of email addresses with admin access
|
||||
ADMIN_EMAILS=admin@example.com
|
||||
Reference in New Issue
Block a user