feat(api): implement GET /bounties/:id (bounty detail)#55
feat(api): implement GET /bounties/:id (bounty detail)#55genesis-ai-labs-star wants to merge 1 commit intodevasignhq:mainfrom
Conversation
|
review |
🟡 AI Code Review ResultsStatus: Review Recommended 🟡 Merge Score: 80/100🟡 Recommendation: The PR successfully implements the bounty detail endpoint with a secure, parameterized SQL query and good use of dependency injection for testability. Key areas for improvement include adding UUID validation for the bounty ID and refactoring the database-to-API response mapping into a reusable function to enhance maintainability. 💡 Code Suggestions (3)🔴 High Priority (1)
💭 Reasoning: This enforces a strict contract for the endpoint's input, improving security and robustness by failing fast on invalid data and preventing malformed IDs from reaching the database. Suggested Code: 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);
}🟡 Medium Priority (2)
💭 Reasoning: This abstraction separates the data transformation logic from the request handling flow, adhering to the Single Responsibility Principle and making the code easier to manage and test as the API grows. Suggested Code: // Define this helper function within app.ts or in a shared utils file
function mapDbBountyToApiBounty(row: any) {
return {
id: row.id,
githubIssueId: row.github_issue_id,
repoOwner: row.repo_owner,
repoName: row.repo_name,
title: row.title,
description: row.description,
amountUsdc: row.amount_usdc,
techTags: row.tech_tags,
difficulty: row.difficulty,
status: row.status,
deadline: row.deadline,
createdAt: row.created_at,
updatedAt: row.updated_at,
creator: {
id: row.creator_id,
username: row.creator_username,
avatarUrl: row.creator_avatar_url,
},
applicationCount: row.application_count ?? 0,
assignee: row.assignee_id
? {
id: row.assignee_id,
username: row.assignee_username,
avatarUrl: row.assignee_avatar_url,
}
: null,
};
}
// ... inside the handler, replace the large c.json block with this:
return c.json(mapDbBountyToApiBounty(row));
💭 Reasoning: Comprehensive testing includes covering both primary paths and common edge cases. This test case validates the correct behavior for an unassigned bounty, which is a critical state in the data model. Suggested Code: it('should return assignee as null when not assigned', async () => {
const unassignedDb: DbLike = {
execute: async () => ({
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',
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_id: null,
assignee_username: null,
assignee_avatar_url: null,
application_count: 3,
},
],
}),
};
const app3 = createApp({ db: unassignedDb });
const res = await app3.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();
});📊 Review Metadata
|
Implements GET /bounties/:id returning full bounty details including creator info, application count, assignee info (when assigned), and status.\n\nRefs: #21