diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js index 736c2ea..50710b7 100644 --- a/src/controllers/admin.controller.js +++ b/src/controllers/admin.controller.js @@ -1,6 +1,5 @@ import * as adminService from '../services/admin.service.js'; import * as statusService from '../services/status.service.js'; -import { logUserCreation, logRoleChange, logUserDeleted } from '../services/audit.service.js'; import { triggerEventNotifications } from '../cron/index.js'; /** @@ -21,17 +20,8 @@ export const createUser = async (req, res, next) => { lastName, role, email, - emailPassword - ); - - // Log user creation - await logUserCreation( - adminId, - result.user.id, - username, - result.user.role, - ipAddress, - userAgent + emailPassword, + { userId: adminId, ipAddress, userAgent } ); res.status(201).json({ @@ -110,10 +100,7 @@ export const changeUserRole = async (req, res, next) => { const userAgent = req.headers['user-agent']; try { - const result = await adminService.changeUserRole(userId, role); - - // Log role change - await logRoleChange(adminId, userId, result.oldRole, result.newRole, ipAddress, userAgent); + const result = await adminService.changeUserRole(userId, role, { userId: adminId, ipAddress, userAgent }); res.status(200).json({ success: true, @@ -177,13 +164,7 @@ export const deleteUser = async (req, res, next) => { const userAgent = req.headers['user-agent']; try { - // Get user info before deletion for audit - const userToDelete = await adminService.getUserById(userId); - - const result = await adminService.deleteUser(userId); - - // Log user deletion - await logUserDeleted(adminId, userId, userToDelete.username, ipAddress, userAgent); + const result = await adminService.deleteUser(userId, { userId: adminId, ipAddress, userAgent }); res.status(200).json({ success: true, diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 409577d..8232359 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,11 +1,4 @@ import * as authService from '../services/auth.service.js'; -import { - logLoginAttempt, - logPasswordChange, - logEmailLink, - logLogin, - logLogout, -} from '../services/audit.service.js'; import { verifyRefreshToken, generateAccessToken } from '../utils/jwt.js'; import { getUserById } from '../services/auth.service.js'; @@ -26,10 +19,6 @@ export const login = async (req, res, next) => { userAgent ); - // Log successful login - await logLoginAttempt(username, true, ipAddress, userAgent); - await logLogin(result.user.id, username, ipAddress, userAgent); - // Nastav cookie s access tokenom (httpOnly, secure) const isProduction = process.env.NODE_ENV === 'production'; res.cookie('accessToken', result.tokens.accessToken, { @@ -60,9 +49,6 @@ export const login = async (req, res, next) => { message: 'Prihlásenie úspešné', }); } catch (error) { - // Log failed login - await logLoginAttempt(username, false, ipAddress, userAgent, error.message); - next(error); } }; @@ -141,10 +127,7 @@ export const setPassword = async (req, res, next) => { const userAgent = req.headers['user-agent']; try { - const result = await authService.setNewPassword(userId, newPassword); - - // Log password change - await logPasswordChange(userId, ipAddress, userAgent); + const result = await authService.setNewPassword(userId, newPassword, { userId, ipAddress, userAgent }); res.status(200).json({ success: true, @@ -168,10 +151,7 @@ export const linkEmail = async (req, res, next) => { const userAgent = req.headers['user-agent']; try { - const result = await authService.linkEmail(userId, email, emailPassword); - - // Log email link - await logEmailLink(userId, email, ipAddress, userAgent); + const result = await authService.linkEmail(userId, email, emailPassword, { userId, ipAddress, userAgent }); res.status(200).json({ success: true, @@ -218,10 +198,7 @@ export const logout = async (req, res, next) => { const ipAddress = req.ip || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; - // Log logout event - await logLogout(userId, ipAddress, userAgent); - - const result = await authService.logout(); + const result = await authService.logout({ userId, ipAddress, userAgent }); // Vymaž cookies res.clearCookie('accessToken'); diff --git a/src/controllers/company.controller.js b/src/controllers/company.controller.js index d99bc35..47ba82f 100644 --- a/src/controllers/company.controller.js +++ b/src/controllers/company.controller.js @@ -2,16 +2,6 @@ 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 { - logCompanyCreated, - logCompanyDeleted, - logCompanyUpdated, - logCompanyUserAssigned, - logCompanyUserRemoved, - logCompanyReminderCreated, - logCompanyReminderUpdated, - logCompanyReminderDeleted, -} from '../services/audit.service.js'; /** * Get all companies @@ -125,10 +115,7 @@ export const createCompany = async (req, res, next) => { const userId = req.userId; const data = req.body; - const company = await companyService.createCompany(userId, data); - - // Log audit event - await logCompanyCreated(userId, company.id, company.name, req.ip, req.headers['user-agent']); + const company = await companyService.createCompany(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -151,20 +138,7 @@ export const updateCompany = async (req, res, next) => { const { companyId } = req.params; const data = req.body; - // Get old company for audit - const oldCompany = await companyService.getCompanyById(companyId); - - const company = await companyService.updateCompany(companyId, data); - - // Log audit event - await logCompanyUpdated( - userId, - companyId, - { name: oldCompany.name }, - { name: company.name }, - req.ip, - req.headers['user-agent'] - ); + const company = await companyService.updateCompany(companyId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -185,14 +159,7 @@ export const deleteCompany = async (req, res, next) => { const { companyId } = req.params; const userId = req.userId; - // Get company info before deleting - const company = await companyService.getCompanyById(companyId); - const companyName = company?.name; - - const result = await companyService.deleteCompany(companyId); - - // Log audit event - await logCompanyDeleted(userId, companyId, companyName, req.ip, req.headers['user-agent']); + const result = await companyService.deleteCompany(companyId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -318,10 +285,7 @@ export const createCompanyReminder = async (req, res, next) => { const { companyId } = req.params; const { description, dueDate, isChecked } = req.body; - const reminder = await companyReminderService.createReminder(companyId, { description, dueDate, isChecked }); - - // Log audit event - await logCompanyReminderCreated(userId, reminder.id, companyId, dueDate, req.ip, req.headers['user-agent']); + const reminder = await companyReminderService.createReminder(companyId, { description, dueDate, isChecked }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -339,13 +303,7 @@ export const updateCompanyReminder = async (req, res, next) => { const { companyId, reminderId } = req.params; const { description, dueDate, isChecked } = req.body; - // Get old reminder for audit - const oldReminder = await companyReminderService.getReminderById(reminderId); - - const reminder = await companyReminderService.updateReminder(companyId, reminderId, { description, dueDate, isChecked }); - - // Log audit event - await logCompanyReminderUpdated(userId, reminderId, companyId, oldReminder?.dueDate, dueDate, req.ip, req.headers['user-agent']); + const reminder = await companyReminderService.updateReminder(companyId, reminderId, { description, dueDate, isChecked }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -362,13 +320,7 @@ export const deleteCompanyReminder = async (req, res, next) => { const userId = req.userId; const { companyId, reminderId } = req.params; - // Get reminder for audit before deletion - const reminder = await companyReminderService.getReminderById(reminderId); - - const result = await companyReminderService.deleteReminder(companyId, reminderId); - - // Log audit event - await logCompanyReminderDeleted(userId, reminderId, companyId, reminder?.dueDate, req.ip, req.headers['user-agent']); + const result = await companyReminderService.deleteReminder(companyId, reminderId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -453,20 +405,7 @@ export const assignUserToCompany = async (req, res, next) => { const { companyId } = req.params; const { userId, role } = req.body; - // Get company name for audit - const company = await companyService.getCompanyById(companyId); - - const assignment = await companyService.assignUserToCompany(companyId, userId, currentUserId, role); - - // Log audit event - await logCompanyUserAssigned( - currentUserId, - companyId, - userId, - company.name, - req.ip, - req.headers['user-agent'] - ); + const assignment = await companyService.assignUserToCompany(companyId, userId, currentUserId, role, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -487,20 +426,7 @@ export const removeUserFromCompany = async (req, res, next) => { const currentUserId = req.userId; const { companyId, userId } = req.params; - // Get company name for audit - const company = await companyService.getCompanyById(companyId); - - const result = await companyService.removeUserFromCompany(companyId, userId); - - // Log audit event - await logCompanyUserRemoved( - currentUserId, - companyId, - userId, - company.name, - req.ip, - req.headers['user-agent'] - ); + const result = await companyService.removeUserFromCompany(companyId, userId, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, diff --git a/src/controllers/contact.controller.js b/src/controllers/contact.controller.js index 39ecdbe..4f668e4 100644 --- a/src/controllers/contact.controller.js +++ b/src/controllers/contact.controller.js @@ -1,7 +1,6 @@ import * as contactService from '../services/contact.service.js'; import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap/index.js'; import * as emailAccountService from '../services/email-account.service.js'; -import { logContactLinkedToCompany, logCompanyCreatedFromContact } from '../services/audit.service.js'; /** * Get all contacts for an email account @@ -248,17 +247,7 @@ export const linkCompanyToContact = async (req, res, next) => { // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); - const updated = await contactService.linkCompanyToContact(contactId, accountId, companyId); - - // Log audit event - await logContactLinkedToCompany( - userId, - contactId, - companyId, - updated.company?.name || companyId, - req.ip, - req.headers['user-agent'] - ); + const updated = await contactService.linkCompanyToContact(contactId, accountId, companyId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -330,17 +319,7 @@ export const createCompanyFromContact = async (req, res, next) => { // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); - const result = await contactService.createCompanyFromContact(contactId, accountId, userId, companyData); - - // Log audit event - await logCompanyCreatedFromContact( - userId, - contactId, - result.company?.id, - result.company?.name, - req.ip, - req.headers['user-agent'] - ); + const result = await contactService.createCompanyFromContact(contactId, accountId, userId, companyData, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, diff --git a/src/controllers/note.controller.js b/src/controllers/note.controller.js index fbc5575..8aefe1c 100644 --- a/src/controllers/note.controller.js +++ b/src/controllers/note.controller.js @@ -1,5 +1,4 @@ import * as noteService from '../services/note.service.js'; -import { logNoteCreated, logNoteUpdated, logNoteDeleted } from '../services/audit.service.js'; /** * Get all notes @@ -58,10 +57,7 @@ export const createNote = async (req, res, next) => { const userId = req.userId; const data = req.body; - const note = await noteService.createNote(userId, data); - - // Log audit event - await logNoteCreated(userId, note.id, note.content, req.ip, req.headers['user-agent']); + const note = await noteService.createNote(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -84,13 +80,7 @@ export const updateNote = async (req, res, next) => { const { noteId } = req.params; const data = req.body; - // Get old note for audit - const oldNote = await noteService.getNoteById(noteId); - - const note = await noteService.updateNote(noteId, data); - - // Log audit event - await logNoteUpdated(userId, noteId, oldNote.content, note.content, req.ip, req.headers['user-agent']); + const note = await noteService.updateNote(noteId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -111,13 +101,7 @@ export const deleteNote = async (req, res, next) => { const userId = req.userId; const { noteId } = req.params; - // Get note for audit before deletion - const note = await noteService.getNoteById(noteId); - - const result = await noteService.deleteNote(noteId); - - // Log audit event - await logNoteDeleted(userId, noteId, note.content, req.ip, req.headers['user-agent']); + const result = await noteService.deleteNote(noteId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, diff --git a/src/controllers/project.controller.js b/src/controllers/project.controller.js index bf53a7e..cc4a399 100644 --- a/src/controllers/project.controller.js +++ b/src/controllers/project.controller.js @@ -1,12 +1,5 @@ import * as projectService from '../services/project.service.js'; import * as noteService from '../services/note.service.js'; -import { - logProjectCreated, - logProjectDeleted, - logProjectUpdated, - logProjectUserAssigned, - logProjectUserRemoved, -} from '../services/audit.service.js'; /** * Get all projects @@ -79,10 +72,7 @@ export const createProject = async (req, res, next) => { const userId = req.userId; const data = req.body; - const project = await projectService.createProject(userId, data); - - // Log audit event - await logProjectCreated(userId, project.id, project.name, req.ip, req.headers['user-agent']); + const project = await projectService.createProject(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -105,20 +95,7 @@ export const updateProject = async (req, res, next) => { const { projectId } = req.params; const data = req.body; - // Get old project for audit - const oldProject = await projectService.getProjectById(projectId); - - const project = await projectService.updateProject(projectId, data); - - // Log audit event - await logProjectUpdated( - userId, - projectId, - { name: oldProject.name }, - { name: project.name }, - req.ip, - req.headers['user-agent'] - ); + const project = await projectService.updateProject(projectId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -139,14 +116,7 @@ export const deleteProject = async (req, res, next) => { const { projectId } = req.params; const userId = req.userId; - // Get project info before deleting - const project = await projectService.getProjectById(projectId); - const projectName = project?.name; - - const result = await projectService.deleteProject(projectId); - - // Log audit event - await logProjectDeleted(userId, projectId, projectName, req.ip, req.headers['user-agent']); + const result = await projectService.deleteProject(projectId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -277,24 +247,12 @@ export const assignUserToProject = async (req, res, next) => { const { projectId } = req.params; const { userId, role } = req.body; - // Get project name for audit - const project = await projectService.getProjectById(projectId); - const assignment = await projectService.assignUserToProject( projectId, userId, currentUserId, - role - ); - - // Log audit event - await logProjectUserAssigned( - currentUserId, - projectId, - userId, - project.name, - req.ip, - req.headers['user-agent'] + role, + { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] } ); res.status(201).json({ @@ -316,20 +274,7 @@ export const removeUserFromProject = async (req, res, next) => { const currentUserId = req.userId; const { projectId, userId } = req.params; - // Get project name for audit - const project = await projectService.getProjectById(projectId); - - const result = await projectService.removeUserFromProject(projectId, userId); - - // Log audit event - await logProjectUserRemoved( - currentUserId, - projectId, - userId, - project.name, - req.ip, - req.headers['user-agent'] - ); + const result = await projectService.removeUserFromProject(projectId, userId, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, diff --git a/src/controllers/time-tracking.controller.js b/src/controllers/time-tracking.controller.js index 8d8d4cd..4533a39 100644 --- a/src/controllers/time-tracking.controller.js +++ b/src/controllers/time-tracking.controller.js @@ -1,12 +1,4 @@ import * as timeTrackingService from '../services/time-tracking.service.js'; -import { - logTimerStarted, - logTimerStopped, - logTimerPaused, - logTimerResumed, - logTimeEntryUpdated, - logTimeEntryDeleted, -} from '../services/audit.service.js'; /** * Start a new time entry @@ -22,10 +14,7 @@ export const startTimeEntry = async (req, res, next) => { todoId, companyId, description, - }); - - // Log audit event - await logTimerStarted(userId, entry.id, description || 'Timer', req.ip, req.headers['user-agent']); + }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -52,10 +41,7 @@ export const stopTimeEntry = async (req, res, next) => { todoId, companyId, description, - }); - - // Log audit event - await logTimerStopped(userId, entry.id, description || 'Timer', entry.duration, req.ip, req.headers['user-agent']); + }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -76,9 +62,7 @@ export const pauseTimeEntry = async (req, res, next) => { const userId = req.userId; const { entryId } = req.params; - const entry = await timeTrackingService.pauseTimeEntry(entryId, userId); - - await logTimerPaused(userId, entry.id, req.ip, req.headers['user-agent']); + const entry = await timeTrackingService.pauseTimeEntry(entryId, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -99,9 +83,7 @@ export const resumeTimeEntry = async (req, res, next) => { const userId = req.userId; const { entryId } = req.params; - const entry = await timeTrackingService.resumeTimeEntry(entryId, userId); - - await logTimerResumed(userId, entry.id, req.ip, req.headers['user-agent']); + const entry = await timeTrackingService.resumeTimeEntry(entryId, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -280,9 +262,6 @@ export const updateTimeEntry = async (req, res, next) => { const { entryId } = req.params; const { startTime, endTime, projectId, todoId, companyId, description } = req.body; - // Get old entry for audit - const oldEntry = await timeTrackingService.getTimeEntryById(entryId); - const entry = await timeTrackingService.updateTimeEntry(entryId, { userId, role: req.user.role, @@ -293,17 +272,7 @@ export const updateTimeEntry = async (req, res, next) => { todoId, companyId, description, - }); - - // Log audit event - await logTimeEntryUpdated( - userId, - entryId, - { description: oldEntry.description, duration: oldEntry.duration }, - { description: entry.description, duration: entry.duration }, - req.ip, - req.headers['user-agent'] - ); + }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -324,23 +293,10 @@ export const deleteTimeEntry = async (req, res, next) => { const userId = req.userId; const { entryId } = req.params; - // Get entry for audit before deletion - const entry = await timeTrackingService.getTimeEntryById(entryId); - const result = await timeTrackingService.deleteTimeEntry(entryId, { userId, role: req.user.role, - }); - - // Log audit event - await logTimeEntryDeleted( - userId, - entryId, - entry.description || 'Time entry', - entry.duration, - req.ip, - req.headers['user-agent'] - ); + }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json(result); } catch (error) { diff --git a/src/controllers/timesheet.controller.js b/src/controllers/timesheet.controller.js index 1ad7bdd..5a9f5ac 100644 --- a/src/controllers/timesheet.controller.js +++ b/src/controllers/timesheet.controller.js @@ -1,6 +1,5 @@ import * as timesheetService from '../services/timesheet.service.js'; import { ForbiddenError } from '../utils/errors.js'; -import { logTimesheetUploaded, logTimesheetDeleted } from '../services/audit.service.js'; /** * Upload timesheet @@ -26,11 +25,9 @@ export const uploadTimesheet = async (req, res, next) => { year, month, file: req.file, + auditContext: { userId: req.userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }, }); - // Log audit event - await logTimesheetUploaded(req.userId, timesheet.id, year, month, req.ip, req.headers['user-agent']); - res.status(201).json({ success: true, data: { timesheet }, @@ -112,16 +109,10 @@ export const deleteTimesheet = async (req, res, next) => { const userId = req.userId; const { timesheetId } = req.params; - // Get timesheet for audit before deletion - const timesheet = await timesheetService.getTimesheetById(timesheetId); - await timesheetService.deleteTimesheet(timesheetId, { userId, role: req.user.role, - }); - - // Log audit event - await logTimesheetDeleted(userId, timesheetId, timesheet?.year, timesheet?.month, req.ip, req.headers['user-agent']); + }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, diff --git a/src/controllers/todo.controller.js b/src/controllers/todo.controller.js index 2f9929e..7e459a7 100644 --- a/src/controllers/todo.controller.js +++ b/src/controllers/todo.controller.js @@ -1,11 +1,4 @@ import * as todoService from '../services/todo.service.js'; -import { - logTodoCreated, - logTodoDeleted, - logTodoCompleted, - logTodoUpdated, - logTodoUncompleted, -} from '../services/audit.service.js'; /** * Get all todos @@ -119,10 +112,7 @@ export const createTodo = async (req, res, next) => { const userId = req.userId; const data = req.body; - const todo = await todoService.createTodo(userId, data); - - // Log audit event - await logTodoCreated(userId, todo.id, todo.title, req.ip, req.headers['user-agent']); + const todo = await todoService.createTodo(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(201).json({ success: true, @@ -145,20 +135,7 @@ export const updateTodo = async (req, res, next) => { const { todoId } = req.params; const data = req.body; - // Get old todo for audit - const oldTodo = await todoService.getTodoById(todoId); - - const todo = await todoService.updateTodo(todoId, data, userId); - - // Log audit event - await logTodoUpdated( - userId, - todoId, - { title: oldTodo.title }, - { title: todo.title }, - req.ip, - req.headers['user-agent'] - ); + const todo = await todoService.updateTodo(todoId, data, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -179,14 +156,7 @@ export const deleteTodo = async (req, res, next) => { const { todoId } = req.params; const userId = req.userId; - // Get todo info before deleting - const todo = await todoService.getTodoById(todoId); - const todoTitle = todo?.title; - - const result = await todoService.deleteTodo(todoId); - - // Log audit event - await logTodoDeleted(userId, todoId, todoTitle, req.ip, req.headers['user-agent']); + const result = await todoService.deleteTodo(todoId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, @@ -213,14 +183,7 @@ export const toggleTodo = async (req, res, next) => { // Toggle completed status const updated = await todoService.updateTodo(todoId, { status: wasCompleted ? 'pending' : 'completed', - }); - - // Log audit event - if (!wasCompleted) { - await logTodoCompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']); - } else { - await logTodoUncompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']); - } + }, null, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }); res.status(200).json({ success: true, diff --git a/src/services/admin.service.js b/src/services/admin.service.js index 1a55c3c..e093ff8 100644 --- a/src/services/admin.service.js +++ b/src/services/admin.service.js @@ -5,6 +5,7 @@ import { hashPassword, generateTempPassword } from '../utils/password.js'; import { ConflictError, NotFoundError } from '../utils/errors.js'; import * as emailAccountService from './email-account.service.js'; import { logger } from '../utils/logger.js'; +import { logUserCreation, logRoleChange, logUserDeleted } from './audit.service.js'; /** * Skontroluj či username už neexistuje @@ -22,7 +23,7 @@ export const checkUsernameExists = async (username) => { /** * Vytvorenie nového usera s automatic temporary password */ -export const createUser = async (username, firstName, lastName, role, email, emailPassword) => { +export const createUser = async (username, firstName, lastName, role, email, emailPassword, auditContext = null) => { // Skontroluj či username už neexistuje if (await checkUsernameExists(username)) { throw new ConflictError('Username už existuje'); @@ -72,6 +73,10 @@ export const createUser = async (username, firstName, lastName, role, email, ema } } + if (auditContext) { + await logUserCreation(auditContext.userId, newUser.id, username, newUser.role, auditContext.ipAddress, auditContext.userAgent); + } + return { user: newUser, tempPassword, @@ -136,7 +141,7 @@ export const getUserById = async (userId) => { /** * Zmena role usera */ -export const changeUserRole = async (userId, newRole) => { +export const changeUserRole = async (userId, newRole, auditContext = null) => { // Získaj starú rolu const [user] = await db .select() @@ -159,6 +164,10 @@ export const changeUserRole = async (userId, newRole) => { }) .where(eq(users.id, userId)); + if (auditContext) { + await logRoleChange(auditContext.userId, userId, oldRole, newRole, auditContext.ipAddress, auditContext.userAgent); + } + return { userId, oldRole, newRole }; }; @@ -233,7 +242,7 @@ export const resetUserPassword = async (userId) => { /** * Zmazanie usera */ -export const deleteUser = async (userId) => { +export const deleteUser = async (userId, auditContext = null) => { const [user] = await db .select() .from(users) @@ -264,6 +273,9 @@ export const deleteUser = async (userId) => { const emailAccountIds = userEmailAccountLinks.map(link => link.emailAccountId); + // Save username before deletion for audit + const deletedUsername = user.username; + // Delete user (cascades userEmailAccounts links) await db.delete(users).where(eq(users.id, userId)); @@ -281,5 +293,9 @@ export const deleteUser = async (userId) => { } } + if (auditContext) { + await logUserDeleted(auditContext.userId, userId, deletedUsername, auditContext.ipAddress, auditContext.userAgent); + } + return { deletedEmailAccounts }; }; diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 715fec3..472c5f6 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -8,73 +8,84 @@ import { AuthenticationError, NotFoundError, } from '../utils/errors.js'; +import { logLoginAttempt, logLogin, logLogout, logPasswordChange, logEmailLink } from './audit.service.js'; /** * KROK 1: Login s temporary password */ export const loginWithTempPassword = async (username, password, ipAddress, userAgent) => { - // Najdi usera - const [user] = await db - .select() - .from(users) - .where(eq(users.username, username)) - .limit(1); + try { + // Najdi usera + const [user] = await db + .select() + .from(users) + .where(eq(users.username, username)) + .limit(1); - if (!user) { - throw new AuthenticationError('Nesprávne prihlasovacie údaje'); - } - - // Ak už user zmenil heslo, použije sa permanentné heslo - if (user.changedPassword && user.password) { - const isValid = await comparePassword(password, user.password); - if (!isValid) { + if (!user) { throw new AuthenticationError('Nesprávne prihlasovacie údaje'); } - } else { - // Ak ešte nezmenil heslo, použije sa temporary password - if (!user.tempPassword) { - throw new AuthenticationError('Účet nie je správne nastavený'); + + // Ak už user zmenil heslo, použije sa permanentné heslo + if (user.changedPassword && user.password) { + const isValid = await comparePassword(password, user.password); + if (!isValid) { + throw new AuthenticationError('Nesprávne prihlasovacie údaje'); + } + } else { + // Ak ešte nezmenil heslo, použije sa temporary password + if (!user.tempPassword) { + throw new AuthenticationError('Účet nie je správne nastavený'); + } + + // Temporary password môže byť plain text alebo hash (závisí od seeding) + // Pre bezpečnosť ho budeme hashovať pri vytvorení + const isValid = await comparePassword(password, user.tempPassword); + if (!isValid) { + throw new AuthenticationError('Nesprávne prihlasovacie údaje'); + } } - // Temporary password môže byť plain text alebo hash (závisí od seeding) - // Pre bezpečnosť ho budeme hashovať pri vytvorení - const isValid = await comparePassword(password, user.tempPassword); - if (!isValid) { - throw new AuthenticationError('Nesprávne prihlasovacie údaje'); - } + // Update last login + await db + .update(users) + .set({ lastLogin: new Date() }) + .where(eq(users.id, user.id)); + + // Generuj JWT tokeny + const tokens = generateTokenPair(user); + + // Check if user has email accounts (many-to-many) + const userEmailAccounts = await emailAccountService.getUserEmailAccounts(user.id); + + // Log successful login + await logLoginAttempt(username, true, ipAddress, userAgent); + await logLogin(user.id, username, ipAddress, userAgent); + + return { + user: { + id: user.id, + username: user.username, + firstName: user.firstName, + lastName: user.lastName, + role: user.role, + changedPassword: user.changedPassword, + }, + tokens, + needsPasswordChange: !user.changedPassword, + needsEmailSetup: userEmailAccounts.length === 0, + }; + } catch (error) { + // Log failed login attempt + await logLoginAttempt(username, false, ipAddress, userAgent, error.message); + throw error; } - - // Update last login - await db - .update(users) - .set({ lastLogin: new Date() }) - .where(eq(users.id, user.id)); - - // Generuj JWT tokeny - const tokens = generateTokenPair(user); - - // Check if user has email accounts (many-to-many) - const userEmailAccounts = await emailAccountService.getUserEmailAccounts(user.id); - - return { - user: { - id: user.id, - username: user.username, - firstName: user.firstName, - lastName: user.lastName, - role: user.role, - changedPassword: user.changedPassword, - }, - tokens, - needsPasswordChange: !user.changedPassword, - needsEmailSetup: userEmailAccounts.length === 0, - }; }; /** * KROK 2: Nastavenie nového hesla */ -export const setNewPassword = async (userId, newPassword) => { +export const setNewPassword = async (userId, newPassword, auditContext = null) => { const [user] = await db .select() .from(users) @@ -104,6 +115,10 @@ export const setNewPassword = async (userId, newPassword) => { }) .where(eq(users.id, userId)); + if (auditContext) { + await logPasswordChange(auditContext.userId, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Heslo úspešne nastavené', @@ -114,7 +129,7 @@ export const setNewPassword = async (userId, newPassword) => { * KROK 3: Pripojenie emailu s JMAP validáciou * Používa many-to-many vzťah cez userEmailAccounts */ -export const linkEmail = async (userId, email, emailPassword) => { +export const linkEmail = async (userId, email, emailPassword, auditContext = null) => { const [user] = await db .select() .from(users) @@ -132,6 +147,10 @@ export const linkEmail = async (userId, email, emailPassword) => { emailPassword ); + if (auditContext) { + await logEmailLink(auditContext.userId, email, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, accountId: newEmailAccount.jmapAccountId, @@ -167,7 +186,11 @@ export const skipEmailSetup = async (userId) => { /** * Logout - clear tokens (handled on client side) */ -export const logout = async () => { +export const logout = async (auditContext = null) => { + if (auditContext) { + await logLogout(auditContext.userId, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Úspešne odhlásený', diff --git a/src/services/company-reminder.service.js b/src/services/company-reminder.service.js index 063e200..ecaf2b5 100644 --- a/src/services/company-reminder.service.js +++ b/src/services/company-reminder.service.js @@ -3,6 +3,7 @@ import { companies, companyReminders } from '../db/schema.js'; import { eq, desc, sql, and, lte, gte, isNull, or, inArray } from 'drizzle-orm'; import { NotFoundError, BadRequestError } from '../utils/errors.js'; import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js'; +import { logCompanyReminderCreated, logCompanyReminderUpdated, logCompanyReminderDeleted } from './audit.service.js'; const ensureCompanyExists = async (companyId) => { const [company] = await db @@ -44,7 +45,7 @@ export const getRemindersByCompanyId = async (companyId) => { return reminders; }; -export const createReminder = async (companyId, data) => { +export const createReminder = async (companyId, data, auditContext = null) => { await ensureCompanyExists(companyId); const description = data.description?.trim(); @@ -62,10 +63,14 @@ export const createReminder = async (companyId, data) => { }) .returning(); + if (auditContext) { + await logCompanyReminderCreated(auditContext.userId, reminder.id, companyId, data.dueDate, auditContext.ipAddress, auditContext.userAgent); + } + return reminder; }; -export const updateReminder = async (companyId, reminderId, data) => { +export const updateReminder = async (companyId, reminderId, data, auditContext = null) => { const reminder = await getReminderById(reminderId); if (reminder.companyId !== companyId) { @@ -91,10 +96,14 @@ export const updateReminder = async (companyId, reminderId, data) => { .where(eq(companyReminders.id, reminderId)) .returning(); + if (auditContext) { + await logCompanyReminderUpdated(auditContext.userId, reminderId, companyId, reminder.dueDate, data.dueDate, auditContext.ipAddress, auditContext.userAgent); + } + return updatedReminder; }; -export const deleteReminder = async (companyId, reminderId) => { +export const deleteReminder = async (companyId, reminderId, auditContext = null) => { const reminder = await getReminderById(reminderId); if (reminder.companyId !== companyId) { @@ -103,6 +112,10 @@ export const deleteReminder = async (companyId, reminderId) => { await db.delete(companyReminders).where(eq(companyReminders.id, reminderId)); + if (auditContext) { + await logCompanyReminderDeleted(auditContext.userId, reminderId, companyId, reminder.dueDate, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Reminder bol odstránený' }; }; diff --git a/src/services/company.service.js b/src/services/company.service.js index 460f4e7..16d48f0 100644 --- a/src/services/company.service.js +++ b/src/services/company.service.js @@ -3,6 +3,10 @@ import { companies, projects, todos, notes, companyReminders, companyUsers, user import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm'; import { NotFoundError, ConflictError } from '../utils/errors.js'; import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js'; +import { + logCompanyCreated, logCompanyDeleted, logCompanyUpdated, + logCompanyUserAssigned, logCompanyUserRemoved, +} from './audit.service.js'; /** * Get all companies @@ -111,7 +115,7 @@ export const getCompanyById = async (companyId) => { /** * Create new company */ -export const createCompany = async (userId, data) => { +export const createCompany = async (userId, data, auditContext = null) => { const { name, description, address, city, postalCode, country, phone, email, website, status } = data; // Check if company with same name already exists @@ -150,13 +154,17 @@ export const createCompany = async (userId, data) => { addedBy: userId, }); + if (auditContext) { + await logCompanyCreated(auditContext.userId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent); + } + return newCompany; }; /** * Update company */ -export const updateCompany = async (companyId, data) => { +export const updateCompany = async (companyId, data, auditContext = null) => { const company = await getCompanyById(companyId); const { name, description, address, city, postalCode, country, phone, email, website, status } = data; @@ -192,17 +200,25 @@ export const updateCompany = async (companyId, data) => { .where(eq(companies.id, companyId)) .returning(); + if (auditContext) { + await logCompanyUpdated(auditContext.userId, companyId, { name: company.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Delete company */ -export const deleteCompany = async (companyId) => { - await getCompanyById(companyId); // Check if exists +export const deleteCompany = async (companyId, auditContext = null) => { + const company = await getCompanyById(companyId); // Check if exists await db.delete(companies).where(eq(companies.id, companyId)); + if (auditContext) { + await logCompanyDeleted(auditContext.userId, companyId, company.name, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Firma bola odstránená' }; }; @@ -281,8 +297,8 @@ export const getCompanyUsers = async (companyId) => { /** * Assign user to company */ -export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null) => { - await getCompanyById(companyId); // Verify company exists +export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null, auditContext = null) => { + const company = await getCompanyById(companyId); // Verify company exists // Verify user exists const [user] = await db @@ -325,6 +341,10 @@ export const assignUserToCompany = async (companyId, userId, addedByUserId, role .where(eq(companyUsers.id, assignment.id)) .limit(1); + if (auditContext) { + await logCompanyUserAssigned(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent); + } + return { id: row.company_users.id, userId: row.company_users.userId, @@ -343,8 +363,8 @@ export const assignUserToCompany = async (companyId, userId, addedByUserId, role /** * Remove user from company */ -export const removeUserFromCompany = async (companyId, userId) => { - await getCompanyById(companyId); // Verify company exists +export const removeUserFromCompany = async (companyId, userId, auditContext = null) => { + const company = await getCompanyById(companyId); // Verify company exists // Check if user is assigned const [existing] = await db @@ -362,6 +382,10 @@ export const removeUserFromCompany = async (companyId, userId) => { .delete(companyUsers) .where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId))); + if (auditContext) { + await logCompanyUserRemoved(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Používateľ bol odstránený z firmy' }; }; diff --git a/src/services/contact.service.js b/src/services/contact.service.js index c0f6ff8..acd86b8 100644 --- a/src/services/contact.service.js +++ b/src/services/contact.service.js @@ -3,6 +3,7 @@ import { contacts, emails, companies, emailAccounts } from '../db/schema.js'; import { eq, and, desc, or, ne } from 'drizzle-orm'; import { NotFoundError, ConflictError } from '../utils/errors.js'; import { syncEmailsFromSender } from './jmap/index.js'; +import { logContactLinkedToCompany, logCompanyCreatedFromContact } from './audit.service.js'; /** * Get contacts with related data (emailAccount, company) using joins @@ -185,7 +186,7 @@ export const updateContact = async (contactId, emailAccountId, { name, notes }) /** * Link company to contact */ -export const linkCompanyToContact = async (contactId, emailAccountId, companyId) => { +export const linkCompanyToContact = async (contactId, emailAccountId, companyId, auditContext = null) => { const contact = await getContactById(contactId, emailAccountId); const [updated] = await db @@ -206,6 +207,10 @@ export const linkCompanyToContact = async (contactId, emailAccountId, companyId) }) .where(eq(emails.contactId, contactId)); + if (auditContext) { + await logContactLinkedToCompany(auditContext.userId, contactId, companyId, updated.company?.name || companyId, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; @@ -240,7 +245,7 @@ export const unlinkCompanyFromContact = async (contactId, emailAccountId) => { * Create company from contact * Creates a new company using contact's information and links it */ -export const createCompanyFromContact = async (contactId, emailAccountId, userId, companyData = {}) => { +export const createCompanyFromContact = async (contactId, emailAccountId, userId, companyData = {}, auditContext = null) => { const contact = await getContactById(contactId, emailAccountId); // Check if company with same name already exists @@ -291,6 +296,10 @@ export const createCompanyFromContact = async (contactId, emailAccountId, userId }) .where(eq(emails.contactId, contactId)); + if (auditContext) { + await logCompanyCreatedFromContact(auditContext.userId, contactId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent); + } + return { company: newCompany, contact: updatedContact, diff --git a/src/services/note.service.js b/src/services/note.service.js index 1592ddd..52c8d12 100644 --- a/src/services/note.service.js +++ b/src/services/note.service.js @@ -2,6 +2,7 @@ import { db } from '../config/database.js'; import { notes, companies, projects, todos, contacts, users } from '../db/schema.js'; import { eq, desc, ilike, or, and } from 'drizzle-orm'; import { NotFoundError } from '../utils/errors.js'; +import { logNoteCreated, logNoteUpdated, logNoteDeleted } from './audit.service.js'; /** * Get all notes @@ -86,7 +87,7 @@ export const getNoteById = async (noteId) => { /** * Create new note */ -export const createNote = async (userId, data) => { +export const createNote = async (userId, data, auditContext = null) => { const { title, content, companyId, projectId, todoId, contactId, dueDate } = data; // Verify company exists if provided @@ -155,13 +156,17 @@ export const createNote = async (userId, data) => { }) .returning(); + if (auditContext) { + await logNoteCreated(auditContext.userId, newNote.id, newNote.content, auditContext.ipAddress, auditContext.userAgent); + } + return newNote; }; /** * Update note */ -export const updateNote = async (noteId, data) => { +export const updateNote = async (noteId, data, auditContext = null) => { const note = await getNoteById(noteId); const { title, content, companyId, projectId, todoId, contactId, dueDate } = data; @@ -239,17 +244,25 @@ export const updateNote = async (noteId, data) => { .where(eq(notes.id, noteId)) .returning(); + if (auditContext) { + await logNoteUpdated(auditContext.userId, noteId, note.content, updated.content, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Delete note */ -export const deleteNote = async (noteId) => { - await getNoteById(noteId); // Check if exists +export const deleteNote = async (noteId, auditContext = null) => { + const note = await getNoteById(noteId); // Check if exists await db.delete(notes).where(eq(notes.id, noteId)); + if (auditContext) { + await logNoteDeleted(auditContext.userId, noteId, note.content, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Poznámka bola odstránená' }; }; diff --git a/src/services/project.service.js b/src/services/project.service.js index f4b37ef..cd7dc07 100644 --- a/src/services/project.service.js +++ b/src/services/project.service.js @@ -3,6 +3,10 @@ import { projects, todos, notes, timesheets, companies, projectUsers, users } fr import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm'; import { NotFoundError, ConflictError } from '../utils/errors.js'; import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js'; +import { + logProjectCreated, logProjectDeleted, logProjectUpdated, + logProjectUserAssigned, logProjectUserRemoved, +} from './audit.service.js'; /** * Get all projects @@ -109,7 +113,7 @@ export const getProjectById = async (projectId) => { /** * Create new project */ -export const createProject = async (userId, data) => { +export const createProject = async (userId, data, auditContext = null) => { const { name, description, companyId, status, startDate, endDate } = data; // If companyId is provided, verify company exists @@ -146,13 +150,17 @@ export const createProject = async (userId, data) => { addedBy: userId, }); + if (auditContext) { + await logProjectCreated(auditContext.userId, newProject.id, newProject.name, auditContext.ipAddress, auditContext.userAgent); + } + return newProject; }; /** * Update project */ -export const updateProject = async (projectId, data) => { +export const updateProject = async (projectId, data, auditContext = null) => { const project = await getProjectById(projectId); const { name, description, companyId, status, startDate, endDate } = data; @@ -187,17 +195,25 @@ export const updateProject = async (projectId, data) => { .where(eq(projects.id, projectId)) .returning(); + if (auditContext) { + await logProjectUpdated(auditContext.userId, projectId, { name: project.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Delete project */ -export const deleteProject = async (projectId) => { - await getProjectById(projectId); // Check if exists +export const deleteProject = async (projectId, auditContext = null) => { + const project = await getProjectById(projectId); // Check if exists await db.delete(projects).where(eq(projects.id, projectId)); + if (auditContext) { + await logProjectDeleted(auditContext.userId, projectId, project.name, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Projekt bol odstránený' }; }; @@ -314,8 +330,8 @@ export const getProjectUsers = async (projectId) => { /** * Assign user to project */ -export const assignUserToProject = async (projectId, userId, addedByUserId, role = null) => { - await getProjectById(projectId); // Verify project exists +export const assignUserToProject = async (projectId, userId, addedByUserId, role = null, auditContext = null) => { + const project = await getProjectById(projectId); // Verify project exists // Verify user exists const [user] = await db @@ -358,6 +374,10 @@ export const assignUserToProject = async (projectId, userId, addedByUserId, role .where(eq(projectUsers.id, assignment.id)) .limit(1); + if (auditContext) { + await logProjectUserAssigned(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent); + } + return { id: row.project_users.id, userId: row.project_users.userId, @@ -376,8 +396,8 @@ export const assignUserToProject = async (projectId, userId, addedByUserId, role /** * Remove user from project */ -export const removeUserFromProject = async (projectId, userId) => { - await getProjectById(projectId); // Verify project exists +export const removeUserFromProject = async (projectId, userId, auditContext = null) => { + const project = await getProjectById(projectId); // Verify project exists // Check if user is assigned const [existing] = await db @@ -395,6 +415,10 @@ export const removeUserFromProject = async (projectId, userId) => { .delete(projectUsers) .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId))); + if (auditContext) { + await logProjectUserRemoved(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Používateľ bol odstránený z projektu' }; }; diff --git a/src/services/time-tracking.service.js b/src/services/time-tracking.service.js index cae4763..5084581 100644 --- a/src/services/time-tracking.service.js +++ b/src/services/time-tracking.service.js @@ -5,6 +5,10 @@ import { NotFoundError, BadRequestError, ForbiddenError } from '../utils/errors. import ExcelJS from 'exceljs'; import fs from 'fs/promises'; import path from 'path'; +import { + logTimerStarted, logTimerStopped, logTimerPaused, logTimerResumed, + logTimeEntryUpdated, logTimeEntryDeleted, +} from './audit.service.js'; // Helpers to normalize optional payload fields const normalizeOptionalId = (value) => { @@ -216,7 +220,7 @@ const saveTimesheetFile = async (workbook, { userId, year, month, filename }) => /** * Start a new time entry */ -export const startTimeEntry = async (userId, data) => { +export const startTimeEntry = async (userId, data, auditContext = null) => { const projectId = normalizeOptionalId(data.projectId); const todoId = normalizeOptionalId(data.todoId); const companyId = normalizeOptionalId(data.companyId); @@ -268,13 +272,17 @@ export const startTimeEntry = async (userId, data) => { }) .returning(); + if (auditContext) { + await logTimerStarted(auditContext.userId, newEntry.id, data.description || 'Timer', auditContext.ipAddress, auditContext.userAgent); + } + return newEntry; }; /** * Stop a running time entry */ -export const stopTimeEntry = async (entryId, userId, data = {}) => { +export const stopTimeEntry = async (entryId, userId, data = {}, auditContext = null) => { const projectId = normalizeOptionalId(data.projectId); const todoId = normalizeOptionalId(data.todoId); const companyId = normalizeOptionalId(data.companyId); @@ -315,13 +323,17 @@ export const stopTimeEntry = async (entryId, userId, data = {}) => { .where(eq(timeEntries.id, entryId)) .returning(); + if (auditContext) { + await logTimerStopped(auditContext.userId, updated.id, data.description || 'Timer', updated.duration, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Pause a running time entry */ -export const pauseTimeEntry = async (entryId, userId) => { +export const pauseTimeEntry = async (entryId, userId, auditContext = null) => { const entry = await getTimeEntryById(entryId); if (entry.userId !== userId) { @@ -345,13 +357,17 @@ export const pauseTimeEntry = async (entryId, userId) => { .where(eq(timeEntries.id, entryId)) .returning(); + if (auditContext) { + await logTimerPaused(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Resume a paused time entry */ -export const resumeTimeEntry = async (entryId, userId) => { +export const resumeTimeEntry = async (entryId, userId, auditContext = null) => { const entry = await getTimeEntryById(entryId); if (entry.userId !== userId) { @@ -380,6 +396,10 @@ export const resumeTimeEntry = async (entryId, userId) => { .where(eq(timeEntries.id, entryId)) .returning(); + if (auditContext) { + await logTimerResumed(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; @@ -603,7 +623,7 @@ export const generateMonthlyTimesheet = async (userId, year, month) => { /** * Update time entry */ -export const updateTimeEntry = async (entryId, actor, data) => { +export const updateTimeEntry = async (entryId, actor, data, auditContext = null) => { const { userId, role } = actor; const entry = await getTimeEntryById(entryId); @@ -652,13 +672,17 @@ export const updateTimeEntry = async (entryId, actor, data) => { .where(eq(timeEntries.id, entryId)) .returning(); + if (auditContext) { + await logTimeEntryUpdated(auditContext.userId, entryId, { description: entry.description, duration: entry.duration }, { description: updated.description, duration: updated.duration }, auditContext.ipAddress, auditContext.userAgent); + } + return updated; }; /** * Delete time entry */ -export const deleteTimeEntry = async (entryId, actor) => { +export const deleteTimeEntry = async (entryId, actor, auditContext = null) => { const { userId, role } = actor; const entry = await getTimeEntryById(entryId); @@ -672,6 +696,10 @@ export const deleteTimeEntry = async (entryId, actor) => { await db.delete(timeEntries).where(eq(timeEntries.id, entryId)); + if (auditContext) { + await logTimeEntryDeleted(auditContext.userId, entryId, entry.description || 'Time entry', entry.duration, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Záznam bol odstránený' }; }; diff --git a/src/services/timesheet.service.js b/src/services/timesheet.service.js index 6c4b058..e1d0e7b 100644 --- a/src/services/timesheet.service.js +++ b/src/services/timesheet.service.js @@ -5,6 +5,7 @@ import { timesheets, users } from '../db/schema.js'; import { and, desc, eq } from 'drizzle-orm'; import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js'; import { logger } from '../utils/logger.js'; +import { logTimesheetUploaded, logTimesheetDeleted } from './audit.service.js'; const ALLOWED_MIME_TYPES = [ 'application/pdf', @@ -91,7 +92,7 @@ const generateTimesheetFileName = (firstName, lastName, username, year, month, f return `${namePrefix}-vykazprace-${year}-${monthStr}${fileExt}`; }; -export const uploadTimesheet = async ({ userId, year, month, file }) => { +export const uploadTimesheet = async ({ userId, year, month, file, auditContext = null }) => { if (!file) { throw new BadRequestError('Súbor nebol nahraný'); } @@ -138,7 +139,13 @@ export const uploadTimesheet = async ({ userId, year, month, file }) => { }) .returning(); - return sanitizeTimesheet(newTimesheet); + const sanitized = sanitizeTimesheet(newTimesheet); + + if (auditContext) { + await logTimesheetUploaded(auditContext.userId, newTimesheet.id, parsedYear, parsedMonth, auditContext.ipAddress, auditContext.userAgent); + } + + return sanitized; } catch (error) { await safeUnlink(filePath); throw error; @@ -225,10 +232,14 @@ export const getDownloadInfo = async (timesheetId, { userId, role }) => { }; }; -export const deleteTimesheet = async (timesheetId, { userId, role }) => { +export const deleteTimesheet = async (timesheetId, { userId, role }, auditContext = null) => { const timesheet = await ensureTimesheetExists(timesheetId); assertAccess(timesheet, { userId, role }); await safeUnlink(timesheet.filePath); await db.delete(timesheets).where(eq(timesheets.id, timesheetId)); + + if (auditContext) { + await logTimesheetDeleted(auditContext.userId, timesheetId, timesheet.year, timesheet.month, auditContext.ipAddress, auditContext.userAgent); + } }; diff --git a/src/services/todo.service.js b/src/services/todo.service.js index 254be4a..c0b3dcd 100644 --- a/src/services/todo.service.js +++ b/src/services/todo.service.js @@ -5,6 +5,9 @@ import { NotFoundError } from '../utils/errors.js'; import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js'; import { sendPushNotificationToUsers } from './push.service.js'; import { logger } from '../utils/logger.js'; +import { + logTodoCreated, logTodoDeleted, logTodoCompleted, logTodoUpdated, logTodoUncompleted, +} from './audit.service.js'; /** * Get all todos @@ -142,7 +145,7 @@ export const getTodoById = async (todoId) => { * @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, auditContext = null) => { const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data; // Verify project exists if provided @@ -228,6 +231,10 @@ export const createTodo = async (userId, data) => { } } + if (auditContext) { + await logTodoCreated(auditContext.userId, newTodo.id, newTodo.title, auditContext.ipAddress, auditContext.userAgent); + } + return newTodo; }; @@ -237,7 +244,7 @@ export const createTodo = async (userId, data) => { * @param {object} data - Updated data including assignedUserIds array * @param {string} updatedByUserId - ID of user making the update (for notifications) */ -export const updateTodo = async (todoId, data, updatedByUserId = null) => { +export const updateTodo = async (todoId, data, updatedByUserId = null, auditContext = null) => { const todo = await getTodoById(todoId); const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data; @@ -352,17 +359,32 @@ export const updateTodo = async (todoId, data, updatedByUserId = null) => { } } + if (auditContext) { + // Detect status changes for specific audit events + if (status === 'completed' && todo.status !== 'completed') { + await logTodoCompleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent); + } else if (status && status !== 'completed' && todo.status === 'completed') { + await logTodoUncompleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent); + } else { + await logTodoUpdated(auditContext.userId, todoId, { title: todo.title }, { title: updated.title }, auditContext.ipAddress, auditContext.userAgent); + } + } + return updated; }; /** * Delete todo */ -export const deleteTodo = async (todoId) => { - await getTodoById(todoId); // Check if exists +export const deleteTodo = async (todoId, auditContext = null) => { + const todo = await getTodoById(todoId); // Check if exists await db.delete(todos).where(eq(todos.id, todoId)); + if (auditContext) { + await logTodoDeleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent); + } + return { success: true, message: 'Todo bolo odstránené' }; };