Commit Graph

14 Commits

Author SHA1 Message Date
Jason Stedwell 48d3445b09 docs: document new features, ops env vars, and API surface; refresh roadmap
- README: feature map updated (redirect types, protected links, QR vector
  export, audience analytics, API keys, admin observability, operations);
  API reference covers new endpoints and params. Roadmap now lists
  self-serve accounts, security hardening (rate limiting/CSRF/SSRF guard),
  Stripe billing, custom domains, and remaining power features.
- INSTALL: new environment variables (WEBHOOK_URL, GEOIP_DB_PATH,
  GEO_API_FALLBACK, IP_ANONYMIZE, CLICK_RETENTION_DAYS, BACKUPS,
  BACKUP_DIR, BACKUP_KEEP) with GeoLite2 setup notes and the ip-api.com
  licensing caveat.
- api-docs page: API-key authentication, new link/analytics/QR params,
  aggregate analytics, admin overview/audit/keys/purge, unlock route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:40:44 -05:00
Jason Stedwell 45be86bb36 feat: admin observability — platform overview, audit log, purge, notifications
- GET /api/admin/overview: platform totals (users, orgs, links, clicks),
  a 30-day click series, top links, and per-user quota consumption with
  near-limit flags (org members show pooled usage).
- Audit logging wired into all admin mutations: user create/update/delete,
  password resets, org create/update/delete, purges. Browsable via
  GET /api/admin/audit and a new Audit Log section in the admin UI.
- POST /api/admin/purge hard-deletes soft-deleted links older than N days,
  freeing their short codes and click history.
- Contact-form submissions now fire a WEBHOOK_URL notification.
- Fix: deleting a user who owned links or tags failed with a foreign-key
  500 — their links/tags are now deliberately orphaned (kept in the DB,
  unowned) and their API keys removed, matching the UI's wording.
- Admin UI: Platform Overview stat cards, quota-usage table with ⚠ NEAR
  LIMIT badges, audit table, and a purge action.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:40:29 -05:00
Jason Stedwell a321ff6171 feat: unique visitors, bot filtering, and account-wide audience analytics
- Per-link analytics gain unique-visitor counts (period total and per-day),
  a bot_clicks figure, and an exclude_bots=1 filter applied across every
  breakdown, the daily series, and the heatmap. Bot classification comes
  from the is_bot flag stamped at click time.
- New GET /api/analytics/aggregate: one chart across the whole account (or
  org pool), optionally scoped to a single tag — daily clicks + uniques,
  referrers, devices, browsers, countries, and top links, capped to the
  plan's analytics window.
- Dashboard: Audience panel with tag/window/bot controls wired into
  loadDashboard, plus a NO BOTS toggle and PERIOD/UNIQUE/BOTS stat line on
  each link's analytics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:40:14 -05:00
Jason Stedwell 8f2bfd23d2 feat: API key management endpoints and dashboard panel
Self-serve keys: GET/POST /api/keys, DELETE /api/keys/:id (admins may
revoke any key), GET /api/admin/keys for a platform-wide view. Tokens are
qrk_-prefixed, stored only as a SHA-256 hash, and returned exactly once at
creation. Read-scope keys are rejected with 403 on non-GET requests; key
creation and revocation are recorded in the audit log.

Dashboard gets a self-serve key panel with copy-once token display, scope
badges, and last-used timestamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:39:50 -05:00
Jason Stedwell a1cb6ec5f2 feat: QR export as SVG and PDF with selectable error correction
All QR endpoints (/api/qr/:code, /api/qr/custom GET+POST) accept
format=png|svg|pdf, ec=L|M|Q|H, and download=1 for attachment responses,
unified behind a shared qr_response() renderer. SVG is generated as a true
vector (one path of modules, crispEdges) with an optional logo embedded as
a data URI over a cleared centre tile; PDF wraps the raster render for
print. A logo always forces error correction H. Dot styles remain
raster-only.

QR Studio: format and error-correction selectors, PDF previews as PNG,
format-aware download button with contextual hints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:39:50 -05:00
Jason Stedwell 14d6e8c50e feat: per-link redirect type, password-protected links, branded visitor pages
- 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>
2026-07-03 01:39:50 -05:00
Jason Stedwell d0e99b5244 ops: async click pipeline, honest uptime, backups, retention; auth/audit plumbing
- 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>
2026-07-03 01:39:50 -05:00
Jason Stedwell c892588d31 db: add schema for API keys, audit log, and per-minute uptime; migrate links/clicks
New tables: api_keys (hashed bearer tokens with scopes), audit_log (admin
action trail), uptime_minutes (multi-worker-safe availability marks).
Idempotent column migrations: links.redirect_type (default 302),
links.password_hash, links.deleted_at, clicks.is_bot. Existing databases
upgrade in place on next startup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:36:56 -05:00
jason e928770105 Upload files to "static" 2026-03-06 10:18:49 -06:00
jason efeba8c21d Upload files to "/" 2026-03-06 10:18:24 -06:00
jason 21004e6305 Upload files to "/" 2026-03-06 10:18:15 -06:00
jason 5a0916346b Upload files to "/" 2026-03-06 10:17:59 -06:00
jason 93cc03d804 Upload files to "/" 2026-03-06 10:16:10 -06:00
jason d9f4bfa5c3 Initial commit 2026-03-06 10:14:07 -06:00