tududi/backend/modules/tasks/events.js
Chris 542be2c1e9
Fix bug 366 (#764)
* Optimize DB

* Clean up names

* fixup! Clean up names

* fixup! fixup! Clean up names
2026-01-07 18:18:07 +02:00

229 lines
7.4 KiB
JavaScript

const express = require('express');
const { Task, TaskEvent } = require('../../models');
const { isValidUid } = require('../../utils/slug-utils');
const {
getTaskTimeline,
getTaskCompletionTime,
getUserProductivityMetrics,
getTaskActivitySummary,
} = require('./taskEventService');
const { logError } = require('../../services/logService');
const router = express.Router();
// GET /api/task/:uid/timeline - Get task event timeline
router.get('/task/:uid/timeline', async (req, res) => {
try {
if (!isValidUid(req.params.uid))
return res.status(400).json({ error: 'Invalid UID' });
const permissionsService = require('../../services/permissionsService');
// Check if user has access to the task (either owns it or has access through shared project)
const access = await permissionsService.getAccess(
req.currentUser.id,
'task',
req.params.uid
);
if (access === 'none') {
return res.status(404).json({ error: 'Task not found' });
}
const task = await Task.findOne({
where: { uid: req.params.uid },
});
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
const timeline = await getTaskTimeline(task.id);
res.json(timeline);
} catch (error) {
logError('Error fetching task timeline:', error);
res.status(500).json({ error: 'Failed to fetch task timeline' });
}
});
// GET /api/task/:uid/completion-time - Get task completion analytics
router.get('/task/:uid/completion-time', async (req, res) => {
try {
if (!isValidUid(req.params.uid))
return res.status(400).json({ error: 'Invalid UID' });
const permissionsService = require('../../services/permissionsService');
// Check if user has access to the task (either owns it or has access through shared project)
const access = await permissionsService.getAccess(
req.currentUser.id,
'task',
req.params.uid
);
if (access === 'none') {
return res.status(404).json({ error: 'Task not found' });
}
const task = await Task.findOne({
where: { uid: req.params.uid },
});
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
const completionTime = await getTaskCompletionTime(task.id);
if (!completionTime) {
return res
.status(404)
.json({ error: 'Task completion data not found' });
}
res.json(completionTime);
} catch (error) {
logError('Error fetching task completion time:', error);
res.status(500).json({ error: 'Failed to fetch task completion time' });
}
});
// GET /api/user/productivity-metrics - Get user productivity metrics
router.get('/user/productivity-metrics', async (req, res) => {
try {
const { startDate, endDate } = req.query;
const metrics = await getUserProductivityMetrics(
req.currentUser.id,
startDate ? new Date(startDate) : null,
endDate ? new Date(endDate) : null
);
res.json(metrics);
} catch (error) {
logError('Error fetching productivity metrics:', error);
res.status(500).json({ error: 'Failed to fetch productivity metrics' });
}
});
// GET /api/user/activity-summary - Get task activity summary
router.get('/user/activity-summary', async (req, res) => {
try {
const { startDate, endDate } = req.query;
if (!startDate || !endDate) {
return res
.status(400)
.json({ error: 'startDate and endDate are required' });
}
const activitySummary = await getTaskActivitySummary(
req.currentUser.id,
new Date(startDate),
new Date(endDate)
);
res.json(activitySummary);
} catch (error) {
logError('Error fetching activity summary:', error);
res.status(500).json({ error: 'Failed to fetch activity summary' });
}
});
// GET /api/tasks/completion-analytics - Get completion time analytics for multiple tasks
router.get('/tasks/completion-analytics', async (req, res) => {
try {
const { limit = 50, offset = 0, projectUid } = req.query;
// Get completed tasks for the user
const { Task, Project } = require('../../models');
const { Op } = require('sequelize');
const whereClause = {
user_id: req.currentUser.id,
status: 2, // completed
};
// If projectUid is provided, find the project and filter by its ID
if (projectUid) {
if (!isValidUid(projectUid)) {
return res.status(400).json({ error: 'Invalid project UID' });
}
const project = await Project.findOne({
where: { uid: projectUid, user_id: req.currentUser.id },
});
if (!project) {
return res.status(404).json({ error: 'Project not found' });
}
whereClause.project_id = project.id;
}
const completedTasks = await Task.findAll({
where: whereClause,
include: [
{ model: Project, attributes: ['name'], required: false },
],
order: [['completed_at', 'DESC']],
limit: parseInt(limit),
offset: parseInt(offset),
});
// Get completion time analytics for each task
const analytics = [];
for (const task of completedTasks) {
const completionTime = await getTaskCompletionTime(task.id);
if (completionTime) {
analytics.push({
task_id: task.id,
task_name: task.name,
project_name: task.Project?.name || null,
...completionTime,
});
}
}
// Calculate summary statistics
const summary = {
total_tasks: analytics.length,
average_completion_hours:
analytics.length > 0
? analytics.reduce((sum, a) => sum + a.duration_hours, 0) /
analytics.length
: 0,
median_completion_hours: 0,
fastest_completion:
analytics.length > 0
? Math.min(...analytics.map((a) => a.duration_hours))
: 0,
slowest_completion:
analytics.length > 0
? Math.max(...analytics.map((a) => a.duration_hours))
: 0,
};
// Calculate median
if (analytics.length > 0) {
const sorted = analytics
.map((a) => a.duration_hours)
.sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
summary.median_completion_hours =
sorted.length % 2 === 0
? (sorted[middle - 1] + sorted[middle]) / 2
: sorted[middle];
}
res.json({
tasks: analytics,
summary,
});
} catch (error) {
logError('Error fetching completion analytics:', error);
res.status(500).json({ error: 'Failed to fetch completion analytics' });
}
});
module.exports = router;