feat: Add comprehensive audit logging system
- Add audit logging for contacts (link company, create company from contact) - Add audit logging for notes (create, update, delete) - Add audit logging for companies (update, user assign/remove, reminder CRUD) - Add audit logging for projects (update, user assign/remove) - Add audit logging for todos (update, uncomplete) - Add audit logging for time entries (update, delete) - Add audit logging for timesheets (upload, delete) - Add audit logging for user deletion - Add pagination and filters to audit logs API (userId, action, resource, dateFrom, dateTo) - Add endpoints for distinct actions and resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import * as adminService from '../services/admin.service.js';
|
import * as adminService from '../services/admin.service.js';
|
||||||
import * as statusService from '../services/status.service.js';
|
import * as statusService from '../services/status.service.js';
|
||||||
import { logUserCreation, logRoleChange } from '../services/audit.service.js';
|
import { logUserCreation, logRoleChange, logUserDeleted } from '../services/audit.service.js';
|
||||||
import { triggerEventNotifications } from '../cron/index.js';
|
import { triggerEventNotifications } from '../cron/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,10 +131,19 @@ export const changeUserRole = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteUser = async (req, res, next) => {
|
export const deleteUser = async (req, res, next) => {
|
||||||
const { userId } = req.params;
|
const { userId } = req.params;
|
||||||
|
const adminId = req.userId;
|
||||||
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
||||||
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Get user info before deletion for audit
|
||||||
|
const userToDelete = await adminService.getUserById(userId);
|
||||||
|
|
||||||
const result = await adminService.deleteUser(userId);
|
const result = await adminService.deleteUser(userId);
|
||||||
|
|
||||||
|
// Log user deletion
|
||||||
|
await logUserDeleted(adminId, userId, userToDelete.username, ipAddress, userAgent);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Používateľ bol zmazaný',
|
message: 'Používateľ bol zmazaný',
|
||||||
|
|||||||
@@ -1,12 +1,62 @@
|
|||||||
import { db } from '../config/database.js';
|
import { db } from '../config/database.js';
|
||||||
import { auditLogs, users } from '../db/schema.js';
|
import { auditLogs, users } from '../db/schema.js';
|
||||||
import { desc, eq } from 'drizzle-orm';
|
import { desc, eq, and, gte, lte, like, sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export const getRecentAuditLogs = async (req, res, next) => {
|
export const getRecentAuditLogs = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { limit = 20, userId } = req.query;
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 50,
|
||||||
|
userId,
|
||||||
|
action,
|
||||||
|
resource,
|
||||||
|
dateFrom,
|
||||||
|
dateTo,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
let query = db
|
const pageNum = parseInt(page);
|
||||||
|
const limitNum = parseInt(limit);
|
||||||
|
const offset = (pageNum - 1) * limitNum;
|
||||||
|
|
||||||
|
// Build conditions array
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
conditions.push(eq(auditLogs.userId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
conditions.push(eq(auditLogs.action, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource) {
|
||||||
|
conditions.push(eq(auditLogs.resource, resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateFrom) {
|
||||||
|
conditions.push(gte(auditLogs.createdAt, new Date(dateFrom)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateTo) {
|
||||||
|
// Add one day to include the entire end date
|
||||||
|
const endDate = new Date(dateTo);
|
||||||
|
endDate.setDate(endDate.getDate() + 1);
|
||||||
|
conditions.push(lte(auditLogs.createdAt, endDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
||||||
|
|
||||||
|
// Get total count for pagination
|
||||||
|
const [countResult] = await db
|
||||||
|
.select({ count: sql`count(*)::int` })
|
||||||
|
.from(auditLogs)
|
||||||
|
.where(whereClause);
|
||||||
|
|
||||||
|
const totalCount = countResult?.count || 0;
|
||||||
|
const totalPages = Math.ceil(totalCount / limitNum);
|
||||||
|
|
||||||
|
// Get paginated logs
|
||||||
|
const logs = await db
|
||||||
.select({
|
.select({
|
||||||
id: auditLogs.id,
|
id: auditLogs.id,
|
||||||
userId: auditLogs.userId,
|
userId: auditLogs.userId,
|
||||||
@@ -15,6 +65,7 @@ export const getRecentAuditLogs = async (req, res, next) => {
|
|||||||
resourceId: auditLogs.resourceId,
|
resourceId: auditLogs.resourceId,
|
||||||
oldValue: auditLogs.oldValue,
|
oldValue: auditLogs.oldValue,
|
||||||
newValue: auditLogs.newValue,
|
newValue: auditLogs.newValue,
|
||||||
|
ipAddress: auditLogs.ipAddress,
|
||||||
success: auditLogs.success,
|
success: auditLogs.success,
|
||||||
createdAt: auditLogs.createdAt,
|
createdAt: auditLogs.createdAt,
|
||||||
// User info
|
// User info
|
||||||
@@ -24,18 +75,58 @@ export const getRecentAuditLogs = async (req, res, next) => {
|
|||||||
})
|
})
|
||||||
.from(auditLogs)
|
.from(auditLogs)
|
||||||
.leftJoin(users, eq(auditLogs.userId, users.id))
|
.leftJoin(users, eq(auditLogs.userId, users.id))
|
||||||
|
.where(whereClause)
|
||||||
.orderBy(desc(auditLogs.createdAt))
|
.orderBy(desc(auditLogs.createdAt))
|
||||||
.limit(parseInt(limit));
|
.limit(limitNum)
|
||||||
|
.offset(offset);
|
||||||
if (userId) {
|
|
||||||
query = query.where(eq(auditLogs.userId, userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
const logs = await query;
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: logs,
|
data: {
|
||||||
|
logs,
|
||||||
|
pagination: {
|
||||||
|
page: pageNum,
|
||||||
|
limit: limitNum,
|
||||||
|
totalCount,
|
||||||
|
totalPages,
|
||||||
|
hasNextPage: pageNum < totalPages,
|
||||||
|
hasPrevPage: pageNum > 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get distinct actions for filter dropdown
|
||||||
|
export const getAuditActions = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const actions = await db
|
||||||
|
.selectDistinct({ action: auditLogs.action })
|
||||||
|
.from(auditLogs)
|
||||||
|
.orderBy(auditLogs.action);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: actions.map((a) => a.action),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get distinct resources for filter dropdown
|
||||||
|
export const getAuditResources = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const resources = await db
|
||||||
|
.selectDistinct({ resource: auditLogs.resource })
|
||||||
|
.from(auditLogs)
|
||||||
|
.orderBy(auditLogs.resource);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: resources.map((r) => r.resource),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -2,7 +2,16 @@ import * as companyService from '../services/company.service.js';
|
|||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
import * as companyReminderService from '../services/company-reminder.service.js';
|
import * as companyReminderService from '../services/company-reminder.service.js';
|
||||||
import * as companyEmailService from '../services/company-email.service.js';
|
import * as companyEmailService from '../services/company-email.service.js';
|
||||||
import { logCompanyCreated, logCompanyDeleted } from '../services/audit.service.js';
|
import {
|
||||||
|
logCompanyCreated,
|
||||||
|
logCompanyDeleted,
|
||||||
|
logCompanyUpdated,
|
||||||
|
logCompanyUserAssigned,
|
||||||
|
logCompanyUserRemoved,
|
||||||
|
logCompanyReminderCreated,
|
||||||
|
logCompanyReminderUpdated,
|
||||||
|
logCompanyReminderDeleted,
|
||||||
|
} from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all companies
|
* Get all companies
|
||||||
@@ -138,11 +147,25 @@ export const createCompany = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const updateCompany = async (req, res, next) => {
|
export const updateCompany = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
// Get old company for audit
|
||||||
|
const oldCompany = await companyService.getCompanyById(companyId);
|
||||||
|
|
||||||
const company = await companyService.updateCompany(companyId, data);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: company,
|
data: company,
|
||||||
@@ -291,11 +314,15 @@ export const getCompanyReminders = async (req, res, next) => {
|
|||||||
|
|
||||||
export const createCompanyReminder = async (req, res, next) => {
|
export const createCompanyReminder = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const { description, dueDate, isChecked } = req.body;
|
const { description, dueDate, isChecked } = req.body;
|
||||||
|
|
||||||
const reminder = await companyReminderService.createReminder(companyId, { description, dueDate, isChecked });
|
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']);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: reminder,
|
data: reminder,
|
||||||
@@ -308,11 +335,18 @@ export const createCompanyReminder = async (req, res, next) => {
|
|||||||
|
|
||||||
export const updateCompanyReminder = async (req, res, next) => {
|
export const updateCompanyReminder = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { companyId, reminderId } = req.params;
|
const { companyId, reminderId } = req.params;
|
||||||
const { description, dueDate, isChecked } = req.body;
|
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 });
|
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']);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: reminder,
|
data: reminder,
|
||||||
@@ -325,10 +359,17 @@ export const updateCompanyReminder = async (req, res, next) => {
|
|||||||
|
|
||||||
export const deleteCompanyReminder = async (req, res, next) => {
|
export const deleteCompanyReminder = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { companyId, reminderId } = req.params;
|
const { companyId, reminderId } = req.params;
|
||||||
|
|
||||||
|
// Get reminder for audit before deletion
|
||||||
|
const reminder = await companyReminderService.getReminderById(reminderId);
|
||||||
|
|
||||||
const result = await companyReminderService.deleteReminder(companyId, reminderId);
|
const result = await companyReminderService.deleteReminder(companyId, reminderId);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logCompanyReminderDeleted(userId, reminderId, companyId, reminder?.dueDate, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: result.message,
|
message: result.message,
|
||||||
@@ -412,8 +453,21 @@ export const assignUserToCompany = async (req, res, next) => {
|
|||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const { userId, role } = req.body;
|
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);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: assignment,
|
data: assignment,
|
||||||
@@ -430,10 +484,24 @@ export const assignUserToCompany = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const removeUserFromCompany = async (req, res, next) => {
|
export const removeUserFromCompany = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const currentUserId = req.userId;
|
||||||
const { companyId, userId } = req.params;
|
const { companyId, userId } = req.params;
|
||||||
|
|
||||||
|
// Get company name for audit
|
||||||
|
const company = await companyService.getCompanyById(companyId);
|
||||||
|
|
||||||
const result = await companyService.removeUserFromCompany(companyId, userId);
|
const result = await companyService.removeUserFromCompany(companyId, userId);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logCompanyUserRemoved(
|
||||||
|
currentUserId,
|
||||||
|
companyId,
|
||||||
|
userId,
|
||||||
|
company.name,
|
||||||
|
req.ip,
|
||||||
|
req.headers['user-agent']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: result.message,
|
message: result.message,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as contactService from '../services/contact.service.js';
|
import * as contactService from '../services/contact.service.js';
|
||||||
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap/index.js';
|
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap/index.js';
|
||||||
import * as emailAccountService from '../services/email-account.service.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
|
* Get all contacts for an email account
|
||||||
@@ -249,6 +250,16 @@ export const linkCompanyToContact = async (req, res, next) => {
|
|||||||
|
|
||||||
const updated = await contactService.linkCompanyToContact(contactId, accountId, companyId);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: updated,
|
data: updated,
|
||||||
@@ -321,6 +332,16 @@ export const createCompanyFromContact = async (req, res, next) => {
|
|||||||
|
|
||||||
const result = await contactService.createCompanyFromContact(contactId, accountId, userId, companyData);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: result,
|
data: result,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
|
import { logNoteCreated, logNoteUpdated, logNoteDeleted } from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all notes
|
* Get all notes
|
||||||
@@ -59,6 +60,9 @@ export const createNote = async (req, res, next) => {
|
|||||||
|
|
||||||
const note = await noteService.createNote(userId, data);
|
const note = await noteService.createNote(userId, data);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logNoteCreated(userId, note.id, note.content, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: note,
|
data: note,
|
||||||
@@ -76,11 +80,18 @@ export const createNote = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const updateNote = async (req, res, next) => {
|
export const updateNote = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { noteId } = req.params;
|
const { noteId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
// Get old note for audit
|
||||||
|
const oldNote = await noteService.getNoteById(noteId);
|
||||||
|
|
||||||
const note = await noteService.updateNote(noteId, data);
|
const note = await noteService.updateNote(noteId, data);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logNoteUpdated(userId, noteId, oldNote.content, note.content, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: note,
|
data: note,
|
||||||
@@ -97,10 +108,17 @@ export const updateNote = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteNote = async (req, res, next) => {
|
export const deleteNote = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { noteId } = req.params;
|
const { noteId } = req.params;
|
||||||
|
|
||||||
|
// Get note for audit before deletion
|
||||||
|
const note = await noteService.getNoteById(noteId);
|
||||||
|
|
||||||
const result = await noteService.deleteNote(noteId);
|
const result = await noteService.deleteNote(noteId);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logNoteDeleted(userId, noteId, note.content, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: result.message,
|
message: result.message,
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import * as projectService from '../services/project.service.js';
|
import * as projectService from '../services/project.service.js';
|
||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
import { logProjectCreated, logProjectDeleted } from '../services/audit.service.js';
|
import {
|
||||||
|
logProjectCreated,
|
||||||
|
logProjectDeleted,
|
||||||
|
logProjectUpdated,
|
||||||
|
logProjectUserAssigned,
|
||||||
|
logProjectUserRemoved,
|
||||||
|
} from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all projects
|
* Get all projects
|
||||||
@@ -95,11 +101,25 @@ export const createProject = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const updateProject = async (req, res, next) => {
|
export const updateProject = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
// Get old project for audit
|
||||||
|
const oldProject = await projectService.getProjectById(projectId);
|
||||||
|
|
||||||
const project = await projectService.updateProject(projectId, data);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: project,
|
data: project,
|
||||||
@@ -257,6 +277,9 @@ export const assignUserToProject = async (req, res, next) => {
|
|||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const { userId, role } = req.body;
|
const { userId, role } = req.body;
|
||||||
|
|
||||||
|
// Get project name for audit
|
||||||
|
const project = await projectService.getProjectById(projectId);
|
||||||
|
|
||||||
const assignment = await projectService.assignUserToProject(
|
const assignment = await projectService.assignUserToProject(
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
@@ -264,6 +287,16 @@ export const assignUserToProject = async (req, res, next) => {
|
|||||||
role
|
role
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logProjectUserAssigned(
|
||||||
|
currentUserId,
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
project.name,
|
||||||
|
req.ip,
|
||||||
|
req.headers['user-agent']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: assignment,
|
data: assignment,
|
||||||
@@ -280,10 +313,24 @@ export const assignUserToProject = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const removeUserFromProject = async (req, res, next) => {
|
export const removeUserFromProject = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const currentUserId = req.userId;
|
||||||
const { projectId, userId } = req.params;
|
const { projectId, userId } = req.params;
|
||||||
|
|
||||||
|
// Get project name for audit
|
||||||
|
const project = await projectService.getProjectById(projectId);
|
||||||
|
|
||||||
const result = await projectService.removeUserFromProject(projectId, userId);
|
const result = await projectService.removeUserFromProject(projectId, userId);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logProjectUserRemoved(
|
||||||
|
currentUserId,
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
project.name,
|
||||||
|
req.ip,
|
||||||
|
req.headers['user-agent']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: result.message,
|
message: result.message,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import * as timeTrackingService from '../services/time-tracking.service.js';
|
import * as timeTrackingService from '../services/time-tracking.service.js';
|
||||||
import { logTimerStarted, logTimerStopped } from '../services/audit.service.js';
|
import {
|
||||||
|
logTimerStarted,
|
||||||
|
logTimerStopped,
|
||||||
|
logTimeEntryUpdated,
|
||||||
|
logTimeEntryDeleted,
|
||||||
|
} from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new time entry
|
* Start a new time entry
|
||||||
@@ -223,11 +228,15 @@ export const getTimeEntryWithRelations = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const updateTimeEntry = async (req, res, next) => {
|
export const updateTimeEntry = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
const { startTime, endTime, projectId, todoId, companyId, description } = req.body;
|
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, {
|
const entry = await timeTrackingService.updateTimeEntry(entryId, {
|
||||||
userId: req.userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
}, {
|
}, {
|
||||||
startTime,
|
startTime,
|
||||||
@@ -238,6 +247,16 @@ export const updateTimeEntry = async (req, res, next) => {
|
|||||||
description,
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: entry,
|
data: entry,
|
||||||
@@ -254,13 +273,27 @@ export const updateTimeEntry = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteTimeEntry = async (req, res, next) => {
|
export const deleteTimeEntry = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
|
|
||||||
|
// Get entry for audit before deletion
|
||||||
|
const entry = await timeTrackingService.getTimeEntryById(entryId);
|
||||||
|
|
||||||
const result = await timeTrackingService.deleteTimeEntry(entryId, {
|
const result = await timeTrackingService.deleteTimeEntry(entryId, {
|
||||||
userId: req.userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logTimeEntryDeleted(
|
||||||
|
userId,
|
||||||
|
entryId,
|
||||||
|
entry.description || 'Time entry',
|
||||||
|
entry.duration,
|
||||||
|
req.ip,
|
||||||
|
req.headers['user-agent']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json(result);
|
res.status(200).json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as timesheetService from '../services/timesheet.service.js';
|
import * as timesheetService from '../services/timesheet.service.js';
|
||||||
import { ForbiddenError } from '../utils/errors.js';
|
import { ForbiddenError } from '../utils/errors.js';
|
||||||
|
import { logTimesheetUploaded, logTimesheetDeleted } from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload timesheet
|
* Upload timesheet
|
||||||
@@ -27,6 +28,9 @@ export const uploadTimesheet = async (req, res, next) => {
|
|||||||
file: req.file,
|
file: req.file,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logTimesheetUploaded(req.userId, timesheet.id, year, month, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: { timesheet },
|
data: { timesheet },
|
||||||
@@ -105,13 +109,20 @@ export const downloadTimesheet = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteTimesheet = async (req, res, next) => {
|
export const deleteTimesheet = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { timesheetId } = req.params;
|
const { timesheetId } = req.params;
|
||||||
|
|
||||||
|
// Get timesheet for audit before deletion
|
||||||
|
const timesheet = await timesheetService.getTimesheetById(timesheetId);
|
||||||
|
|
||||||
await timesheetService.deleteTimesheet(timesheetId, {
|
await timesheetService.deleteTimesheet(timesheetId, {
|
||||||
userId: req.userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logTimesheetDeleted(userId, timesheetId, timesheet?.year, timesheet?.month, req.ip, req.headers['user-agent']);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Timesheet bol zmazaný',
|
message: 'Timesheet bol zmazaný',
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as todoService from '../services/todo.service.js';
|
import * as todoService from '../services/todo.service.js';
|
||||||
import { logTodoCreated, logTodoDeleted, logTodoCompleted } from '../services/audit.service.js';
|
import {
|
||||||
|
logTodoCreated,
|
||||||
|
logTodoDeleted,
|
||||||
|
logTodoCompleted,
|
||||||
|
logTodoUpdated,
|
||||||
|
logTodoUncompleted,
|
||||||
|
} from '../services/audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all todos
|
* Get all todos
|
||||||
@@ -135,11 +141,25 @@ export const createTodo = async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
export const updateTodo = async (req, res, next) => {
|
export const updateTodo = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
const userId = req.userId;
|
||||||
const { todoId } = req.params;
|
const { todoId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
|
// Get old todo for audit
|
||||||
|
const oldTodo = await todoService.getTodoById(todoId);
|
||||||
|
|
||||||
const todo = await todoService.updateTodo(todoId, data);
|
const todo = await todoService.updateTodo(todoId, data);
|
||||||
|
|
||||||
|
// Log audit event
|
||||||
|
await logTodoUpdated(
|
||||||
|
userId,
|
||||||
|
todoId,
|
||||||
|
{ title: oldTodo.title },
|
||||||
|
{ title: todo.title },
|
||||||
|
req.ip,
|
||||||
|
req.headers['user-agent']
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: todo,
|
data: todo,
|
||||||
@@ -195,9 +215,11 @@ export const toggleTodo = async (req, res, next) => {
|
|||||||
status: wasCompleted ? 'pending' : 'completed',
|
status: wasCompleted ? 'pending' : 'completed',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log audit event if todo was completed
|
// Log audit event
|
||||||
if (!wasCompleted) {
|
if (!wasCompleted) {
|
||||||
await logTodoCompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']);
|
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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { getRecentAuditLogs } from '../controllers/audit.controller.js';
|
import { getRecentAuditLogs, getAuditActions, getAuditResources } from '../controllers/audit.controller.js';
|
||||||
import { authenticate } from '../middlewares/auth/authMiddleware.js';
|
import { authenticate } from '../middlewares/auth/authMiddleware.js';
|
||||||
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
|
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
|
||||||
|
|
||||||
@@ -7,5 +7,7 @@ const router = Router();
|
|||||||
|
|
||||||
// Audit logs are admin only
|
// Audit logs are admin only
|
||||||
router.get('/', authenticate, requireAdmin, getRecentAuditLogs);
|
router.get('/', authenticate, requireAdmin, getRecentAuditLogs);
|
||||||
|
router.get('/actions', authenticate, requireAdmin, getAuditActions);
|
||||||
|
router.get('/resources', authenticate, requireAdmin, getAuditResources);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -242,3 +242,270 @@ export const logLogout = async (userId, ipAddress, userAgent) => {
|
|||||||
userAgent,
|
userAgent,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// User deletion
|
||||||
|
export const logUserDeleted = async (adminId, deletedUserId, username, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId: adminId,
|
||||||
|
action: 'user_deleted',
|
||||||
|
resource: 'user',
|
||||||
|
resourceId: deletedUserId,
|
||||||
|
oldValue: { username },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contacts
|
||||||
|
export const logContactLinkedToCompany = async (userId, contactId, companyId, companyName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'contact_linked_to_company',
|
||||||
|
resource: 'contact',
|
||||||
|
resourceId: contactId,
|
||||||
|
newValue: { companyId, companyName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logCompanyCreatedFromContact = async (userId, contactId, companyId, companyName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_created_from_contact',
|
||||||
|
resource: 'contact',
|
||||||
|
resourceId: contactId,
|
||||||
|
newValue: { companyId, companyName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
export const logNoteCreated = async (userId, noteId, noteContent, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'note_created',
|
||||||
|
resource: 'note',
|
||||||
|
resourceId: noteId,
|
||||||
|
newValue: { content: noteContent?.substring(0, 100) },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logNoteUpdated = async (userId, noteId, oldContent, newContent, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'note_updated',
|
||||||
|
resource: 'note',
|
||||||
|
resourceId: noteId,
|
||||||
|
oldValue: { content: oldContent?.substring(0, 100) },
|
||||||
|
newValue: { content: newContent?.substring(0, 100) },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logNoteDeleted = async (userId, noteId, noteContent, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'note_deleted',
|
||||||
|
resource: 'note',
|
||||||
|
resourceId: noteId,
|
||||||
|
oldValue: { content: noteContent?.substring(0, 100) },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Company - updates and user management
|
||||||
|
export const logCompanyUpdated = async (userId, companyId, oldValues, newValues, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_updated',
|
||||||
|
resource: 'company',
|
||||||
|
resourceId: companyId,
|
||||||
|
oldValue: oldValues,
|
||||||
|
newValue: newValues,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logCompanyUserAssigned = async (userId, companyId, assignedUserId, companyName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_user_assigned',
|
||||||
|
resource: 'company',
|
||||||
|
resourceId: companyId,
|
||||||
|
newValue: { assignedUserId, companyName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logCompanyUserRemoved = async (userId, companyId, removedUserId, companyName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_user_removed',
|
||||||
|
resource: 'company',
|
||||||
|
resourceId: companyId,
|
||||||
|
oldValue: { removedUserId, companyName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Company reminders
|
||||||
|
export const logCompanyReminderCreated = async (userId, reminderId, companyId, reminderDate, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_reminder_created',
|
||||||
|
resource: 'company_reminder',
|
||||||
|
resourceId: reminderId,
|
||||||
|
newValue: { companyId, reminderDate },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logCompanyReminderUpdated = async (userId, reminderId, companyId, oldDate, newDate, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_reminder_updated',
|
||||||
|
resource: 'company_reminder',
|
||||||
|
resourceId: reminderId,
|
||||||
|
oldValue: { companyId, reminderDate: oldDate },
|
||||||
|
newValue: { companyId, reminderDate: newDate },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logCompanyReminderDeleted = async (userId, reminderId, companyId, reminderDate, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'company_reminder_deleted',
|
||||||
|
resource: 'company_reminder',
|
||||||
|
resourceId: reminderId,
|
||||||
|
oldValue: { companyId, reminderDate },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Project - updates and user management
|
||||||
|
export const logProjectUpdated = async (userId, projectId, oldValues, newValues, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'project_updated',
|
||||||
|
resource: 'project',
|
||||||
|
resourceId: projectId,
|
||||||
|
oldValue: oldValues,
|
||||||
|
newValue: newValues,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logProjectUserAssigned = async (userId, projectId, assignedUserId, projectName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'project_user_assigned',
|
||||||
|
resource: 'project',
|
||||||
|
resourceId: projectId,
|
||||||
|
newValue: { assignedUserId, projectName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logProjectUserRemoved = async (userId, projectId, removedUserId, projectName, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'project_user_removed',
|
||||||
|
resource: 'project',
|
||||||
|
resourceId: projectId,
|
||||||
|
oldValue: { removedUserId, projectName },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Todos - update and uncomplete
|
||||||
|
export const logTodoUpdated = async (userId, todoId, oldValues, newValues, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'todo_updated',
|
||||||
|
resource: 'todo',
|
||||||
|
resourceId: todoId,
|
||||||
|
oldValue: oldValues,
|
||||||
|
newValue: newValues,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logTodoUncompleted = async (userId, todoId, todoTitle, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'todo_uncompleted',
|
||||||
|
resource: 'todo',
|
||||||
|
resourceId: todoId,
|
||||||
|
newValue: { title: todoTitle },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Time Entry - update and delete
|
||||||
|
export const logTimeEntryUpdated = async (userId, entryId, oldValues, newValues, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'time_entry_updated',
|
||||||
|
resource: 'time_entry',
|
||||||
|
resourceId: entryId,
|
||||||
|
oldValue: oldValues,
|
||||||
|
newValue: newValues,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logTimeEntryDeleted = async (userId, entryId, projectName, duration, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'time_entry_deleted',
|
||||||
|
resource: 'time_entry',
|
||||||
|
resourceId: entryId,
|
||||||
|
oldValue: { project: projectName, duration },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timesheets
|
||||||
|
export const logTimesheetUploaded = async (userId, timesheetId, year, month, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'timesheet_uploaded',
|
||||||
|
resource: 'timesheet',
|
||||||
|
resourceId: timesheetId,
|
||||||
|
newValue: { year, month },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logTimesheetDeleted = async (userId, timesheetId, year, month, ipAddress, userAgent) => {
|
||||||
|
await logAuditEvent({
|
||||||
|
userId,
|
||||||
|
action: 'timesheet_deleted',
|
||||||
|
resource: 'timesheet',
|
||||||
|
resourceId: timesheetId,
|
||||||
|
oldValue: { year, month },
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const ensureCompanyExists = async (companyId) => {
|
|||||||
return company;
|
return company;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getReminderById = async (reminderId) => {
|
export const getReminderById = async (reminderId) => {
|
||||||
const [reminder] = await db
|
const [reminder] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(companyReminders)
|
.from(companyReminders)
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ const ensureTimesheetExists = async (timesheetId) => {
|
|||||||
return timesheet;
|
return timesheet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTimesheetById = async (timesheetId) => {
|
||||||
|
return ensureTimesheetExists(timesheetId);
|
||||||
|
};
|
||||||
|
|
||||||
const assertAccess = (timesheet, { userId, role }) => {
|
const assertAccess = (timesheet, { userId, role }) => {
|
||||||
if (role !== 'admin' && timesheet.userId !== userId) {
|
if (role !== 'admin' && timesheet.userId !== userId) {
|
||||||
throw new ForbiddenError('Nemáte oprávnenie k tomuto timesheetu');
|
throw new ForbiddenError('Nemáte oprávnenie k tomuto timesheetu');
|
||||||
|
|||||||
Reference in New Issue
Block a user