From fdf20a984b2ef66ef100d1027e63367f10c9b249 Mon Sep 17 00:00:00 2001 From: zscy0710 <97370522+zscy0710@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:18:02 +0800 Subject: [PATCH] Improve Algora source configuration support --- README.md | 39 +++++++++++++++ src/app/api/v1/sources/route.test.ts | 72 ++++++++++++++++++++++++++++ src/app/api/v1/sources/route.ts | 10 ++-- 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/app/api/v1/sources/route.test.ts diff --git a/README.md b/README.md index 5208166..7090656 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,45 @@ Open [http://localhost:3000](http://localhost:3000) to view the dashboard. - [ ] CLI tool - [ ] Showcase/portfolio features +## External bounty integrations + +ClawFreelance already includes an internal bounty aggregator with support for: + +- GitHub bounty issues +- GitHub issue discovery +- Algora bounty discovery +- Immunefi +- Bugcrowd + +### Algora integration + +Algora bounties are discovered from GitHub issues and comments using: + +- labels like `💎 Bounty`, `algora`, and `bounty` +- issue body patterns such as `/bounty $100` +- reward information posted in issue comments + +Relevant implementation: + +- `src/lib/aggregator/sources/algora.ts` +- `src/app/api/v1/sources/route.ts` +- `src/app/api/v1/sync/route.ts` + +To preview an Algora sync configuration in development: + +```bash +curl -X POST http://localhost:3000/api/v1/sources \ + -H 'Content-Type: application/json' \ + -d '{ + "type": "algora", + "config": { + "repositories": ["zio/zio", "omnigres/omnigres"] + } + }' +``` + +To trigger a sync, call `/api/v1/sync` with `sources.algora.enabled=true` and optional repositories. + ## Contributing We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. diff --git a/src/app/api/v1/sources/route.test.ts b/src/app/api/v1/sources/route.test.ts new file mode 100644 index 0000000..da71c3f --- /dev/null +++ b/src/app/api/v1/sources/route.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from 'vitest'; + +import { GET, POST } from './route'; + +function makeJsonRequest(body: unknown, headers?: Record) { + return new Request('http://localhost/api/v1/sources', { + method: 'POST', + headers: { + 'content-type': 'application/json', + ...(headers || {}), + }, + body: JSON.stringify(body), + }) as any; +} + +describe('GET /api/v1/sources', () => { + it('includes Algora as an active source', async () => { + const res = await GET({} as any); + const json = await res.json(); + const algora = json.data.sources.find((s: any) => s.type === 'algora'); + + expect(json.success).toBe(true); + expect(algora).toBeTruthy(); + expect(algora.status).toBe('active'); + expect(algora.config.repositories.length).toBeGreaterThan(0); + }); +}); + +describe('POST /api/v1/sources', () => { + it('accepts Algora source configuration in development mode', async () => { + const prev = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + const req = makeJsonRequest({ + type: 'algora', + config: { + repositories: ['foo/bar', 'baz/qux'], + labels: ['💎 Bounty', 'algora'], + }, + }); + + const res = await POST(req); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(json.success).toBe(true); + expect(json.data.type).toBe('algora'); + expect(json.data.config.repositories).toEqual(['foo/bar', 'baz/qux']); + + process.env.NODE_ENV = prev; + }); + + it('still rejects unsupported source types', async () => { + const prev = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + const req = makeJsonRequest({ + type: 'opencollective', + config: { + repositories: ['foo/bar'], + }, + }); + + const res = await POST(req); + const json = await res.json(); + + expect(res.status).toBe(400); + expect(json.success).toBe(false); + + process.env.NODE_ENV = prev; + }); +}); diff --git a/src/app/api/v1/sources/route.ts b/src/app/api/v1/sources/route.ts index 43a3565..6201a66 100644 --- a/src/app/api/v1/sources/route.ts +++ b/src/app/api/v1/sources/route.ts @@ -123,8 +123,9 @@ export async function POST(request: NextRequest) { const { type, config } = validated.data; - // For now, we only support dynamic GitHub repos - if (type !== 'github') { + const supportedDynamicSources = new Set(['github', 'algora']); + + if (!supportedDynamicSources.has(type)) { return NextResponse.json( { success: false, @@ -134,8 +135,9 @@ export async function POST(request: NextRequest) { ); } - // In a full implementation, this would save to database - // For now, return success with info about what would be added + // In a full implementation, this would save to database. + // For now, return success with info about what would be added, + // so operators can preview GitHub/Algora sync configuration. return NextResponse.json({ success: true, message: 'Source configuration received',