Problem
GET /projects (apps/backend/lambdas/projects/handler.ts, the projects list route) does:
const projects = await db.selectFrom("branch.projects").selectAll().execute();
return json(200, projects);
It returns every project row + every column with no limit/offset — an unbounded fetch-all. It doesn't scale as project count grows, and the frontend list page renders the entire array at once.
Pre-existing — predates the route-table/dispatcher refactor, which preserved the behavior verbatim.
Expected
- Support
page / limit query params and return the repo's standard { data, pagination: { page, limit, totalItems, totalPages } } shape (see the expenditures / reports list endpoints for the convention).
- Absent params → keep current behavior or default to a sensible page size (match sibling endpoints).
Scope
- Backend:
listProjects in projects/handler.ts — add pagination (offset/limit + count).
- Frontend: projects list page — consume
{ data, pagination } + wire the existing Pagination component.
- Update
projects/openapi.yaml for the new query params + response shape.
Note
Access control on this route (it currently has no auth / membership filtering) is tracked separately.
Problem
GET /projects(apps/backend/lambdas/projects/handler.ts, the projects list route) does:It returns every project row + every column with no
limit/offset— an unbounded fetch-all. It doesn't scale as project count grows, and the frontend list page renders the entire array at once.Pre-existing — predates the route-table/dispatcher refactor, which preserved the behavior verbatim.
Expected
page/limitquery params and return the repo's standard{ data, pagination: { page, limit, totalItems, totalPages } }shape (see theexpenditures/reportslist endpoints for the convention).Scope
listProjectsinprojects/handler.ts— add pagination (offset/limit + count).{ data, pagination }+ wire the existingPaginationcomponent.projects/openapi.yamlfor the new query params + response shape.Note
Access control on this route (it currently has no auth / membership filtering) is tracked separately.