Skip to content

feat(api): add GET /bounties/:id bounty detail endpoint#56

Open
genesis-ai-labs-star wants to merge 3 commits intodevasignhq:mainfrom
genesis-ai-labs-star:feat/bounty-detail-endpoint
Open

feat(api): add GET /bounties/:id bounty detail endpoint#56
genesis-ai-labs-star wants to merge 3 commits intodevasignhq:mainfrom
genesis-ai-labs-star:feat/bounty-detail-endpoint

Conversation

@genesis-ai-labs-star
Copy link

Implements GET /bounties/:id to return full bounty detail including creator info, assignee info (if assigned), application count, and status.\n\n- Adds endpoint in packages/api (Hono)\n- Injects db dependency into createApp for testability\n- Adds tests for the new endpoint\n\nFixes #21

@devasign-app
Copy link

devasign-app bot commented Feb 20, 2026

🟠 AI Code Review Results

Status: Changes Needed
Confidence: 100%


🟠 Merge Score: 50/100

🔴 ██████████░░░░░░░░░░ 50%

Recommendation: ❌ This PR needs significant improvements before it should be merged.

The PR successfully implements the new bounty detail endpoint and introduces a good dependency injection pattern for testability. However, it has a critical security gap due to missing input validation on the bounty ID. The tests are also incomplete, missing key edge cases. I'm requesting changes to address the validation and improve test coverage before this can be merged.

💡 Code Suggestions (5)

🔴 High Priority (1)

  1. packages/api/src/app.ts (Line 68)
    🔧 The id path parameter is used in the database query without being validated as a UUID. While parameterization prevents SQL injection, passing an incorrectly formatted string will cause a database-level error, resulting in a 500 Internal Server Error response. The endpoint should validate the ID format and return a 400 Bad Request for invalid inputs.

💭 Reasoning: Validating input at the edge of the application is a fundamental security and robustness principle. This change ensures that only validly formatted IDs reach the database layer, providing clearer error messages to clients (400 vs. 500) and preventing unexpected database errors.

Suggested Code:

    app.get('/bounties/:id', async (c) => {
        const bountyId = c.req.param('id');

        const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
        if (!uuidRegex.test(bountyId)) {
            return c.json({ error: 'Invalid bounty ID format' }, 400);
        }

        const db = deps?.db;

🟡 Medium Priority (3)

  1. packages/api/src/tests/app.test.ts (Line 85)
    🔧 The test suite is missing coverage for critical edge cases: handling an invalid ID format and fetching a bounty that has not yet been assigned. These are common scenarios that should be explicitly tested.

💭 Reasoning: Adding these tests ensures the endpoint is robust. The invalid ID test verifies the input validation, while the unassigned bounty test confirms that the conditional logic for the assignee field works correctly, preventing potential null pointer errors in clients.

Suggested Code:

        it('should return 400 for invalid bounty ID format', async () => {
            const res = await app.request('/bounties/not-a-valid-uuid');
            expect(res.status).toBe(400);
        });

        it('should return bounty details with null assignee if unassigned', async () => {
            const unassignedDb: DbLike = {
                execute: async () => ({
                    rows: [
                        {
                            id: '11111111-1111-1111-1111-111111111111',
                            title: 'Test bounty',
                            // ... other fields
                            creator_id: '22222222-2222-2222-2222-222222222222',
                            assignee_id: null,
                            creator_username: 'creator',
                            creator_avatar_url: 'https://example.com/c.png',
                            assignee_username: null,
                            assignee_avatar_url: null,
                            application_count: 3,
                        },
                    ],
                }),
            };

            const appWithUnassigned = createApp({ db: unassignedDb });
            const res = await appWithUnassigned.request('/bounties/11111111-1111-1111-1111-111111111111');
            expect(res.status).toBe(200);
            const body = await res.json();

            expect(body.id).toBe('11111111-1111-1111-1111-111111111111');
            expect(body.assignee).toBeNull();
        });
  1. packages/api/src/app.ts (Line 67)
    ✨ This endpoint appears to be public, as it lacks any authentication or authorization middleware. Exposing internal user UUIDs (creator.id, assignee.id) in a public endpoint can be a security concern, as it may leak information about user accounts. Please confirm if this is the intended behavior. If the endpoint is meant to be public, consider omitting these IDs from the response.

💭 Reasoning: Following the principle of least privilege, APIs should not expose more information than necessary. Internal identifiers like UUIDs should generally not be part of a public contract unless there is a specific reason for it, as it increases the surface area for information gathering by potential attackers.

  1. packages/api/src/app.ts (Line 79)
    ✨ The PR introduces Drizzle ORM but implements the data fetching with a large, raw SQL string. To better leverage Drizzle's capabilities for type safety and maintainability, this query should be refactored using the Drizzle query builder. This would involve defining schemas for bounties, users, and applications.

💭 Reasoning: Using the ORM's query builder provides better type safety, makes the code easier to refactor, and prevents entire classes of errors related to typos in column or table names. It aligns the implementation with the choice to add Drizzle as a dependency.

Suggested Code:

// This is a conceptual example assuming schemas are defined in './db/schema.ts'
// import { bounties, users, applications } from './db/schema';
// import { eq, count } from 'drizzle-orm';

// const subquery = db.select({ value: count() }).from(applications).where(eq(applications.bountyId, bounties.id)).as('application_count');

// const result = await db.select({
//   ...bounties,
//   creatorUsername: users.username,
//   creatorAvatarUrl: users.avatarUrl,
//   assigneeUsername: sql<string>`assignee.username`,
//   assigneeAvatarUrl: sql<string>`assignee.avatar_url`,
//   applicationCount: sql<number>`${subquery}`
// })
// .from(bounties)
// .innerJoin(users, eq(bounties.creatorId, users.id))
// .leftJoin(users, eq(bounties.assigneeId, users.id))
// .where(eq(bounties.id, bountyId))
// .limit(1);

🔵 Low Priority (1)

  1. packages/api/src/tests/app.test.ts (Line 128)
    ✨ The test for a found bounty creates a new app instance (app2) and uses sparse assertions. This is inefficient and makes the test less effective. The test should reuse the existing app instance and use a more comprehensive assertion like toMatchObject to validate the entire response structure.

💭 Reasoning: Spying on the mock dependency is a standard and efficient testing pattern. Using toMatchObject creates a more robust test that verifies the contract of the API response, catching unintended changes in field names or structure, which is especially important with manual data mapping.

Suggested Code:

        it('should return bounty details when found', async () => {
            const mockExecute = vi.spyOn(mockDb, 'execute').mockResolvedValueOnce({
                rows: [
                    {
                        id: '11111111-1111-1111-1111-111111111111',
                        github_issue_id: 123,
                        repo_owner: 'acme',
                        repo_name: 'repo',
                        title: 'Test bounty',
                        description: 'Desc',
                        amount_usdc: '100',
                        tech_tags: ['ts'],
                        difficulty: 'beginner',
                        status: 'open',
                        deadline: null,
                        creator_id: '22222222-2222-2222-2222-222222222222',
                        assignee_id: '33333333-3333-3333-3333-333333333333',
                        created_at: '2026-02-19T00:00:00.000Z',
                        updated_at: '2026-02-19T00:00:00.000Z',
                        creator_username: 'creator',
                        creator_avatar_url: 'https://example.com/c.png',
                        assignee_username: 'assignee',
                        assignee_avatar_url: 'https://example.com/a.png',
                        application_count: 7,
                    },
                ],
            });

            const res = await app.request('/bounties/11111111-1111-1111-1111-111111111111');
            expect(res.status).toBe(200);
            const body = await res.json();

            expect(body).toMatchObject({
                id: '11111111-1111-1111-1111-111111111111',
                repoOwner: 'acme',
                repoName: 'repo',
                title: 'Test bounty',
                amountUsdc: '100',
                applicationCount: 7,
                creator: {
                    id: '22222222-2222-2222-2222-222222222222',
                    username: 'creator',
                },
                assignee: {
                    id: '33333333-3333-3333-3333-333333333333',
                    username: 'assignee',
                },
            });

            mockExecute.mockRestore();
        });
📊 Review Metadata
  • Processing Time: 256s
  • Analysis Date: 2/20/2026, 12:32:12 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.

@genesis-ai-labs-star
Copy link
Author

Update: addressed review feedback.

  • Validate :id as UUID -> return 400 for invalid format
  • Keep parameterized query (no SQLi) and avoid DB-level errors for malformed IDs
  • Added tests for invalid id, not-found, found, and unassigned (assignee=null)
  • Omit internal user UUIDs from public creator/assignee objects

All api tests pass: .

@devasign-app
Copy link

devasign-app bot commented Feb 20, 2026

🔄 Follow-Up AI Code Review

Status: Ready to Merge
Confidence: 100%


🟢 Updated Merge Score: 95/100

🟢 ███████████████████░ 95%

Recommendation: ✅ This PR looks great and is ready for merge!

📋 Previous Review Summary

The PR successfully implements the new bounty detail endpoint and introduces a good dependency injection pattern for testability. However, it has a critical security gap due to missing input validation on the bounty ID. The tests are also incomplete, missing key edge cases. I'm requesting changes to address the validation and improve test coverage before this can be merged.

Excellent work. The author has fully addressed all concerns from the previous review and gone above and beyond.

Resolved Concerns:

  • Input Validation: The critical security issue with the bounty ID has been resolved by adding proper UUID format validation. This prevents potential database errors and hardens the endpoint against injection-style attacks.
  • Test Coverage: The test suite has been significantly improved. It now correctly covers edge cases like invalid ID formats and unassigned bounties. The refactoring to use vi.fn() also makes the tests cleaner and more maintainable.

Additional Improvements:

  • The author proactively identified and fixed an information leakage vulnerability by removing internal user UUIDs from the API response. They also added a test case to verify this fix, which is commendable.

The PR is now in excellent shape and I recommend merging it.

💡 Code Suggestions

✨ Great job! No specific suggestions at this time.

📊 Review Metadata
  • Processing Time: 133s
  • Analysis Date: 2/20/2026, 1:29:24 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.

@genesis-ai-labs-star
Copy link
Author

Follow-up: CI/tests are green and the bot review now marks this PR as Ready to Merge (95/100).\n\nIf this looks good, could you please merge? Happy to adjust any style or response shape preferences.

@genesis-ai-labs-star
Copy link
Author

@bishopBethel @truemperor quick ping — bot follow-up review marks this PR as Ready to Merge (95/100) and the branch is clean/mergeable.

If the response shape looks good to you, could you please merge?

(Recap: added UUID validation for :id (400 on invalid), improved tests incl. edge cases, and removed internal user UUIDs from public response.)

@bishopBethel
Copy link
Member

bishopBethel commented Feb 20, 2026

@genesis-ai-labs-star I'm following the issue sequence, hence I can't merge and close #21 when there are other open issues before it

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