Skip to content

feat: implement GET /bounties with cursor filters#62

Open
autonomy414941 wants to merge 2 commits intodevasignhq:mainfrom
autonomy414941:feat/issue20-bounties-listing
Open

feat: implement GET /bounties with cursor filters#62
autonomy414941 wants to merge 2 commits intodevasignhq:mainfrom
autonomy414941:feat/issue20-bounties-listing

Conversation

@autonomy414941
Copy link

Summary

  • add GET /bounties route with cursor-based pagination (meta.next_cursor, meta.has_more)
  • implement filters for tech_stack (JSONB containment), amount_min/amount_max, difficulty, and status
  • validate query parameters with clear 400 error responses
  • register the new route in createApp()
  • add focused API tests for pagination envelope and filter/cursor validation behavior

Validation

  • npm test -- src/__tests__/bounties.test.ts
  • npm run typecheck

Fixes #20

@devasign-app
Copy link

devasign-app bot commented Feb 23, 2026

🟡 AI Code Review Results

Status: Review Recommended
Confidence: 100%


🟡 Merge Score: 75/100

🟡 ███████████████░░░░░ 75%

Recommendation: ⚠️ This PR is mostly good but could benefit from some improvements before merging.

This is a strong contribution that correctly implements the core requirements for the bounties endpoint, including pagination and filtering, backed by solid tests. However, a critical bug in the amount filtering logic needs to be addressed before merging. I've also included a minor suggestion to improve code clarity.

💡 Code Suggestions (2)

🔴 High Priority (1)

  1. packages/api/src/routes/bounties.ts (Line 158)
    🔧 Amount filtering is performed using string comparison, which can lead to incorrect results.

💭 Reasoning: When comparing numeric values like currency, using string comparison leads to lexicographical sorting (e.g., '100' is less than '20'). The query should explicitly cast the amountUsdc column to a numeric type to ensure correct numerical comparison.

Suggested Code:

                if (amountMin !== null) {
                    conditions.push(sql`cast(${table.amountUsdc} as numeric) >= ${amountMin}`);
                }
                if (amountMax !== null) {
                    conditions.push(sql`cast(${table.amountUsdc} as numeric) <= ${amountMax}`);
                }

🔵 Low Priority (1)

  1. packages/api/src/routes/bounties.ts (Line 167)
    ✨ The code contains a redundant if check when adding the cursor condition to the query.

💭 Reasoning: The or() function from Drizzle-ORM returns a SQL object, which is always a truthy value. Therefore, the if (cursorCondition) check is unnecessary and can be removed to simplify the code.

Suggested Code:

                if (cursor !== null) {
                    const cursorCondition = or(
                        lt(table.createdAt, cursor.createdAt),
                        and(eq(table.createdAt, cursor.createdAt), lt(table.id, cursor.id)),
                    );
                    conditions.push(cursorCondition);
                }
📊 Review Metadata
  • Processing Time: 178s
  • Analysis Date: 2/23/2026, 2:10:25 AM

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

💬 Questions about this review? Open an issue or contact support.

@devasign-app
Copy link

devasign-app bot commented Feb 23, 2026

🔄 Follow-Up AI Code Review

Status: Review Recommended
Confidence: 100%


🟡 Updated Merge Score: 70/100

🟡 ██████████████░░░░░░ 70%

Recommendation: ⚠️ This PR is mostly good but could benefit from some improvements before merging.

📋 Previous Review Summary

This is a strong contribution that correctly implements the core requirements for the bounties endpoint, including pagination and filtering, backed by solid tests. However, a critical bug in the amount filtering logic needs to be addressed before merging. I've also included a minor suggestion to improve code clarity.

The author has added a test case for amount filtering, which is a good step. However, the critical bug in the amount filtering logic itself was not addressed and remains in the code. The implementation is still brittle as it relies on implicit type casting. A minor code clarity issue also remains. The PR cannot be merged until the filtering logic is made robust.

💡 Code Suggestions (2)

🔴 High Priority (1)

  1. packages/api/src/routes/bounties.ts (Line 150)
    🔧 The critical issue with amount filtering remains unfixed. The code converts the amount to a JavaScript number (risking precision loss for currency) and then back to a string, relying on fragile implicit database casting for comparison. While you've added a test, the underlying implementation is still brittle.

💭 Reasoning: To ensure correct numeric comparison and preserve full precision for currency values, you should validate the amount but keep it as a string. Pass this string directly to Drizzle's gte/lte operators. The database will correctly handle the comparison against a NUMERIC or DECIMAL column. This avoids floating-point issues and makes the code's intent clearer. This requires changing parseAmount to return a string and updating the validation logic accordingly.

Suggested Code:

if (amountMin !== null) {
    // This assumes `amountMin` is a validated string, not a number.
    conditions.push(gte(table.amountUsdc, amountMin));
}
if (amountMax !== null) {
    conditions.push(lte(table.amountUsdc, amountMax));
}

🔵 Low Priority (1)

  1. packages/api/src/routes/bounties.ts (Line 163)
    ✨ The check if (cursorCondition) is redundant because Drizzle's or() function always returns a SQL object, which is a truthy value.

💭 Reasoning: Removing the unnecessary conditional statement simplifies the code and makes the logic for adding the cursor condition more direct and easier to read.

Suggested Code:

if (cursor !== null) {
    const cursorCondition = or(
        lt(table.createdAt, cursor.createdAt),
        and(eq(table.createdAt, cursor.createdAt), lt(table.id, cursor.id)),
    );
    conditions.push(cursorCondition);
}
📊 Review Metadata
  • Processing Time: 84s
  • Analysis Date: 2/23/2026, 2:33:44 AM
  • Review Type: Follow-Up (triggered by new push)

🤖 This is a follow-up review generated after new commits were pushed to the PR.

💬 Questions? Open an issue or contact support.

@autonomy414941
Copy link
Author

autonomy414941 commented Feb 23, 2026

Maintainers: could you please give a binary decision on this implementation for issue #20 bounty (accept or reject)?

If accepted, please share exact payout mechanics so I can provide the correct proof package:

  • payer account (GitHub handle or org wallet owner)
  • chain + token (example: Base USDC)
  • required receipt proof URL format (tx explorer link, issue comment link, or both)

I currently do not see a competing implementation for issue #20 in this repo.

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.

Implement GET /bounties — paginated listing with filters

1 participant