hotfix: Security, performance, and code cleanup
- Remove hardcoded database password fallback - Add encryption salt validation (min 32 chars) - Separate EMAIL_ENCRYPTION_KEY from JWT_SECRET - Fix command injection in status.service.js (use execFileSync) - Remove unnecessary SQL injection regex middleware - Create shared utilities (queryBuilder, pagination, emailAccountHelper) - Fix N+1 query problems in contact and todo services - Merge duplicate JMAP config functions - Add database indexes migration - Standardize error responses with error codes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -325,54 +325,51 @@ export const deleteTodo = async (todoId) => {
|
||||
|
||||
/**
|
||||
* Get todo with related data (notes, project, company, assigned users)
|
||||
* Optimized to use joins and Promise.all to avoid N+1 query problem
|
||||
*/
|
||||
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
|
||||
// Fetch todo with project and company in single query using joins
|
||||
const [todoResult] = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
firstName: users.firstName,
|
||||
lastName: users.lastName,
|
||||
assignedAt: todoUsers.assignedAt,
|
||||
todo: todos,
|
||||
project: projects,
|
||||
company: companies,
|
||||
})
|
||||
.from(todoUsers)
|
||||
.innerJoin(users, eq(todoUsers.userId, users.id))
|
||||
.where(eq(todoUsers.todoId, todoId));
|
||||
.from(todos)
|
||||
.leftJoin(projects, eq(todos.projectId, projects.id))
|
||||
.leftJoin(companies, eq(todos.companyId, companies.id))
|
||||
.where(eq(todos.id, todoId))
|
||||
.limit(1);
|
||||
|
||||
// Get related notes
|
||||
const todoNotes = await db
|
||||
.select()
|
||||
.from(notes)
|
||||
.where(eq(notes.todoId, todoId))
|
||||
.orderBy(desc(notes.createdAt));
|
||||
if (!todoResult) {
|
||||
throw new NotFoundError('Todo nenajdene');
|
||||
}
|
||||
|
||||
// Batch fetch for assigned users and notes in parallel
|
||||
const [assignedUsers, todoNotes] = await Promise.all([
|
||||
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)),
|
||||
|
||||
db
|
||||
.select()
|
||||
.from(notes)
|
||||
.where(eq(notes.todoId, todoId))
|
||||
.orderBy(desc(notes.createdAt)),
|
||||
]);
|
||||
|
||||
return {
|
||||
...todo,
|
||||
project,
|
||||
company,
|
||||
...todoResult.todo,
|
||||
project: todoResult.project,
|
||||
company: todoResult.company,
|
||||
assignedUsers,
|
||||
notes: todoNotes,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user