Skip to content

fix(bounties): validate inputs, return pagination, log errors, expose updated_at/questions - #387

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
jhosepm352-design:master
Jun 4, 2026
Merged

fix(bounties): validate inputs, return pagination, log errors, expose updated_at/questions#387
ralyodio merged 1 commit into
profullstack:masterfrom
jhosepm352-design:master

Conversation

@jhosepm352-design

Copy link
Copy Markdown
Contributor

Bug Fix: /api/bounties endpoint improvements

Found while testing ugig.net as part of bug bounty c3137a9d ("I will pay for every bug fix found and PR submitted that fix").

Bugs Found

Bug 1: Missing fields in GET response
The select() clause omits updated_at and questions columns that exist in the bounties table. Frontend has no way to know when a bounty was last modified, and the questions metadata is unavailable for the public listing endpoint.

Bug 2: Invalid status query param silently returns []
?status=invalid returns empty array instead of a 400 error. Clients can't tell if they made a typo or there are no results.

Bug 3: No pagination metadata
Response only returns data array. Clients can't tell if more pages exist or how many. Adding total and total_pages is the standard fix.

Bug 4: Generic 500 errors with no logging
Both GET and POST catch all exceptions and return {"error":"Unexpected error"} with no console.error. Production debugging is impossible.

Bug 5: No input validation feedback on POST
When createBountySchema.safeParse() fails, only the first issue message is returned. The full issues array is dropped, making client-side validation work harder.

Changes

  1. Added updated_at and questions to the select() clause.
  2. Validate status param against allowed values (open|paused|closed), return 400 for invalid.
  3. Validate limit and page params, return 400 for non-positive integers.
  4. Added { count: "exact" } to Supabase query to get total count.
  5. Added pagination object to response: { page, limit, total, total_pages }.
  6. Added console.error logging for both expected and unexpected errors.
  7. Return full issues array in POST validation error response.

Testing

After merging, verify:

# Before: returns [] silently
curl https://ugig.net/api/bounties?status=invalid
# After: returns 400
curl https://ugig.net/api/bounties?status=invalid
# {"error":"Invalid status. Must be one of: open, paused, closed"}

# After: pagination metadata
curl https://ugig.net/api/bounties?page=1
# {"data":[...], "pagination":{"page":1,"limit":50,"total":5,"total_pages":1}}

Related

  • Bug bounty: c3137a9d on ugig.net
  • Found by: opencode-agent-jhosep
  • Files changed: 1 (src/app/api/bounties/route.ts)
  • Lines: 3076 → 4479 bytes

Looking forward to your review.

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves the /api/bounties endpoint with input validation, pagination metadata, error logging, and additional fields in the GET response. The changes are well-scoped to a single file and address real usability gaps.

  • GET now validates status, limit, and page params (returning descriptive 400s), adds { count: \"exact\" } to Supabase query, returns a pagination object, and exposes updated_at, questions, and payment_coin in the select clause.
  • POST now returns the full Zod issues array alongside the first error message, and both handlers gain console.error logging in error paths.

Confidence Score: 4/5

Safe to merge — all changes are additive or fix real silent failures; no regressions introduced in the happy path.

The validation logic correctly rejects obviously bad inputs and the pagination math is sound. The one gap — float values like 1.5 silently passing the "positive integer" guard — is a minor contract inconsistency that doesn't break any current callers. All other changes (logging, select fields, count) are straightforward and low-risk.

src/app/api/bounties/route.ts — the limit/page guards should also reject non-integer values to match the error message they return.

Important Files Changed

Filename Overview
src/app/api/bounties/route.ts Adds status/limit/page validation, exact-count pagination, error logging, and exposes updated_at/questions/payment_coin in GET. Float inputs pass the "positive integer" guard and are silently truncated — a minor inconsistency in the validation contract.

Sequence Diagram

sequenceDiagram
    participant Client
    participant GET_handler as GET /api/bounties
    participant Supabase

    Client->>GET_handler: "GET ?status=open&page=1&limit=50"
    alt invalid status
        GET_handler-->>Client: "400 { error: "Invalid status..." }"
    else invalid limit
        GET_handler-->>Client: "400 { error: "Invalid limit..." }"
    else invalid page
        GET_handler-->>Client: "400 { error: "Invalid page..." }"
    else params valid
        GET_handler->>Supabase: ".select(cols, { count: "exact" }).eq("status",...).range(...)"
        alt Supabase error
            Supabase-->>GET_handler: error
            GET_handler-->>Client: "400 { error: message }"
        else success
            Supabase-->>GET_handler: "{ data, count }"
            GET_handler-->>Client: "200 { data, pagination: { page, limit, total, total_pages } }"
        end
    end
Loading

Reviews (1): Last reviewed commit: "fix(bounties): add updated_at/questions ..." | Re-trigger Greptile

Comment on lines +25 to +40
if (params.get("limit") !== null && (!Number.isFinite(limitRaw) || limitRaw <= 0)) {
return NextResponse.json(
{ error: "Invalid limit. Must be a positive integer." },
{ status: 400 }
);
}
const limitCandidate =
Number.isFinite(limitRaw) && limitRaw > 0 ? Math.floor(limitRaw) : defaultLimit;
const limit = Math.min(limitCandidate, 100);

const pageRaw = Number(params.get("page"));
if (params.get("page") !== null && (!Number.isFinite(pageRaw) || pageRaw <= 0)) {
return NextResponse.json(
{ error: "Invalid page. Must be a positive integer." },
{ status: 400 }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Float values bypass "positive integer" validation

The validation rejects non-finite and non-positive values but accepts fractional numbers — ?limit=1.5 and ?page=2.7 both pass the guard and are silently floored to 1 and 2 respectively. The error message says "Must be a positive integer" but the actual contract accepted is any positive finite number. A caller following the error message would not expect floats to succeed, so the guard should also reject limitRaw !== Math.floor(limitRaw) (and the same for pageRaw).

@ralyodio
ralyodio merged commit b8f247c into profullstack:master Jun 4, 2026
4 checks passed
jhosepm352-design added a commit to jhosepm352-design/ugig.net that referenced this pull request Jun 4, 2026
jhosepm352-design added a commit to jhosepm352-design/ugig.net that referenced this pull request Jun 4, 2026
ralyodio pushed a commit that referenced this pull request Jun 4, 2026
…tion issues (matches #387-#390) (#391)

* fix(bounties): add updated_at/questions to select, validate inputs, return pagination

* fix(gigs): validate status, page, limit query params (matches /api/bounties fix in #387)

* fix(notifications): validate limit/offset query params (matches #387, #388)

* chore: sync src/app/api/gigs/route.ts with upstream master

* chore: sync src/app/api/notifications/route.ts with upstream master

* fix(reviews): validate limit/offset/gig_id, log errors, expose validation issues (matches #387-#390)
ralyodio pushed a commit that referenced this pull request Jun 4, 2026
…#389) (#390)

* fix(bounties): add updated_at/questions to select, validate inputs, return pagination

* fix(gigs): validate status, page, limit query params (matches /api/bounties fix in #387)

* fix(notifications): validate limit/offset query params (matches #387, #388)

* fix(activity): validate limit/offset query params (matches #387, #388, #389)

* chore: restore src/app/api/gigs/route.ts to upstream (was: 4487, now: 8988)

* chore: restore src/app/api/notifications/route.ts to upstream (was: 2955, now: 2180)

* chore: restore src/app/api/activity/route.ts to upstream (was: 2258, now: 1650)
ralyodio pushed a commit that referenced this pull request Jun 4, 2026
…388) (#389)

* fix(bounties): add updated_at/questions to select, validate inputs, return pagination

* fix(gigs): validate status, page, limit query params (matches /api/bounties fix in #387)

* fix(notifications): validate limit/offset query params (matches #387, #388)

* fix(notifications): validate limit/offset query params (matches #387, #388)

* chore: restore src/app/api/gigs/route.ts to upstream (was: 4487, now: 8988)

* chore: restore src/app/api/notifications/route.ts to upstream (was: 2658, now: 2180)
ralyodio pushed a commit that referenced this pull request Jun 4, 2026
…unties fix in #387) (#388)

* fix(bounties): add updated_at/questions to select, validate inputs, return pagination

* fix(gigs): validate status, page, limit query params (matches /api/bounties fix in #387)

* fix(notifications): validate limit/offset query params (matches #387, #388)

* chore: sync src/app/api/gigs/route.ts with upstream master

* chore: sync src/app/api/notifications/route.ts with upstream master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants