Nobody has started this yet — be first.
Business impact
GET /api/audit-log is the compliance/incident-response surface for this whole product -- it is what an admin reaches for during "who changed this and when." Right now the response includes total (the row count) and per_page, but total_pages is hardcoded to 1 regardless of either, so any frontend or CLI tool paging through a large audit log either has to reimplement ceil(total / per_page) itself or -- more likely -- just does not bother, and only ever shows page 1 with no indication there is more. For a tenant with months of history, that is the difference between "the audit log works" and "the audit log quietly only shows the most recent 25 entries and nobody notices."
Problem
list_audit_log's response always reports total_pages: 1, regardless of the real total or per_page.
Current behavior
The response dict hardcodes "total_pages": 1 as a placeholder that was added to keep the response shape stable for callers already expecting the key, without ever computing the real value from the pagination object already in scope.
Expected behavior
total_pages reflects ceil(total / per_page), including the edge case of total == 0, which should report total_pages: 0 -- there are no pages of zero items.
Steps to reproduce
Why this matters
This is a pure computation over values the endpoint already has -- pagination.total and per_page are both already in scope in list_audit_log. Flask-SQLAlchemy's Pagination object (returned by .paginate(...)) already computes this as .pages -- the fix is reading an answer that already exists rather than writing new math.
Suggested approach
Look at the pagination variable in list_audit_log right where the response dict is built. query.paginate(...) a few lines above already returns an object that knows more about itself than what is currently being read off it.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_04_audit_log_total_pages.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b feat/audit-log-total-pagesFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/audit-log-total-pagesSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inacmeglobexmake seed
*.localhost subdomains resolve to 127.0.0.1 out of the box in curl and every modern browser -- no /etc/hosts editing needed:
curl -s -X POST http://acme.localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
--cookie-jar /tmp/acme.cookies --cookie /tmp/acme.cookies \
-d '{"email": "owner@acme.test", "password": "owner-acme"}'
curl -s http://acme.localhost:5000/api/tenant/settings --cookie /tmp/acme.cookies
For tooling that cannot fake subdomains (some HTTP clients, CI), send X-Tenant-Slug instead -- honored only when TRUST_TENANT_HEADER=true (the Docker dev setup enables it; production does not):
curl -s http://localhost:5000/api/tenant/settings -H "X-Tenant-Slug: acme"
You do not strictly need Docker to work the tickets, but -- unlike a project that can fake its backing services -- this one genuinely needs a real Postgres and Redis, because the whole point is proving isolation against the real database, not a mock. Without Docker: cp .env.example .env, point DATABASE_URL/ADMIN_DATABASE_URL/TEST_DATABASE_URL/TEST_ADMIN_DATABASE_URL/REDIS_URL at a local Postgres/Redis (docker/init-db.sql documents the exact roles/databases the app expects -- notably the saas_admin/BYPASSRLS role migrations need and the plain saas role every request uses), then:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/flask db upgrade
.venv/bin/pytest practice_tickets/tests/test_ticket_07_webhook_signature_raw_body.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
./practice_tickets/run_tickets.sh 07 12 13 # just a few, by number
All 15 tests fail at the start; as you fix each ticket in app/ (or, for TICKET-12, in migrations/versions/0002_row_level_security.py, plus re-applying it), its test -- and only its test -- should flip to passing. make test runs the project's own main suite; several of its tests (RBAC, tenant-spoofing, and billing-webhook tests among them) fail at the start for the same underlying reasons as specific tickets -- fixing those tickets fixes those failures too, not a separate task.
Level 2
Implement a feature
Extend the system within its own patterns.