- 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
The promote workflow previously pulled :latest, which could be stale if the
staging build hadn't finished yet. Now it pulls the exact :SHA image that
deploy.yml produces, with retry logic (up to 10min) if staging is still building.
- Fix download button: exclude #demoDownload from smooth scroll handler
that was calling preventDefault() on blob: URLs after PDF generation
- Replace '5,000 PDFs per month' with 'High-volume PDF generation' in pricing
- Update schema.org structured data to remove specific limits
- Add 3 pre-built templates (Invoice, Report, Custom HTML)
- Split-pane editor with live HTML preview (updates as you type)
- Generation timer shows actual response time
- Before/after comparison (free watermarked vs Pro clean)
- Pro CTA integrated into result panel
- Fully responsive: stacks on mobile
- Professional polish matching site design language
- Remove Free tier pricing card entirely
- Remove signup modal (no more free signups)
- Add interactive playground section (paste HTML → watermarked PDF)
- Hero CTAs: 'Try Demo →' and 'Get Pro API Key — €9/mo'
- Pricing: single Pro card at €9/mo
- Update structured data to remove Free offer
- Add swagger-jsdoc dependency for auto-generating OpenAPI spec from JSDoc
- Add JSDoc @openapi annotations to all route handlers
- Create scripts/generate-openapi.mjs build step
- OpenAPI spec now auto-generated from code — no manual JSON editing
- All 13 endpoints documented with full parameters
- New demo endpoints documented, signup marked as deprecated
- Updated info description: demo-first, no free tier references
- Dockerfile updated to run openapi generation during build
- Build script updated: npm run build generates spec before compile
- Remove free account signup flow entirely
- Add POST /v1/demo/html and /v1/demo/markdown (public, no auth)
- Demo: 5 requests/hour per IP, 50KB body limit, watermarked PDFs
- Landing page: interactive playground replaces 'Get Free API Key'
- Pricing: Demo (free) + Pro (€9/mo), no more Free tier
- /v1/signup returns 410 Gone with redirect to demo/pro
- Keep /v1/recover for existing Pro users
- Update JSON-LD, API discovery, verify page text
- Rate limit /checkout to 3 requests per IP per hour via express-rate-limit
- Reject request bodies >1KB (413)
- Log checkout session creation with client IP
- Bump version to 0.3.4
- queryWithRetry now uses explicit client checkout; on transient error,
calls client.release(true) to DESTROY the dead connection instead of
returning it to pool. Fresh connections are created on retry.
- connectWithRetry validates connections with SELECT 1 before returning
- Health check destroys bad connections on failure
- Reduced idleTimeoutMillis from 30s to 10s for faster stale connection eviction
- Fixes BUG-075: pool kept reusing dead TCP sockets after PgBouncer pod restart
- 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
express.static was serving docs.html before the /docs route handler,
causing Helmet default CSP to be used instead of the custom Swagger UI CSP.
This blocked unsafe-eval and blob: workers needed by Swagger UI.
Swagger UI 5.x uses new Function() via ajv for JSON schema validation.
Helmet default CSP (script-src self) blocks this in Firefox, causing
TypeError: NetworkError when attempting to fetch resource on Try It.
Override CSP on /docs route to allow unsafe-eval.
- Support SMTP_USER/SMTP_PASS env vars for authenticated SMTP
- Support SMTP_FROM env var for configurable sender address
- Auto-detect secure mode for port 465
- Backwards compatible: falls back to unauthenticated local relay
- Push to main builds ARM64 image and deploys to docfast-staging namespace
- Push a version tag (v*) promotes latest image to docfast namespace (prod)
- Both use same deployer SA with namespace-scoped RBAC
- Build ARM64 image via QEMU/buildx on x86 runner
- Push to Forgejo container registry (uses built-in GITHUB_TOKEN)
- Deploy via kubectl with scoped deployer SA (docfast namespace only)
- No SSH, no secrets on infra, no Docker on k3s-mgr
- BUG-053: Add terser JS minification to build process
- BUG-060: Add og:image, twitter:card, twitter:image to sub-pages
- BUG-067: Update skip-link to #main-content on all pages
- BUG-055: Remove duplicate preconnect tags from homepage
- BUG-058: Add twitter:image meta tag to homepage
- BUG-060: Add og:title/description/url to sub-pages (impressum/privacy/terms/status)
- BUG-061: Already done in sitemap.xml
- BUG-067: Add skip-to-content link via nav partial + styles_base
- BUG-069: Footer already added to docs.html
- BUG-053: Minify app.js and status.js, update HTML refs
- BUG-055: Remove duplicate preconnect tags from homepage
- BUG-058: Add twitter:image meta tag to homepage
- BUG-060: Add og:title/description/url to sub-pages
- BUG-061: Add /status to sitemap.xml
- BUG-067: Add skip-to-content link on all pages
- BUG-069: Add legal footer to /docs page
- BUG-053: Minify app.js with terser
- 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.