- Fixed unused function parameters by prefixing with underscore - Removed unused imports and variables - Applied eslint --fix for automatic style fixes - Property shorthand - String template literals - Prefer const over let where appropriate - Spacing and formatting Reduces lint errors from 108+ to 78 (61 unused vars, 17 other issues) Related to CI lint failures in previous commit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
414 lines
9.2 KiB
JavaScript
414 lines
9.2 KiB
JavaScript
/**
|
|
* 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;
|