Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
72 changes: 72 additions & 0 deletions src/app/api/v1/sources/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';

import { GET, POST } from './route';

function makeJsonRequest(body: unknown, headers?: Record<string, string>) {
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;
});
});
10 changes: 6 additions & 4 deletions src/app/api/v1/sources/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
Expand Down