Bounty/Milestone/MaintenancePool/User list endpoints have no pagination and return the entire table in one unbounded response

Đang mở
#63 3 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@senmalong đang làm issue này rồi.

Từ ngày 16/8/2026.

  • #67 của @ghzhost — đang mở
  • #73 của @senmalong — đang mở

Đánh giá

Issue này chưa được đánh giá.

Mô tả

GrantFox OSS Maybe Rewarded Official Campaign | FWC26 performance Third Campaign very hard

Overview

The primary list() methods behind this API's core GET collection endpoints fetch every row in the table with no LIMIT/OFFSET and no way for a caller to request a page:

// src/bounties/bounties.service.ts:174-176
async list(status?: BountyStatus): Promise<Bounty[]> {
  return this.bountyRepo.find({ where: status ? { status } : {} });
}
// src/milestones/milestones.service.ts:131-133
async list(): Promise<Milestone[]> {
  return this.milestoneRepo.find();
}
// src/maintenance-pool/maintenance-pool.service.ts:109-111
async list(): Promise<MaintenancePool[]> {
  return this.poolRepo.find();
}
// src/users/users.service.ts:101-103
async list(): Promise<User[]> {
  return this.userRepo.find();
}

Each is wired straight to a public, unpaginated GET route (GET /bounties, GET /milestones, GET /maintenance-pools, GET /users) with no query parameters accepted for limit/offset/cursor anywhere in the corresponding controllers. BountiesController.list does accept a status filter, which narrows the result set somewhat, but every un-filtered call — and every call on the other three endpoints, which don't even offer a filter — returns literally every row that has ever existed in that table, in one response, every time. For a platform whose entire premise is an open, growing catalog of GitHub bounties across many repositories, bounties/milestones are exactly the tables expected to grow without bound over the platform's lifetime; this isn't a "might matter someday at extreme scale" concern, it's the shape every one of these tables is designed to take on by the time the platform has any meaningful usage history.

This is a different problem from the two related, already-tracked issues in this repo: the open "N+1 query and unbounded loop patterns across bounty expiry, milestone resolution, and reputation computation" issue is about repeated per-row queries inside a loop (an N+1 shape); the open "Analytics payout heatmap and top-clients queries likely load unbounded result sets into memory" issue is about AnalyticsService's own aggregation queries for a single user's history. Neither names BountiesController.list, MilestonesController.list, MaintenancePoolController.list, or UsersController.list — this issue is about the primary bulk-listing endpoints of four separate resources returning their entire backing table in a single unbounded query and a single unbounded HTTP response body, a distinct failure shape (one query, unboundedly large, not many small ones) from both of those.

Requirements

  • Add standard limit/offset (or cursor-based) pagination query parameters to GET /bounties, GET /milestones, GET /maintenance-pools, and GET /users, with a sane default page size and an enforced maximum (so a caller can't request limit=999999999 and recreate the exact same problem via the query string).
  • Apply the corresponding take/skip (or keyset equivalent) to each service's underlying find()/query-builder call.
  • Return enough metadata in the response for a client to know whether more pages exist (a total count, a hasMore flag, or a nextCursor, per whatever pagination convention the rest of this API's forthcoming versioning work — see the related open "Add API versioning strategy" issue — ultimately settles on; this issue shouldn't block on that decision, but should use a reasonable, documented shape now rather than inventing something that has to change again later).
  • Add a test that seeds more rows than the default page size for at least one of the four endpoints and asserts the response is capped and paginated, not returning everything.

Acceptance Criteria

  • GET /bounties, GET /milestones, GET /maintenance-pools, and GET /users all accept and correctly apply limit/offset (or equivalent) parameters.
  • Each has an enforced maximum page size that can't be bypassed via the query string.
  • A test proves that seeding N rows greater than the default/max page size still returns a capped, paginated result.
  • Existing callers relying on the current unpaginated shape (if any exist in this codebase, e.g. internal service-to-service calls to list()) are audited and updated as needed rather than silently truncated.

Additional Notes

Precise references: src/bounties/bounties.service.ts:174-176 and src/bounties/bounties.controller.ts:25-28; src/milestones/milestones.service.ts:131-133 and src/milestones/milestones.controller.ts:32-35; src/maintenance-pool/maintenance-pool.service.ts:109-111 and src/maintenance-pool/maintenance-pool.controller.ts:39-42; src/users/users.service.ts:101-103 and src/users/users.controller.ts:17-20.

Test/reproduction plan:

for (let i = 0; i < 250; i++) {
  await bountyRepo.save({ issueId: /* ... */, sponsorId, amount: '10.0000000', status: BountyStatus.OPEN });
}
const res = await request(app).get('/bounties').expect(200);
// pre-fix: res.body.length === 250 (or however many were seeded — unbounded)
// post-fix: res.body.length <= the enforced default/max page size, with pagination metadata present

Cross-references: distinct from the open "N+1 query and unbounded loop patterns across bounty expiry, milestone resolution, and reputation computation" issue (a different problem shape — repeated small queries in a loop, not one large unbounded one) and the open "Analytics payout heatmap and top-clients queries likely load unbounded result sets" issue (scoped to AnalyticsService's own per-user aggregation queries, not the primary resource-listing endpoints this issue covers). Loosely related to the open "Add API versioning strategy" issue, since the pagination response shape chosen here is exactly the kind of API contract decision that's painful to change later without versioning in place — worth a one-line acknowledgment in the PR, not a blocker.

Ngôn ngữ chính
TypeScript
Star
2
Fork
90
Merge trung bình
31 phút
Pull request đã merge (30 ngày)
20

Chuẩn bị môi trường

  • Có Dockerfile hoặc tệp Docker Compose
  • Không có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của MergeFi/backend

Tất cả issue của MergeFi/backend

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.