feat: REST API — Bounty CRUD Endpoints#114
feat: REST API — Bounty CRUD Endpoints#114chronoeth-creator merged 3 commits intoSolFoundry:mainfrom
Conversation
- POST /api/bounties: create bounty (title, description, tier, reward, skills, deadline) - GET /api/bounties: list with filters (tier, status, skills) and pagination - GET /api/bounties/:id: get single bounty with submissions - PATCH /api/bounties/:id: update with status transition validation (open->in_progress->completed->paid) - DELETE /api/bounties/:id: delete bounty - POST /api/bounties/:id/submit: submit PR URL as solution - GET /api/bounties/:id/submissions: list submissions - POST /api/bounties/:id/claim: claim bounty (T2/T3 with reputation gates) - PostgreSQL schema migration (001_create_bounties.sql) - Docker Compose for local dev (API + Postgres) - Dockerfile for backend container - 43 passing tests covering all endpoints and edge cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove claim/unclaim/approve/reject endpoints (belongs to Issue SolFoundry#16) - Remove ClaimRecord, ClaimCreate, ClaimResponse, ClaimStatus models - Remove Dockerfile, docker-compose.yml, SQL migration (inconsistent with in-memory service layer; will be added when DB is integrated) - Tighten input validation: title min 3 chars, reward min/max range, skill format regex, max 20 skills, GitHub URL validation - Ensure model -> service -> API layer consistency (same field names, same shapes, no orphaned fields) - Add comprehensive edge-case tests (81 total): boundary values, pagination beyond total, special characters, exhaustive invalid status transition matrix, delete idempotency, duplicate submissions across bounties, response shape assertions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR implements a complete bounty management REST API with CRUD endpoints, including bounty creation, listing with filters, updates with status transitions, deletion, and solution submission capabilities. The implementation includes Pydantic models for validation, an in-memory service layer, and comprehensive test coverage. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Router as API Router
participant Service as Bounty Service
participant Model as Models & Validation
Client->>Router: POST /api/bounties (BountyCreate)
Router->>Model: Validate input data
Model-->>Router: Validated payload
Router->>Service: create_bounty(data)
Service->>Model: Create BountyDB with UUID
Service-->>Router: BountyResponse
Router-->>Client: 201 + BountyResponse
Client->>Router: GET /api/bounties?status=open&tier=1
Router->>Service: list_bounties(status, tier, skills, skip, limit)
Service->>Service: Filter & paginate results
Service-->>Router: BountyListResponse
Router-->>Client: 200 + BountyListResponse
Client->>Router: PATCH /api/bounties/{id} (status: in_progress)
Router->>Model: Validate BountyUpdate
Model-->>Router: Validated payload
Router->>Service: update_bounty(id, data)
Service->>Service: Validate status transition
Service->>Service: Update BountyDB, set updated_at
Service-->>Router: BountyResponse or error
Router-->>Client: 200/400/404 + response
Client->>Router: POST /api/bounties/{id}/submit (SubmissionCreate)
Router->>Model: Validate PR URL & submitter
Model-->>Router: Validated payload
Router->>Service: submit_solution(id, data)
Service->>Service: Check bounty exists & state
Service->>Service: Check for duplicate PR URLs
Service->>Service: Create SubmissionRecord
Service-->>Router: SubmissionResponse or error
Router-->>Client: 201/400/404 + response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Important Merge conflicts detected (Beta)
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment Tip CodeRabbit can use your project's `pylint` configuration to improve the quality of Python code reviews.Add a pylint configuration file to your project to customize how CodeRabbit runs |
✅ Multi-LLM Code Review — APPROVEAggregated Score: 7.2/10 (from 3 models) Model Verdicts
Category Scores (Averaged)
SummaryGPT-5.4: This submission covers the requested bounty API surface and shows decent structure, validation, and naming discipline. However, it does not fully meet the backend delivery expectations because the persistence and environment integration are still effectively stubbed, and the test story is not fully trustworthy from the provided diff. Issues
Suggestions
📈 Tier Progression NoteYour submission passes Tier 1 (minimum 6/10), but Tier 2 has stricter scoring — judges evaluate T2 with higher expectations on tests, error handling, and integration. To level up for T2:
Strong T1 work builds the foundation for higher-tier bounties. Keep shipping! 🏭 Reviewed by SolFoundry Multi-LLM Pipeline: GPT-5.4, Gemini 2.5 Pro, Grok 4 Review profile: backend — scoring weights adjusted for this domain |
|
✅ Review Passed! (Score: 7.2/10)
@ItachiDevv, please update your branch: git fetch origin
git merge origin/main
# Resolve any conflicts, then:
git pushOnce conflicts are resolved, the review will re-run automatically and go to approval. SolFoundry Review Bot |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/app/main.py (1)
20-26:⚠️ Potential issue | 🟠 MajorCORS configuration with
allow_origins=["*"]andallow_credentials=Trueis insecure.This combination is problematic: when credentials are allowed, browsers require a specific origin (not
*). While some frameworks silently handle this, it can lead to unexpected behavior or security issues in production. Consider restricting origins to specific trusted domains before deployment.🛡️ Recommended production-ready configuration
app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=["http://localhost:3000"], # TODO: Configure from env for production allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app/main.py` around lines 20 - 26, The CORS setup uses app.add_middleware with CORSMiddleware and currently sets allow_origins=["*"] while allow_credentials=True, which is insecure and incompatible with credentialed requests; update the middleware configuration (the call to app.add_middleware/CORSMiddleware) to replace the wildcard origin with a restricted list of trusted origins (e.g., a config-driven list) and ensure allow_credentials remains True only when allow_origins contains explicit origins (not "*"), or set allow_credentials=False if you must allow "*" in a non-production/dev context; make the allowed origins configurable (env/config) so production deployments can lock it down.
🧹 Nitpick comments (5)
backend/app/models/bounty.py (1)
76-81: Consider enforcing HTTPS-only for GitHub URLs.Both
pr_urlandgithub_issue_urlvalidators accepthttp://github.com/which is typically redirected to HTTPS anyway. For security consistency, consider requiring HTTPS.♻️ Enforce HTTPS-only
`@field_validator`("pr_url") `@classmethod` def validate_pr_url(cls, v: str) -> str: - if not v.startswith(("https://github.com/", "http://github.com/")): + if not v.startswith("https://github.com/"): raise ValueError("pr_url must be a valid GitHub URL") return vAlso applies to: 128-133
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app/models/bounty.py` around lines 76 - 81, Update the URL validators to require HTTPS only: in the validate_pr_url and validate_github_issue_url class methods replace the allowed prefix check that accepts both "http://github.com/" and "https://github.com/" with a strict check for "https://github.com/"; raise the same ValueError message on failure and return the value unchanged on success so behavior and error handling remain consistent.backend/tests/test_bounties.py (1)
66-80: Minor style improvement for list concatenation.The BFS helper works correctly. Consider using spread syntax for slightly cleaner code as suggested by Ruff.
♻️ Use list spread syntax
def _status_path(start: BountyStatus, end: BountyStatus): """BFS through VALID_STATUS_TRANSITIONS to find a path from start to end.""" if start == end: return [start] queue = deque([(start, [start])]) seen = {start} while queue: current, path = queue.popleft() for next_status in VALID_STATUS_TRANSITIONS.get(current, set()): if next_status == end: - return path + [next_status] + return [*path, next_status] if next_status not in seen: seen.add(next_status) - queue.append((next_status, path + [next_status])) + queue.append((next_status, [*path, next_status])) return None🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/tests/test_bounties.py` around lines 66 - 80, The helper _status_path uses list concatenation (path + [next_status]); replace those with list spread syntax [*path, next_status] for cleaner, slightly faster construction and update both occurrences: the return path in the if next_status == end branch and the queue.append call where you currently append (next_status, path + [next_status]); leave logic and variable names (VALID_STATUS_TRANSITIONS, queue, seen) unchanged.backend/app/services/bounty_service.py (2)
24-28: In-memory store is not thread-safe for concurrent requests.The module-level
_bounty_storedict can experience race conditions under concurrent async requests (e.g., two simultaneous updates to the same bounty). This is acceptable for MVP/development but should be noted for production considerations.For production, consider using
asyncio.Lockfor critical sections or migrating to a database with proper transaction support as noted in the docstring.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app/services/bounty_service.py` around lines 24 - 28, The module-level in-memory dict _bounty_store (type dict[str, BountyDB]) is not thread/async-safe and can race under concurrent requests; protect accesses by introducing an asyncio.Lock (e.g., _bounty_store_lock) and acquire/release it around all read-modify-write sequences that touch _bounty_store (create/update/delete/get operations), or implement per-bounty locks keyed by bounty id if you need finer-grained concurrency; ensure functions that currently access _bounty_store (where BountyDB objects are created/updated) await the lock before mutating and release it after to prevent races, and add a comment noting this is a development-only safeguard until the service is backed by a transactional DB.
107-137: Type annotation mismatch fortierparameter.The
tierparameter is annotated asOptional[int]but the API layer passesOptional[BountyTier]. While this works at runtime becauseBountyTieris anintenum, the annotation is misleading and could cause type checker issues.♻️ Fix type annotation
def list_bounties( *, status: Optional[BountyStatus] = None, - tier: Optional[int] = None, + tier: Optional[BountyTier] = None, skills: Optional[list[str]] = None, skip: int = 0, limit: int = 20, ) -> BountyListResponse:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app/services/bounty_service.py` around lines 107 - 137, The tier parameter in list_bounties is annotated as Optional[int] but the API passes Optional[BountyTier]; update the type annotation to Optional[BountyTier] so the signature matches callers and static type checkers. Ensure BountyTier is imported in this module (or referenced via its module) and keep the runtime comparison (b.tier == tier) unchanged since BountyTier is an int-backed Enum.backend/app/api/bounties.py (1)
77-82: String-based error classification is fragile.Using
"not found" in error.lower()to determine HTTP status codes couples the API layer to specific error message text. If the service layer error messages change, this logic breaks silently. Consider using structured error types or error codes instead.♻️ Alternative: Use a custom exception or error enum
# In a shared errors module or bounty_service.py from enum import Enum class BountyError(str, Enum): NOT_FOUND = "not_found" INVALID_TRANSITION = "invalid_transition" DUPLICATE_SUBMISSION = "duplicate_submission" NOT_ACCEPTING = "not_accepting" # Service returns: tuple[Optional[T], Optional[BountyError], Optional[str]] # Or raises custom exceptions that the API layer catchesAlso applies to: 101-106
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app/api/bounties.py` around lines 77 - 82, The API currently inspects plain text from bounty_service.update_bounty in update_bounty to decide HTTP status (using "not found" in error.lower()), which is fragile; change the service to return a structured error (e.g., an Enum like BountyError or raise custom exceptions such as BountyNotFoundError, InvalidTransitionError) and update the API handler (update_bounty and the other handlers at lines ~101-106) to map those specific error types/codes to HTTPException status codes (404 for BountyNotFoundError/BountyError.NOT_FOUND, 400 for invalid requests, etc.) instead of matching substrings of error messages so the contract is stable and robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@backend/app/main.py`:
- Around line 20-26: The CORS setup uses app.add_middleware with CORSMiddleware
and currently sets allow_origins=["*"] while allow_credentials=True, which is
insecure and incompatible with credentialed requests; update the middleware
configuration (the call to app.add_middleware/CORSMiddleware) to replace the
wildcard origin with a restricted list of trusted origins (e.g., a config-driven
list) and ensure allow_credentials remains True only when allow_origins contains
explicit origins (not "*"), or set allow_credentials=False if you must allow "*"
in a non-production/dev context; make the allowed origins configurable
(env/config) so production deployments can lock it down.
---
Nitpick comments:
In `@backend/app/api/bounties.py`:
- Around line 77-82: The API currently inspects plain text from
bounty_service.update_bounty in update_bounty to decide HTTP status (using "not
found" in error.lower()), which is fragile; change the service to return a
structured error (e.g., an Enum like BountyError or raise custom exceptions such
as BountyNotFoundError, InvalidTransitionError) and update the API handler
(update_bounty and the other handlers at lines ~101-106) to map those specific
error types/codes to HTTPException status codes (404 for
BountyNotFoundError/BountyError.NOT_FOUND, 400 for invalid requests, etc.)
instead of matching substrings of error messages so the contract is stable and
robust.
In `@backend/app/models/bounty.py`:
- Around line 76-81: Update the URL validators to require HTTPS only: in the
validate_pr_url and validate_github_issue_url class methods replace the allowed
prefix check that accepts both "http://github.com/" and "https://github.com/"
with a strict check for "https://github.com/"; raise the same ValueError message
on failure and return the value unchanged on success so behavior and error
handling remain consistent.
In `@backend/app/services/bounty_service.py`:
- Around line 24-28: The module-level in-memory dict _bounty_store (type
dict[str, BountyDB]) is not thread/async-safe and can race under concurrent
requests; protect accesses by introducing an asyncio.Lock (e.g.,
_bounty_store_lock) and acquire/release it around all read-modify-write
sequences that touch _bounty_store (create/update/delete/get operations), or
implement per-bounty locks keyed by bounty id if you need finer-grained
concurrency; ensure functions that currently access _bounty_store (where
BountyDB objects are created/updated) await the lock before mutating and release
it after to prevent races, and add a comment noting this is a development-only
safeguard until the service is backed by a transactional DB.
- Around line 107-137: The tier parameter in list_bounties is annotated as
Optional[int] but the API passes Optional[BountyTier]; update the type
annotation to Optional[BountyTier] so the signature matches callers and static
type checkers. Ensure BountyTier is imported in this module (or referenced via
its module) and keep the runtime comparison (b.tier == tier) unchanged since
BountyTier is an int-backed Enum.
In `@backend/tests/test_bounties.py`:
- Around line 66-80: The helper _status_path uses list concatenation (path +
[next_status]); replace those with list spread syntax [*path, next_status] for
cleaner, slightly faster construction and update both occurrences: the return
path in the if next_status == end branch and the queue.append call where you
currently append (next_status, path + [next_status]); leave logic and variable
names (VALID_STATUS_TRANSITIONS, queue, seen) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9e8e40e8-5b88-415b-8e30-3003429cfac3
📒 Files selected for processing (5)
backend/app/api/bounties.pybackend/app/main.pybackend/app/models/bounty.pybackend/app/services/bounty_service.pybackend/tests/test_bounties.py
✅ Multi-LLM Code Review — APPROVEAggregated Score: 6.5/10 (from 3 models) Model Verdicts
Category Scores (Averaged)
SummaryGPT-5.4: This PR shows a structured attempt at the bounty API and the endpoint surface is close to the requested feature set. However, the implementation appears to drift from the expected backend architecture and there are project-coherence concerns that make it hard to treat as a solid integration into the SolFoundry codebase. Issues
Suggestions
📈 Tier Progression NoteYour submission passes Tier 1 (minimum 6/10), but Tier 2 has stricter scoring — judges evaluate T2 with higher expectations on tests, error handling, and integration. To level up for T2:
Strong T1 work builds the foundation for higher-tier bounties. Keep shipping! 🏭 Reviewed by SolFoundry Multi-LLM Pipeline: GPT-5.4, Gemini 2.5 Pro, Grok 4 Review profile: backend — scoring weights adjusted for this domain |
…ty API Addresses review feedback from PR SolFoundry#114: - 100% docstring coverage on bounty files (was 11%) - PostgreSQL migration for bounties and submissions tables - Docker Compose for local development - Dockerfile for backend container Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Description
Implements the core REST API for managing bounties with full CRUD, filtering, pagination, and solution submission.
Closes #3
Endpoints
POST /api/bounties— create bountyGET /api/bounties— list with filters (tier, status, skills) + paginationGET /api/bounties/:id— get single bountyPATCH /api/bounties/:id— update status (open → in_progress → completed → paid)POST /api/bounties/:id/submit— submit a PR URL as a solutionGET /api/bounties/:id/submissions— list submissionsImplementation
Solana Wallet for Payout
Wallet:
97VihHW2Br7BKUU16c7RxjiEMHsD4dWisGDT2Y3LyJxFChecklist
Summary by CodeRabbit
Release Notes
New Features
Chores