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:
richardtekula
2026-01-19 07:17:23 +01:00
parent 0523087961
commit 73a3c6bf95
15 changed files with 278 additions and 114 deletions

View File

@@ -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,
};