- Redirects default to 302 so browsers stop permanently caching the hop —
click analytics stay accurate and destination edits take effect for
returning visitors. 301 is a per-link opt-in (create/edit/API).
- Links can carry a password: visitors get a branded unlock page at /:code
and POST to /:code/unlock. Unlocks always redirect with 302.
- Missing, expired, and quota-limited links now show branded standalone
visitor pages (404/410/429) instead of bouncing to the marketing site
with ?error= query params.
- Clicks are recorded through the async pipeline with bot/crawler tagging
(UA heuristics), GeoLite2 country lookup (ip-api fallback gated behind
GEO_API_FALLBACK), and the IP_ANONYMIZE policy applied at write time.
- Deletes stamp deleted_at for later hard-purge.
- Dashboard: redirect-type selector and password set/replace/remove in the
edit form, lock badge on protected links.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Click events are enqueued and written by a background thread — redirects
never block on DB writes or geo lookups.
- Uptime heartbeat marks per-minute rows (INSERT OR IGNORE), safe across
Gunicorn workers; /api/uptime now measures real availability from each
day's first heartbeat instead of a per-process counter that could only
report 100%. Legacy uptime_daily rows still honoured.
- Hourly maintenance thread: daily SQLite backup via VACUUM INTO (atomic
temp-file rename, race-safe across workers, BACKUP_KEEP retention),
CLICK_RETENTION_DAYS purge, uptime-mark pruning.
- notify_webhook() helper for Slack/Discord-compatible WEBHOOK_URL.
- Auth groundwork: _try_api_key_auth() lets the login/admin decorators
accept Bearer qrk_ tokens (scope-checked, no Set-Cookie), and
log_audit() records admin actions — used by the following feature
commits.
- New env config: WEBHOOK_URL, GEOIP_DB_PATH, GEO_API_FALLBACK,
IP_ANONYMIZE, CLICK_RETENTION_DAYS, BACKUPS, BACKUP_DIR, BACKUP_KEEP.
- requirements: add geoip2 for local GeoLite2 lookups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>