6.4 KiB
Chapter 11 — Final API Contract (post Ch08–12 additions)
Consolidated per Chapter 11's own instruction to update/extend the route table as new routes are introduced elsewhere, flagging additions explicitly.
Additions beyond the original Chapter 11 table
| Method | Path | Blueprint | Purpose | Response | Added in |
|---|---|---|---|---|---|
| GET | /api/overview/browser-breakdown |
overview | Human-only browser/OS breakdown | JSON | Ch08 (Method A) |
| GET | / |
(root) | Redirect to /overview if authenticated, else /login |
redirect | Ch12 follow-up |
| GET | /api/uploads |
uploads | List uploaded files, paginated | JSON (Grid.js) | Ch12 follow-up |
| DELETE | /api/uploads |
uploads | Bulk-delete uploaded files + derived data | JSON | Ch12 follow-up |
/login, /logout, and /healthz were already listed in the original
table (Ch02/Ch04) and are now actually implemented (Ch12) — no table
change needed, just noting they're no longer placeholders. One narrowing:
/logout is implemented as POST-only, not GET/POST, so a state-changing
action isn't reachable via a plain GET (CSRF-safety; see
app/blueprints/auth/routes.py).
Architecture note: rollup recompute is now delete-safe (Ch12 follow-up)
Deleting an uploaded file (app/services/file_deletion.py) removes its
log_entries/bot_hits/suspicious_events rows explicitly (this
project's SQLite connections don't have PRAGMA foreign_keys=ON, so the
ondelete="CASCADE" in the migrations is documentation, not enforced
behavior) and recomputes rollups for whatever dates the file touched.
That recompute exposed a real bug in app/services/aggregator.py: three
of its five writers only ever upserted-when-data-present and silently
left stale rows behind when a day's data disappeared entirely — never
reachable before deletion existed (rollups only ever grew). All five
writers now use delete-then-insert consistently. See the module
docstrings in aggregator.py and file_deletion.py for the full
reasoning, including the deliberately-out-of-scope limitation around
ip_registry/blocklist_suggestions not being recomputed on delete.
Architecture note: cron is now optional (Ch12 follow-up)
POST /uploads now triggers processing automatically in a background
thread (app/services/background.py), and GET /overview opportunistically
resumes any file left incomplete. flask process-logs and flask cleanup
(app/cli.py) still exist and work identically to before for anyone who
wants a cron-based fallback, but nothing in the app requires it anymore.
See the module docstring in app/services/background.py for the explicit
tradeoff this introduces relative to the original Chapter 02/03 "no
persistent background workers, cron-triggered CLI only" stance.
Full current route table
| Method | Path | Blueprint | Purpose | Response | Auth |
|---|---|---|---|---|---|
| GET | /overview |
overview | Overview tab | full page / HTMX partial | required |
| GET | /api/overview/kpis |
overview | KPI card values | JSON | required |
| GET | /api/overview/traffic-chart |
overview | Traffic-over-time series | JSON | required |
| GET | /api/overview/status-codes |
overview | Status-code breakdown | JSON | required |
| GET | /api/overview/top-urls |
overview | Top URLs table | JSON (Grid.js) | required |
| GET | /api/overview/top-referrers |
overview | Top referrers table | JSON (Grid.js) | required |
| GET | /api/overview/browser-breakdown |
overview | Human browser/OS breakdown | JSON | required |
| GET | /seo |
seo | SEO tab | full page / HTMX partial | required |
| GET | /api/seo/bot-summary |
seo | Bot summary cards | JSON | required |
| GET | /api/seo/crawl-chart-data |
seo | Crawl frequency series | JSON | required |
| GET | /api/seo/bot-status-codes |
seo | Status codes served to bots | JSON | required |
| GET | /api/seo/crawled-vs-visited |
seo | Bot vs. human URL comparison | JSON (Grid.js) | required |
| GET | /security |
security | Security tab | full page / HTMX partial | required |
| GET | /api/security/events |
security | Suspicious events table | JSON (Grid.js) | required |
| GET | /api/security/sensitive-paths |
security | Sensitive-path summary | JSON | required |
| GET | /api/security/ip/<ip> |
security | IP history drill-down | HTMX fragment | required |
| GET | /api/security/export-blocklist |
security | Blocklist export | text/plain download | required |
| POST | /uploads |
uploads | Upload a log file | HTMX fragment (queued state) | required |
| GET | /api/uploads/<id>/status |
uploads | Poll parse status | JSON or HTMX fragment | required |
| GET | /api/uploads |
uploads | List uploaded files | JSON (Grid.js) | required |
| DELETE | /api/uploads |
uploads | Bulk-delete uploaded files | JSON | required |
| GET | /login |
auth | Show login form | full page | public |
| POST | /login |
auth | Authenticate | redirect | public |
| POST | /logout |
auth | End session | redirect | required |
| GET | /healthz |
(root) | Liveness check | JSON | public |
| GET | / |
(root) | Auth-based redirect | redirect | public |
Envelope compliance audit (Chapter 12)
Every /api/... JSON endpoint above was checked against Chapter 11's
{"data": ..., "meta": {...}} / {"error": {"code", "message"}}
convention. All conform. app/utils/envelope.py was added this chapter
as a shared helper for new endpoints going forward (used by the
unauthorized_handler in app/__init__.py) — existing endpoints already
matched the shape by hand and weren't rewritten, to avoid churn on
working code.
New this chapter: every /api/... path now returns a JSON 401 with
{"error": {"code": "unauthorized", ...}} when unauthenticated, instead
of Flask-Login's default redirect — consistent with the envelope even
for auth failures. /api/security/ip/<ip> and /api/security/export- blocklist are the two exceptions where a successful response isn't
JSON (HTMX fragment / text download, per the table above and Chapter 10)
— their auth-failure response is still the JSON envelope for consistency.
Auth model (Chapter 12)
Every blueprint except auth and the root /healthz route requires a
logged-in session (@bp.before_request + flask_login.login_required in
each blueprint's __init__.py). This closes a gap that existed from
Chapter 04 through Chapter 10: all dashboard and /api/... routes were
reachable without authentication until Chapter 12 wired in Flask-Login,
even though Chapter 01 specifies "one authenticated single-page shell."