Add audit logging for CRUD operations
- Extend audit.service.js with logging functions for projects, todos, companies, time tracking, and auth - Create audit.controller.js for fetching recent audit logs with user info - Create audit.routes.js with GET /api/audit-logs endpoint - Add audit logging to project, todo, company, time-tracking, and auth controllers - Log create/delete operations for projects, todos, companies - Log timer start/stop for time tracking - Log login/logout events 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
43
src/controllers/audit.controller.js
Normal file
43
src/controllers/audit.controller.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { auditLogs, users } from '../db/schema.js';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
|
||||
export const getRecentAuditLogs = async (req, res, next) => {
|
||||
try {
|
||||
const { limit = 20, userId } = req.query;
|
||||
|
||||
let query = db
|
||||
.select({
|
||||
id: auditLogs.id,
|
||||
userId: auditLogs.userId,
|
||||
action: auditLogs.action,
|
||||
resource: auditLogs.resource,
|
||||
resourceId: auditLogs.resourceId,
|
||||
oldValue: auditLogs.oldValue,
|
||||
newValue: auditLogs.newValue,
|
||||
success: auditLogs.success,
|
||||
createdAt: auditLogs.createdAt,
|
||||
// User info
|
||||
userFirstName: users.firstName,
|
||||
userLastName: users.lastName,
|
||||
username: users.username,
|
||||
})
|
||||
.from(auditLogs)
|
||||
.leftJoin(users, eq(auditLogs.userId, users.id))
|
||||
.orderBy(desc(auditLogs.createdAt))
|
||||
.limit(parseInt(limit));
|
||||
|
||||
if (userId) {
|
||||
query = query.where(eq(auditLogs.userId, userId));
|
||||
}
|
||||
|
||||
const logs = await query;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: logs,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
logLoginAttempt,
|
||||
logPasswordChange,
|
||||
logEmailLink,
|
||||
logLogin,
|
||||
logLogout,
|
||||
} from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,7 @@ export const login = async (req, res, next) => {
|
||||
|
||||
// Log successful login
|
||||
await logLoginAttempt(username, true, ipAddress, userAgent);
|
||||
await logLogin(result.user.id, username, ipAddress, userAgent);
|
||||
|
||||
// Nastav cookie s access tokenom (httpOnly, secure)
|
||||
res.cookie('accessToken', result.tokens.accessToken, {
|
||||
@@ -143,6 +146,13 @@ export const skipEmail = async (req, res, next) => {
|
||||
*/
|
||||
export const logout = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
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();
|
||||
|
||||
// Vymaž cookies
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 } from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
* Get all companies
|
||||
@@ -114,6 +115,9 @@ export const createCompany = async (req, res, next) => {
|
||||
|
||||
const company = await companyService.createCompany(userId, data);
|
||||
|
||||
// Log audit event
|
||||
await logCompanyCreated(userId, company.id, company.name, req.ip, req.headers['user-agent']);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: company,
|
||||
@@ -153,9 +157,17 @@ export const updateCompany = async (req, res, next) => {
|
||||
export const deleteCompany = async (req, res, next) => {
|
||||
try {
|
||||
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']);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as projectService from '../services/project.service.js';
|
||||
import * as noteService from '../services/note.service.js';
|
||||
import { logProjectCreated, logProjectDeleted } from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
* Get all projects
|
||||
@@ -71,6 +72,9 @@ export const createProject = async (req, res, next) => {
|
||||
|
||||
const project = await projectService.createProject(userId, data);
|
||||
|
||||
// Log audit event
|
||||
await logProjectCreated(userId, project.id, project.name, req.ip, req.headers['user-agent']);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: project,
|
||||
@@ -110,9 +114,17 @@ export const updateProject = async (req, res, next) => {
|
||||
export const deleteProject = async (req, res, next) => {
|
||||
try {
|
||||
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']);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as timeTrackingService from '../services/time-tracking.service.js';
|
||||
import { logTimerStarted, logTimerStopped } from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
* Start a new time entry
|
||||
@@ -16,6 +17,9 @@ export const startTimeEntry = async (req, res, next) => {
|
||||
description,
|
||||
});
|
||||
|
||||
// Log audit event
|
||||
await logTimerStarted(userId, entry.id, description || 'Timer', req.ip, req.headers['user-agent']);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
@@ -43,6 +47,9 @@ export const stopTimeEntry = async (req, res, next) => {
|
||||
description,
|
||||
});
|
||||
|
||||
// Log audit event
|
||||
await logTimerStopped(userId, entry.id, description || 'Timer', entry.duration, req.ip, req.headers['user-agent']);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as todoService from '../services/todo.service.js';
|
||||
import { logTodoCreated, logTodoDeleted, logTodoCompleted } from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
* Get all todos
|
||||
@@ -112,6 +113,9 @@ export const createTodo = async (req, res, next) => {
|
||||
console.log('Backend received todo data:', data);
|
||||
const todo = await todoService.createTodo(userId, data);
|
||||
|
||||
// Log audit event
|
||||
await logTodoCreated(userId, todo.id, todo.title, req.ip, req.headers['user-agent']);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: todo,
|
||||
@@ -152,9 +156,17 @@ export const updateTodo = async (req, res, next) => {
|
||||
export const deleteTodo = async (req, res, next) => {
|
||||
try {
|
||||
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']);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
@@ -171,15 +183,22 @@ export const deleteTodo = async (req, res, next) => {
|
||||
export const toggleTodo = async (req, res, next) => {
|
||||
try {
|
||||
const { todoId } = req.params;
|
||||
const userId = req.userId;
|
||||
|
||||
// Get current todo
|
||||
const todo = await todoService.getTodoById(todoId);
|
||||
const wasCompleted = todo.status === 'completed';
|
||||
|
||||
// Toggle completed status
|
||||
const updated = await todoService.updateTodo(todoId, {
|
||||
status: todo.status === 'completed' ? 'pending' : 'completed',
|
||||
status: wasCompleted ? 'pending' : 'completed',
|
||||
});
|
||||
|
||||
// Log audit event if todo was completed
|
||||
if (!wasCompleted) {
|
||||
await logTodoCompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: updated,
|
||||
|
||||
Reference in New Issue
Block a user