Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
120 changes: 65 additions & 55 deletions app/api/bounties/[id]/claim/route.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,70 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { addDays } from 'date-fns';
import { getCurrentUser } from '@/lib/server-auth';
import { NextResponse } from "next/server";
import { BountyStore } from "@/lib/store";
import { getCurrentUser } from "@/lib/server-auth";

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

try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

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

// If client sends contributorId, ensure it matches the authenticated user
if (contributorId && contributorId !== user.id) {
return NextResponse.json({ error: 'Contributor ID mismatch' }, { status: 403 });
}

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

if (bounty.claimingModel !== 'single-claim') {
return NextResponse.json({ error: 'Invalid claiming model for this action' }, { status: 400 });
}

if (bounty.status !== 'open') {
return NextResponse.json({ error: 'Bounty is not available' }, { status: 409 });
}

const now = new Date();
const updates = {
status: 'claimed' as const,
claimedBy: user.id, // Use authenticated user ID
claimedAt: now.toISOString(),
claimExpiresAt: addDays(now, 7).toISOString(),
updatedAt: now.toISOString()
};

const updatedBounty = BountyStore.updateBounty(bountyId, updates);

if (!updatedBounty) {
return NextResponse.json({ success: false, error: 'Failed to update bounty' }, { status: 500 });
}

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

} catch (error) {
console.error('Error claiming bounty:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
const { id: bountyId } = await params;

try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

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

// If client sends contributorId, ensure it matches the authenticated user
if (contributorId && contributorId !== user.id) {
return NextResponse.json(
{ error: "Contributor ID mismatch" },
{ status: 403 },
);
}

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

if (bounty.type !== "FIXED_PRICE") {
return NextResponse.json(
{ error: "Invalid bounty type for this action" },
{ status: 400 },
);
}

if (bounty.status !== "OPEN") {
return NextResponse.json(
{ error: "Bounty is not available" },
{ status: 409 },
);
}

const now = new Date();
const updates = {
status: "IN_PROGRESS" as const,
updatedAt: now.toISOString(),
};

const updatedBounty = BountyStore.updateBounty(bountyId, updates);

if (!updatedBounty) {
return NextResponse.json(
{ success: false, error: "Failed to update bounty" },
{ status: 500 },
);
}

return NextResponse.json({ success: true, data: updatedBounty });
} catch (error) {
console.error("Error claiming bounty:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}
}
112 changes: 62 additions & 50 deletions app/api/bounties/[id]/competition/join/route.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,69 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { CompetitionParticipation } from '@/types/participation';
import { getCurrentUser } from '@/lib/server-auth';
import { NextResponse } from "next/server";
import { BountyStore } from "@/lib/store";
import { CompetitionParticipation } from "@/types/participation";
import { getCurrentUser } from "@/lib/server-auth";

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

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

try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

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

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

// Validate status is open
if (bounty.status !== 'open') {
return NextResponse.json({ error: 'Bounty is not open for registration' }, { status: 409 });
}

const existing = BountyStore.getCompetitionParticipationsByBounty(bountyId)
.find(p => p.contributorId === user.id);

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

const participation: CompetitionParticipation = {
id: generateId(),
bountyId,
contributorId: user.id, // Use authenticated user ID
status: 'registered',
registeredAt: new Date().toISOString()
};

BountyStore.addCompetitionParticipation(participation);

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

} catch (error) {
console.error('Error joining competition:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
const { id: bountyId } = await params;

try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

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

if (bounty.type !== "COMPETITION") {
return NextResponse.json(
{ error: "Invalid bounty type for this action" },
{ status: 400 },
);
}

// Validate status is open
if (bounty.status !== "OPEN") {
return NextResponse.json(
{ error: "Bounty is not open for registration" },
{ status: 409 },
);
}

const existing = BountyStore.getCompetitionParticipationsByBounty(
bountyId,
).find((p) => p.contributorId === user.id);

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

const participation: CompetitionParticipation = {
id: generateId(),
bountyId,
contributorId: user.id, // Use authenticated user ID
status: "registered",
registeredAt: new Date().toISOString(),
};

BountyStore.addCompetitionParticipation(participation);

return NextResponse.json({ success: true, data: participation });
} catch (error) {
console.error("Error joining competition:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}
}
92 changes: 52 additions & 40 deletions app/api/bounties/[id]/join/route.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,67 @@
import { NextResponse } from 'next/server';
import { BountyStore } from '@/lib/store';
import { MilestoneParticipation } from '@/types/participation';
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 }> }
request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const { id: bountyId } = await params;
const { id: bountyId } = await params;

try {
const body = await request.json();
const { contributorId } = body;
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 (!contributorId) {
return NextResponse.json(
{ error: "Missing contributorId" },
{ status: 400 },
);
}

if (bounty.claimingModel !== 'milestone') {
return NextResponse.json({ error: 'Invalid claiming model' }, { status: 400 });
}
const bounty = BountyStore.getBountyById(bountyId);
if (!bounty) {
return NextResponse.json({ error: "Bounty not found" }, { status: 404 });
}

// Check if already joined
const existing = BountyStore.getMilestoneParticipationsByBounty(bountyId)
.find(p => p.contributorId === contributorId);
if (bounty.type !== "MILESTONE_BASED") {
return NextResponse.json(
{ error: "Invalid bounty type" },
{ status: 400 },
);
}

if (existing) {
return NextResponse.json({ error: 'Already joined this bounty' }, { status: 409 });
}
// Check if already joined
const existing = BountyStore.getMilestoneParticipationsByBounty(
bountyId,
).find((p) => p.contributorId === contributorId);

const participation: MilestoneParticipation = {
id: generateId(),
bountyId,
contributorId,
currentMilestone: 1, // Start at milestone 1
status: 'active',
joinedAt: new Date().toISOString(),
lastUpdatedAt: new Date().toISOString()
};
if (existing) {
return NextResponse.json(
{ error: "Already joined this bounty" },
{ status: 409 },
);
}

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

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

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