diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts new file mode 100644 index 0000000..8100b1e --- /dev/null +++ b/src/app/api/tasks/route.ts @@ -0,0 +1,150 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDb } from '@/server/db'; +import { tasks, priorityEnum, statusEnum, recurrenceEnum, type NewTask, type Task } from '@/server/db/schema'; +import { eq, and, or, gte, lte, asc, desc, like, ilike, sql } from 'drizzle-orm'; +import { addDays, addWeeks, addMonths, addYears } from 'date-fns'; + +export async function GET(request: NextRequest) { + const db = getDb(); + const { searchParams } = new URL(request.url); + + const project = searchParams.get('project'); + const search = searchParams.get('search'); + const status = searchParams.get('status'); + const priority = searchParams.get('priority'); + const dueBefore = searchParams.get('due_before'); + const dueAfter = searchParams.get('due_after'); + const completed = searchParams.get('completed'); + const sortBy = searchParams.get('sort_by') || 'due_date'; + const sortOrder = searchParams.get('sort_order') || 'asc'; + + try { + let query = db.select().from(tasks); + + // Build WHERE conditions + const conditions = []; + + if (project) { + conditions.push(eq(tasks.projectId, project)); + } + + if (status) { + conditions.push(eq(tasks.status, status as any)); + } + + if (priority) { + conditions.push(eq(tasks.priority, priority as any)); + } + + if (completed !== null && completed !== undefined) { + conditions.push(eq(tasks.completed, completed === 'true')); + } + + if (dueBefore) { + conditions.push(lte(tasks.dueDate, new Date(dueBefore))); + } + + if (dueAfter) { + conditions.push(gte(tasks.dueDate, new Date(dueAfter))); + } + + if (search) { + conditions.push( + or( + ilike(tasks.title, `%${search}%`), + ilike(tasks.description, `%${search}%`) + ) + ); + } + + if (conditions.length > 0) { + query = query.where(and(...conditions)); + } + + // Sort + let sortColumn: any; + switch (sortBy) { + case 'priority': + sortColumn = sql`CASE ${tasks.priority} WHEN 'urgent' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 ELSE 5 END`; + break; + case 'due_date': + default: + sortColumn = tasks.dueDate; + break; + } + + query = query.orderBy(sortOrder === 'desc' ? desc(sortColumn) : asc(sortColumn)); + + const result = await query; + + return NextResponse.json(result); + } catch (error) { + console.error('Error fetching tasks:', error); + return NextResponse.json({ error: 'Failed to fetch tasks' }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + const db = getDb(); + let body; + + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }); + } + + const { + title, + description = '', + projectId, + priority = 'medium', + dueDate, + status = 'todo', + parentTaskId, + recurrenceRule = 'none', + recurrenceInterval = 1, + nextOccurrence, + } = body as NewTask & { dueDate?: string | null }; + + if (!title?.trim()) { + return NextResponse.json({ error: 'Title is required' }, { status: 400 }); + } + + try { + const now = new Date(); + + // Get max sort order for this project + const maxSort = await db + .select({ max: tasks.sortOrder }) + .from(tasks) + .where(eq(tasks.projectId, projectId)) + .limit(1); + + const sortOrder = (maxSort[0]?.max ?? -1) + 1; + + const [newTask] = await db + .insert(tasks) + .values({ + title: title.trim(), + description: description?.trim() || '', + projectId, + priority, + dueDate: dueDate ? new Date(dueDate) : null, + status, + parentTaskId, + recurrenceRule, + recurrenceInterval, + nextOccurrence: nextOccurrence ? new Date(nextOccurrence) : null, + sortOrder, + createdAt: now, + updatedAt: now, + }) + .returning(); + + return NextResponse.json(newTask, { status: 201 }); + } catch (error) { + console.error('Error creating task:', error); + return NextResponse.json({ error: 'Failed to create task' }, { status: 500 }); + } +}