- updateEmailByCustomer: DB fallback when stripe_customer_id not in cache
- updateKeyEmail: DB fallback when key not in cache
- POST /v1/recover: DB fallback when email not in cache (was only on verify)
- 6 TDD tests added (keys-email-update.test.ts, recover-initial-db-fallback.test.ts)
- 547 tests total, all passing
- downgradeByCustomer now queries DB when key not in memory cache,
preventing cancelled customers from keeping Pro access in multi-pod setups
- recover/verify endpoint falls back to DB lookup when cache miss on email
- Added TDD tests for both fallback paths (4 new tests)
- Add partial unique index on api_keys(stripe_customer_id) WHERE NOT NULL
- Use INSERT ... ON CONFLICT in createProKey for cross-pod dedup
- Add findKeyByCustomerId() to query DB directly
- Success page checks DB before creating key (survives pod restarts)
- Refresh in-memory cache after UPSERT
- Enable TCP keepalive on pg.Pool to detect dead connections
- Add connectionTimeoutMillis (5s) to prevent hanging on stale connections
- Add queryWithRetry() with exponential backoff for transient DB errors
- Add connectWithRetry() for transaction-based operations
- Detect PgBouncer "no available server" and other transient errors
- Health check has 3s timeout and returns 503 on DB failure
- All DB operations in keys, verification, usage use retry logic
Fixes BUG-075: PgBouncer failover causes permanent pod failures
- Add /change-email as a proper standalone page (public/src/change-email.html)
with API key input, new email input, verification code flow, and success state
- Update footer partial: change "/#change-email" link to "/change-email" on all pages
- Remove email change modal HTML and hash-handler JS from index page source
- Add /change-email to sitemap.xml
- Rebuild all HTML files via build-html.cjs
- Add updateEmailByCustomer() to src/services/keys.ts
- Add customer.updated webhook handler in src/routes/billing.ts
to sync email changes made via Stripe dashboard back to DocFast
- Replace revokeByCustomer with downgradeByCustomer in keys.ts
- Sets tier='free' in cache and DB (UPDATE, not DELETE)
- Add isDocFastSubscription() product filter helper in billing.ts
- Filters all subscription events by prod_TygeG8tQPtEAdE
- Handle customer.subscription.updated event
- Downgrades on status=canceled/past_due/unpaid or cancel_at_period_end=true
- Handle customer.subscription.deleted with product filter
- Downgrades to free (was incorrectly deleting the key)
Fixes revenue integrity bug: cancelled Pro subscribers kept Pro access.
- Replace JSON file storage with PostgreSQL (pg package)
- Add db.ts service for connection pool and schema init
- Rewrite keys.ts, verification.ts, usage.ts for async PostgreSQL
- Update all routes for async function signatures
- Add migration script (scripts/migrate-to-postgres.mjs)
- Update docker-compose.yml with DATABASE_* env vars
- Change SLA from 99.9% to 99.5% in landing page
- Fix swagger-ui symlink in Dockerfile (was pointing to /opt/docfast instead of /app)
- Add CSP directives to allow inline scripts/styles and Google Fonts
- Add email-change.ts route with rate limiting (3/hr) and verification
- Add updateKeyEmail to keys service
- Add email-change route to index.ts with CORS support
- Added /v1/signup/free endpoint for instant API key provisioning
- Built unified key store (services/keys.ts) with file-based persistence
- Refactored auth middleware to use key store (no more hardcoded env keys)
- Refactored usage middleware to check key tier from store
- Updated billing to use key store for Pro key provisioning
- Landing page: replaced mailto: link with signup modal
- Landing page: Pro checkout button now properly calls /v1/billing/checkout
- Added Docker volume for persistent key storage
- Success page now renders HTML instead of raw JSON
- Tested: signup → key → PDF generation works end-to-end