Skip to content

Commit fdff566

Browse files
author
Gerome El-assaad
committed
Add tasks API endpoints
1 parent 55bb498 commit fdff566

2 files changed

Lines changed: 287 additions & 0 deletions

File tree

app/api/tasks/[taskId]/route.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { createServerClient } from '@/lib/supabase-server'
3+
4+
interface RouteParams {
5+
params: Promise<{
6+
taskId: string
7+
}>
8+
}
9+
10+
export async function GET(request: NextRequest, { params }: RouteParams) {
11+
try {
12+
const { taskId } = await params
13+
const supabase = createServerClient()
14+
15+
const { data: task, error } = await supabase
16+
.from('tasks')
17+
.select('*')
18+
.eq('id', taskId)
19+
.single()
20+
21+
if (error || !task) {
22+
return NextResponse.json({ error: 'Task not found' }, { status: 404 })
23+
}
24+
25+
// Parse logs from JSON string
26+
const taskWithLogs = {
27+
...task,
28+
logs: JSON.parse(task.logs || '[]')
29+
}
30+
31+
return NextResponse.json({ task: taskWithLogs })
32+
} catch (error) {
33+
console.error('Error fetching task:', error)
34+
return NextResponse.json({ error: 'Failed to fetch task' }, { status: 500 })
35+
}
36+
}
37+
38+
export async function DELETE(request: NextRequest, { params }: RouteParams) {
39+
try {
40+
const { taskId } = await params
41+
const supabase = createServerClient()
42+
43+
// Check if task exists first
44+
const { data: existingTask, error: checkError } = await supabase
45+
.from('tasks')
46+
.select('id')
47+
.eq('id', taskId)
48+
.single()
49+
50+
if (checkError || !existingTask) {
51+
return NextResponse.json({ error: 'Task not found' }, { status: 404 })
52+
}
53+
54+
// Delete the task
55+
const { error: deleteError } = await supabase
56+
.from('tasks')
57+
.delete()
58+
.eq('id', taskId)
59+
60+
if (deleteError) {
61+
throw deleteError
62+
}
63+
64+
return NextResponse.json({ message: 'Task deleted successfully' })
65+
} catch (error) {
66+
console.error('Error deleting task:', error)
67+
return NextResponse.json({ error: 'Failed to delete task' }, { status: 500 })
68+
}
69+
}

app/api/tasks/route.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { createServerClient } from '@/lib/supabase-server'
3+
import {
4+
Task,
5+
CreateTaskData,
6+
createTask,
7+
getAllTasks,
8+
deleteTasksByStatus,
9+
createTaskLogger,
10+
} from '@/lib/tasks'
11+
12+
export const maxDuration = 300 // 5 minutes timeout
13+
export const runtime = 'nodejs'
14+
15+
export async function GET() {
16+
try {
17+
const supabase = createServerClient()
18+
const tasks = await getAllTasks(supabase)
19+
return NextResponse.json({ tasks })
20+
} catch (error) {
21+
console.error('Error fetching tasks:', error)
22+
return NextResponse.json({ error: 'Failed to fetch tasks' }, { status: 500 })
23+
}
24+
}
25+
26+
export async function POST(request: NextRequest) {
27+
try {
28+
const supabase = createServerClient()
29+
const body = await request.json()
30+
31+
// Get user from Supabase auth
32+
const { data: { user } } = await supabase.auth.getUser()
33+
34+
const taskData: CreateTaskData = {
35+
prompt: body.prompt,
36+
repo_url: body.repoUrl,
37+
selected_agent: body.selectedAgent || 'claude',
38+
selected_model: body.selectedModel,
39+
user_id: user?.id,
40+
}
41+
42+
// Validate required fields
43+
if (!taskData.prompt) {
44+
return NextResponse.json({ error: 'Prompt is required' }, { status: 400 })
45+
}
46+
47+
const newTask = await createTask(supabase, taskData)
48+
49+
if (!newTask) {
50+
return NextResponse.json({ error: 'Failed to create task' }, { status: 500 })
51+
}
52+
53+
// Process the task asynchronously with timeout
54+
processTaskWithTimeout(newTask.id, taskData)
55+
56+
return NextResponse.json({ task: newTask })
57+
} catch (error) {
58+
console.error('Error creating task:', error)
59+
return NextResponse.json({ error: 'Failed to create task' }, { status: 500 })
60+
}
61+
}
62+
63+
async function processTaskWithTimeout(taskId: string, taskData: CreateTaskData) {
64+
const TASK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes in milliseconds
65+
66+
// Add a warning at 4 minutes
67+
const warningTimeout = setTimeout(async () => {
68+
try {
69+
const supabase = createServerClient()
70+
const logger = createTaskLogger(supabase, taskId)
71+
await logger.info('Task is taking longer than expected (4+ minutes). Will timeout in 1 minute.')
72+
} catch (error) {
73+
console.error('Failed to add timeout warning:', error)
74+
}
75+
}, 4 * 60 * 1000) // 4 minutes
76+
77+
const timeoutPromise = new Promise<never>((_, reject) => {
78+
setTimeout(() => {
79+
reject(new Error('Task execution timed out after 5 minutes'))
80+
}, TASK_TIMEOUT_MS)
81+
})
82+
83+
try {
84+
await Promise.race([
85+
processTask(taskId, taskData),
86+
timeoutPromise
87+
])
88+
89+
// Clear the warning timeout if task completes successfully
90+
clearTimeout(warningTimeout)
91+
} catch (error: any) {
92+
// Clear the warning timeout on any error
93+
clearTimeout(warningTimeout)
94+
95+
// Handle timeout specifically
96+
if (error.message?.includes('timed out after 5 minutes')) {
97+
console.error('Task timed out:', taskId)
98+
99+
const supabase = createServerClient()
100+
const logger = createTaskLogger(supabase, taskId)
101+
await logger.error('Task execution timed out after 5 minutes')
102+
await logger.updateStatus('error', 'Task execution timed out after 5 minutes. The operation took too long to complete.')
103+
} else {
104+
// Re-throw other errors to be handled by the original error handler
105+
throw error
106+
}
107+
}
108+
}
109+
110+
async function processTask(taskId: string, taskData: CreateTaskData) {
111+
const supabase = createServerClient()
112+
const logger = createTaskLogger(supabase, taskId)
113+
114+
try {
115+
// Update task status to processing with real-time logging
116+
await logger.updateStatus('processing', 'Task created, preparing to start...')
117+
await logger.updateProgress(10, 'Initializing task execution...')
118+
119+
// Simulate task processing (replace with actual implementation)
120+
await logger.updateProgress(25, 'Setting up environment...')
121+
122+
// Simulate some work
123+
await new Promise(resolve => setTimeout(resolve, 2000))
124+
125+
await logger.updateProgress(50, 'Processing request...')
126+
127+
// For now, we'll simulate the task completion
128+
// In a real implementation, you would:
129+
// 1. Create sandbox environment
130+
// 2. Execute the selected agent
131+
// 3. Process the results
132+
// 4. Handle git operations
133+
134+
await logger.success('Task processing completed successfully')
135+
136+
// Simulate more work
137+
await new Promise(resolve => setTimeout(resolve, 1000))
138+
139+
await logger.updateProgress(75, 'Finalizing results...')
140+
141+
// Simulate final steps
142+
await new Promise(resolve => setTimeout(resolve, 1000))
143+
144+
await logger.updateProgress(100, 'Task completed successfully')
145+
await logger.updateStatus('completed')
146+
147+
await logger.success(`Task completed: ${taskData.prompt}`)
148+
149+
} catch (error) {
150+
console.error('Error processing task:', error)
151+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
152+
153+
await logger.error(`Error: ${errorMessage}`)
154+
await logger.updateStatus('error', errorMessage)
155+
}
156+
}
157+
158+
export async function DELETE(request: NextRequest) {
159+
try {
160+
const supabase = createServerClient()
161+
const url = new URL(request.url)
162+
const action = url.searchParams.get('action')
163+
164+
if (!action) {
165+
return NextResponse.json({ error: 'Action parameter is required' }, { status: 400 })
166+
}
167+
168+
const actions = action.split(',').map((a) => a.trim())
169+
const validActions = ['completed', 'failed']
170+
const invalidActions = actions.filter((a) => !validActions.includes(a))
171+
172+
if (invalidActions.length > 0) {
173+
return NextResponse.json(
174+
{
175+
error: `Invalid action(s): ${invalidActions.join(', ')}. Valid actions: ${validActions.join(', ')}`,
176+
},
177+
{ status: 400 }
178+
)
179+
}
180+
181+
// Map actions to task statuses
182+
const statusesToDelete: Task['status'][] = []
183+
if (actions.includes('completed')) {
184+
statusesToDelete.push('completed')
185+
}
186+
if (actions.includes('failed')) {
187+
statusesToDelete.push('error')
188+
}
189+
190+
if (statusesToDelete.length === 0) {
191+
return NextResponse.json({ error: 'No valid actions specified' }, { status: 400 })
192+
}
193+
194+
// Delete tasks based on statuses
195+
const deletedCount = await deleteTasksByStatus(supabase, statusesToDelete)
196+
197+
// Build response message
198+
const actionMessages = []
199+
if (actions.includes('completed')) {
200+
actionMessages.push('completed')
201+
}
202+
if (actions.includes('failed')) {
203+
actionMessages.push('failed')
204+
}
205+
206+
const message = deletedCount > 0
207+
? `${deletedCount} ${actionMessages.join(' and ')} task(s) deleted successfully`
208+
: 'No tasks found to delete'
209+
210+
return NextResponse.json({
211+
message,
212+
deletedCount,
213+
})
214+
} catch (error) {
215+
console.error('Error deleting tasks:', error)
216+
return NextResponse.json({ error: 'Failed to delete tasks' }, { status: 500 })
217+
}
218+
}

0 commit comments

Comments
 (0)