Skip to content
Merged
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
34 changes: 34 additions & 0 deletions app/api/applications/[id]/review/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { ApplicationStatus } from '@/types/participation';

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: appId } = await params;

try {
const body = await request.json();
const { status, feedback } = body;

if (!status || !['approved', 'rejected'].includes(status)) {
return NextResponse.json({ error: 'Invalid status' }, { status: 400 });
}

const updatedApp = BountyStore.updateApplication(appId, {
status: status as ApplicationStatus,
feedback,
reviewedAt: new Date().toISOString()
});

if (!updatedApp) {
return NextResponse.json({ error: 'Application not found' }, { status: 404 });
}

return NextResponse.json({ success: true, data: updatedApp });

} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
11 changes: 11 additions & 0 deletions app/api/bounties/[id]/applications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';

export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;
const applications = BountyStore.getApplicationsByBounty(bountyId);
return NextResponse.json({ data: applications });
}
49 changes: 49 additions & 0 deletions app/api/bounties/[id]/apply/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { Application } from '@/types/participation';

const generateId = () => crypto.randomUUID();

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;
try {
const body = await request.json();
const { applicantId, coverLetter, portfolioUrl } = body;

if (!applicantId || !coverLetter) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}

const bounty = BountyStore.getBountyById(bountyId);
if (!bounty) {
return NextResponse.json({ error: 'Bounty not found' }, { status: 404 });
}

const existingApplication = BountyStore.getApplicationsByBounty(bountyId).find(
(app) => app.applicantId === applicantId
);

if (existingApplication) {
return NextResponse.json({ error: 'Application already exists' }, { status: 409 });
}

const application: Application = {
id: generateId(),
bountyId: bountyId,
applicantId,
coverLetter,
portfolioUrl,
status: 'pending',
submittedAt: new Date().toISOString(),
};

BountyStore.addApplication(application);

return NextResponse.json({ success: true, data: application });
} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
55 changes: 55 additions & 0 deletions app/api/bounties/[id]/join/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { MilestoneParticipation } from '@/types/participation';

const generateId = () => crypto.randomUUID();

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;

try {
const body = await request.json();
const { contributorId } = body;

if (!contributorId) {
return NextResponse.json({ error: 'Missing contributorId' }, { status: 400 });
}

const bounty = BountyStore.getBountyById(bountyId);
if (!bounty) {
return NextResponse.json({ error: 'Bounty not found' }, { status: 404 });
}

if (bounty.claimingModel !== 'milestone') {
return NextResponse.json({ error: 'Invalid claiming model' }, { status: 400 });
}

// Check if already joined
const existing = BountyStore.getMilestoneParticipationsByBounty(bountyId)
.find(p => p.contributorId === contributorId);

if (existing) {
return NextResponse.json({ error: 'Already joined this bounty' }, { status: 409 });
}

const participation: MilestoneParticipation = {
id: generateId(),
bountyId,
contributorId,
currentMilestone: 1, // Start at milestone 1
status: 'active',
joinedAt: new Date().toISOString(),
lastUpdatedAt: new Date().toISOString()
};

BountyStore.addMilestoneParticipation(participation);

return NextResponse.json({ success: true, data: participation });

} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
70 changes: 70 additions & 0 deletions app/api/bounties/[id]/milestones/advance/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
// import { MilestoneStatus } from '@/types/participation';

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;

try {
const body = await request.json();
const { contributorId, action } = body; // action: 'advance' | 'complete' | 'remove'

if (!contributorId || !action) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}

if (!['advance', 'complete', 'remove'].includes(action)) {
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
}

const participations = BountyStore.getMilestoneParticipationsByBounty(bountyId);
const participation = participations.find(p => p.contributorId === contributorId);

if (!participation) {
return NextResponse.json({ error: 'Participation not found' }, { status: 404 });
}

const bounty = BountyStore.getBountyById(bountyId);

if (!bounty) {
return NextResponse.json({ error: 'Bounty not found' }, { status: 404 });
}

let updates: Partial<typeof participation> = {
lastUpdatedAt: new Date().toISOString()
};

const totalMilestones = participation.totalMilestones || bounty.milestones?.length;

if (action === 'advance') {
if (participation.status === 'completed') {
return NextResponse.json({ error: 'Cannot advance completed participation' }, { status: 409 });
}
if (!totalMilestones) {
return NextResponse.json({ error: 'Cannot determine total milestones' }, { status: 500 });
}
if (participation.currentMilestone >= totalMilestones) {
return NextResponse.json({ error: 'Already at last milestone' }, { status: 409 });
}
updates.currentMilestone = participation.currentMilestone + 1;
updates.status = 'advanced';
} else if (action === 'complete') {
if (participation.status === 'completed') {
return NextResponse.json({ error: 'Already completed' }, { status: 409 });
}
updates.status = 'completed';
} else if (action === 'remove') {
return NextResponse.json({ error: 'Remove action not supported yet' }, { status: 400 });
}

const updated = BountyStore.updateMilestoneParticipation(participation.id, updates);

return NextResponse.json({ success: true, data: updated });

} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
11 changes: 11 additions & 0 deletions app/api/bounties/[id]/submissions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';

export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;
const submissions = BountyStore.getSubmissionsByBounty(bountyId);
return NextResponse.json({ data: submissions });
}
55 changes: 55 additions & 0 deletions app/api/bounties/[id]/submit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { Submission } from '@/types/participation';

const generateId = () => crypto.randomUUID();

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: bountyId } = await params;

try {
const body = await request.json();
const { contributorId, content } = body;

if (!contributorId || !content) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}

const bounty = BountyStore.getBountyById(bountyId);
if (!bounty) {
return NextResponse.json({ error: 'Bounty not found' }, { status: 404 });
}

const allowedModels = ['single-claim', 'competition', 'multi-winner', 'application'];
if (!allowedModels.includes(bounty.claimingModel)) {
return NextResponse.json({ error: 'Submission not allowed for this bounty type' }, { status: 400 });
}

const existingSubmission = BountyStore.getSubmissionsByBounty(bountyId).find(
s => s.contributorId === contributorId
);

if (existingSubmission) {
return NextResponse.json({ error: 'Duplicate submission' }, { status: 409 });
}

const submission: Submission = {
id: generateId(),
bountyId,
contributorId,
content,
status: 'pending',
submittedAt: new Date().toISOString(),
};

BountyStore.addSubmission(submission);

return NextResponse.json({ success: true, data: submission });

} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
35 changes: 35 additions & 0 deletions app/api/submissions/[id]/select/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { SubmissionStatus } from '@/types/participation';

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id: subId } = await params;

try {
const body = await request.json();
// 'accepted' implies winner
const { status, feedback } = body;

if (!status || !['accepted', 'rejected'].includes(status)) {
return NextResponse.json({ error: 'Invalid status' }, { status: 400 });
}

const updatedSub = BountyStore.updateSubmission(subId, {
status: status as SubmissionStatus,
feedback,
reviewedAt: new Date().toISOString()
});

if (!updatedSub) {
return NextResponse.json({ error: 'Submission not found' }, { status: 404 });
}

return NextResponse.json({ success: true, data: updatedSub });

} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
Loading