Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ app.use(
'http://localhost:4173',
'https://www.dayframe.ru',
'https://dayframe.ru',
'https://dayframe.na4u.ru',
],
methods: 'GET,POST,PATCH,DELETE',
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'],
Expand Down
37 changes: 22 additions & 15 deletions src/entities/task/models/subtask.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,20 @@ function getSubtasksAllByUserId(user_id) {
return db.query(query, [user_id]);
}

function getAllSubtasksByUserId(user_id) {
return db
.query('SELECT * FROM subtasks WHERE user_id = ?', [user_id])
.then(([subtasks]) => [subtasks])
.catch((error) => {
console.error('Ошибка при получении подзадач:', error);
throw error;
});
}

function getAllSubtasksByParentTaskId(parent_task_id) {
return db.query('SELECT * FROM subtasks WHERE parent_task_id = ?', [parent_task_id]);
return db.query('SELECT * FROM subtasks WHERE parent_task_id = ?', [
parent_task_id,
]);
}

function addSubtask(user_id, subtask) {
const { parent_task_id, title, is_done = false, position = 0, special_id } = subtask;
const {
parent_task_id,
title,
is_done = false,
position = 0,
special_id,
} = subtask;

if (!special_id) {
throw new Error('special_id is required for subtasks');
Expand All @@ -30,7 +28,14 @@ function addSubtask(user_id, subtask) {
INSERT INTO subtasks (parent_task_id, title, is_done, position, special_id, user_id, created_at)
VALUES (?, ?, ?, ?, ?, ?, NOW())
`;
return db.query(query, [parent_task_id, title, is_done, position, special_id, user_id]);
return db.query(query, [
parent_task_id,
title,
is_done,
position,
special_id,
user_id,
]);
}

function getSubtaskById(id) {
Expand All @@ -42,7 +47,10 @@ function deleteSubtaskById(id) {
}

function updateSubtaskStatus(id, is_done) {
return db.query('UPDATE subtasks SET is_done = ? WHERE id = ?', [is_done, id]);
return db.query('UPDATE subtasks SET is_done = ? WHERE id = ?', [
is_done,
id,
]);
}

function updateSubtasksStatusByParentTaskId(parent_task_id, is_done) {
Expand Down Expand Up @@ -80,7 +88,6 @@ function countCompletedSubtaskByParentTaskId(parent_task_id) {

module.exports = {
getSubtasksAllByUserId,
getAllSubtasksByUserId,
getAllSubtasksByParentTaskId,
addSubtask,
getSubtaskById,
Expand Down
66 changes: 54 additions & 12 deletions src/entities/task/models/task.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const db = require('../../../config/db');

/**
* Get all tasks for a specific user
* @deprecated Use getAllTasksWithSubtasksByUser for better performance when subtasks are needed
* @param {number} user_id - User ID
*/
function getAllTasksByUser(user_id) {
Expand Down Expand Up @@ -67,7 +68,7 @@ function addTask(task) {
special_id,
is_done,
task_date,
]
],
);
}

Expand Down Expand Up @@ -98,7 +99,16 @@ function setTaskDate(task_date, id) {
* @param {Object} task - Task object with updated fields
*/
function updateTaskById(id, task) {
const { title, description, category, priority, start_time, end_time, task_date, is_done } = task;
const {
title,
description,
category,
priority,
start_time,
end_time,
task_date,
is_done,
} = task;

return db.query(
`UPDATE tasks SET
Expand All @@ -111,7 +121,17 @@ function updateTaskById(id, task) {
task_date = ?,
is_done = ?
WHERE id = ?`,
[title, description, category, priority, start_time, end_time, task_date, is_done, id]
[
title,
description,
category,
priority,
start_time,
end_time,
task_date,
is_done,
id,
],
);
}

Expand All @@ -123,20 +143,42 @@ function getTasksForDate(user_id, task_date) {
}

/**
* Get tasks for a specific date period
* Get all tasks with their subtasks for a user using SQL JOIN (optimized)
* @param {number} user_id - User ID
* @param {string} startDate - Start date in YYYY-MM-DD format
* @param {string} endDate - End date in YYYY-MM-DD format
* @returns {Promise} Promise resolving to tasks with subtasks in a flat structure
*/
function getTasksForPeriod(user_id, startDate, endDate) {
function getAllTasksWithSubtasksByUser(user_id) {
return db
.query(
'SELECT * FROM tasks WHERE user_id = ? AND (task_date BETWEEN ? AND ? OR task_date IS NULL)',
[user_id, startDate, endDate]
`SELECT
t.id,
t.title,
t.description,
t.category,
t.priority,
t.exp,
t.start_time,
t.end_time,
t.task_date,
t.user_id,
t.special_id,
t.is_done,
t.created_at,
s.id as subtask_id,
s.title as subtask_title,
s.is_done as subtask_is_done,
s.position as subtask_position,
s.special_id as subtask_special_id,
s.created_at as subtask_created_at
FROM tasks t
LEFT JOIN subtasks s ON t.id = s.parent_task_id
WHERE t.user_id = ?
ORDER BY t.id, s.position`,
[user_id]
)
.then(([tasks]) => [tasks])
.then(([rows]) => [rows])
.catch((error) => {
console.error('Ошибка при получении задач за период:', error);
console.error('Ошибка при получении задач с подзадачами:', error);
throw error;
});
}
Expand All @@ -150,5 +192,5 @@ module.exports = {
setTaskDate,
updateTaskById,
getTasksForDate,
getTasksForPeriod,
getAllTasksWithSubtasksByUser,
};
66 changes: 10 additions & 56 deletions src/entities/task/task.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const taskService = require('./task.service');
class TaskController {
/**
* Get all tasks with their subtasks for a user
* @deprecated Use getAllTasksOptimized instead. This method uses inefficient N+1 queries.
*/
getTasksWithSubTasks(req, res) {
const userId = Number(req.user.id);
Expand All @@ -19,44 +18,13 @@ class TaskController {
});
}

/**
* Optimized version: Get all tasks with subtasks using optimized query
* Includes timeout handling to prevent 502 errors
*/
getAllTasksOptimized(req, res) {
const userId = Number(req.user.id);
if (!userId) {
return res.status(400).json({ error: 'Не передан user-id в заголовке' });
}

// Add timeout of 25 seconds (less than 30 seconds at Express level)
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 25000)
);

Promise.race([taskService.getAllTasksWithSubTasksOptimized(userId), timeoutPromise])
.then((result) => {
if (res.headersSent) return; // Check if response already sent
res.status(result.status).json(result.data);
})
.catch((err) => {
if (res.headersSent) return; // Check if response already sent
console.error('❌ Ошибка при получении задач:', err);
res.status(500).json({
error:
err.message === 'Request timeout'
? 'Превышено время ожидания ответа от сервера'
: 'Ошибка при получении задач',
});
});
}

/**
* Create a new task
*/
createTask(req, res) {
const userId = Number(req.user.id);
if (!userId) return res.status(400).json({ error: 'Не передан user-id в заголовке' });
if (!userId)
return res.status(400).json({ error: 'Не передан user-id в заголовке' });

taskService.createTask(userId, req.body).then((result) => {
res.status(result.status).json(result.data);
Expand Down Expand Up @@ -113,38 +81,24 @@ class TaskController {

// Check input data
if (typeof is_done !== 'boolean') {
return res.status(400).json({ error: 'Поле is_done должно быть boolean (true/false)' });
return res
.status(400)
.json({ error: 'Поле is_done должно быть boolean (true/false)' });
}

try {
// Update subtask status
const subtaskResult = await taskService.updateSubtaskStatus(subtaskId, is_done, userId);
const subtaskResult = await taskService.updateSubtaskStatus(
subtaskId,
is_done,
userId,
);
res.status(subtaskResult.status).json(subtaskResult.data);
} catch (err) {
console.error('❌ Ошибка при обновлении подзадачи:', err);
res.status(500).json({ error: 'Ошибка при обновлении подзадачи' });
}
}

/**
* Get tasks for a specific date period
*/
getTasksForPeriod(req, res) {
const userId = Number(req.user.id);
const { startDate, endDate } = req.query;

if (!userId) {
return res.status(400).json({ error: 'Не передан user-id в заголовке' });
}

if (!startDate || !endDate) {
return res.status(400).json({ error: 'Не переданы параметры startDate и endDate' });
}

taskService.getTasksForPeriod(userId, startDate, endDate).then((result) => {
res.status(result.status).json(result.data);
});
}
}

module.exports = new TaskController();
23 changes: 6 additions & 17 deletions src/entities/task/task.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,22 @@ const authorizeTask = require('../../middleware/authorizeTask');
const authorizeSubTask = require('../../middleware/authorizeSubTask');
const authenticate = require('../../middleware/authenticate');

// Получить все задачи по id пользователя (оптимизированная версия)
router.get('/', authenticate, (req, res) => taskController.getAllTasksOptimized(req, res));

// Получить задачи за период
router.get('/period', authenticate, (req, res) => taskController.getTasksForPeriod(req, res));
// Получить все задачи по id пользователя
router.get('/', authenticate, (req, res) => taskController.getTasksWithSubTasks(req, res));

// Добавить новую задачу
router.post('/', authenticate, (req, res) => taskController.createTask(req, res));

// Удалить задачу
router.delete('/:id', authenticate, authorizeTask, (req, res) =>
taskController.deleteTask(req, res)
);
router.delete('/:id', authenticate, authorizeTask, (req, res) => taskController.deleteTask(req, res));

// Отметить задачу как выполненную
router.patch('/is_done/:id', authenticate, authorizeTask, (req, res) =>
taskController.updateTaskStatus(req, res)
);
router.patch('/is_done/:id', authenticate, authorizeTask, (req, res) => taskController.updateTaskStatus(req, res));

// 🔄 Обновить задачу
router.patch('/:id', authenticate, authorizeTask, (req, res) =>
taskController.updateTask(req, res)
);
router.patch('/:id', authenticate, authorizeTask, (req, res) => taskController.updateTask(req, res));

// Отметить подзадачу как выполненную
router.patch('/subtasks/:id', authenticate, authorizeSubTask, (req, res) =>
taskController.updateSubtaskStatus(req, res)
);
router.patch('/subtasks/:id', authenticate, authorizeSubTask, (req, res) => taskController.updateSubtaskStatus(req, res));

module.exports = router;
Loading
Loading