import { db } from '../config/database.js'; import { todos, todoUsers, notes, projects, companies, users } from '../db/schema.js'; import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm'; import { NotFoundError } from '../utils/errors.js'; /** * Get all todos * Optionally filter by search, project, company, assigned user, or status */ export const getAllTodos = async (filters = {}) => { const { searchTerm, projectId, companyId, assignedTo, status } = filters; // If filtering by assignedTo, we need to join with todo_users if (assignedTo) { const todoIdsWithUser = await db .select({ todoId: todoUsers.todoId }) .from(todoUsers) .where(eq(todoUsers.userId, assignedTo)); const todoIds = todoIdsWithUser.map((row) => row.todoId); if (todoIds.length === 0) { return []; } let query = db.select().from(todos).where(inArray(todos.id, todoIds)); const conditions = []; if (searchTerm) { conditions.push( or( ilike(todos.title, `%${searchTerm}%`), ilike(todos.description, `%${searchTerm}%`) ) ); } if (projectId) { conditions.push(eq(todos.projectId, projectId)); } if (companyId) { conditions.push(eq(todos.companyId, companyId)); } if (status) { conditions.push(eq(todos.status, status)); } if (conditions.length > 0) { query = query.where(and(...conditions)); } const result = await query.orderBy(desc(todos.createdAt)); // Fetch assigned users for all todos if (result.length > 0) { const todoIds = result.map(todo => todo.id); const assignedUsersData = await db .select({ todoId: todoUsers.todoId, userId: users.id, username: users.username, firstName: users.firstName, lastName: users.lastName, }) .from(todoUsers) .innerJoin(users, eq(todoUsers.userId, users.id)) .where(inArray(todoUsers.todoId, todoIds)); // Group assigned users by todoId const usersByTodoId = {}; for (const row of assignedUsersData) { if (!usersByTodoId[row.todoId]) { usersByTodoId[row.todoId] = []; } usersByTodoId[row.todoId].push({ id: row.userId, username: row.username, firstName: row.firstName, lastName: row.lastName, }); } // Attach assigned users to each todo return result.map(todo => ({ ...todo, assignedUsers: usersByTodoId[todo.id] || [], })); } return result; } // No assignedTo filter - simple query let query = db.select().from(todos); const conditions = []; if (searchTerm) { conditions.push( or( ilike(todos.title, `%${searchTerm}%`), ilike(todos.description, `%${searchTerm}%`) ) ); } if (projectId) { conditions.push(eq(todos.projectId, projectId)); } if (companyId) { conditions.push(eq(todos.companyId, companyId)); } if (status) { conditions.push(eq(todos.status, status)); } if (conditions.length > 0) { query = query.where(and(...conditions)); } const result = await query.orderBy(desc(todos.createdAt)); // Fetch assigned users for all todos if (result.length > 0) { const todoIds = result.map(todo => todo.id); const assignedUsersData = await db .select({ todoId: todoUsers.todoId, userId: users.id, username: users.username, firstName: users.firstName, lastName: users.lastName, }) .from(todoUsers) .innerJoin(users, eq(todoUsers.userId, users.id)) .where(inArray(todoUsers.todoId, todoIds)); // Group assigned users by todoId const usersByTodoId = {}; for (const row of assignedUsersData) { if (!usersByTodoId[row.todoId]) { usersByTodoId[row.todoId] = []; } usersByTodoId[row.todoId].push({ id: row.userId, username: row.username, firstName: row.firstName, lastName: row.lastName, }); } // Attach assigned users to each todo return result.map(todo => ({ ...todo, assignedUsers: usersByTodoId[todo.id] || [], })); } return result; }; /** * Get todo by ID */ export const getTodoById = async (todoId) => { const [todo] = await db .select() .from(todos) .where(eq(todos.id, todoId)) .limit(1); if (!todo) { throw new NotFoundError('Todo nenájdené'); } return todo; }; /** * Create new todo * @param {string} userId - ID of user creating the todo * @param {object} data - Todo data including assignedUserIds array */ export const createTodo = async (userId, data) => { const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data; console.log('Service createTodo - assignedUserIds:', assignedUserIds); // Verify project exists if provided if (projectId) { const [project] = await db .select() .from(projects) .where(eq(projects.id, projectId)) .limit(1); if (!project) { throw new NotFoundError('Projekt nenájdený'); } } // Verify company exists if provided if (companyId) { const [company] = await db .select() .from(companies) .where(eq(companies.id, companyId)) .limit(1); if (!company) { throw new NotFoundError('Firma nenájdená'); } } // Verify assigned users exist if provided if (assignedUserIds && Array.isArray(assignedUserIds) && assignedUserIds.length > 0) { const existingUsers = await db .select({ id: users.id }) .from(users) .where(inArray(users.id, assignedUserIds)); if (existingUsers.length !== assignedUserIds.length) { throw new NotFoundError('Niektorí používatelia neboli nájdení'); } } // Create the todo const [newTodo] = await db .insert(todos) .values({ title, description: description || null, projectId: projectId || null, companyId: companyId || null, status: status || 'pending', priority: priority || 'medium', dueDate: dueDate ? new Date(dueDate) : null, createdBy: userId, }) .returning(); // Assign users to the todo if (assignedUserIds && Array.isArray(assignedUserIds) && assignedUserIds.length > 0) { const todoUserInserts = assignedUserIds.map((assignedUserId) => ({ todoId: newTodo.id, userId: assignedUserId, assignedBy: userId, })); console.log('Inserting todo_users:', todoUserInserts); await db.insert(todoUsers).values(todoUserInserts); } else { console.log('No users to assign - assignedUserIds:', assignedUserIds); } return newTodo; }; /** * Update todo * @param {string} todoId - ID of todo to update * @param {object} data - Updated data including assignedUserIds array */ export const updateTodo = async (todoId, data) => { const todo = await getTodoById(todoId); const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data; // Verify project exists if being changed if (projectId !== undefined && projectId !== null && projectId !== todo.projectId) { const [project] = await db .select() .from(projects) .where(eq(projects.id, projectId)) .limit(1); if (!project) { throw new NotFoundError('Projekt nenájdený'); } } // Verify company exists if being changed if (companyId !== undefined && companyId !== null && companyId !== todo.companyId) { const [company] = await db .select() .from(companies) .where(eq(companies.id, companyId)) .limit(1); if (!company) { throw new NotFoundError('Firma nenájdená'); } } // Verify assigned users exist if being changed if (assignedUserIds !== undefined && Array.isArray(assignedUserIds) && assignedUserIds.length > 0) { const existingUsers = await db .select({ id: users.id }) .from(users) .where(inArray(users.id, assignedUserIds)); if (existingUsers.length !== assignedUserIds.length) { throw new NotFoundError('Niektorí používatelia neboli nájdení'); } } // Set completedAt when status is changed to 'completed' let completedAt = todo.completedAt; if (status === 'completed' && todo.status !== 'completed') { completedAt = new Date(); } else if (status && status !== 'completed') { completedAt = null; } const [updated] = await db .update(todos) .set({ title: title !== undefined ? title : todo.title, description: description !== undefined ? description : todo.description, projectId: projectId !== undefined ? projectId : todo.projectId, companyId: companyId !== undefined ? companyId : todo.companyId, status: status !== undefined ? status : todo.status, priority: priority !== undefined ? priority : todo.priority, dueDate: dueDate !== undefined ? (dueDate ? new Date(dueDate) : null) : todo.dueDate, completedAt, updatedAt: new Date(), }) .where(eq(todos.id, todoId)) .returning(); // Update assigned users if provided if (assignedUserIds !== undefined) { // Delete existing assignments await db.delete(todoUsers).where(eq(todoUsers.todoId, todoId)); // Create new assignments if (Array.isArray(assignedUserIds) && assignedUserIds.length > 0) { const todoUserInserts = assignedUserIds.map((userId) => ({ todoId: todoId, userId: userId, assignedBy: null, // We don't track who made the update })); await db.insert(todoUsers).values(todoUserInserts); } } return updated; }; /** * Delete todo */ export const deleteTodo = async (todoId) => { await getTodoById(todoId); // Check if exists await db.delete(todos).where(eq(todos.id, todoId)); return { success: true, message: 'Todo bolo odstránené' }; }; /** * Get todo with related data (notes, project, company, assigned users) */ export const getTodoWithRelations = async (todoId) => { const todo = await getTodoById(todoId); // Get project if exists let project = null; if (todo.projectId) { [project] = await db .select() .from(projects) .where(eq(projects.id, todo.projectId)) .limit(1); } // Get company if exists let company = null; if (todo.companyId) { [company] = await db .select() .from(companies) .where(eq(companies.id, todo.companyId)) .limit(1); } // Get assigned users from todo_users junction table const assignedUsers = await db .select({ id: users.id, username: users.username, firstName: users.firstName, lastName: users.lastName, assignedAt: todoUsers.assignedAt, }) .from(todoUsers) .innerJoin(users, eq(todoUsers.userId, users.id)) .where(eq(todoUsers.todoId, todoId)); // Get related notes const todoNotes = await db .select() .from(notes) .where(eq(notes.todoId, todoId)) .orderBy(desc(notes.createdAt)); return { ...todo, project, company, assignedUsers, notes: todoNotes, }; }; /** * Get todos by project ID */ export const getTodosByProjectId = async (projectId) => { return await db .select() .from(todos) .where(eq(todos.projectId, projectId)) .orderBy(desc(todos.createdAt)); }; /** * Get todos by company ID */ export const getTodosByCompanyId = async (companyId) => { return await db .select() .from(todos) .where(eq(todos.companyId, companyId)) .orderBy(desc(todos.createdAt)); }; /** * Get todos assigned to a user */ export const getTodosByUserId = async (userId) => { const todoIdsWithUser = await db .select({ todoId: todoUsers.todoId }) .from(todoUsers) .where(eq(todoUsers.userId, userId)); const todoIds = todoIdsWithUser.map((row) => row.todoId); if (todoIds.length === 0) { return []; } return await db .select() .from(todos) .where(inArray(todos.id, todoIds)) .orderBy(desc(todos.createdAt)); };