/** * Calendar Routes - Scheduled Tasks API * * RESTful API for multi-project calendar and reminder system * * Copyright 2025 Tractatus Project * Licensed under Apache License 2.0 */ const express = require('express'); const router = express.Router(); const ScheduledTask = require('../models/ScheduledTask.model'); const { authenticateToken } = require('../middleware/auth.middleware'); // Apply authentication to all calendar routes router.use(authenticateToken); /** * GET /api/calendar/tasks - Get all scheduled tasks with filters */ router.get('/tasks', async (req, res) => { try { const { status, category, priority, projectId, includeCompleted, limit = 100, skip = 0 } = req.query; const query = {}; // Filter by status if (status) { query.status = status; } else if (includeCompleted !== 'true') { query.status = { $in: ['pending', 'in_progress', 'overdue'] }; } // Filter by category if (category) { query.category = category; } // Filter by priority if (priority) { query.priority = priority; } // Filter by project if (projectId) { query.projectId = projectId; } const tasks = await ScheduledTask.find(query) .sort({ priority: -1, dueDate: 1 }) .limit(parseInt(limit)) .skip(parseInt(skip)) .populate('projectId', 'name active'); const total = await ScheduledTask.countDocuments(query); res.json({ success: true, tasks, pagination: { total, limit: parseInt(limit), skip: parseInt(skip), hasMore: total > (parseInt(skip) + parseInt(limit)) } }); } catch (error) { console.error('Error fetching scheduled tasks:', error); res.status(500).json({ success: false, error: 'Failed to fetch scheduled tasks' }); } }); /** * GET /api/calendar/tasks/due-soon - Get tasks due within N days (for session-init) */ router.get('/tasks/due-soon', async (req, res) => { try { const { days = 7 } = req.query; const tasks = await ScheduledTask.getDueSoon(parseInt(days)); res.json({ success: true, tasks, count: tasks.length }); } catch (error) { console.error('Error fetching due-soon tasks:', error); res.status(500).json({ success: false, error: 'Failed to fetch due-soon tasks' }); } }); /** * GET /api/calendar/tasks/overdue - Get overdue tasks */ router.get('/tasks/overdue', async (req, res) => { try { const tasks = await ScheduledTask.getOverdue(); res.json({ success: true, tasks, count: tasks.length }); } catch (error) { console.error('Error fetching overdue tasks:', error); res.status(500).json({ success: false, error: 'Failed to fetch overdue tasks' }); } }); /** * GET /api/calendar/tasks/:id - Get single task */ router.get('/tasks/:id', async (req, res) => { try { const task = await ScheduledTask.findById(req.params.id) .populate('projectId', 'name active'); if (!task) { return res.status(404).json({ success: false, error: 'Task not found' }); } res.json({ success: true, task }); } catch (error) { console.error('Error fetching task:', error); res.status(500).json({ success: false, error: 'Failed to fetch task' }); } }); /** * POST /api/calendar/tasks - Create new scheduled task */ router.post('/tasks', async (req, res) => { try { const { title, description, dueDate, priority, category, projectId, projectName, recurrence, documentRef, links, assignedTo, metadata, tags, showInSessionInit, reminderDaysBefore } = req.body; // Validation if (!title || !description || !dueDate) { return res.status(400).json({ success: false, error: 'Title, description, and dueDate are required' }); } const task = new ScheduledTask({ title, description, dueDate: new Date(dueDate), priority: priority || 'MEDIUM', category: category || 'other', projectId: projectId || null, projectName: projectName || null, recurrence: recurrence || 'once', documentRef: documentRef || null, links: links || [], createdBy: 'user', assignedTo: assignedTo || 'PM', metadata: metadata || {}, tags: tags || [], showInSessionInit: showInSessionInit !== false, reminderDaysBefore: reminderDaysBefore || 7 }); await task.save(); res.status(201).json({ success: true, task, message: 'Scheduled task created successfully' }); } catch (error) { console.error('Error creating scheduled task:', error); res.status(500).json({ success: false, error: 'Failed to create scheduled task' }); } }); /** * PUT /api/calendar/tasks/:id - Update scheduled task */ router.put('/tasks/:id', async (req, res) => { try { const task = await ScheduledTask.findById(req.params.id); if (!task) { return res.status(404).json({ success: false, error: 'Task not found' }); } // Update allowed fields const allowedFields = [ 'title', 'description', 'dueDate', 'priority', 'category', 'projectId', 'projectName', 'recurrence', 'status', 'documentRef', 'links', 'assignedTo', 'metadata', 'tags', 'showInSessionInit', 'reminderDaysBefore' ]; allowedFields.forEach(field => { if (req.body[field] !== undefined) { task[field] = req.body[field]; } }); await task.save(); res.json({ success: true, task, message: 'Task updated successfully' }); } catch (error) { console.error('Error updating task:', error); res.status(500).json({ success: false, error: 'Failed to update task' }); } }); /** * POST /api/calendar/tasks/:id/complete - Mark task as completed */ router.post('/tasks/:id/complete', async (req, res) => { try { const task = await ScheduledTask.findById(req.params.id); if (!task) { return res.status(404).json({ success: false, error: 'Task not found' }); } await task.complete(); res.json({ success: true, task, message: 'Task marked as completed' }); } catch (error) { console.error('Error completing task:', error); res.status(500).json({ success: false, error: 'Failed to complete task' }); } }); /** * POST /api/calendar/tasks/:id/dismiss - Dismiss task */ router.post('/tasks/:id/dismiss', async (req, res) => { try { const { reason } = req.body; const task = await ScheduledTask.findById(req.params.id); if (!task) { return res.status(404).json({ success: false, error: 'Task not found' }); } await task.dismiss(reason); res.json({ success: true, task, message: 'Task dismissed' }); } catch (error) { console.error('Error dismissing task:', error); res.status(500).json({ success: false, error: 'Failed to dismiss task' }); } }); /** * DELETE /api/calendar/tasks/:id - Delete scheduled task */ router.delete('/tasks/:id', async (req, res) => { try { const task = await ScheduledTask.findByIdAndDelete(req.params.id); if (!task) { return res.status(404).json({ success: false, error: 'Task not found' }); } res.json({ success: true, message: 'Task deleted successfully' }); } catch (error) { console.error('Error deleting task:', error); res.status(500).json({ success: false, error: 'Failed to delete task' }); } }); /** * GET /api/calendar/stats - Get calendar statistics */ router.get('/stats', async (req, res) => { try { const stats = { total: await ScheduledTask.countDocuments(), pending: await ScheduledTask.countDocuments({ status: 'pending' }), overdue: await ScheduledTask.countDocuments({ status: 'overdue' }), completed: await ScheduledTask.countDocuments({ status: 'completed' }), byCategory: {}, byPriority: {}, dueSoon: await ScheduledTask.countDocuments({ dueDate: { $lte: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) }, status: 'pending' }) }; // Category breakdown const categories = ['framework', 'governance', 'security', 'project', 'research', 'deployment', 'other']; for (const cat of categories) { stats.byCategory[cat] = await ScheduledTask.countDocuments({ category: cat, status: { $in: ['pending', 'in_progress', 'overdue'] } }); } // Priority breakdown const priorities = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']; for (const pri of priorities) { stats.byPriority[pri] = await ScheduledTask.countDocuments({ priority: pri, status: { $in: ['pending', 'in_progress', 'overdue'] } }); } res.json({ success: true, stats }); } catch (error) { console.error('Error fetching calendar stats:', error); res.status(500).json({ success: false, error: 'Failed to fetch calendar statistics' }); } }); module.exports = router;