Implement many-to-many TODO user assignments
- Create todo_users junction table for many-to-many relationship - Add migration to create todo_users table and migrate existing data - Update validators to accept assignedUserIds array instead of assignedTo - Update todo service to handle multiple user assignments - Fetch and return assigned users with each TODO 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -100,13 +100,14 @@ export const getTodoWithRelations = async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* Create new todo
|
* Create new todo
|
||||||
* POST /api/todos
|
* POST /api/todos
|
||||||
* Body: { title, description, projectId, companyId, assignedTo, status, priority, dueDate }
|
* Body: { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate }
|
||||||
*/
|
*/
|
||||||
export const createTodo = async (req, res) => {
|
export const createTodo = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
console.log('Backend received todo data:', data);
|
||||||
const todo = await todoService.createTodo(userId, data);
|
const todo = await todoService.createTodo(userId, data);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@@ -123,13 +124,14 @@ export const createTodo = async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* Update todo
|
* Update todo
|
||||||
* PATCH /api/todos/:todoId
|
* PATCH /api/todos/:todoId
|
||||||
* Body: { title, description, projectId, companyId, assignedTo, status, priority, dueDate }
|
* Body: { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate }
|
||||||
*/
|
*/
|
||||||
export const updateTodo = async (req, res) => {
|
export const updateTodo = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { todoId } = req.params;
|
const { todoId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
console.log('Backend received update data:', data);
|
||||||
const todo = await todoService.updateTodo(todoId, data);
|
const todo = await todoService.updateTodo(todoId, data);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
|||||||
29
src/db/migrations/0006_add_todo_users_table.sql
Normal file
29
src/db/migrations/0006_add_todo_users_table.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- Migration: Add todo_users junction table and migrate from assignedTo
|
||||||
|
-- Created: 2025-11-24
|
||||||
|
-- Description: Allows many-to-many relationship between todos and users
|
||||||
|
|
||||||
|
-- Create todo_users junction table
|
||||||
|
CREATE TABLE IF NOT EXISTS todo_users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
todo_id UUID NOT NULL REFERENCES todos(id) ON DELETE CASCADE,
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
assigned_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
CONSTRAINT todo_user_unique UNIQUE(todo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for better query performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todo_users_todo_id ON todo_users(todo_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todo_users_user_id ON todo_users(user_id);
|
||||||
|
|
||||||
|
-- Migrate existing assignedTo data to todo_users table
|
||||||
|
INSERT INTO todo_users (todo_id, user_id, assigned_by, assigned_at)
|
||||||
|
SELECT id, assigned_to, created_by, created_at
|
||||||
|
FROM todos
|
||||||
|
WHERE assigned_to IS NOT NULL;
|
||||||
|
|
||||||
|
-- Drop the old assigned_to column
|
||||||
|
ALTER TABLE todos DROP COLUMN IF EXISTS assigned_to;
|
||||||
|
|
||||||
|
-- Add comment
|
||||||
|
COMMENT ON TABLE todo_users IS 'Junction table for many-to-many relationship between todos and users (assigned users)';
|
||||||
@@ -149,7 +149,6 @@ export const todos = pgTable('todos', {
|
|||||||
description: text('description'),
|
description: text('description'),
|
||||||
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // todo môže patriť projektu
|
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // todo môže patriť projektu
|
||||||
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), // alebo firme
|
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), // alebo firme
|
||||||
assignedTo: uuid('assigned_to').references(() => users.id, { onDelete: 'set null' }), // komu je priradené
|
|
||||||
status: todoStatusEnum('status').default('pending').notNull(),
|
status: todoStatusEnum('status').default('pending').notNull(),
|
||||||
priority: todoPriorityEnum('priority').default('medium').notNull(),
|
priority: todoPriorityEnum('priority').default('medium').notNull(),
|
||||||
dueDate: timestamp('due_date'),
|
dueDate: timestamp('due_date'),
|
||||||
@@ -159,6 +158,17 @@ export const todos = pgTable('todos', {
|
|||||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Todo Users - many-to-many medzi todos a users (priradení používatelia)
|
||||||
|
export const todoUsers = pgTable('todo_users', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
todoId: uuid('todo_id').references(() => todos.id, { onDelete: 'cascade' }).notNull(),
|
||||||
|
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||||
|
assignedBy: uuid('assigned_by').references(() => users.id, { onDelete: 'set null' }), // kto pridal používateľa k todo
|
||||||
|
assignedAt: timestamp('assigned_at').defaultNow().notNull(),
|
||||||
|
}, (table) => ({
|
||||||
|
todoUserUnique: unique('todo_user_unique').on(table.todoId, table.userId),
|
||||||
|
}));
|
||||||
|
|
||||||
// Notes table - poznámky
|
// Notes table - poznámky
|
||||||
export const notes = pgTable('notes', {
|
export const notes = pgTable('notes', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from '../config/database.js';
|
import { db } from '../config/database.js';
|
||||||
import { todos, notes, projects, companies, users } from '../db/schema.js';
|
import { todos, todoUsers, notes, projects, companies, users } from '../db/schema.js';
|
||||||
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||||
import { NotFoundError } from '../utils/errors.js';
|
import { NotFoundError } from '../utils/errors.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,6 +10,90 @@ import { NotFoundError } from '../utils/errors.js';
|
|||||||
export const getAllTodos = async (filters = {}) => {
|
export const getAllTodos = async (filters = {}) => {
|
||||||
const { searchTerm, projectId, companyId, assignedTo, status } = 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);
|
let query = db.select().from(todos);
|
||||||
|
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
@@ -31,10 +115,6 @@ export const getAllTodos = async (filters = {}) => {
|
|||||||
conditions.push(eq(todos.companyId, companyId));
|
conditions.push(eq(todos.companyId, companyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assignedTo) {
|
|
||||||
conditions.push(eq(todos.assignedTo, assignedTo));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
conditions.push(eq(todos.status, status));
|
conditions.push(eq(todos.status, status));
|
||||||
}
|
}
|
||||||
@@ -44,6 +124,43 @@ export const getAllTodos = async (filters = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await query.orderBy(desc(todos.createdAt));
|
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;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,9 +183,12 @@ export const getTodoById = async (todoId) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new 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) => {
|
export const createTodo = async (userId, data) => {
|
||||||
const { title, description, projectId, companyId, assignedTo, status, priority, dueDate } = data;
|
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
||||||
|
console.log('Service createTodo - assignedUserIds:', assignedUserIds);
|
||||||
|
|
||||||
// Verify project exists if provided
|
// Verify project exists if provided
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
@@ -96,19 +216,19 @@ export const createTodo = async (userId, data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify assigned user exists if provided
|
// Verify assigned users exist if provided
|
||||||
if (assignedTo) {
|
if (assignedUserIds && Array.isArray(assignedUserIds) && assignedUserIds.length > 0) {
|
||||||
const [user] = await db
|
const existingUsers = await db
|
||||||
.select()
|
.select({ id: users.id })
|
||||||
.from(users)
|
.from(users)
|
||||||
.where(eq(users.id, assignedTo))
|
.where(inArray(users.id, assignedUserIds));
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!user) {
|
if (existingUsers.length !== assignedUserIds.length) {
|
||||||
throw new NotFoundError('Používateľ nenájdený');
|
throw new NotFoundError('Niektorí používatelia neboli nájdení');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the todo
|
||||||
const [newTodo] = await db
|
const [newTodo] = await db
|
||||||
.insert(todos)
|
.insert(todos)
|
||||||
.values({
|
.values({
|
||||||
@@ -116,7 +236,6 @@ export const createTodo = async (userId, data) => {
|
|||||||
description: description || null,
|
description: description || null,
|
||||||
projectId: projectId || null,
|
projectId: projectId || null,
|
||||||
companyId: companyId || null,
|
companyId: companyId || null,
|
||||||
assignedTo: assignedTo || null,
|
|
||||||
status: status || 'pending',
|
status: status || 'pending',
|
||||||
priority: priority || 'medium',
|
priority: priority || 'medium',
|
||||||
dueDate: dueDate ? new Date(dueDate) : null,
|
dueDate: dueDate ? new Date(dueDate) : null,
|
||||||
@@ -124,16 +243,32 @@ export const createTodo = async (userId, data) => {
|
|||||||
})
|
})
|
||||||
.returning();
|
.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;
|
return newTodo;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update todo
|
* Update todo
|
||||||
|
* @param {string} todoId - ID of todo to update
|
||||||
|
* @param {object} data - Updated data including assignedUserIds array
|
||||||
*/
|
*/
|
||||||
export const updateTodo = async (todoId, data) => {
|
export const updateTodo = async (todoId, data) => {
|
||||||
const todo = await getTodoById(todoId);
|
const todo = await getTodoById(todoId);
|
||||||
|
|
||||||
const { title, description, projectId, companyId, assignedTo, status, priority, dueDate } = data;
|
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
||||||
|
|
||||||
// Verify project exists if being changed
|
// Verify project exists if being changed
|
||||||
if (projectId !== undefined && projectId !== null && projectId !== todo.projectId) {
|
if (projectId !== undefined && projectId !== null && projectId !== todo.projectId) {
|
||||||
@@ -161,16 +296,15 @@ export const updateTodo = async (todoId, data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify assigned user exists if being changed
|
// Verify assigned users exist if being changed
|
||||||
if (assignedTo !== undefined && assignedTo !== null && assignedTo !== todo.assignedTo) {
|
if (assignedUserIds !== undefined && Array.isArray(assignedUserIds) && assignedUserIds.length > 0) {
|
||||||
const [user] = await db
|
const existingUsers = await db
|
||||||
.select()
|
.select({ id: users.id })
|
||||||
.from(users)
|
.from(users)
|
||||||
.where(eq(users.id, assignedTo))
|
.where(inArray(users.id, assignedUserIds));
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!user) {
|
if (existingUsers.length !== assignedUserIds.length) {
|
||||||
throw new NotFoundError('Používateľ nenájdený');
|
throw new NotFoundError('Niektorí používatelia neboli nájdení');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +323,6 @@ export const updateTodo = async (todoId, data) => {
|
|||||||
description: description !== undefined ? description : todo.description,
|
description: description !== undefined ? description : todo.description,
|
||||||
projectId: projectId !== undefined ? projectId : todo.projectId,
|
projectId: projectId !== undefined ? projectId : todo.projectId,
|
||||||
companyId: companyId !== undefined ? companyId : todo.companyId,
|
companyId: companyId !== undefined ? companyId : todo.companyId,
|
||||||
assignedTo: assignedTo !== undefined ? assignedTo : todo.assignedTo,
|
|
||||||
status: status !== undefined ? status : todo.status,
|
status: status !== undefined ? status : todo.status,
|
||||||
priority: priority !== undefined ? priority : todo.priority,
|
priority: priority !== undefined ? priority : todo.priority,
|
||||||
dueDate: dueDate !== undefined ? (dueDate ? new Date(dueDate) : null) : todo.dueDate,
|
dueDate: dueDate !== undefined ? (dueDate ? new Date(dueDate) : null) : todo.dueDate,
|
||||||
@@ -199,6 +332,23 @@ export const updateTodo = async (todoId, data) => {
|
|||||||
.where(eq(todos.id, todoId))
|
.where(eq(todos.id, todoId))
|
||||||
.returning();
|
.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;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -214,7 +364,7 @@ export const deleteTodo = async (todoId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get todo with related data (notes, project, company, assigned user)
|
* Get todo with related data (notes, project, company, assigned users)
|
||||||
*/
|
*/
|
||||||
export const getTodoWithRelations = async (todoId) => {
|
export const getTodoWithRelations = async (todoId) => {
|
||||||
const todo = await getTodoById(todoId);
|
const todo = await getTodoById(todoId);
|
||||||
@@ -239,20 +389,18 @@ export const getTodoWithRelations = async (todoId) => {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get assigned user if exists
|
// Get assigned users from todo_users junction table
|
||||||
let assignedUser = null;
|
const assignedUsers = await db
|
||||||
if (todo.assignedTo) {
|
|
||||||
[assignedUser] = await db
|
|
||||||
.select({
|
.select({
|
||||||
id: users.id,
|
id: users.id,
|
||||||
username: users.username,
|
username: users.username,
|
||||||
firstName: users.firstName,
|
firstName: users.firstName,
|
||||||
lastName: users.lastName,
|
lastName: users.lastName,
|
||||||
|
assignedAt: todoUsers.assignedAt,
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(todoUsers)
|
||||||
.where(eq(users.id, todo.assignedTo))
|
.innerJoin(users, eq(todoUsers.userId, users.id))
|
||||||
.limit(1);
|
.where(eq(todoUsers.todoId, todoId));
|
||||||
}
|
|
||||||
|
|
||||||
// Get related notes
|
// Get related notes
|
||||||
const todoNotes = await db
|
const todoNotes = await db
|
||||||
@@ -265,7 +413,7 @@ export const getTodoWithRelations = async (todoId) => {
|
|||||||
...todo,
|
...todo,
|
||||||
project,
|
project,
|
||||||
company,
|
company,
|
||||||
assignedUser,
|
assignedUsers,
|
||||||
notes: todoNotes,
|
notes: todoNotes,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -296,9 +444,20 @@ export const getTodosByCompanyId = async (companyId) => {
|
|||||||
* Get todos assigned to a user
|
* Get todos assigned to a user
|
||||||
*/
|
*/
|
||||||
export const getTodosByUserId = async (userId) => {
|
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
|
return await db
|
||||||
.select()
|
.select()
|
||||||
.from(todos)
|
.from(todos)
|
||||||
.where(eq(todos.assignedTo, userId))
|
.where(inArray(todos.id, todoIds))
|
||||||
.orderBy(desc(todos.createdAt));
|
.orderBy(desc(todos.createdAt));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const createTodoSchema = z.object({
|
|||||||
description: z.string().max(1000).optional(),
|
description: z.string().max(1000).optional(),
|
||||||
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')),
|
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')),
|
||||||
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('')),
|
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('')),
|
||||||
assignedTo: z.string().uuid('Neplatný formát user ID').optional().or(z.literal('')),
|
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
|
||||||
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
|
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
|
||||||
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
||||||
dueDate: z.string().optional().or(z.literal('')),
|
dueDate: z.string().optional().or(z.literal('')),
|
||||||
@@ -74,7 +74,7 @@ export const updateTodoSchema = z.object({
|
|||||||
description: z.string().max(1000).optional(),
|
description: z.string().max(1000).optional(),
|
||||||
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())),
|
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())),
|
||||||
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('').or(z.null())),
|
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('').or(z.null())),
|
||||||
assignedTo: z.string().uuid('Neplatný formát user ID').optional().or(z.literal('').or(z.null())),
|
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
|
||||||
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
|
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
|
||||||
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
||||||
dueDate: z.string().optional().or(z.literal('').or(z.null())),
|
dueDate: z.string().optional().or(z.literal('').or(z.null())),
|
||||||
|
|||||||
Reference in New Issue
Block a user