import { Router, Request, Response } from 'express'; import { asyncHandler } from '../utils/asyncHandler'; import { db } from '../db'; import { subtasks, tasks } from '../db/schema'; import { eq, and, asc, desc, isNull } from 'drizzle-orm'; import { authMiddleware } from '../utils/auth'; import { AppError } from '../middleware/errorHandler'; import { subtaskCreateSchema, subtaskUpdateSchema } from '../utils/validation'; const router = Router(); router.use(authMiddleware); // Helper to build nested subtask tree const buildSubtaskTree = (allSubtasks: any[], parentId: string | null = null): any[] => { return allSubtasks .filter((s) => s.parentSubtaskId === parentId) .sort((a, b) => a.order - b.order) .map((s) => ({ ...s, subtasks: buildSubtaskTree(allSubtasks, s.id), })); }; router.get('/task/:taskId', asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.userId; // Verify task exists and belongs to user const task = await db .select() .from(tasks) .where(and(eq(tasks.id, req.params.taskId), eq(tasks.userId, userId))) .limit(1); if (task.length === 0) { throw new AppError('NOT_FOUND', 'Task not found', 404); } const taskSubtasks = await db .select() .from(subtasks) .where(and(eq(subtasks.taskId, req.params.taskId), eq(subtasks.userId, userId))) .orderBy(asc(subtasks.order)); const nestedSubtasks = buildSubtaskTree(taskSubtasks); res.json({ subtasks: nestedSubtasks }); })); router.post('/task/:taskId', asyncHandler(async (req: Request, res: Response) => { const data = subtaskCreateSchema.parse(req.body); const userId = req.user!.userId; const task = await db .select() .from(tasks) .where(and(eq(tasks.id, req.params.taskId), eq(tasks.userId, userId))) .limit(1); if (task.length === 0) { throw new AppError('NOT_FOUND', 'Task not found', 404); } const now = Date.now(); const parentSubtaskId = data.parentSubtaskId || null; const maxOrder = await db .select({ order: subtasks.order }) .from(subtasks) .where(and( eq(subtasks.taskId, req.params.taskId), eq(subtasks.userId, userId), parentSubtaskId ? eq(subtasks.parentSubtaskId, parentSubtaskId) : isNull(subtasks.parentSubtaskId) )) .orderBy(desc(subtasks.order)) .limit(1); const subtaskId = `sub_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; const newSubtask = { id: subtaskId, userId, taskId: req.params.taskId, parentSubtaskId, categoryId: data.categoryId ?? null, tags: data.tags ?? '', title: data.title, description: data.description ?? '', priority: data.priority ?? 'none', completed: false, dueDate: data.dueDate ?? 0, dueTime: data.dueTime ?? '', endTime: data.endTime ?? '', allDay: data.allDay ?? false, repeat: data.repeat ?? 'none', repeatInterval: data.repeatInterval ?? 1, repeatDays: data.repeatDays ?? '', seriesId: data.seriesId ?? '', reminder: data.reminder ?? 'none', reminders: data.reminders ?? '', assigneeId: data.assigneeId ?? null, order: data.order ?? (maxOrder[0]?.order ?? -1) + 1, createdAt: now, updatedAt: now, }; await db.insert(subtasks).values(newSubtask); // Update task updatedAt await db .update(tasks) .set({ updatedAt: now }) .where(and(eq(tasks.id, req.params.taskId), eq(tasks.userId, userId))); res.status(201).json(newSubtask); })); router.patch('/:id', asyncHandler(async (req: Request, res: Response) => { const data = subtaskUpdateSchema.parse(req.body); const userId = req.user!.userId; const existing: any[] = await db .select() .from(subtasks) .where(and(eq(subtasks.id, req.params.id), eq(subtasks.userId, userId))) .limit(1); if (existing.length === 0) { throw new AppError('NOT_FOUND', 'Subtask not found', 404); } const now = Date.now(); const updated = await db .update(subtasks) .set({ ...data, updatedAt: now }) .where(and(eq(subtasks.id, req.params.id), eq(subtasks.userId, userId))) .returning(); // Update task updatedAt await db .update(tasks) .set({ updatedAt: now }) .where(and(eq(tasks.id, existing[0].taskId), eq(tasks.userId, userId))); res.json(updated[0]); })); router.delete('/:id', asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.userId; const existing = await db .select() .from(subtasks) .where(and(eq(subtasks.id, req.params.id), eq(subtasks.userId, userId))) .limit(1); if (existing.length === 0) { throw new AppError('NOT_FOUND', 'Subtask not found', 404); } const taskId = existing[0].taskId; await db .delete(subtasks) .where(and(eq(subtasks.id, req.params.id), eq(subtasks.userId, userId))); // Update task updatedAt await db .update(tasks) .set({ updatedAt: Date.now() }) .where(and(eq(tasks.id, taskId), eq(tasks.userId, userId))); res.status(204).send(); })); export default router;