From b53e3c010498f33b4477b6c5cc4c0d5a47008c5e Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:43:44 +0300 Subject: [PATCH 1/2] Revert "feat: getTasksForPeriod" This reverts commit 1c959112a2247b684c1ca88698135a9cb2881554. --- src/app.js | 1 - src/entities/task/models/subtask.model.js | 37 ++++--- src/entities/task/models/task.model.js | 50 ++++----- src/entities/task/task.controller.js | 66 ++--------- src/entities/task/task.routes.js | 23 +--- src/entities/task/task.service.js | 104 ++++++------------ .../template.task/template.task.controller.js | 50 ++------- .../template.task/template.task.service.js | 49 ++++----- 8 files changed, 129 insertions(+), 251 deletions(-) diff --git a/src/app.js b/src/app.js index c174b3f..758b864 100644 --- a/src/app.js +++ b/src/app.js @@ -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'], diff --git a/src/entities/task/models/subtask.model.js b/src/entities/task/models/subtask.model.js index 591ea7c..a5ebe35 100644 --- a/src/entities/task/models/subtask.model.js +++ b/src/entities/task/models/subtask.model.js @@ -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'); @@ -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) { @@ -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) { @@ -80,7 +88,6 @@ function countCompletedSubtaskByParentTaskId(parent_task_id) { module.exports = { getSubtasksAllByUserId, - getAllSubtasksByUserId, getAllSubtasksByParentTaskId, addSubtask, getSubtaskById, diff --git a/src/entities/task/models/task.model.js b/src/entities/task/models/task.model.js index 47ab509..6bc5a2a 100644 --- a/src/entities/task/models/task.model.js +++ b/src/entities/task/models/task.model.js @@ -67,7 +67,7 @@ function addTask(task) { special_id, is_done, task_date, - ] + ], ); } @@ -98,7 +98,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 @@ -111,34 +120,22 @@ 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, + ], ); } function getTasksForDate(user_id, task_date) { - return db.query('SELECT * FROM tasks WHERE user_id = ? AND task_date = ? OR task_date IS NULL', [ - user_id, - task_date, - ]); -} - -/** - * Get tasks for a specific date period - * @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 - */ -function getTasksForPeriod(user_id, startDate, endDate) { - return db - .query( - 'SELECT * FROM tasks WHERE user_id = ? AND (task_date BETWEEN ? AND ? OR task_date IS NULL)', - [user_id, startDate, endDate] - ) - .then(([tasks]) => [tasks]) - .catch((error) => { - console.error('Ошибка при получении задач за период:', error); - throw error; - }); + return db.query('SELECT * FROM tasks WHERE user_id = ? AND task_date = ? OR task_date IS NULL', [user_id, task_date]); } module.exports = { @@ -150,5 +147,4 @@ module.exports = { setTaskDate, updateTaskById, getTasksForDate, - getTasksForPeriod, }; diff --git a/src/entities/task/task.controller.js b/src/entities/task/task.controller.js index 018bac0..154ddc3 100644 --- a/src/entities/task/task.controller.js +++ b/src/entities/task/task.controller.js @@ -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); @@ -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); @@ -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(); diff --git a/src/entities/task/task.routes.js b/src/entities/task/task.routes.js index f44e1e8..7192353 100644 --- a/src/entities/task/task.routes.js +++ b/src/entities/task/task.routes.js @@ -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; diff --git a/src/entities/task/task.service.js b/src/entities/task/task.service.js index 0e2d1f6..8688ad2 100644 --- a/src/entities/task/task.service.js +++ b/src/entities/task/task.service.js @@ -21,7 +21,9 @@ class TaskService { data: { error: 'Не удалось получить задачу' }, }; - const [subtasks] = await subtaskModel.getAllSubtasksByParentTaskId(taskId); + const [subtasks] = await subtaskModel.getAllSubtasksByParentTaskId( + taskId, + ); return { ...task, subtasks: subtasks || [], @@ -36,7 +38,6 @@ class TaskService { /** * Get all tasks with subtasks for a user - * @deprecated Use getAllTasksWithSubTasksOptimized instead. This method uses inefficient N+1 queries (1 + 2N queries). */ async getTasksWithSubTasks(userId) { try { @@ -66,47 +67,6 @@ class TaskService { } } - /** - * Optimized version: Get all tasks with subtasks using only 2 SQL queries - * Instead of 1 + 2N queries (where N is number of tasks) - */ - async getAllTasksWithSubTasksOptimized(userId) { - try { - // Get all tasks and all subtasks in parallel (2 queries total) - const [[tasks], [subtasks]] = await Promise.all([ - taskModel.getAllTasksByUser(userId), - subtaskModel.getAllSubtasksByUserId(userId), - ]); - - if (!tasks.length) { - return { status: 404, data: { error: 'Задачи не найдены' } }; - } - - // Group subtasks by parent_task_id for O(1) lookup - const subtasksByTaskId = new Map(); - if (subtasks && subtasks.length > 0) { - for (const subtask of subtasks) { - const taskId = subtask.parent_task_id; - if (!subtasksByTaskId.has(taskId)) { - subtasksByTaskId.set(taskId, []); - } - subtasksByTaskId.get(taskId).push(subtask); - } - } - - // Combine tasks with their subtasks - const fullTasks = tasks.map((task) => ({ - ...task, - subtasks: subtasksByTaskId.get(task.id) || [], - })); - - return { status: 200, data: fullTasks }; - } catch (err) { - console.error('❌ Ошибка при получении задач с подзадачами (оптимизированная версия):', err); - return { status: 500, data: { error: err.message } }; - } - } - /** * Create a new task */ @@ -127,7 +87,7 @@ class TaskService { subtaskModel.addSubtask(userId, { ...sub, parent_task_id: taskId, - }) + }), ); } } @@ -191,7 +151,7 @@ class TaskService { subtaskModel.addSubtask(updatedTask.user_id, { ...sub, parent_task_id: taskId, - }) + }), ); // удаление подзадачи @@ -200,7 +160,9 @@ class TaskService { // обновление существующей подзадачи } else if (sub.id) { - promises.push(subtaskModel.updateSubtask(sub.id, sub.title, sub.position)); + promises.push( + subtaskModel.updateSubtask(sub.id, sub.title, sub.position), + ); } } @@ -209,7 +171,11 @@ class TaskService { // 4) Если добавили хоть одну новую — переоткрываем задачу if (needReopen) { - const result = await this.updateTaskStatus(taskId, false, updatedTask.user_id); + const result = await this.updateTaskStatus( + taskId, + false, + updatedTask.user_id, + ); return { status: 200, @@ -252,15 +218,23 @@ class TaskService { if (is_done) { // задача выполненна, начисляем опыт - fullTask.exp > 0 && (await userService.updateUserExp(userId, fullTask.exp)); + fullTask.exp > 0 && + (await userService.updateUserExp(userId, fullTask.exp)); // выполняем все подзадачи fullTask.subtasks && - (await subtaskModel.updateSubtasksStatusByParentTaskId(fullTask.id, is_done)); + (await subtaskModel.updateSubtasksStatusByParentTaskId( + fullTask.id, + is_done, + )); } else { // задача не выполненна, снимаем опыт - fullTask.exp > 0 && (await userService.updateUserExp(userId, -fullTask.exp)); + fullTask.exp > 0 && + (await userService.updateUserExp(userId, -fullTask.exp)); fullTask.subtasks && - (await subtaskModel.updateSubtasksStatusByParentTaskId(fullTask.id, is_done)); + (await subtaskModel.updateSubtasksStatusByParentTaskId( + fullTask.id, + is_done, + )); } // if (fullTask.exp !== 0) { // // Calculate XP delta based on updated status @@ -361,7 +335,11 @@ class TaskService { */ async updateSubtask(subtaskId, title, position, parent_task_id) { try { - const [result] = await subtaskModel.updateSubtask(subtaskId, title, position); + const [result] = await subtaskModel.updateSubtask( + subtaskId, + title, + position, + ); if (result.affectedRows === 0) { return { status: 404, data: { error: 'Подзадача не найдена' } }; } @@ -384,23 +362,6 @@ class TaskService { } } - /** - * Get tasks for a specific date period - * @param {string} userId - * @param {string} startDate - Start date in YYYY-MM-DD format - * @param {string} endDate - End date in YYYY-MM-DD format - * @param {string} timeZone - IANA-имя зоны, например "Europe/Moscow" - */ - async getTasksForPeriod(userId, startDate, endDate, timeZone = 'Europe/Moscow') { - try { - const [tasks] = await taskModel.getTasksForPeriod(userId, startDate, endDate); - return { status: 200, data: tasks }; - } catch (err) { - console.error('❌ Ошибка при получении задач за период:', err); - return { status: 500, data: { error: err.message } }; - } - } - /** * @param {string} userId * @param {string} timeZone — IANA-имя зоны, например "Europe/Moscow" @@ -414,7 +375,10 @@ class TaskService { const local = new Date(new Date().toLocaleString('en-US', { timeZone })); const jsDay = local.getDay(); const dayOfWeek = jsDay === 0 ? 7 : jsDay; - const [templates] = await templateModel.getTemplatesForDay(userId, dayOfWeek); + const [templates] = await templateModel.getTemplatesForDay( + userId, + dayOfWeek, + ); return { status: 200, data: { tasks, templates, dateString, dayOfWeek } }; } catch (err) { diff --git a/src/entities/template.task/template.task.controller.js b/src/entities/template.task/template.task.controller.js index 2dea2c0..1695285 100644 --- a/src/entities/template.task/template.task.controller.js +++ b/src/entities/template.task/template.task.controller.js @@ -7,55 +7,27 @@ class TemplateTaskController { return res.status(400).json({ error: 'Не передан user-id в заголовке' }); } - // Добавляем общий таймаут 25 секунд (меньше чем 30 секунд на уровне Express) - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Request timeout')), 25000) - ); - - Promise.race([templateTaskService.getTemplateTasksWithSubTasks(userId), timeoutPromise]) - .then((result) => { - if (res.headersSent) return; // Проверяем, не отправлен ли уже ответ - res.status(result.status).json(result.data); - }) - .catch((err) => { - if (res.headersSent) return; // Проверяем, не отправлен ли уже ответ - console.error('❌ Ошибка при получении шаблонов задач:', err); - res.status(500).json({ - error: - err.message === 'Request timeout' - ? 'Превышено время ожидания ответа от сервера' - : 'Ошибка при получении шаблонов задач', - }); - }); + templateTaskService.getTemplateTasksWithSubTasks(userId).then((result) => { + res.status(result.status).json(result.data); + }); } createTemplateTask(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 в заголовке' }); - templateTaskService - .createTemplateTask(userId, req.body) - .then((result) => { - res.status(result.status).json(result.data); - }) - .catch((err) => { - console.error('❌ Ошибка при создании шаблона задачи:', err); - res.status(500).json({ error: 'Ошибка при создании шаблона задачи' }); - }); + templateTaskService.createTemplateTask(userId, req.body).then((result) => { + res.status(result.status).json(result.data); + }); } deleteTemplateTask(req, res) { const taskId = Number(req.params.id); - templateTaskService - .deleteTemplateTask(taskId) - .then((result) => { - res.status(result.status).json(result.data); - }) - .catch((err) => { - console.error('❌ Ошибка при удалении шаблона задачи:', err); - res.status(500).json({ error: 'Ошибка при удалении шаблона задачи' }); - }); + templateTaskService.deleteTemplateTask(taskId).then((result) => { + res.status(result.status).json(result.data); + }); } async updateTemplateTask(req, res) { diff --git a/src/entities/template.task/template.task.service.js b/src/entities/template.task/template.task.service.js index 7cd1f02..f0b7a14 100644 --- a/src/entities/template.task/template.task.service.js +++ b/src/entities/template.task/template.task.service.js @@ -12,9 +12,10 @@ class TemplateTaskService { data: { error: 'Не удалось получить шаблон задачи' }, }; - const [subtasks] = await templateSubtaskModel.getAllTemplateSubtasksByParentTemplateTaskId( - taskId - ); + const [subtasks] = + await templateSubtaskModel.getAllTemplateSubtasksByParentTemplateTaskId( + taskId, + ); return { ...task, subtasks: subtasks || [], @@ -32,32 +33,24 @@ class TemplateTaskService { return { status: 404, data: { error: 'Шаблоны задач не найдены' } }; } - // Используем Promise.allSettled для параллельной обработки с таймаутом - const taskPromises = rows.map(async (task) => { + const fullTasks = []; + for (const task of rows) { try { - // Добавляем таймаут 5 секунд на каждый запрос - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Timeout')), 5000) - ); - - const fullTaskPromise = this.getFullTemplateTaskById(task.id); - const fullTask = await Promise.race([fullTaskPromise, timeoutPromise]); - - return fullTask; + const fullTask = await this.getFullTemplateTaskById(task.id); + if (fullTask) { + fullTasks.push(fullTask); + } } catch (err) { - console.error(`Ошибка при получении шаблона задачи ${task.id}:`, err.message || err); - return null; + console.error(`Ошибка при получении шаблона задачи ${task.id}:`, err); } - }); - - const results = await Promise.allSettled(taskPromises); - const fullTasks = results - .map((result) => (result.status === 'fulfilled' ? result.value : null)) - .filter((task) => task !== null); + } return { status: 200, data: fullTasks }; } catch (err) { - console.error('❌ Ошибка при получении шаблонов задач с подзадачами:', err); + console.error( + '❌ Ошибка при получении шаблонов задач с подзадачами:', + err, + ); return { status: 500, data: { error: err.message } }; } } @@ -79,7 +72,7 @@ class TemplateTaskService { templateSubtaskModel.addTemplateSubtask(userId, { ...sub, template_task_id: taskId, - }) + }), ); } } @@ -145,7 +138,7 @@ class TemplateTaskService { templateSubtaskModel.addTemplateSubtask(updatedTask.user_id, { ...sub, template_task_id: taskId, - }) + }), ); // удаление подзадачи @@ -155,7 +148,11 @@ class TemplateTaskService { // обновление существующей подзадачи } else if (sub.id) { promises.push( - templateSubtaskModel.updateTemplateSubtask(sub.id, sub.title, sub.position) + templateSubtaskModel.updateTemplateSubtask( + sub.id, + sub.title, + sub.position, + ), ); } } From d398d59ce35d9d02be6637145e31f5ad6cc2011c Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:54:40 +0300 Subject: [PATCH 2/2] fix: getTasksWithSubTasks optimized with SQL JOIN --- src/entities/task/models/task.model.js | 48 +++++++++++++++++- src/entities/task/task.service.js | 67 ++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/entities/task/models/task.model.js b/src/entities/task/models/task.model.js index 6bc5a2a..ab89523 100644 --- a/src/entities/task/models/task.model.js +++ b/src/entities/task/models/task.model.js @@ -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) { @@ -135,7 +136,51 @@ function updateTaskById(id, task) { } function getTasksForDate(user_id, task_date) { - return db.query('SELECT * FROM tasks WHERE user_id = ? AND task_date = ? OR task_date IS NULL', [user_id, task_date]); + return db.query('SELECT * FROM tasks WHERE user_id = ? AND task_date = ? OR task_date IS NULL', [ + user_id, + task_date, + ]); +} + +/** + * Get all tasks with their subtasks for a user using SQL JOIN (optimized) + * @param {number} user_id - User ID + * @returns {Promise} Promise resolving to tasks with subtasks in a flat structure + */ +function getAllTasksWithSubtasksByUser(user_id) { + return db + .query( + `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(([rows]) => [rows]) + .catch((error) => { + console.error('Ошибка при получении задач с подзадачами:', error); + throw error; + }); } module.exports = { @@ -147,4 +192,5 @@ module.exports = { setTaskDate, updateTaskById, getTasksForDate, + getAllTasksWithSubtasksByUser, }; diff --git a/src/entities/task/task.service.js b/src/entities/task/task.service.js index 8688ad2..6a75287 100644 --- a/src/entities/task/task.service.js +++ b/src/entities/task/task.service.js @@ -10,6 +10,7 @@ const templateModel = require('../template.task/models/template.task.model'); class TaskService { /** * Get full task with all subtasks + * @deprecated For batch operations, use optimized methods. This method is still used for single task operations. */ async getFullTaskById(taskId) { try { @@ -21,9 +22,7 @@ class TaskService { data: { error: 'Не удалось получить задачу' }, }; - const [subtasks] = await subtaskModel.getAllSubtasksByParentTaskId( - taskId, - ); + const [subtasks] = await subtaskModel.getAllSubtasksByParentTaskId(taskId); return { ...task, subtasks: subtasks || [], @@ -37,9 +36,69 @@ class TaskService { } /** - * Get all tasks with subtasks for a user + * Get all tasks with subtasks for a user (optimized with SQL JOIN) */ async getTasksWithSubTasks(userId) { + try { + const [rows] = await taskModel.getAllTasksWithSubtasksByUser(userId); + + if (!rows.length) { + return { status: 404, data: { error: 'Задачи не найдены' } }; + } + + // Группируем результаты по задачам + const tasksMap = new Map(); + + for (const row of rows) { + const taskId = row.id; + + // Создаем задачу, если её еще нет + if (!tasksMap.has(taskId)) { + tasksMap.set(taskId, { + id: row.id, + title: row.title, + description: row.description, + category: row.category, + priority: row.priority, + exp: row.exp, + start_time: row.start_time, + end_time: row.end_time, + task_date: row.task_date, + user_id: row.user_id, + special_id: row.special_id, + is_done: row.is_done, + created_at: row.created_at, + subtasks: [], + }); + } + + // Добавляем подзадачу, если она есть + if (row.subtask_id) { + tasksMap.get(taskId).subtasks.push({ + id: row.subtask_id, + title: row.subtask_title, + is_done: row.subtask_is_done, + position: row.subtask_position, + special_id: row.subtask_special_id, + created_at: row.subtask_created_at, + parent_task_id: taskId, + }); + } + } + + const fullTasks = Array.from(tasksMap.values()); + return { status: 200, data: fullTasks }; + } catch (err) { + console.error('❌ Ошибка при получении задач с подзадачами:', err); + return { status: 500, data: { error: err.message } }; + } + } + + /** + * Get all tasks with subtasks for a user (deprecated - uses N+1 queries) + * @deprecated Use getTasksWithSubTasks instead - it uses optimized SQL JOIN + */ + async getTasksWithSubTasksDeprecated(userId) { try { const [rows] = await taskModel.getAllTasksByUser(userId); if (!rows.length) {