Add src/app/api/tasks/route.ts

This commit is contained in:
2026-05-02 18:03:37 -04:00
parent ab2c916fa5
commit 55a1a356b8

150
src/app/api/tasks/route.ts Normal file
View File

@@ -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 });
}
}