190 lines
4.8 KiB
TypeScript
190 lines
4.8 KiB
TypeScript
/**
|
|
* Voxyz API Routes - Mission Proposals
|
|
* Handles proposal CRUD operations
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import {
|
|
getProposalsForDate,
|
|
approveProposal,
|
|
rejectProposal,
|
|
completeProposal,
|
|
getTopProposalsForToday,
|
|
expireOldProposals
|
|
} from '@/lib/services/proposal.service';
|
|
import { createClient } from '@/lib/supabase/server';
|
|
|
|
// GET /api/voxyz/proposals - Get proposals
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const date = searchParams.get('date');
|
|
const today = searchParams.get('today');
|
|
const status = searchParams.get('status');
|
|
|
|
if (today) {
|
|
const proposals = await getTopProposalsForToday();
|
|
return NextResponse.json({ proposals });
|
|
}
|
|
|
|
if (date) {
|
|
const proposals = await getProposalsForDate(new Date(date));
|
|
return NextResponse.json({ proposals });
|
|
}
|
|
|
|
// Get all proposals with optional status filter
|
|
const supabase = await createClient();
|
|
let query = supabase.from('ops_mission_proposals').select('*');
|
|
|
|
if (status) {
|
|
query = query.eq('status', status);
|
|
}
|
|
|
|
const { data: proposals, error } = await query
|
|
.order('created_at', { ascending: false })
|
|
.limit(100);
|
|
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
|
|
return NextResponse.json({ proposals: proposals || [] });
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching proposals:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch proposals' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// POST /api/voxyz/proposals - Create a new proposal (manual)
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json();
|
|
const { title, description, taskIds, projectIds, priorityScore } = body;
|
|
|
|
if (!title) {
|
|
return NextResponse.json(
|
|
{ error: 'Missing required field: title' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const supabase = await createClient();
|
|
|
|
const proposal = {
|
|
proposal_date: new Date().toISOString().split('T')[0],
|
|
status: 'pending',
|
|
priority_score: priorityScore || 50,
|
|
passes_cap_gates: true,
|
|
cap_gate_violations: [],
|
|
source_task_ids: taskIds || [],
|
|
source_project_ids: projectIds || [],
|
|
title,
|
|
description: description || null,
|
|
rationale: 'Manually created proposal',
|
|
ai_analysis: {},
|
|
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
};
|
|
|
|
const { data, error } = await supabase
|
|
.from('ops_mission_proposals')
|
|
.insert(proposal)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
|
|
return NextResponse.json({ proposal: data }, { status: 201 });
|
|
|
|
} catch (error) {
|
|
console.error('Error creating proposal:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to create proposal' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// PATCH /api/voxyz/proposals - Update proposal status
|
|
export async function PATCH(request: NextRequest) {
|
|
try {
|
|
const body = await request.json();
|
|
const { id, action, reason, notes } = body;
|
|
|
|
if (!id || !action) {
|
|
return NextResponse.json(
|
|
{ error: 'Missing required fields: id, action' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
let success = false;
|
|
|
|
switch (action) {
|
|
case 'approve':
|
|
success = await approveProposal(id, 'manual-user'); // TODO: Get actual user ID
|
|
break;
|
|
|
|
case 'reject':
|
|
if (!reason) {
|
|
return NextResponse.json(
|
|
{ error: 'Rejection requires a reason' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
success = await rejectProposal(id, reason);
|
|
break;
|
|
|
|
case 'complete':
|
|
success = await completeProposal(id, notes);
|
|
break;
|
|
|
|
default:
|
|
return NextResponse.json(
|
|
{ error: `Unknown action: ${action}` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (!success) {
|
|
return NextResponse.json(
|
|
{ error: `Failed to ${action} proposal` },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json({ success: true });
|
|
|
|
} catch (error) {
|
|
console.error('Error updating proposal:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to update proposal' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// DELETE /api/voxyz/proposals - Expire old proposals
|
|
export async function DELETE(request: NextRequest) {
|
|
try {
|
|
const expiredCount = await expireOldProposals();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
expiredCount
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error expiring proposals:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to expire proposals' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|