Add auditContext parameter to service mutating functions. Services now
call audit log functions internally when auditContext is provided.
Controllers pass { userId, ipAddress, userAgent } and no longer import
audit service or fetch extra data for audit purposes.
Files modified:
- 10 service files: added audit imports and auditContext parameter
- 9 controller files: removed audit imports and calls
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
230 lines
7.1 KiB
JavaScript
230 lines
7.1 KiB
JavaScript
import { db } from '../config/database.js';
|
|
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
|
|
.select({ id: companies.id, name: companies.name })
|
|
.from(companies)
|
|
.where(eq(companies.id, companyId))
|
|
.limit(1);
|
|
|
|
if (!company) {
|
|
throw new NotFoundError('Firma nenájdená');
|
|
}
|
|
|
|
return company;
|
|
};
|
|
|
|
export const getReminderById = async (reminderId) => {
|
|
const [reminder] = await db
|
|
.select()
|
|
.from(companyReminders)
|
|
.where(eq(companyReminders.id, reminderId))
|
|
.limit(1);
|
|
|
|
if (!reminder) {
|
|
throw new NotFoundError('Reminder nenájdený');
|
|
}
|
|
|
|
return reminder;
|
|
};
|
|
|
|
export const getRemindersByCompanyId = async (companyId) => {
|
|
await ensureCompanyExists(companyId);
|
|
|
|
const reminders = await db
|
|
.select()
|
|
.from(companyReminders)
|
|
.where(eq(companyReminders.companyId, companyId))
|
|
.orderBy(companyReminders.dueDate, desc(companyReminders.createdAt));
|
|
|
|
return reminders;
|
|
};
|
|
|
|
export const createReminder = async (companyId, data, auditContext = null) => {
|
|
await ensureCompanyExists(companyId);
|
|
|
|
const description = data.description?.trim();
|
|
if (!description) {
|
|
throw new BadRequestError('Popis pripomienky je povinný');
|
|
}
|
|
|
|
const [reminder] = await db
|
|
.insert(companyReminders)
|
|
.values({
|
|
companyId,
|
|
description,
|
|
dueDate: data.dueDate ? new Date(data.dueDate) : null,
|
|
isChecked: data.isChecked ?? false,
|
|
})
|
|
.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, auditContext = null) => {
|
|
const reminder = await getReminderById(reminderId);
|
|
|
|
if (reminder.companyId !== companyId) {
|
|
throw new NotFoundError('Reminder nenájdený');
|
|
}
|
|
|
|
const trimmedDescription = data.description !== undefined
|
|
? data.description.trim()
|
|
: reminder.description;
|
|
|
|
if (data.description !== undefined && !trimmedDescription) {
|
|
throw new BadRequestError('Popis pripomienky je povinný');
|
|
}
|
|
|
|
const [updatedReminder] = await db
|
|
.update(companyReminders)
|
|
.set({
|
|
description: trimmedDescription,
|
|
dueDate: data.dueDate !== undefined ? (data.dueDate ? new Date(data.dueDate) : null) : reminder.dueDate,
|
|
isChecked: data.isChecked !== undefined ? data.isChecked : reminder.isChecked,
|
|
updatedAt: new Date(),
|
|
})
|
|
.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, auditContext = null) => {
|
|
const reminder = await getReminderById(reminderId);
|
|
|
|
if (reminder.companyId !== companyId) {
|
|
throw new NotFoundError('Reminder nenájdený');
|
|
}
|
|
|
|
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ý' };
|
|
};
|
|
|
|
export const getReminderSummary = async (userId = null, userRole = null) => {
|
|
// Pre membera filtruj len pristupne firmy
|
|
let accessibleCompanyIds = null;
|
|
if (userRole && userRole !== 'admin' && userId) {
|
|
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
|
if (accessibleCompanyIds.length === 0) {
|
|
return { total: 0, active: 0, completed: 0 };
|
|
}
|
|
}
|
|
|
|
let query = db
|
|
.select({
|
|
total: sql`COUNT(*)::int`,
|
|
active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`,
|
|
completed: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = true)::int`,
|
|
})
|
|
.from(companyReminders);
|
|
|
|
if (accessibleCompanyIds !== null) {
|
|
query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds));
|
|
}
|
|
|
|
const [row] = await query;
|
|
|
|
return {
|
|
total: row?.total ?? 0,
|
|
active: row?.active ?? 0,
|
|
completed: row?.completed ?? 0,
|
|
};
|
|
};
|
|
|
|
export const getReminderCountsByCompany = async (userId = null, userRole = null) => {
|
|
// Pre membera filtruj len pristupne firmy
|
|
let accessibleCompanyIds = null;
|
|
if (userRole && userRole !== 'admin' && userId) {
|
|
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
|
if (accessibleCompanyIds.length === 0) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
let query = db
|
|
.select({
|
|
companyId: companyReminders.companyId,
|
|
total: sql`COUNT(*)::int`,
|
|
active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`,
|
|
completed: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = true)::int`,
|
|
})
|
|
.from(companyReminders);
|
|
|
|
if (accessibleCompanyIds !== null) {
|
|
query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds));
|
|
}
|
|
|
|
const rows = await query
|
|
.groupBy(companyReminders.companyId)
|
|
.orderBy(desc(companyReminders.companyId));
|
|
|
|
return rows;
|
|
};
|
|
|
|
/**
|
|
* Get upcoming reminders for dashboard
|
|
* Returns reminders due within the next 5 days that are not checked
|
|
* Includes company name for display
|
|
* For members: returns only reminders from companies they are assigned to
|
|
*/
|
|
export const getUpcomingReminders = async (userId = null, userRole = null) => {
|
|
// Pre membera filtruj len pristupne firmy
|
|
let accessibleCompanyIds = null;
|
|
if (userRole && userRole !== 'admin' && userId) {
|
|
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
|
if (accessibleCompanyIds.length === 0) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const now = new Date();
|
|
const fiveDaysFromNow = new Date();
|
|
fiveDaysFromNow.setDate(fiveDaysFromNow.getDate() + 5);
|
|
|
|
const conditions = [
|
|
eq(companyReminders.isChecked, false),
|
|
lte(companyReminders.dueDate, fiveDaysFromNow),
|
|
gte(companyReminders.dueDate, now)
|
|
];
|
|
|
|
if (accessibleCompanyIds !== null) {
|
|
conditions.push(inArray(companyReminders.companyId, accessibleCompanyIds));
|
|
}
|
|
|
|
const reminders = await db
|
|
.select({
|
|
id: companyReminders.id,
|
|
description: companyReminders.description,
|
|
dueDate: companyReminders.dueDate,
|
|
isChecked: companyReminders.isChecked,
|
|
companyId: companyReminders.companyId,
|
|
companyName: companies.name,
|
|
createdAt: companyReminders.createdAt,
|
|
})
|
|
.from(companyReminders)
|
|
.leftJoin(companies, eq(companyReminders.companyId, companies.id))
|
|
.where(and(...conditions))
|
|
.orderBy(companyReminders.dueDate);
|
|
|
|
return reminders;
|
|
};
|