feat: add pagination, cross-board activity endpoint, and Project updated_at #181

Merged
forgejo_admin merged 2 commits from 180-add-pagination-cross-board-activity-endp into main 2026-03-15 04:36:22 +00:00

Summary

Add three features to support the pal-e-app frontend activity feed: pagination on the notes list endpoint, a cross-board activity endpoint for recent board item changes, and an updated_at timestamp on the Project model.

Changes

  • src/pal_e_docs/routes/notes.py: Add limit (int|None, ge=1) and offset (int|None, ge=0) query params to GET /notes. Defaults to None for full backwards compatibility.
  • src/pal_e_docs/routes/boards.py: Add GET /boards/activity endpoint returning board items across all boards sorted by updated_at DESC. Supports limit (default 20, max 100) and column filter. Registered before /{slug} to avoid FastAPI route conflicts.
  • src/pal_e_docs/models.py: Add updated_at column to Project model with server_default=func.now() and onupdate=func.now().
  • src/pal_e_docs/schemas.py: Add updated_at: datetime field to ProjectOut schema.
  • alembic/versions/p6k7l8m9n0o1_add_project_updated_at.py: Alembic migration adding updated_at column to projects table.
  • tests/test_pagination_activity.py: 23 new tests covering all three features.

Test Plan

  • Tests pass locally (577 total: 554 existing + 23 new)
  • ruff format and ruff check clean
  • Pagination: limit, offset, limit+offset, beyond-total, zero/negative validation, filter combination, order preservation
  • Activity: basic retrieval, default/custom limit, column filter, invalid column, cross-board items, sort order, empty state, route conflict avoidance
  • Project updated_at: field presence on create, list, and get
  • No regressions in existing test suite

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #180
  • plan-pal-e-docs -- Phase F11
## Summary Add three features to support the pal-e-app frontend activity feed: pagination on the notes list endpoint, a cross-board activity endpoint for recent board item changes, and an `updated_at` timestamp on the Project model. ## Changes - `src/pal_e_docs/routes/notes.py`: Add `limit` (int|None, ge=1) and `offset` (int|None, ge=0) query params to `GET /notes`. Defaults to None for full backwards compatibility. - `src/pal_e_docs/routes/boards.py`: Add `GET /boards/activity` endpoint returning board items across all boards sorted by `updated_at DESC`. Supports `limit` (default 20, max 100) and `column` filter. Registered before `/{slug}` to avoid FastAPI route conflicts. - `src/pal_e_docs/models.py`: Add `updated_at` column to `Project` model with `server_default=func.now()` and `onupdate=func.now()`. - `src/pal_e_docs/schemas.py`: Add `updated_at: datetime` field to `ProjectOut` schema. - `alembic/versions/p6k7l8m9n0o1_add_project_updated_at.py`: Alembic migration adding `updated_at` column to `projects` table. - `tests/test_pagination_activity.py`: 23 new tests covering all three features. ## Test Plan - [x] Tests pass locally (577 total: 554 existing + 23 new) - [x] `ruff format` and `ruff check` clean - [x] Pagination: limit, offset, limit+offset, beyond-total, zero/negative validation, filter combination, order preservation - [x] Activity: basic retrieval, default/custom limit, column filter, invalid column, cross-board items, sort order, empty state, route conflict avoidance - [x] Project updated_at: field presence on create, list, and get - [x] No regressions in existing test suite ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #180 - `plan-pal-e-docs` -- Phase F11
feat: add pagination, cross-board activity endpoint, and Project updated_at
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
08284ab101
Add limit/offset query params to GET /notes for pagination support.
Add GET /boards/activity endpoint for cross-board item feed sorted
by updated_at. Add updated_at column to Project model with Alembic
migration. Includes 23 new tests covering all three features.

Closes #180

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Self-Review

Verdict: PASS -- no blockers.

Reviewed

  1. Pagination (GET /notes) -- limit (ge=1) and offset (ge=0) default to None for full backwards compatibility. Applied after ordering, works with all existing filters. Validation rejects limit=0 and negative offset via FastAPI Query constraints.

  2. Activity endpoint (GET /boards/activity) -- Registered before /{slug} to avoid route conflicts (matches existing pattern for /backlog/items). Column filter reuses the same BoardColumn enum validation pattern from list_board_items. Default limit of 20, max 100.

  3. Project updated_at -- Follows the identical pattern used by Note.updated_at, Board.updated_at, and BoardItem.updated_at (server_default + onupdate). Schema updated. Migration is clean and minimal.

  4. Tests -- 23 new tests covering edge cases: validation boundaries, empty results, cross-board queries, filter combinations, order preservation, route conflict avoidance.

  5. Lint -- ruff format and ruff check clean. All 577 tests pass.

## Self-Review **Verdict: PASS -- no blockers.** ### Reviewed 1. **Pagination (`GET /notes`)** -- `limit` (ge=1) and `offset` (ge=0) default to `None` for full backwards compatibility. Applied after ordering, works with all existing filters. Validation rejects limit=0 and negative offset via FastAPI Query constraints. 2. **Activity endpoint (`GET /boards/activity`)** -- Registered before `/{slug}` to avoid route conflicts (matches existing pattern for `/backlog/items`). Column filter reuses the same `BoardColumn` enum validation pattern from `list_board_items`. Default limit of 20, max 100. 3. **Project `updated_at`** -- Follows the identical pattern used by `Note.updated_at`, `Board.updated_at`, and `BoardItem.updated_at` (server_default + onupdate). Schema updated. Migration is clean and minimal. 4. **Tests** -- 23 new tests covering edge cases: validation boundaries, empty results, cross-board queries, filter combinations, order preservation, route conflict avoidance. 5. **Lint** -- `ruff format` and `ruff check` clean. All 577 tests pass.
Author
Owner

PR #181 Review

DOMAIN REVIEW

Tech stack: Python / FastAPI / SQLAlchemy / Alembic / Pydantic (pal-e-docs API)

Backwards compatibility (GET /notes): PASS. limit and offset default to None. When both are None, no .offset() or .limit() is applied, and the query returns all results exactly as before. The refactor from two separate return statements (lines 400-401 in old code) to a single return q.all() is clean.

Route ordering (GET /boards/activity): PASS. /activity is registered at line 100, before /{slug} at line 162. Comment documents the ordering requirement. Follows the exact same pattern as /backlog/items at line 70.

Schema consistency (ProjectOut): PASS. updated_at: datetime added to ProjectOut schema (line 60 in schemas.py). Matches the ORM model. from_attributes = True handles the serialization.

Code patterns: The activity endpoint follows the exact same enum validation pattern used in list_board_items and list_backlog_items for column/item_type filtering. Clean and consistent.

onupdate behavior: onupdate=func.now() is ORM-level (not a DB trigger), consistent with 5 other columns in the codebase that use the same pattern. Works correctly with SQLite test fixtures.

Ruff compliance: Code formatting matches existing patterns. No unused imports.

BLOCKERS

1. BLOCKER: Alembic migration creates multiple heads (will break CI and deployments)

Migration p6k7l8m9n0o1 has down_revision = "o5j6k7l8m9n0", but migration b9817e1e5862 (already on main) also has down_revision = "o5j6k7l8m9n0". This creates two Alembic heads branching from the same parent. Running alembic upgrade head will fail with:

FAILED: Multiple head revisions are present for given argument 'head'

Fix: Change the new migration's down_revision from "o5j6k7l8m9n0" to "b9817e1e5862" so the chain is linear:

o5j6k7l8m9n0 -> b9817e1e5862 -> p6k7l8m9n0o1

This is the only change needed. The migration logic itself (additive add_column with server_default) is correct and safe.

NITS

  1. Global mutable state in test helper (_item_counter): The _item_counter global at line 31 of test_pagination_activity.py persists across tests and across test classes within the same process. It works because it only needs uniqueness (not specific values), but a uuid4().hex[:8] suffix would be more idiomatic and immune to test ordering issues. Non-blocking.

  2. Missing negative limit test for /notes: The tests cover limit=0 (422) and offset=-1 (422), but do not test limit=-1. FastAPI's ge=1 constraint handles this, but an explicit test would be more thorough. Non-blocking.

  3. No offset param on /boards/activity: The notes endpoint supports both limit and offset, but the activity endpoint only supports limit. For a frontend activity feed, offset-based pagination (or cursor-based) may be needed later. Not a blocker for this PR since it was not in the issue requirements.

  4. No max limit on /notes pagination: The activity endpoint caps limit at le=100, but the notes endpoint allows any limit >= 1 (e.g., limit=999999). Consider adding le=1000 or similar for consistency and to prevent accidental full-table scans. Non-blocking.

SOP COMPLIANCE

  • Branch named after issue (180-add-pagination-cross-board-activity-endp references issue #180)
  • PR body follows template (Summary, Changes, Test Plan, Related all present)
  • Related references plan slug (plan-pal-e-docs -- Phase F11)
  • Tests exist (23 new tests covering all three features)
  • No secrets committed
  • No unnecessary file changes (6 files, all scoped to the 3 features)
  • Commit messages are descriptive

PROCESS OBSERVATIONS

  • Test quality is strong. 23 tests cover: backwards compatibility, limit/offset/combined, boundary conditions (beyond total, zero, negative), filter+pagination combos, order preservation, cross-board queries, sort verification, empty state, route conflict avoidance. Well-structured into 3 test classes.
  • Migration safety pattern is correct (additive column, server_default, no data migration needed). The only issue is the revision chain, not the migration logic.
  • Change failure risk: The migration head conflict is a deployment blocker that would be caught by CI if Alembic migration testing runs alembic upgrade head (which CI does per PR #169). This is exactly the kind of issue CI should catch.

VERDICT: NOT APPROVED

One blocker: the Alembic migration's down_revision must be updated from "o5j6k7l8m9n0" to "b9817e1e5862" to fix the multiple-heads conflict. All other aspects of this PR are solid -- the code is clean, well-tested, and follows existing patterns. Once the migration chain is fixed, this is ready to merge.

## PR #181 Review ### DOMAIN REVIEW **Tech stack:** Python / FastAPI / SQLAlchemy / Alembic / Pydantic (pal-e-docs API) **Backwards compatibility (GET /notes):** PASS. `limit` and `offset` default to `None`. When both are `None`, no `.offset()` or `.limit()` is applied, and the query returns all results exactly as before. The refactor from two separate `return` statements (lines 400-401 in old code) to a single `return q.all()` is clean. **Route ordering (GET /boards/activity):** PASS. `/activity` is registered at line 100, before `/{slug}` at line 162. Comment documents the ordering requirement. Follows the exact same pattern as `/backlog/items` at line 70. **Schema consistency (ProjectOut):** PASS. `updated_at: datetime` added to `ProjectOut` schema (line 60 in schemas.py). Matches the ORM model. `from_attributes = True` handles the serialization. **Code patterns:** The activity endpoint follows the exact same enum validation pattern used in `list_board_items` and `list_backlog_items` for column/item_type filtering. Clean and consistent. **onupdate behavior:** `onupdate=func.now()` is ORM-level (not a DB trigger), consistent with 5 other columns in the codebase that use the same pattern. Works correctly with SQLite test fixtures. **Ruff compliance:** Code formatting matches existing patterns. No unused imports. ### BLOCKERS **1. BLOCKER: Alembic migration creates multiple heads (will break CI and deployments)** Migration `p6k7l8m9n0o1` has `down_revision = "o5j6k7l8m9n0"`, but migration `b9817e1e5862` (already on main) also has `down_revision = "o5j6k7l8m9n0"`. This creates two Alembic heads branching from the same parent. Running `alembic upgrade head` will fail with: ``` FAILED: Multiple head revisions are present for given argument 'head' ``` **Fix:** Change the new migration's `down_revision` from `"o5j6k7l8m9n0"` to `"b9817e1e5862"` so the chain is linear: ``` o5j6k7l8m9n0 -> b9817e1e5862 -> p6k7l8m9n0o1 ``` This is the only change needed. The migration logic itself (additive `add_column` with `server_default`) is correct and safe. ### NITS 1. **Global mutable state in test helper (`_item_counter`):** The `_item_counter` global at line 31 of `test_pagination_activity.py` persists across tests and across test classes within the same process. It works because it only needs uniqueness (not specific values), but a `uuid4().hex[:8]` suffix would be more idiomatic and immune to test ordering issues. Non-blocking. 2. **Missing negative limit test for `/notes`:** The tests cover `limit=0` (422) and `offset=-1` (422), but do not test `limit=-1`. FastAPI's `ge=1` constraint handles this, but an explicit test would be more thorough. Non-blocking. 3. **No `offset` param on `/boards/activity`:** The notes endpoint supports both `limit` and `offset`, but the activity endpoint only supports `limit`. For a frontend activity feed, offset-based pagination (or cursor-based) may be needed later. Not a blocker for this PR since it was not in the issue requirements. 4. **No max limit on `/notes` pagination:** The activity endpoint caps `limit` at `le=100`, but the notes endpoint allows any `limit >= 1` (e.g., `limit=999999`). Consider adding `le=1000` or similar for consistency and to prevent accidental full-table scans. Non-blocking. ### SOP COMPLIANCE - [x] Branch named after issue (`180-add-pagination-cross-board-activity-endp` references issue #180) - [x] PR body follows template (Summary, Changes, Test Plan, Related all present) - [x] Related references plan slug (`plan-pal-e-docs` -- Phase F11) - [x] Tests exist (23 new tests covering all three features) - [x] No secrets committed - [x] No unnecessary file changes (6 files, all scoped to the 3 features) - [x] Commit messages are descriptive ### PROCESS OBSERVATIONS - **Test quality is strong.** 23 tests cover: backwards compatibility, limit/offset/combined, boundary conditions (beyond total, zero, negative), filter+pagination combos, order preservation, cross-board queries, sort verification, empty state, route conflict avoidance. Well-structured into 3 test classes. - **Migration safety pattern is correct** (additive column, `server_default`, no data migration needed). The only issue is the revision chain, not the migration logic. - **Change failure risk:** The migration head conflict is a deployment blocker that would be caught by CI if Alembic migration testing runs `alembic upgrade head` (which CI does per PR #169). This is exactly the kind of issue CI should catch. ### VERDICT: NOT APPROVED One blocker: the Alembic migration's `down_revision` must be updated from `"o5j6k7l8m9n0"` to `"b9817e1e5862"` to fix the multiple-heads conflict. All other aspects of this PR are solid -- the code is clean, well-tested, and follows existing patterns. Once the migration chain is fixed, this is ready to merge.
fix: correct Alembic down_revision to chain from actual latest migration
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
5a00abb1e0
The migration p6k7l8m9n0o1 referenced a non-existent down_revision
"o5j6k7l8m9n0", creating two Alembic heads and breaking `alembic upgrade
head`. Fixed to chain from b9817e1e5862 (the actual latest migration on
main).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

PR #181 Review (Re-review)

DOMAIN REVIEW

Tech stack: Python / FastAPI / SQLAlchemy / Alembic / Pydantic

Pagination (GET /notes): Clean implementation. limit and offset are optional Query params with proper ge=1 / ge=0 constraints. Defaults to None for full backwards compatibility. Ordering is applied before pagination (correct SQL semantics). The refactor from inline .all() to building the query then applying offset/limit is well-structured.

Activity endpoint (GET /boards/activity): Properly registered before /{slug} to avoid FastAPI route conflicts (same pattern as existing /backlog/items). BoardColumn enum validation with descriptive 422 error. Default limit 20, max 100 via le=100. Sorted by updated_at DESC -- correct for an activity feed.

Project updated_at: Model uses server_default=func.now() and onupdate=func.now() -- consistent with the pattern used on Note, Block, Board, and BoardItem. Schema adds updated_at: datetime to ProjectOut. Migration is minimal and correct (add_column / drop_column).

Alembic migration: down_revision = "b9817e1e5862" is correct. Verified the full migration chain is linear (18 migrations, single head). The previous review's blocker (wrong down_revision) is fixed.

BLOCKERS

None. The previous blocker (Alembic down_revision pointing to non-existent revision) has been fixed. down_revision now correctly points to b9817e1e5862 (the fix_schema_drift_nullable_timestamps migration, which is the actual head on main).

NITS

  1. Module-level mutable state in tests (_item_counter global in test_pagination_activity.py, line 31): Works correctly but is a minor code smell. A pytest fixture or class-level counter would be more idiomatic. Non-blocking.

  2. test_activity_default_limit creates notes that do not exist (lines 180-186): The test creates 25 board items with note_slug=f"note-{i}" but these notes are never created. This works because the API does not validate note existence on board item creation -- but it relies on that implementation detail. Non-blocking since the feature being tested is the limit, not note resolution.

SOP COMPLIANCE

  • Branch named after issue (180-add-pagination-cross-board-activity-endp)
  • PR body follows template (Summary, Changes, Test Plan, Related)
  • Related references plan slug (plan-pal-e-docs -- Phase F11)
  • Related references issue (Closes #180)
  • No secrets committed
  • No unnecessary file changes (6 files, all directly related)
  • Commit messages are descriptive
  • ruff format and ruff check reported clean

PROCESS OBSERVATIONS

  • Test coverage is thorough: 23 new tests covering 3 features across happy path, edge cases, error cases, and integration scenarios. Well-organized into 3 test classes.
  • Backwards compatibility preserved: Pagination defaults to None (no limit/offset), so existing API consumers are unaffected.
  • Change failure risk: Low. The migration is additive (single column add), the new endpoint is isolated, and pagination parameters are optional.
  • Deployment note: The Alembic migration must be applied before deploying the new code, since the ProjectOut schema now expects updated_at from the database.

VERDICT: APPROVED

## PR #181 Review (Re-review) ### DOMAIN REVIEW **Tech stack**: Python / FastAPI / SQLAlchemy / Alembic / Pydantic **Pagination (`GET /notes`)**: Clean implementation. `limit` and `offset` are optional `Query` params with proper `ge=1` / `ge=0` constraints. Defaults to `None` for full backwards compatibility. Ordering is applied before pagination (correct SQL semantics). The refactor from inline `.all()` to building the query then applying offset/limit is well-structured. **Activity endpoint (`GET /boards/activity`)**: Properly registered before `/{slug}` to avoid FastAPI route conflicts (same pattern as existing `/backlog/items`). `BoardColumn` enum validation with descriptive 422 error. Default limit 20, max 100 via `le=100`. Sorted by `updated_at DESC` -- correct for an activity feed. **Project `updated_at`**: Model uses `server_default=func.now()` and `onupdate=func.now()` -- consistent with the pattern used on `Note`, `Block`, `Board`, and `BoardItem`. Schema adds `updated_at: datetime` to `ProjectOut`. Migration is minimal and correct (`add_column` / `drop_column`). **Alembic migration**: `down_revision = "b9817e1e5862"` is correct. Verified the full migration chain is linear (18 migrations, single head). The previous review's blocker (wrong `down_revision`) is fixed. ### BLOCKERS None. The previous blocker (Alembic `down_revision` pointing to non-existent revision) has been fixed. `down_revision` now correctly points to `b9817e1e5862` (the `fix_schema_drift_nullable_timestamps` migration, which is the actual head on main). ### NITS 1. **Module-level mutable state in tests** (`_item_counter` global in `test_pagination_activity.py`, line 31): Works correctly but is a minor code smell. A pytest fixture or class-level counter would be more idiomatic. Non-blocking. 2. **`test_activity_default_limit` creates notes that do not exist** (lines 180-186): The test creates 25 board items with `note_slug=f"note-{i}"` but these notes are never created. This works because the API does not validate note existence on board item creation -- but it relies on that implementation detail. Non-blocking since the feature being tested is the limit, not note resolution. ### SOP COMPLIANCE - [x] Branch named after issue (`180-add-pagination-cross-board-activity-endp`) - [x] PR body follows template (Summary, Changes, Test Plan, Related) - [x] Related references plan slug (`plan-pal-e-docs -- Phase F11`) - [x] Related references issue (`Closes #180`) - [x] No secrets committed - [x] No unnecessary file changes (6 files, all directly related) - [x] Commit messages are descriptive - [x] `ruff format` and `ruff check` reported clean ### PROCESS OBSERVATIONS - **Test coverage is thorough**: 23 new tests covering 3 features across happy path, edge cases, error cases, and integration scenarios. Well-organized into 3 test classes. - **Backwards compatibility preserved**: Pagination defaults to `None` (no limit/offset), so existing API consumers are unaffected. - **Change failure risk**: Low. The migration is additive (single column add), the new endpoint is isolated, and pagination parameters are optional. - **Deployment note**: The Alembic migration must be applied before deploying the new code, since the `ProjectOut` schema now expects `updated_at` from the database. ### VERDICT: APPROVED
forgejo_admin deleted branch 180-add-pagination-cross-board-activity-endp 2026-03-15 04:36:22 +00:00
Sign in to join this conversation.
No description provided.