From 35dfa07668673eb9e9e78cfe8188cdf331d575f7 Mon Sep 17 00:00:00 2001 From: richardtekula Date: Thu, 4 Dec 2025 07:39:52 +0100 Subject: [PATCH] Improve centralized error handling --- src/controllers/admin.controller.js | 56 ++++++++---- src/controllers/auth.controller.js | 36 +++----- src/controllers/company.controller.js | 96 ++++++++------------- src/controllers/contact.controller.js | 41 ++++----- src/controllers/crm-email.controller.js | 56 +++++------- src/controllers/email-account.controller.js | 36 +++----- src/controllers/note.controller.js | 36 +++----- src/controllers/project.controller.js | 71 ++++++--------- src/controllers/time-tracking.controller.js | 73 ++++++++-------- src/controllers/timesheet.controller.js | 33 +++---- src/controllers/todo.controller.js | 41 ++++----- src/middlewares/auth/authMiddleware.js | 8 +- src/middlewares/global/errorHandler.js | 10 ++- src/utils/jwt.js | 9 +- 14 files changed, 266 insertions(+), 336 deletions(-) diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js index ea4eeb5..6aee6fd 100644 --- a/src/controllers/admin.controller.js +++ b/src/controllers/admin.controller.js @@ -1,9 +1,9 @@ import { db } from '../config/database.js'; -import { users } from '../db/schema.js'; -import { eq } from 'drizzle-orm'; +import { users, userEmailAccounts, emailAccounts } from '../db/schema.js'; +import { eq, inArray } from 'drizzle-orm'; import { hashPassword, generateTempPassword } from '../utils/password.js'; import { logUserCreation, logRoleChange } from '../services/audit.service.js'; -import { formatErrorResponse, ConflictError, NotFoundError } from '../utils/errors.js'; +import { ConflictError, NotFoundError } from '../utils/errors.js'; import * as emailAccountService from '../services/email-account.service.js'; /** @@ -11,7 +11,7 @@ import * as emailAccountService from '../services/email-account.service.js'; * Ak je poskytnutý email a emailPassword, automaticky sa fetchne JMAP account ID * POST /api/admin/users */ -export const createUser = async (req, res) => { +export const createUser = async (req, res, next) => { const { username, email, emailPassword, firstName, lastName, role } = req.body; const adminId = req.userId; const ipAddress = req.ip || req.connection.remoteAddress; @@ -100,8 +100,7 @@ export const createUser = async (req, res) => { : 'Používateľ úspešne vytvorený. Email môže byť nastavený neskôr.', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -109,7 +108,7 @@ export const createUser = async (req, res) => { * Zoznam všetkých userov (admin only) * GET /api/admin/users */ -export const getAllUsers = async (req, res) => { +export const getAllUsers = async (req, res, next) => { try { const allUsers = await db .select({ @@ -130,8 +129,7 @@ export const getAllUsers = async (req, res) => { data: allUsers, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -139,7 +137,7 @@ export const getAllUsers = async (req, res) => { * Získanie konkrétneho usera (admin only) * GET /api/admin/users/:userId */ -export const getUser = async (req, res) => { +export const getUser = async (req, res, next) => { const { userId } = req.params; try { @@ -176,8 +174,7 @@ export const getUser = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -185,7 +182,7 @@ export const getUser = async (req, res) => { * Zmena role usera (admin only) * PATCH /api/admin/users/:userId/role */ -export const changeUserRole = async (req, res) => { +export const changeUserRole = async (req, res, next) => { const { userId } = req.params; const { role } = req.body; const adminId = req.userId; @@ -228,8 +225,7 @@ export const changeUserRole = async (req, res) => { message: 'Rola používateľa bola zmenená', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -237,7 +233,7 @@ export const changeUserRole = async (req, res) => { * Zmazanie usera (admin only) * DELETE /api/admin/users/:userId */ -export const deleteUser = async (req, res) => { +export const deleteUser = async (req, res, next) => { const { userId } = req.params; try { @@ -263,14 +259,38 @@ export const deleteUser = async (req, res) => { } } + // Get user's email account IDs before deletion + const userEmailAccountLinks = await db + .select({ emailAccountId: userEmailAccounts.emailAccountId }) + .from(userEmailAccounts) + .where(eq(userEmailAccounts.userId, userId)); + + const emailAccountIds = userEmailAccountLinks.map(link => link.emailAccountId); + + // Delete user (cascades userEmailAccounts links) await db.delete(users).where(eq(users.id, userId)); + // Delete orphaned email accounts (no users linked) + // This will cascade delete contacts and emails + let deletedEmailAccounts = 0; + for (const emailAccountId of emailAccountIds) { + const [remainingLinks] = await db + .select({ count: db.$count(userEmailAccounts) }) + .from(userEmailAccounts) + .where(eq(userEmailAccounts.emailAccountId, emailAccountId)); + + if (remainingLinks.count === 0) { + await db.delete(emailAccounts).where(eq(emailAccounts.id, emailAccountId)); + deletedEmailAccounts++; + } + } + res.status(200).json({ success: true, message: 'Používateľ bol zmazaný', + deletedEmailAccounts, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 27c6097..e12d50e 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -4,13 +4,12 @@ import { logPasswordChange, logEmailLink, } from '../services/audit.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * KROK 1: Login s temporary password * POST /api/auth/login */ -export const login = async (req, res) => { +export const login = async (req, res, next) => { const { username, password } = req.body; const ipAddress = req.ip || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; @@ -55,8 +54,7 @@ export const login = async (req, res) => { // Log failed login await logLoginAttempt(username, false, ipAddress, userAgent, error.message); - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -65,7 +63,7 @@ export const login = async (req, res) => { * POST /api/auth/set-password * Requires: authentication */ -export const setPassword = async (req, res) => { +export const setPassword = async (req, res, next) => { const { newPassword } = req.body; const userId = req.userId; const ipAddress = req.ip || req.connection.remoteAddress; @@ -83,8 +81,7 @@ export const setPassword = async (req, res) => { message: 'Heslo úspešne nastavené', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -93,7 +90,7 @@ export const setPassword = async (req, res) => { * POST /api/auth/link-email * Requires: authentication */ -export const linkEmail = async (req, res) => { +export const linkEmail = async (req, res, next) => { const { email, emailPassword } = req.body; const userId = req.userId; const ipAddress = req.ip || req.connection.remoteAddress; @@ -114,8 +111,7 @@ export const linkEmail = async (req, res) => { message: 'Email účet úspešne pripojený a overený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -124,7 +120,7 @@ export const linkEmail = async (req, res) => { * POST /api/auth/skip-email * Requires: authentication */ -export const skipEmail = async (req, res) => { +export const skipEmail = async (req, res, next) => { const userId = req.userId; try { @@ -136,8 +132,7 @@ export const skipEmail = async (req, res) => { message: 'Email setup preskočený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -146,7 +141,7 @@ export const skipEmail = async (req, res) => { * POST /api/auth/logout * Requires: authentication */ -export const logout = async (req, res) => { +export const logout = async (req, res, next) => { try { const result = await authService.logout(); @@ -159,8 +154,7 @@ export const logout = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -169,7 +163,7 @@ export const logout = async (req, res) => { * GET /api/auth/session * Requires: authentication */ -export const getSession = async (req, res) => { +export const getSession = async (req, res, next) => { try { res.status(200).json({ success: true, @@ -179,8 +173,7 @@ export const getSession = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; @@ -189,7 +182,7 @@ export const getSession = async (req, res) => { * GET /api/auth/me * Requires: authentication */ -export const getMe = async (req, res) => { +export const getMe = async (req, res, next) => { try { res.status(200).json({ success: true, @@ -198,7 +191,6 @@ export const getMe = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + next(error); } }; diff --git a/src/controllers/company.controller.js b/src/controllers/company.controller.js index 857f6ee..8b31469 100644 --- a/src/controllers/company.controller.js +++ b/src/controllers/company.controller.js @@ -2,13 +2,12 @@ import * as companyService from '../services/company.service.js'; import * as noteService from '../services/note.service.js'; import * as companyReminderService from '../services/company-reminder.service.js'; import * as companyEmailService from '../services/company-email.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Get all companies * GET /api/companies?search=query */ -export const getAllCompanies = async (req, res) => { +export const getAllCompanies = async (req, res, next) => { try { const { search } = req.query; @@ -20,8 +19,7 @@ export const getAllCompanies = async (req, res) => { data: companies, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -29,7 +27,7 @@ export const getAllCompanies = async (req, res) => { * Get company by ID * GET /api/companies/:companyId */ -export const getCompanyById = async (req, res) => { +export const getCompanyById = async (req, res, next) => { try { const { companyId } = req.params; @@ -40,8 +38,7 @@ export const getCompanyById = async (req, res) => { data: company, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -49,7 +46,7 @@ export const getCompanyById = async (req, res) => { * Get company email threads aggregated across user's email accounts * GET /api/companies/:companyId/email-threads */ -export const getCompanyEmailThreads = async (req, res) => { +export const getCompanyEmailThreads = async (req, res, next) => { try { const userId = req.userId; const { companyId } = req.params; @@ -64,8 +61,7 @@ export const getCompanyEmailThreads = async (req, res) => { data: result, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -73,7 +69,7 @@ export const getCompanyEmailThreads = async (req, res) => { * Get unread email counts grouped by company for current user * GET /api/companies/email-unread */ -export const getCompanyUnreadCounts = async (req, res) => { +export const getCompanyUnreadCounts = async (req, res, next) => { try { const userId = req.userId; const counts = await companyEmailService.getCompanyUnreadCounts(userId); @@ -83,8 +79,7 @@ export const getCompanyUnreadCounts = async (req, res) => { data: counts, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -92,7 +87,7 @@ export const getCompanyUnreadCounts = async (req, res) => { * Get company with relations (projects, todos, notes) * GET /api/companies/:companyId/details */ -export const getCompanyWithRelations = async (req, res) => { +export const getCompanyWithRelations = async (req, res, next) => { try { const { companyId } = req.params; @@ -103,8 +98,7 @@ export const getCompanyWithRelations = async (req, res) => { data: company, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -113,7 +107,7 @@ export const getCompanyWithRelations = async (req, res) => { * POST /api/companies * Body: { name, description, address, city, country, phone, email, website } */ -export const createCompany = async (req, res) => { +export const createCompany = async (req, res, next) => { try { const userId = req.userId; const data = req.body; @@ -126,8 +120,7 @@ export const createCompany = async (req, res) => { message: 'Firma bola vytvorená', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -136,7 +129,7 @@ export const createCompany = async (req, res) => { * PATCH /api/companies/:companyId * Body: { name, description, address, city, country, phone, email, website } */ -export const updateCompany = async (req, res) => { +export const updateCompany = async (req, res, next) => { try { const { companyId } = req.params; const data = req.body; @@ -149,8 +142,7 @@ export const updateCompany = async (req, res) => { message: 'Firma bola aktualizovaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -158,7 +150,7 @@ export const updateCompany = async (req, res) => { * Delete company * DELETE /api/companies/:companyId */ -export const deleteCompany = async (req, res) => { +export const deleteCompany = async (req, res, next) => { try { const { companyId } = req.params; @@ -169,8 +161,7 @@ export const deleteCompany = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -178,7 +169,7 @@ export const deleteCompany = async (req, res) => { * Get company notes * GET /api/companies/:companyId/notes */ -export const getCompanyNotes = async (req, res) => { +export const getCompanyNotes = async (req, res, next) => { try { const { companyId } = req.params; @@ -190,8 +181,7 @@ export const getCompanyNotes = async (req, res) => { data: notes, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -199,7 +189,7 @@ export const getCompanyNotes = async (req, res) => { * Add company note * POST /api/companies/:companyId/notes */ -export const addCompanyNote = async (req, res) => { +export const addCompanyNote = async (req, res, next) => { try { const userId = req.userId; const { companyId } = req.params; @@ -216,8 +206,7 @@ export const addCompanyNote = async (req, res) => { message: 'Poznámka bola pridaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -225,7 +214,7 @@ export const addCompanyNote = async (req, res) => { * Update company note * PATCH /api/companies/:companyId/notes/:noteId */ -export const updateCompanyNote = async (req, res) => { +export const updateCompanyNote = async (req, res, next) => { try { const { noteId } = req.params; const { content } = req.body; @@ -240,8 +229,7 @@ export const updateCompanyNote = async (req, res) => { message: 'Poznámka bola aktualizovaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -249,7 +237,7 @@ export const updateCompanyNote = async (req, res) => { * Delete company note * DELETE /api/companies/:companyId/notes/:noteId */ -export const deleteCompanyNote = async (req, res) => { +export const deleteCompanyNote = async (req, res, next) => { try { const { noteId } = req.params; @@ -260,8 +248,7 @@ export const deleteCompanyNote = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -269,7 +256,7 @@ export const deleteCompanyNote = async (req, res) => { * Company reminders * CRUD for /api/companies/:companyId/reminders */ -export const getCompanyReminders = async (req, res) => { +export const getCompanyReminders = async (req, res, next) => { try { const { companyId } = req.params; @@ -281,12 +268,11 @@ export const getCompanyReminders = async (req, res) => { data: reminders, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const createCompanyReminder = async (req, res) => { +export const createCompanyReminder = async (req, res, next) => { try { const { companyId } = req.params; const { description, dueDate, isChecked } = req.body; @@ -299,12 +285,11 @@ export const createCompanyReminder = async (req, res) => { message: 'Reminder bol pridaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const updateCompanyReminder = async (req, res) => { +export const updateCompanyReminder = async (req, res, next) => { try { const { companyId, reminderId } = req.params; const { description, dueDate, isChecked } = req.body; @@ -317,12 +302,11 @@ export const updateCompanyReminder = async (req, res) => { message: 'Reminder bol aktualizovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const deleteCompanyReminder = async (req, res) => { +export const deleteCompanyReminder = async (req, res, next) => { try { const { companyId, reminderId } = req.params; @@ -333,12 +317,11 @@ export const deleteCompanyReminder = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const getReminderSummary = async (_req, res) => { +export const getReminderSummary = async (_req, res, next) => { try { const summary = await companyReminderService.getReminderSummary(); res.status(200).json({ @@ -346,12 +329,11 @@ export const getReminderSummary = async (_req, res) => { data: summary, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const getReminderCountsByCompany = async (_req, res) => { +export const getReminderCountsByCompany = async (_req, res, next) => { try { const counts = await companyReminderService.getReminderCountsByCompany(); res.status(200).json({ @@ -359,12 +341,11 @@ export const getReminderCountsByCompany = async (_req, res) => { data: counts, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; -export const getUpcomingReminders = async (_req, res) => { +export const getUpcomingReminders = async (_req, res, next) => { try { const reminders = await companyReminderService.getUpcomingReminders(); res.status(200).json({ @@ -373,7 +354,6 @@ export const getUpcomingReminders = async (_req, res) => { data: reminders, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/contact.controller.js b/src/controllers/contact.controller.js index 5c34e01..779fb4c 100644 --- a/src/controllers/contact.controller.js +++ b/src/controllers/contact.controller.js @@ -1,6 +1,5 @@ import * as contactService from '../services/contact.service.js'; import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; import * as emailAccountService from '../services/email-account.service.js'; import { logger } from '../utils/logger.js'; @@ -8,7 +7,7 @@ import { logger } from '../utils/logger.js'; * Get all contacts for an email account * GET /api/contacts?accountId=xxx (required) */ -export const getContacts = async (req, res) => { +export const getContacts = async (req, res, next) => { try { const userId = req.userId; const { accountId } = req.query; @@ -35,8 +34,7 @@ export const getContacts = async (req, res) => { data: contacts, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -44,7 +42,7 @@ export const getContacts = async (req, res) => { * Discover potential contacts from JMAP (email senders) * GET /api/contacts/discover?accountId=xxx&search=query&limit=50 */ -export const discoverContacts = async (req, res) => { +export const discoverContacts = async (req, res, next) => { try { const userId = req.userId; const { accountId, search = '', limit = 50 } = req.query; @@ -96,8 +94,7 @@ export const discoverContacts = async (req, res) => { }); } catch (error) { logger.error('ERROR in discoverContacts', { error: error.message, stack: error.stack }); - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -106,7 +103,7 @@ export const discoverContacts = async (req, res) => { * POST /api/contacts * Body: { email, name, notes, accountId } */ -export const addContact = async (req, res) => { +export const addContact = async (req, res, next) => { try { const userId = req.userId; logger.debug('Full req.body', { body: req.body }); @@ -162,8 +159,7 @@ export const addContact = async (req, res) => { message: 'Kontakt pridaný a emaily synchronizované', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -171,7 +167,7 @@ export const addContact = async (req, res) => { * Remove a contact * DELETE /api/contacts/:contactId?accountId=xxx */ -export const removeContact = async (req, res) => { +export const removeContact = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -197,8 +193,7 @@ export const removeContact = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -206,7 +201,7 @@ export const removeContact = async (req, res) => { * Update a contact * PATCH /api/contacts/:contactId?accountId=xxx */ -export const updateContact = async (req, res) => { +export const updateContact = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -234,8 +229,7 @@ export const updateContact = async (req, res) => { message: 'Kontakt aktualizovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -244,7 +238,7 @@ export const updateContact = async (req, res) => { * POST /api/contacts/:contactId/link-company?accountId=xxx * Body: { companyId } */ -export const linkCompanyToContact = async (req, res) => { +export const linkCompanyToContact = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -282,8 +276,7 @@ export const linkCompanyToContact = async (req, res) => { message: 'Firma bola linknutá ku kontaktu', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -291,7 +284,7 @@ export const linkCompanyToContact = async (req, res) => { * Unlink company from contact * POST /api/contacts/:contactId/unlink-company?accountId=xxx */ -export const unlinkCompanyFromContact = async (req, res) => { +export const unlinkCompanyFromContact = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -318,8 +311,7 @@ export const unlinkCompanyFromContact = async (req, res) => { message: 'Firma bola odlinknutá od kontaktu', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -328,7 +320,7 @@ export const unlinkCompanyFromContact = async (req, res) => { * POST /api/contacts/:contactId/create-company?accountId=xxx * Body: { name, email, phone, address, city, country, website, description } (all optional, uses contact data as defaults) */ -export const createCompanyFromContact = async (req, res) => { +export const createCompanyFromContact = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -356,7 +348,6 @@ export const createCompanyFromContact = async (req, res) => { message: 'Firma bola vytvorená z kontaktu', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/crm-email.controller.js b/src/controllers/crm-email.controller.js index 159e767..31875b3 100644 --- a/src/controllers/crm-email.controller.js +++ b/src/controllers/crm-email.controller.js @@ -2,7 +2,6 @@ import * as crmEmailService from '../services/crm-email.service.js'; import * as contactService from '../services/contact.service.js'; import * as emailAccountService from '../services/email-account.service.js'; import { markEmailAsRead, sendEmail, getJmapConfig, getJmapConfigFromAccount, syncEmailsFromSender, searchEmailsJMAP as searchEmailsJMAPService } from '../services/jmap.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; import { getUserById } from '../services/auth.service.js'; import { logger } from '../utils/logger.js'; @@ -10,7 +9,7 @@ import { logger } from '../utils/logger.js'; * Get all emails for authenticated user * GET /api/emails?accountId=xxx (REQUIRED) */ -export const getEmails = async (req, res) => { +export const getEmails = async (req, res, next) => { try { const userId = req.userId; const { accountId } = req.query; @@ -36,8 +35,7 @@ export const getEmails = async (req, res) => { data: emails, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -45,7 +43,7 @@ export const getEmails = async (req, res) => { * Get emails by thread (conversation) * GET /api/emails/thread/:threadId?accountId=xxx (accountId required) */ -export const getThread = async (req, res) => { +export const getThread = async (req, res, next) => { try { const userId = req.userId; const { threadId } = req.params; @@ -72,8 +70,7 @@ export const getThread = async (req, res) => { data: thread, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -81,7 +78,7 @@ export const getThread = async (req, res) => { * Search emails * GET /api/emails/search?q=query&accountId=xxx (accountId required) */ -export const searchEmails = async (req, res) => { +export const searchEmails = async (req, res, next) => { try { const userId = req.userId; const { q, accountId } = req.query; @@ -107,8 +104,7 @@ export const searchEmails = async (req, res) => { data: results, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -117,7 +113,7 @@ export const searchEmails = async (req, res) => { * GET /api/emails/unread-count * Returns total unread count and per-account counts */ -export const getUnreadCount = async (req, res) => { +export const getUnreadCount = async (req, res, next) => { try { const userId = req.userId; @@ -139,8 +135,7 @@ export const getUnreadCount = async (req, res) => { }); } catch (error) { logger.error('ERROR in getUnreadCount', { error: error.message }); - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -149,7 +144,7 @@ export const getUnreadCount = async (req, res) => { * POST /api/emails/sync * Body: { accountId } (optional - defaults to primary account) */ -export const syncEmails = async (req, res) => { +export const syncEmails = async (req, res, next) => { try { const userId = req.userId; const { accountId } = req.body; @@ -213,8 +208,7 @@ export const syncEmails = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -222,7 +216,7 @@ export const syncEmails = async (req, res) => { * Mark email as read/unread * PATCH /api/emails/:jmapId/read?accountId=xxx */ -export const markAsRead = async (req, res) => { +export const markAsRead = async (req, res, next) => { try { const userId = req.userId; const { jmapId } = req.params; @@ -249,8 +243,7 @@ export const markAsRead = async (req, res) => { message: `Email označený ako ${isRead ? 'prečítaný' : 'neprečítaný'}`, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -258,7 +251,7 @@ export const markAsRead = async (req, res) => { * Mark all emails from contact as read * POST /api/emails/contact/:contactId/read?accountId=xxx */ -export const markContactEmailsRead = async (req, res) => { +export const markContactEmailsRead = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -308,8 +301,7 @@ export const markContactEmailsRead = async (req, res) => { }); } catch (error) { logger.error('ERROR in markContactEmailsRead', { error: error.message }); - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -317,7 +309,7 @@ export const markContactEmailsRead = async (req, res) => { * Mark entire thread as read * POST /api/emails/thread/:threadId/read?accountId=xxx */ -export const markThreadRead = async (req, res) => { +export const markThreadRead = async (req, res, next) => { try { const userId = req.userId; const { threadId } = req.params; @@ -361,8 +353,7 @@ export const markThreadRead = async (req, res) => { count, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -371,7 +362,7 @@ export const markThreadRead = async (req, res) => { * POST /api/emails/reply * Body: { to, subject, body, inReplyTo, threadId, accountId } */ -export const replyToEmail = async (req, res) => { +export const replyToEmail = async (req, res, next) => { try { const userId = req.userId; const { to, subject, body, inReplyTo = null, threadId = null, accountId } = req.body; @@ -414,8 +405,7 @@ export const replyToEmail = async (req, res) => { data: result, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -423,7 +413,7 @@ export const replyToEmail = async (req, res) => { * Get emails for a specific contact * GET /api/emails/contact/:contactId?accountId=xxx */ -export const getContactEmails = async (req, res) => { +export const getContactEmails = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; @@ -450,8 +440,7 @@ export const getContactEmails = async (req, res) => { data: emails, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -460,7 +449,7 @@ export const getContactEmails = async (req, res) => { * GET /api/emails/search-jmap?query=text&limit=50&offset=0&accountId=xxx * Searches in: from, to, subject, and email body */ -export const searchEmailsJMAP = async (req, res) => { +export const searchEmailsJMAP = async (req, res, next) => { try { const userId = req.userId; const { query = '', limit = 50, offset = 0, accountId } = req.query; @@ -505,7 +494,6 @@ export const searchEmailsJMAP = async (req, res) => { }); } catch (error) { logger.error('ERROR in searchEmailsJMAP', { error: error.message }); - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/email-account.controller.js b/src/controllers/email-account.controller.js index 5a974c1..cf5725b 100644 --- a/src/controllers/email-account.controller.js +++ b/src/controllers/email-account.controller.js @@ -2,13 +2,12 @@ import * as emailAccountService from '../services/email-account.service.js'; import { logEmailLink, } from '../services/audit.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Get all email accounts for logged-in user * GET /api/email-accounts */ -export const getEmailAccounts = async (req, res) => { +export const getEmailAccounts = async (req, res, next) => { try { const userId = req.userId; const accounts = await emailAccountService.getUserEmailAccounts(userId); @@ -18,8 +17,7 @@ export const getEmailAccounts = async (req, res) => { data: accounts, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -27,7 +25,7 @@ export const getEmailAccounts = async (req, res) => { * Get a specific email account * GET /api/email-accounts/:id */ -export const getEmailAccount = async (req, res) => { +export const getEmailAccount = async (req, res, next) => { try { const userId = req.userId; const { id } = req.params; @@ -39,8 +37,7 @@ export const getEmailAccount = async (req, res) => { data: account, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -48,7 +45,7 @@ export const getEmailAccount = async (req, res) => { * Create a new email account * POST /api/email-accounts */ -export const createEmailAccount = async (req, res) => { +export const createEmailAccount = async (req, res, next) => { const { email, emailPassword } = req.body; const userId = req.userId; const ipAddress = req.ip || req.connection.remoteAddress; @@ -70,8 +67,7 @@ export const createEmailAccount = async (req, res) => { message: 'Email účet úspešne pripojený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -79,7 +75,7 @@ export const createEmailAccount = async (req, res) => { * Update email account password * PATCH /api/email-accounts/:id/password */ -export const updateEmailAccountPassword = async (req, res) => { +export const updateEmailAccountPassword = async (req, res, next) => { try { const userId = req.userId; const { id } = req.params; @@ -97,8 +93,7 @@ export const updateEmailAccountPassword = async (req, res) => { message: 'Heslo k emailovému účtu bolo aktualizované', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -106,7 +101,7 @@ export const updateEmailAccountPassword = async (req, res) => { * Toggle email account active status * PATCH /api/email-accounts/:id/status */ -export const toggleEmailAccountStatus = async (req, res) => { +export const toggleEmailAccountStatus = async (req, res, next) => { try { const userId = req.userId; const { id } = req.params; @@ -124,8 +119,7 @@ export const toggleEmailAccountStatus = async (req, res) => { message: `Email účet ${isActive ? 'aktivovaný' : 'deaktivovaný'}`, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -133,7 +127,7 @@ export const toggleEmailAccountStatus = async (req, res) => { * Set email account as primary * POST /api/email-accounts/:id/set-primary */ -export const setPrimaryEmailAccount = async (req, res) => { +export const setPrimaryEmailAccount = async (req, res, next) => { try { const userId = req.userId; const { id } = req.params; @@ -146,8 +140,7 @@ export const setPrimaryEmailAccount = async (req, res) => { message: 'Primárny email účet bol nastavený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -155,7 +148,7 @@ export const setPrimaryEmailAccount = async (req, res) => { * Delete email account * DELETE /api/email-accounts/:id */ -export const deleteEmailAccount = async (req, res) => { +export const deleteEmailAccount = async (req, res, next) => { try { const userId = req.userId; const { id } = req.params; @@ -168,7 +161,6 @@ export const deleteEmailAccount = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/note.controller.js b/src/controllers/note.controller.js index ee6f4c9..e88ed3d 100644 --- a/src/controllers/note.controller.js +++ b/src/controllers/note.controller.js @@ -1,11 +1,10 @@ import * as noteService from '../services/note.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Get all notes * GET /api/notes?search=query&companyId=xxx&projectId=xxx&todoId=xxx&contactId=xxx */ -export const getAllNotes = async (req, res) => { +export const getAllNotes = async (req, res, next) => { try { const { search, companyId, projectId, todoId, contactId } = req.query; @@ -25,8 +24,7 @@ export const getAllNotes = async (req, res) => { data: notes, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -34,7 +32,7 @@ export const getAllNotes = async (req, res) => { * Get note by ID * GET /api/notes/:noteId */ -export const getNoteById = async (req, res) => { +export const getNoteById = async (req, res, next) => { try { const { noteId } = req.params; @@ -45,8 +43,7 @@ export const getNoteById = async (req, res) => { data: note, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -55,7 +52,7 @@ export const getNoteById = async (req, res) => { * POST /api/notes * Body: { title, content, companyId, projectId, todoId, contactId } */ -export const createNote = async (req, res) => { +export const createNote = async (req, res, next) => { try { const userId = req.userId; const data = req.body; @@ -68,8 +65,7 @@ export const createNote = async (req, res) => { message: 'Poznámka bola vytvorená', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -78,7 +74,7 @@ export const createNote = async (req, res) => { * PATCH /api/notes/:noteId * Body: { title, content, companyId, projectId, todoId, contactId } */ -export const updateNote = async (req, res) => { +export const updateNote = async (req, res, next) => { try { const { noteId } = req.params; const data = req.body; @@ -91,8 +87,7 @@ export const updateNote = async (req, res) => { message: 'Poznámka bola aktualizovaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -100,7 +95,7 @@ export const updateNote = async (req, res) => { * Delete note * DELETE /api/notes/:noteId */ -export const deleteNote = async (req, res) => { +export const deleteNote = async (req, res, next) => { try { const { noteId } = req.params; @@ -111,8 +106,7 @@ export const deleteNote = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -120,7 +114,7 @@ export const deleteNote = async (req, res) => { * Get upcoming reminders for current user * GET /api/notes/my-reminders */ -export const getMyReminders = async (req, res) => { +export const getMyReminders = async (req, res, next) => { try { const userId = req.userId; @@ -132,8 +126,7 @@ export const getMyReminders = async (req, res) => { data: reminders, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -141,7 +134,7 @@ export const getMyReminders = async (req, res) => { * Mark reminder as sent * POST /api/notes/:noteId/mark-reminder-sent */ -export const markReminderSent = async (req, res) => { +export const markReminderSent = async (req, res, next) => { try { const { noteId } = req.params; @@ -153,7 +146,6 @@ export const markReminderSent = async (req, res) => { message: 'Reminder označený ako odoslaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/project.controller.js b/src/controllers/project.controller.js index 3840937..bf4bcec 100644 --- a/src/controllers/project.controller.js +++ b/src/controllers/project.controller.js @@ -1,12 +1,11 @@ import * as projectService from '../services/project.service.js'; import * as noteService from '../services/note.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Get all projects * GET /api/projects?search=query&companyId=xxx */ -export const getAllProjects = async (req, res) => { +export const getAllProjects = async (req, res, next) => { try { const { search, companyId } = req.query; @@ -18,8 +17,7 @@ export const getAllProjects = async (req, res) => { data: projects, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -27,7 +25,7 @@ export const getAllProjects = async (req, res) => { * Get project by ID * GET /api/projects/:projectId */ -export const getProjectById = async (req, res) => { +export const getProjectById = async (req, res, next) => { try { const { projectId } = req.params; @@ -38,8 +36,7 @@ export const getProjectById = async (req, res) => { data: project, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -47,7 +44,7 @@ export const getProjectById = async (req, res) => { * Get project with relations (company, todos, notes, timesheets) * GET /api/projects/:projectId/details */ -export const getProjectWithRelations = async (req, res) => { +export const getProjectWithRelations = async (req, res, next) => { try { const { projectId } = req.params; @@ -58,8 +55,7 @@ export const getProjectWithRelations = async (req, res) => { data: project, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -68,7 +64,7 @@ export const getProjectWithRelations = async (req, res) => { * POST /api/projects * Body: { name, description, companyId, status, startDate, endDate } */ -export const createProject = async (req, res) => { +export const createProject = async (req, res, next) => { try { const userId = req.userId; const data = req.body; @@ -81,8 +77,7 @@ export const createProject = async (req, res) => { message: 'Projekt bol vytvorený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -91,7 +86,7 @@ export const createProject = async (req, res) => { * PATCH /api/projects/:projectId * Body: { name, description, companyId, status, startDate, endDate } */ -export const updateProject = async (req, res) => { +export const updateProject = async (req, res, next) => { try { const { projectId } = req.params; const data = req.body; @@ -104,8 +99,7 @@ export const updateProject = async (req, res) => { message: 'Projekt bol aktualizovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -113,7 +107,7 @@ export const updateProject = async (req, res) => { * Delete project * DELETE /api/projects/:projectId */ -export const deleteProject = async (req, res) => { +export const deleteProject = async (req, res, next) => { try { const { projectId } = req.params; @@ -124,8 +118,7 @@ export const deleteProject = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -133,7 +126,7 @@ export const deleteProject = async (req, res) => { * Get project notes * GET /api/projects/:projectId/notes */ -export const getProjectNotes = async (req, res) => { +export const getProjectNotes = async (req, res, next) => { try { const { projectId } = req.params; @@ -145,8 +138,7 @@ export const getProjectNotes = async (req, res) => { data: notes, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -154,7 +146,7 @@ export const getProjectNotes = async (req, res) => { * Add project note * POST /api/projects/:projectId/notes */ -export const addProjectNote = async (req, res) => { +export const addProjectNote = async (req, res, next) => { try { const userId = req.userId; const { projectId } = req.params; @@ -172,8 +164,7 @@ export const addProjectNote = async (req, res) => { message: 'Poznámka bola pridaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -181,7 +172,7 @@ export const addProjectNote = async (req, res) => { * Update project note * PATCH /api/projects/:projectId/notes/:noteId */ -export const updateProjectNote = async (req, res) => { +export const updateProjectNote = async (req, res, next) => { try { const { noteId } = req.params; const { content, reminderAt } = req.body; @@ -197,8 +188,7 @@ export const updateProjectNote = async (req, res) => { message: 'Poznámka bola aktualizovaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -206,7 +196,7 @@ export const updateProjectNote = async (req, res) => { * Delete project note * DELETE /api/projects/:projectId/notes/:noteId */ -export const deleteProjectNote = async (req, res) => { +export const deleteProjectNote = async (req, res, next) => { try { const { noteId } = req.params; @@ -217,8 +207,7 @@ export const deleteProjectNote = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -226,7 +215,7 @@ export const deleteProjectNote = async (req, res) => { * Get project users (team members) * GET /api/projects/:projectId/users */ -export const getProjectUsers = async (req, res) => { +export const getProjectUsers = async (req, res, next) => { try { const { projectId } = req.params; @@ -238,8 +227,7 @@ export const getProjectUsers = async (req, res) => { data: users, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -248,7 +236,7 @@ export const getProjectUsers = async (req, res) => { * POST /api/projects/:projectId/users * Body: { userId, role } */ -export const assignUserToProject = async (req, res) => { +export const assignUserToProject = async (req, res, next) => { try { const currentUserId = req.userId; const { projectId } = req.params; @@ -267,8 +255,7 @@ export const assignUserToProject = async (req, res) => { message: 'Používateľ bol priradený k projektu', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -276,7 +263,7 @@ export const assignUserToProject = async (req, res) => { * Remove user from project * DELETE /api/projects/:projectId/users/:userId */ -export const removeUserFromProject = async (req, res) => { +export const removeUserFromProject = async (req, res, next) => { try { const { projectId, userId } = req.params; @@ -287,8 +274,7 @@ export const removeUserFromProject = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -297,7 +283,7 @@ export const removeUserFromProject = async (req, res) => { * PATCH /api/projects/:projectId/users/:userId * Body: { role } */ -export const updateUserRoleOnProject = async (req, res) => { +export const updateUserRoleOnProject = async (req, res, next) => { try { const { projectId, userId } = req.params; const { role } = req.body; @@ -310,7 +296,6 @@ export const updateUserRoleOnProject = async (req, res) => { message: 'Rola používateľa bola aktualizovaná', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/time-tracking.controller.js b/src/controllers/time-tracking.controller.js index be7bc7d..004a20a 100644 --- a/src/controllers/time-tracking.controller.js +++ b/src/controllers/time-tracking.controller.js @@ -1,11 +1,10 @@ import * as timeTrackingService from '../services/time-tracking.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Start a new time entry * POST /api/time-tracking/start */ -export const startTimeEntry = async (req, res) => { +export const startTimeEntry = async (req, res, next) => { try { const userId = req.userId; const { projectId, todoId, companyId, description } = req.body; @@ -23,8 +22,7 @@ export const startTimeEntry = async (req, res) => { message: 'Časovač bol spustený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -32,7 +30,7 @@ export const startTimeEntry = async (req, res) => { * Stop a running time entry * POST /api/time-tracking/:entryId/stop */ -export const stopTimeEntry = async (req, res) => { +export const stopTimeEntry = async (req, res, next) => { try { const userId = req.userId; const { entryId } = req.params; @@ -51,8 +49,7 @@ export const stopTimeEntry = async (req, res) => { message: 'Časovač bol zastavený', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -60,7 +57,7 @@ export const stopTimeEntry = async (req, res) => { * Get running time entry for current user * GET /api/time-tracking/running */ -export const getRunningTimeEntry = async (req, res) => { +export const getRunningTimeEntry = async (req, res, next) => { try { const userId = req.userId; @@ -71,8 +68,24 @@ export const getRunningTimeEntry = async (req, res) => { data: entry, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); + } +}; + +/** + * Get all running time entries (for dashboard) + * GET /api/time-tracking/running-all + */ +export const getAllRunningTimeEntries = async (req, res, next) => { + try { + const entries = await timeTrackingService.getAllRunningTimeEntries(); + + res.status(200).json({ + success: true, + data: entries, + }); + } catch (error) { + return next(error); } }; @@ -80,7 +93,7 @@ export const getRunningTimeEntry = async (req, res) => { * Get all time entries for current user with filters * GET /api/time-tracking?projectId=xxx&todoId=xxx&companyId=xxx&startDate=xxx&endDate=xxx */ -export const getAllTimeEntries = async (req, res) => { +export const getAllTimeEntries = async (req, res, next) => { try { const userId = req.userId; const { projectId, todoId, companyId, startDate, endDate } = req.query; @@ -101,8 +114,7 @@ export const getAllTimeEntries = async (req, res) => { data: entries, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -110,7 +122,7 @@ export const getAllTimeEntries = async (req, res) => { * Get time entries for a specific month * GET /api/time-tracking/month/:year/:month */ -export const getMonthlyTimeEntries = async (req, res) => { +export const getMonthlyTimeEntries = async (req, res, next) => { try { const userId = req.userId; const userRole = req.user.role; @@ -129,8 +141,7 @@ export const getMonthlyTimeEntries = async (req, res) => { data: entries, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -138,7 +149,7 @@ export const getMonthlyTimeEntries = async (req, res) => { * Generate timesheet file for a month * POST /api/time-tracking/month/:year/:month/generate */ -export const generateMonthlyTimesheet = async (req, res) => { +export const generateMonthlyTimesheet = async (req, res, next) => { try { const userId = req.userId; const userRole = req.user.role; @@ -157,8 +168,7 @@ export const generateMonthlyTimesheet = async (req, res) => { message: 'Timesheet bol vygenerovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -166,7 +176,7 @@ export const generateMonthlyTimesheet = async (req, res) => { * Get time entry by ID * GET /api/time-tracking/:entryId */ -export const getTimeEntryById = async (req, res) => { +export const getTimeEntryById = async (req, res, next) => { try { const { entryId } = req.params; @@ -177,8 +187,7 @@ export const getTimeEntryById = async (req, res) => { data: entry, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -186,7 +195,7 @@ export const getTimeEntryById = async (req, res) => { * Get time entry with related data * GET /api/time-tracking/:entryId/details */ -export const getTimeEntryWithRelations = async (req, res) => { +export const getTimeEntryWithRelations = async (req, res, next) => { try { const { entryId } = req.params; @@ -197,8 +206,7 @@ export const getTimeEntryWithRelations = async (req, res) => { data: entry, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -206,7 +214,7 @@ export const getTimeEntryWithRelations = async (req, res) => { * Update time entry * PATCH /api/time-tracking/:entryId */ -export const updateTimeEntry = async (req, res) => { +export const updateTimeEntry = async (req, res, next) => { try { const { entryId } = req.params; const { startTime, endTime, projectId, todoId, companyId, description } = req.body; @@ -229,8 +237,7 @@ export const updateTimeEntry = async (req, res) => { message: 'Záznam bol aktualizovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -238,7 +245,7 @@ export const updateTimeEntry = async (req, res) => { * Delete time entry * DELETE /api/time-tracking/:entryId */ -export const deleteTimeEntry = async (req, res) => { +export const deleteTimeEntry = async (req, res, next) => { try { const { entryId } = req.params; @@ -249,8 +256,7 @@ export const deleteTimeEntry = async (req, res) => { res.status(200).json(result); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -258,7 +264,7 @@ export const deleteTimeEntry = async (req, res) => { * Get monthly statistics * GET /api/time-tracking/stats/monthly/:year/:month */ -export const getMonthlyStats = async (req, res) => { +export const getMonthlyStats = async (req, res, next) => { try { const userId = req.userId; const userRole = req.user.role; @@ -276,7 +282,6 @@ export const getMonthlyStats = async (req, res) => { data: stats, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/timesheet.controller.js b/src/controllers/timesheet.controller.js index c3023d1..7b4b215 100644 --- a/src/controllers/timesheet.controller.js +++ b/src/controllers/timesheet.controller.js @@ -1,11 +1,11 @@ import * as timesheetService from '../services/timesheet.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; +import { ForbiddenError } from '../utils/errors.js'; /** * Upload timesheet * POST /api/timesheets/upload */ -export const uploadTimesheet = async (req, res) => { +export const uploadTimesheet = async (req, res, next) => { try { const { year, month, userId: requestUserId } = req.body; @@ -15,11 +15,7 @@ export const uploadTimesheet = async (req, res) => { let targetUserId = req.userId; if (requestUserId) { if (req.user.role !== 'admin') { - const errorResponse = formatErrorResponse( - new Error('Iba admin môže nahrávať timesheets za iných používateľov'), - process.env.NODE_ENV === 'development' - ); - return res.status(403).json(errorResponse); + throw new ForbiddenError('Iba admin môže nahrávať timesheets za iných používateľov'); } targetUserId = requestUserId; } @@ -37,8 +33,7 @@ export const uploadTimesheet = async (req, res) => { message: 'Timesheet bol úspešne nahraný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -46,7 +41,7 @@ export const uploadTimesheet = async (req, res) => { * Get user's timesheets (with optional filters) * GET /api/timesheets/my */ -export const getMyTimesheets = async (req, res) => { +export const getMyTimesheets = async (req, res, next) => { try { const { year, month } = req.query; @@ -60,8 +55,7 @@ export const getMyTimesheets = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -69,7 +63,7 @@ export const getMyTimesheets = async (req, res) => { * Get all users' timesheets (admin only) - grouped by user * GET /api/timesheets/all */ -export const getAllTimesheets = async (req, res) => { +export const getAllTimesheets = async (req, res, next) => { try { const { userId, year, month } = req.query; @@ -83,8 +77,7 @@ export const getAllTimesheets = async (req, res) => { }, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -92,7 +85,7 @@ export const getAllTimesheets = async (req, res) => { * Download timesheet file * GET /api/timesheets/:timesheetId/download */ -export const downloadTimesheet = async (req, res) => { +export const downloadTimesheet = async (req, res, next) => { try { const { timesheetId } = req.params; const { filePath, fileName } = await timesheetService.getDownloadInfo(timesheetId, { @@ -102,8 +95,7 @@ export const downloadTimesheet = async (req, res) => { res.download(filePath, fileName); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -111,7 +103,7 @@ export const downloadTimesheet = async (req, res) => { * Delete timesheet * DELETE /api/timesheets/:timesheetId */ -export const deleteTimesheet = async (req, res) => { +export const deleteTimesheet = async (req, res, next) => { try { const { timesheetId } = req.params; @@ -125,7 +117,6 @@ export const deleteTimesheet = async (req, res) => { message: 'Timesheet bol zmazaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/controllers/todo.controller.js b/src/controllers/todo.controller.js index 539c5b3..c773300 100644 --- a/src/controllers/todo.controller.js +++ b/src/controllers/todo.controller.js @@ -1,11 +1,10 @@ import * as todoService from '../services/todo.service.js'; -import { formatErrorResponse } from '../utils/errors.js'; /** * Get all todos * GET /api/todos?search=query&projectId=xxx&companyId=xxx&assignedTo=xxx&status=xxx */ -export const getAllTodos = async (req, res) => { +export const getAllTodos = async (req, res, next) => { try { const { search, projectId, companyId, assignedTo, status, completed, priority } = req.query; @@ -32,8 +31,7 @@ export const getAllTodos = async (req, res) => { data: todos, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -41,7 +39,7 @@ export const getAllTodos = async (req, res) => { * Get my todos (assigned to current user) * GET /api/todos/my?status=xxx */ -export const getMyTodos = async (req, res) => { +export const getMyTodos = async (req, res, next) => { try { const userId = req.userId; const { status } = req.query; @@ -59,8 +57,7 @@ export const getMyTodos = async (req, res) => { data: todos, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -68,7 +65,7 @@ export const getMyTodos = async (req, res) => { * Get todo by ID * GET /api/todos/:todoId */ -export const getTodoById = async (req, res) => { +export const getTodoById = async (req, res, next) => { try { const { todoId } = req.params; @@ -79,8 +76,7 @@ export const getTodoById = async (req, res) => { data: todo, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -88,7 +84,7 @@ export const getTodoById = async (req, res) => { * Get todo with relations (project, company, assigned user, notes) * GET /api/todos/:todoId/details */ -export const getTodoWithRelations = async (req, res) => { +export const getTodoWithRelations = async (req, res, next) => { try { const { todoId } = req.params; @@ -99,8 +95,7 @@ export const getTodoWithRelations = async (req, res) => { data: todo, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -109,7 +104,7 @@ export const getTodoWithRelations = async (req, res) => { * POST /api/todos * Body: { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } */ -export const createTodo = async (req, res) => { +export const createTodo = async (req, res, next) => { try { const userId = req.userId; const data = req.body; @@ -123,8 +118,7 @@ export const createTodo = async (req, res) => { message: 'Todo bolo vytvorené', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -133,7 +127,7 @@ export const createTodo = async (req, res) => { * PATCH /api/todos/:todoId * Body: { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } */ -export const updateTodo = async (req, res) => { +export const updateTodo = async (req, res, next) => { try { const { todoId } = req.params; const data = req.body; @@ -147,8 +141,7 @@ export const updateTodo = async (req, res) => { message: 'Todo bolo aktualizované', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -156,7 +149,7 @@ export const updateTodo = async (req, res) => { * Delete todo * DELETE /api/todos/:todoId */ -export const deleteTodo = async (req, res) => { +export const deleteTodo = async (req, res, next) => { try { const { todoId } = req.params; @@ -167,8 +160,7 @@ export const deleteTodo = async (req, res) => { message: result.message, }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; @@ -176,7 +168,7 @@ export const deleteTodo = async (req, res) => { * Toggle todo completion status * PATCH /api/todos/:todoId/toggle */ -export const toggleTodo = async (req, res) => { +export const toggleTodo = async (req, res, next) => { try { const { todoId } = req.params; @@ -194,7 +186,6 @@ export const toggleTodo = async (req, res) => { message: 'Todo status aktualizovaný', }); } catch (error) { - const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development'); - res.status(error.statusCode || 500).json(errorResponse); + return next(error); } }; diff --git a/src/middlewares/auth/authMiddleware.js b/src/middlewares/auth/authMiddleware.js index bb11c9b..ef6baf5 100644 --- a/src/middlewares/auth/authMiddleware.js +++ b/src/middlewares/auth/authMiddleware.js @@ -48,12 +48,6 @@ export const authenticate = async (req, res, next) => { }); } - return res.status(401).json({ - success: false, - error: { - message: 'Neplatný alebo expirovaný token', - statusCode: 401, - }, - }); + return next(error); } }; diff --git a/src/middlewares/global/errorHandler.js b/src/middlewares/global/errorHandler.js index e308108..efc3924 100644 --- a/src/middlewares/global/errorHandler.js +++ b/src/middlewares/global/errorHandler.js @@ -2,11 +2,19 @@ import { formatErrorResponse } from '../../utils/errors.js'; import { logger } from '../../utils/logger.js'; export function errorHandler(err, req, res, next) { + if (res.headersSent) { + return next(err); + } + // Log error logger.error('Unhandled error', err); // Get status code - const statusCode = err.statusCode || res.statusCode !== 200 ? res.statusCode : 500; + const statusCode = typeof err.statusCode === 'number' + ? err.statusCode + : res.statusCode >= 400 + ? res.statusCode + : 500; // Format error response const errorResponse = formatErrorResponse(err, process.env.NODE_ENV === 'development'); diff --git a/src/utils/jwt.js b/src/utils/jwt.js index cd7ab3d..5f8d37e 100644 --- a/src/utils/jwt.js +++ b/src/utils/jwt.js @@ -1,4 +1,5 @@ import jwt from 'jsonwebtoken'; +import { AuthenticationError } from './errors.js'; /** * Generuje access JWT token @@ -33,10 +34,10 @@ export const verifyAccessToken = (token) => { return jwt.verify(token, process.env.JWT_SECRET); } catch (error) { if (error.name === 'TokenExpiredError') { - throw new Error('Token expiroval'); + throw new AuthenticationError('Token expiroval'); } if (error.name === 'JsonWebTokenError') { - throw new Error('Neplatný token'); + throw new AuthenticationError('Neplatný token'); } throw error; } @@ -53,10 +54,10 @@ export const verifyRefreshToken = (token) => { return jwt.verify(token, process.env.JWT_REFRESH_SECRET); } catch (error) { if (error.name === 'TokenExpiredError') { - throw new Error('Refresh token expiroval'); + throw new AuthenticationError('Refresh token expiroval'); } if (error.name === 'JsonWebTokenError') { - throw new Error('Neplatný refresh token'); + throw new AuthenticationError('Neplatný refresh token'); } throw error; }