refactor: Deduplicate event-notifier.js (603 -> 418 lines)
Extract shared helpers: - getEventsInRange(start, end) replaces getTomorrowEvents + getUpcomingEvents - groupEventsByUser() deduplicates event grouping logic from 3 functions - sendNotificationsToUsers() deduplicates notification loop from 3 functions - buildJmapConfig() removes repeated JMAP config construction Remove unused standalone range helper functions (getTomorrowRange, getOneHourRange) — date ranges computed inline where needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,8 @@ import {
|
|||||||
generateEventNotificationSubject,
|
generateEventNotificationSubject,
|
||||||
} from './email-template.js';
|
} from './email-template.js';
|
||||||
|
|
||||||
/**
|
// --- Private helpers ---
|
||||||
* Get sender email account credentials from database
|
|
||||||
* @returns {Promise<Object|null>} Sender account with decrypted password
|
|
||||||
*/
|
|
||||||
const getSenderAccount = async () => {
|
const getSenderAccount = async () => {
|
||||||
const senderEmail = process.env.NOTIFICATION_SENDER_EMAIL;
|
const senderEmail = process.env.NOTIFICATION_SENDER_EMAIL;
|
||||||
|
|
||||||
@@ -47,239 +45,6 @@ const getSenderAccount = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tomorrow's date range (start of day to end of day)
|
|
||||||
* @returns {{ startOfTomorrow: Date, endOfTomorrow: Date }}
|
|
||||||
*/
|
|
||||||
const getTomorrowRange = () => {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Start of tomorrow (00:00:00)
|
|
||||||
const startOfTomorrow = new Date(now);
|
|
||||||
startOfTomorrow.setDate(startOfTomorrow.getDate() + 1);
|
|
||||||
startOfTomorrow.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// End of tomorrow (23:59:59.999)
|
|
||||||
const endOfTomorrow = new Date(startOfTomorrow);
|
|
||||||
endOfTomorrow.setHours(23, 59, 59, 999);
|
|
||||||
|
|
||||||
return { startOfTomorrow, endOfTomorrow };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get range for events starting in the next hour
|
|
||||||
* @returns {{ startOfRange: Date, endOfRange: Date }}
|
|
||||||
*/
|
|
||||||
const getOneHourRange = () => {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Start of range: now
|
|
||||||
const startOfRange = new Date(now);
|
|
||||||
|
|
||||||
// End of range: 1 hour from now
|
|
||||||
const endOfRange = new Date(now);
|
|
||||||
endOfRange.setHours(endOfRange.getHours() + 1);
|
|
||||||
|
|
||||||
return { startOfRange, endOfRange };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get events starting tomorrow with assigned users
|
|
||||||
* @returns {Promise<Array>} Events with user info
|
|
||||||
*/
|
|
||||||
const getTomorrowEvents = async () => {
|
|
||||||
const { startOfTomorrow, endOfTomorrow } = getTomorrowRange();
|
|
||||||
|
|
||||||
logger.info(`Hľadám udalosti od ${startOfTomorrow.toISOString()} do ${endOfTomorrow.toISOString()}`);
|
|
||||||
|
|
||||||
// Get events starting tomorrow
|
|
||||||
const tomorrowEvents = await db
|
|
||||||
.select({
|
|
||||||
eventId: events.id,
|
|
||||||
title: events.title,
|
|
||||||
description: events.description,
|
|
||||||
type: events.type,
|
|
||||||
start: events.start,
|
|
||||||
end: events.end,
|
|
||||||
userId: users.id,
|
|
||||||
username: users.username,
|
|
||||||
firstName: users.firstName,
|
|
||||||
lastName: users.lastName,
|
|
||||||
})
|
|
||||||
.from(events)
|
|
||||||
.innerJoin(eventUsers, eq(events.id, eventUsers.eventId))
|
|
||||||
.innerJoin(users, eq(eventUsers.userId, users.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
gte(events.start, startOfTomorrow),
|
|
||||||
lt(events.start, endOfTomorrow)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return tomorrowEvents;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get events starting in the next hour with assigned users
|
|
||||||
* @returns {Promise<Array>} Events with user info
|
|
||||||
*/
|
|
||||||
const getUpcomingEvents = async () => {
|
|
||||||
const { startOfRange, endOfRange } = getOneHourRange();
|
|
||||||
|
|
||||||
logger.info(`Hľadám udalosti začínajúce v najbližšej hodine: ${startOfRange.toISOString()} - ${endOfRange.toISOString()}`);
|
|
||||||
|
|
||||||
// Get events starting in the next hour
|
|
||||||
const upcomingEvents = await db
|
|
||||||
.select({
|
|
||||||
eventId: events.id,
|
|
||||||
title: events.title,
|
|
||||||
description: events.description,
|
|
||||||
type: events.type,
|
|
||||||
start: events.start,
|
|
||||||
end: events.end,
|
|
||||||
userId: users.id,
|
|
||||||
username: users.username,
|
|
||||||
firstName: users.firstName,
|
|
||||||
lastName: users.lastName,
|
|
||||||
})
|
|
||||||
.from(events)
|
|
||||||
.innerJoin(eventUsers, eq(events.id, eventUsers.eventId))
|
|
||||||
.innerJoin(users, eq(eventUsers.userId, users.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
gte(events.start, startOfRange),
|
|
||||||
lt(events.start, endOfRange)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return upcomingEvents;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get user's primary email address
|
|
||||||
* @param {string} userId
|
|
||||||
* @returns {Promise<string|null>}
|
|
||||||
*/
|
|
||||||
const getUserEmail = async (userId) => {
|
|
||||||
const [result] = await db
|
|
||||||
.select({
|
|
||||||
email: emailAccounts.email,
|
|
||||||
})
|
|
||||||
.from(userEmailAccounts)
|
|
||||||
.innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userEmailAccounts.userId, userId),
|
|
||||||
eq(userEmailAccounts.isPrimary, true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
return result?.email || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send email via JMAP (simplified version for notifications)
|
|
||||||
* @param {Object} jmapConfig - JMAP configuration
|
|
||||||
* @param {string} to - Recipient email
|
|
||||||
* @param {string} subject - Email subject
|
|
||||||
* @param {string} htmlBody - HTML body
|
|
||||||
* @param {string} textBody - Plain text body
|
|
||||||
* @returns {Promise<boolean>} Success status
|
|
||||||
*/
|
|
||||||
const sendNotificationEmail = async (jmapConfig, to, subject, htmlBody, textBody) => {
|
|
||||||
try {
|
|
||||||
// Get mailboxes
|
|
||||||
const mailboxes = await getMailboxes(jmapConfig);
|
|
||||||
const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');
|
|
||||||
|
|
||||||
if (!sentMailbox) {
|
|
||||||
logger.error('Priečinok Odoslané nebol nájdený');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create email with HTML body
|
|
||||||
const createResponse = await jmapRequest(jmapConfig, [
|
|
||||||
[
|
|
||||||
'Email/set',
|
|
||||||
{
|
|
||||||
accountId: jmapConfig.accountId,
|
|
||||||
create: {
|
|
||||||
draft: {
|
|
||||||
mailboxIds: {
|
|
||||||
[sentMailbox.id]: true,
|
|
||||||
},
|
|
||||||
keywords: {
|
|
||||||
$draft: true,
|
|
||||||
},
|
|
||||||
from: [{ email: jmapConfig.username }],
|
|
||||||
to: [{ email: to }],
|
|
||||||
subject: subject,
|
|
||||||
htmlBody: [{ partId: 'html', type: 'text/html' }],
|
|
||||||
textBody: [{ partId: 'text', type: 'text/plain' }],
|
|
||||||
bodyValues: {
|
|
||||||
html: { value: htmlBody },
|
|
||||||
text: { value: textBody },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'set1',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const createdEmailId = createResponse.methodResponses[0][1].created?.draft?.id;
|
|
||||||
|
|
||||||
if (!createdEmailId) {
|
|
||||||
logger.error('Nepodarilo sa vytvoriť koncept emailu', createResponse.methodResponses[0][1].notCreated);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get identity for sending
|
|
||||||
const identities = await getIdentities(jmapConfig);
|
|
||||||
const identity = identities.find((i) => i.email === jmapConfig.username) || identities[0];
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
logger.error('Nenašla sa identita pre odosielanie emailov');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit the email
|
|
||||||
const submitResponse = await jmapRequest(jmapConfig, [
|
|
||||||
[
|
|
||||||
'EmailSubmission/set',
|
|
||||||
{
|
|
||||||
accountId: jmapConfig.accountId,
|
|
||||||
create: {
|
|
||||||
submission: {
|
|
||||||
emailId: createdEmailId,
|
|
||||||
identityId: identity.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'submit1',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const submissionId = submitResponse.methodResponses[0][1].created?.submission?.id;
|
|
||||||
|
|
||||||
if (!submissionId) {
|
|
||||||
logger.error('Nepodarilo sa odoslať email', submitResponse.methodResponses[0][1].notCreated);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Chyba pri odosielaní emailu na ${to}`, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sender account by user ID (admin's primary email)
|
|
||||||
* @param {string} userId - Admin user ID
|
|
||||||
* @returns {Promise<Object|null>} Sender account with decrypted password
|
|
||||||
*/
|
|
||||||
const getSenderAccountByUserId = async (userId) => {
|
const getSenderAccountByUserId = async (userId) => {
|
||||||
const [result] = await db
|
const [result] = await db
|
||||||
.select({
|
.select({
|
||||||
@@ -317,33 +82,217 @@ const getSenderAccountByUserId = async (userId) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getEventsInRange = async (start, end) => {
|
||||||
|
logger.info(`Hľadám udalosti od ${start.toISOString()} do ${end.toISOString()}`);
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
eventId: events.id,
|
||||||
|
title: events.title,
|
||||||
|
description: events.description,
|
||||||
|
type: events.type,
|
||||||
|
start: events.start,
|
||||||
|
end: events.end,
|
||||||
|
userId: users.id,
|
||||||
|
username: users.username,
|
||||||
|
firstName: users.firstName,
|
||||||
|
lastName: users.lastName,
|
||||||
|
})
|
||||||
|
.from(events)
|
||||||
|
.innerJoin(eventUsers, eq(events.id, eventUsers.eventId))
|
||||||
|
.innerJoin(users, eq(eventUsers.userId, users.id))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
gte(events.start, start),
|
||||||
|
lt(events.start, end)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupEventsByUser = (eventRows) => {
|
||||||
|
const userNotifications = new Map();
|
||||||
|
|
||||||
|
for (const row of eventRows) {
|
||||||
|
const key = `${row.userId}-${row.eventId}`;
|
||||||
|
if (!userNotifications.has(key)) {
|
||||||
|
userNotifications.set(key, {
|
||||||
|
userId: row.userId,
|
||||||
|
username: row.username,
|
||||||
|
firstName: row.firstName,
|
||||||
|
lastName: row.lastName,
|
||||||
|
event: {
|
||||||
|
id: row.eventId,
|
||||||
|
title: row.title,
|
||||||
|
description: row.description,
|
||||||
|
type: row.type,
|
||||||
|
start: row.start,
|
||||||
|
end: row.end,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userNotifications;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserEmail = async (userId) => {
|
||||||
|
const [result] = await db
|
||||||
|
.select({
|
||||||
|
email: emailAccounts.email,
|
||||||
|
})
|
||||||
|
.from(userEmailAccounts)
|
||||||
|
.innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userEmailAccounts.userId, userId),
|
||||||
|
eq(userEmailAccounts.isPrimary, true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return result?.email || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotificationEmail = async (jmapConfig, to, subject, htmlBody, textBody) => {
|
||||||
|
try {
|
||||||
|
const mailboxes = await getMailboxes(jmapConfig);
|
||||||
|
const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');
|
||||||
|
|
||||||
|
if (!sentMailbox) {
|
||||||
|
logger.error('Priečinok Odoslané nebol nájdený');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResponse = await jmapRequest(jmapConfig, [
|
||||||
|
[
|
||||||
|
'Email/set',
|
||||||
|
{
|
||||||
|
accountId: jmapConfig.accountId,
|
||||||
|
create: {
|
||||||
|
draft: {
|
||||||
|
mailboxIds: {
|
||||||
|
[sentMailbox.id]: true,
|
||||||
|
},
|
||||||
|
keywords: {
|
||||||
|
$draft: true,
|
||||||
|
},
|
||||||
|
from: [{ email: jmapConfig.username }],
|
||||||
|
to: [{ email: to }],
|
||||||
|
subject: subject,
|
||||||
|
htmlBody: [{ partId: 'html', type: 'text/html' }],
|
||||||
|
textBody: [{ partId: 'text', type: 'text/plain' }],
|
||||||
|
bodyValues: {
|
||||||
|
html: { value: htmlBody },
|
||||||
|
text: { value: textBody },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'set1',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const createdEmailId = createResponse.methodResponses[0][1].created?.draft?.id;
|
||||||
|
|
||||||
|
if (!createdEmailId) {
|
||||||
|
logger.error('Nepodarilo sa vytvoriť koncept emailu', createResponse.methodResponses[0][1].notCreated);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identities = await getIdentities(jmapConfig);
|
||||||
|
const identity = identities.find((i) => i.email === jmapConfig.username) || identities[0];
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
logger.error('Nenašla sa identita pre odosielanie emailov');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitResponse = await jmapRequest(jmapConfig, [
|
||||||
|
[
|
||||||
|
'EmailSubmission/set',
|
||||||
|
{
|
||||||
|
accountId: jmapConfig.accountId,
|
||||||
|
create: {
|
||||||
|
submission: {
|
||||||
|
emailId: createdEmailId,
|
||||||
|
identityId: identity.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'submit1',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const submissionId = submitResponse.methodResponses[0][1].created?.submission?.id;
|
||||||
|
|
||||||
|
if (!submissionId) {
|
||||||
|
logger.error('Nepodarilo sa odoslať email', submitResponse.methodResponses[0][1].notCreated);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Chyba pri odosielaní emailu na ${to}`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotificationsToUsers = async (jmapConfig, userNotifications, { getSubject, logPrefix }) => {
|
||||||
|
const stats = { sent: 0, failed: 0, skipped: 0 };
|
||||||
|
|
||||||
|
for (const [, data] of userNotifications) {
|
||||||
|
const { userId, username, firstName, event } = data;
|
||||||
|
|
||||||
|
const userEmail = await getUserEmail(userId);
|
||||||
|
|
||||||
|
if (!userEmail) {
|
||||||
|
stats.skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subject = getSubject(event);
|
||||||
|
const htmlBody = generateEventNotificationHtml({ firstName, username, event });
|
||||||
|
const textBody = generateEventNotificationText({ firstName, username, event });
|
||||||
|
|
||||||
|
logger.info(`Odosielam ${logPrefix}notifikáciu pre ${username} - udalosť: ${event.title}`);
|
||||||
|
|
||||||
|
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
logger.success(`Email úspešne odoslaný na ${userEmail}`);
|
||||||
|
stats.sent++;
|
||||||
|
} else {
|
||||||
|
logger.error(`Nepodarilo sa odoslať email na ${userEmail}`);
|
||||||
|
stats.failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildJmapConfig = (senderAccount) => ({
|
||||||
|
server: process.env.JMAP_SERVER,
|
||||||
|
username: senderAccount.email,
|
||||||
|
password: senderAccount.password,
|
||||||
|
accountId: senderAccount.jmapAccountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Public API ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send notification for a single event to all assigned users
|
* Send notification for a single event to all assigned users
|
||||||
* @param {string} eventId - Event ID
|
|
||||||
* @param {string} adminUserId - Admin user ID (sender)
|
|
||||||
* @returns {Promise<{ sent: number, failed: number, skipped: number, eventTitle: string }>}
|
|
||||||
*/
|
*/
|
||||||
export const sendSingleEventNotification = async (eventId, adminUserId) => {
|
export const sendSingleEventNotification = async (eventId, adminUserId) => {
|
||||||
logger.info(`=== Odosielam notifikácie pre event ${eventId} od admina ${adminUserId} ===`);
|
logger.info(`=== Odosielam notifikácie pre event ${eventId} od admina ${adminUserId} ===`);
|
||||||
|
|
||||||
const stats = { sent: 0, failed: 0, skipped: 0, eventTitle: '' };
|
|
||||||
|
|
||||||
// Get admin's primary email account
|
|
||||||
const senderAccount = await getSenderAccountByUserId(adminUserId);
|
const senderAccount = await getSenderAccountByUserId(adminUserId);
|
||||||
if (!senderAccount) {
|
if (!senderAccount) {
|
||||||
throw new Error('Nemáte nastavený primárny email účet. Nastavte ho v profile.');
|
throw new Error('Nemáte nastavený primárny email účet. Nastavte ho v profile.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const jmapConfig = {
|
const jmapConfig = buildJmapConfig(senderAccount);
|
||||||
server: process.env.JMAP_SERVER,
|
|
||||||
username: senderAccount.email,
|
|
||||||
password: senderAccount.password,
|
|
||||||
accountId: senderAccount.jmapAccountId,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info(`Odosielam z účtu: ${senderAccount.email}`);
|
logger.info(`Odosielam z účtu: ${senderAccount.email}`);
|
||||||
|
|
||||||
// Get the event with assigned users
|
|
||||||
const eventData = await db
|
const eventData = await db
|
||||||
.select({
|
.select({
|
||||||
eventId: events.id,
|
eventId: events.id,
|
||||||
@@ -366,141 +315,58 @@ export const sendSingleEventNotification = async (eventId, adminUserId) => {
|
|||||||
throw new Error('Event nebol nájdený alebo nemá priradených používateľov');
|
throw new Error('Event nebol nájdený alebo nemá priradených používateľov');
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.eventTitle = eventData[0].title;
|
const eventTitle = eventData[0].title;
|
||||||
logger.info(`Event: ${stats.eventTitle}, priradených používateľov: ${eventData.length}`);
|
logger.info(`Event: ${eventTitle}, priradených používateľov: ${eventData.length}`);
|
||||||
|
|
||||||
// Send notifications to each assigned user
|
const userNotifications = groupEventsByUser(eventData);
|
||||||
for (const row of eventData) {
|
|
||||||
const { userId, username, firstName } = row;
|
|
||||||
const event = {
|
|
||||||
id: row.eventId,
|
|
||||||
title: row.title,
|
|
||||||
description: row.description,
|
|
||||||
type: row.type,
|
|
||||||
start: row.start,
|
|
||||||
end: row.end,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get user's email
|
const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
|
||||||
const userEmail = await getUserEmail(userId);
|
getSubject: generateEventNotificationSubject,
|
||||||
|
logPrefix: '',
|
||||||
if (!userEmail) {
|
});
|
||||||
stats.skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate email content
|
|
||||||
const subject = generateEventNotificationSubject(event);
|
|
||||||
const htmlBody = generateEventNotificationHtml({ firstName, username, event });
|
|
||||||
const textBody = generateEventNotificationText({ firstName, username, event });
|
|
||||||
|
|
||||||
// Send email
|
|
||||||
logger.info(`Odosielam notifikáciu pre ${username}`);
|
|
||||||
|
|
||||||
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
logger.success(`Email úspešne odoslaný na ${userEmail}`);
|
|
||||||
stats.sent++;
|
|
||||||
} else {
|
|
||||||
logger.error(`Nepodarilo sa odoslať email na ${userEmail}`);
|
|
||||||
stats.failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
||||||
|
|
||||||
return stats;
|
return { ...stats, eventTitle };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function to send event notifications
|
* Send notifications for tomorrow's events
|
||||||
* @returns {Promise<{ sent: number, failed: number, skipped: number }>}
|
|
||||||
*/
|
*/
|
||||||
export const sendEventNotifications = async () => {
|
export const sendEventNotifications = async () => {
|
||||||
logger.info('=== Spúšťam kontrolu zajtrajších udalostí ===');
|
logger.info('=== Spúšťam kontrolu zajtrajších udalostí ===');
|
||||||
|
|
||||||
const stats = { sent: 0, failed: 0, skipped: 0 };
|
|
||||||
|
|
||||||
// Get sender account
|
|
||||||
const senderAccount = await getSenderAccount();
|
const senderAccount = await getSenderAccount();
|
||||||
if (!senderAccount) {
|
if (!senderAccount) {
|
||||||
logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
|
logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
|
||||||
return stats;
|
return { sent: 0, failed: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const jmapConfig = {
|
const jmapConfig = buildJmapConfig(senderAccount);
|
||||||
server: process.env.JMAP_SERVER,
|
|
||||||
username: senderAccount.email,
|
|
||||||
password: senderAccount.password,
|
|
||||||
accountId: senderAccount.jmapAccountId,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get tomorrow's events with assigned users
|
const now = new Date();
|
||||||
const tomorrowEvents = await getTomorrowEvents();
|
const startOfTomorrow = new Date(now);
|
||||||
|
startOfTomorrow.setDate(startOfTomorrow.getDate() + 1);
|
||||||
|
startOfTomorrow.setHours(0, 0, 0, 0);
|
||||||
|
const endOfTomorrow = new Date(startOfTomorrow);
|
||||||
|
endOfTomorrow.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
const tomorrowEvents = await getEventsInRange(startOfTomorrow, endOfTomorrow);
|
||||||
|
|
||||||
if (tomorrowEvents.length === 0) {
|
if (tomorrowEvents.length === 0) {
|
||||||
logger.info('Žiadne udalosti na zajtra');
|
logger.info('Žiadne udalosti na zajtra');
|
||||||
return stats;
|
return { sent: 0, failed: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Nájdených ${tomorrowEvents.length} priradení udalostí na zajtra`);
|
logger.info(`Nájdených ${tomorrowEvents.length} priradení udalostí na zajtra`);
|
||||||
|
|
||||||
// Group events by user to avoid duplicate notifications for same event
|
const userNotifications = groupEventsByUser(tomorrowEvents);
|
||||||
const userNotifications = new Map();
|
|
||||||
|
|
||||||
for (const row of tomorrowEvents) {
|
|
||||||
const key = `${row.userId}-${row.eventId}`;
|
|
||||||
if (!userNotifications.has(key)) {
|
|
||||||
userNotifications.set(key, {
|
|
||||||
userId: row.userId,
|
|
||||||
username: row.username,
|
|
||||||
firstName: row.firstName,
|
|
||||||
lastName: row.lastName,
|
|
||||||
event: {
|
|
||||||
id: row.eventId,
|
|
||||||
title: row.title,
|
|
||||||
description: row.description,
|
|
||||||
type: row.type,
|
|
||||||
start: row.start,
|
|
||||||
end: row.end,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);
|
logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);
|
||||||
|
|
||||||
// Send notifications
|
const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
|
||||||
for (const [key, data] of userNotifications) {
|
getSubject: generateEventNotificationSubject,
|
||||||
const { userId, username, firstName, event } = data;
|
logPrefix: '',
|
||||||
|
});
|
||||||
// Get user's email
|
|
||||||
const userEmail = await getUserEmail(userId);
|
|
||||||
|
|
||||||
if (!userEmail) {
|
|
||||||
stats.skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate email content
|
|
||||||
const subject = generateEventNotificationSubject(event);
|
|
||||||
const htmlBody = generateEventNotificationHtml({ firstName, username, event });
|
|
||||||
const textBody = generateEventNotificationText({ firstName, username, event });
|
|
||||||
|
|
||||||
// Send email
|
|
||||||
logger.info(`Odosielam notifikáciu pre ${username} - udalosť: ${event.title}`);
|
|
||||||
|
|
||||||
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
logger.success(`Email úspešne odoslaný na ${userEmail}`);
|
|
||||||
stats.sent++;
|
|
||||||
} else {
|
|
||||||
logger.error(`Nepodarilo sa odoslať email na ${userEmail}`);
|
|
||||||
stats.failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
||||||
|
|
||||||
@@ -508,94 +374,42 @@ export const sendEventNotifications = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send notifications for events starting in the next hour (1 hour before meeting)
|
* Send notifications for events starting in the next hour
|
||||||
* @returns {Promise<{ sent: number, failed: number, skipped: number }>}
|
|
||||||
*/
|
*/
|
||||||
export const sendOneHourBeforeNotifications = async () => {
|
export const sendOneHourBeforeNotifications = async () => {
|
||||||
logger.info('=== Spúšťam kontrolu udalostí začínajúcich v najbližšej hodine ===');
|
logger.info('=== Spúšťam kontrolu udalostí začínajúcich v najbližšej hodine ===');
|
||||||
|
|
||||||
const stats = { sent: 0, failed: 0, skipped: 0 };
|
|
||||||
|
|
||||||
// Get sender account
|
|
||||||
const senderAccount = await getSenderAccount();
|
const senderAccount = await getSenderAccount();
|
||||||
if (!senderAccount) {
|
if (!senderAccount) {
|
||||||
logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
|
logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
|
||||||
return stats;
|
return { sent: 0, failed: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const jmapConfig = {
|
const jmapConfig = buildJmapConfig(senderAccount);
|
||||||
server: process.env.JMAP_SERVER,
|
|
||||||
username: senderAccount.email,
|
|
||||||
password: senderAccount.password,
|
|
||||||
accountId: senderAccount.jmapAccountId,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get events starting in the next hour
|
const now = new Date();
|
||||||
const upcomingEvents = await getUpcomingEvents();
|
const endOfRange = new Date(now);
|
||||||
|
endOfRange.setHours(endOfRange.getHours() + 1);
|
||||||
|
|
||||||
|
const upcomingEvents = await getEventsInRange(now, endOfRange);
|
||||||
|
|
||||||
if (upcomingEvents.length === 0) {
|
if (upcomingEvents.length === 0) {
|
||||||
logger.info('Žiadne udalosti v najbližšej hodine');
|
logger.info('Žiadne udalosti v najbližšej hodine');
|
||||||
return stats;
|
return { sent: 0, failed: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Nájdených ${upcomingEvents.length} priradení udalostí v najbližšej hodine`);
|
logger.info(`Nájdených ${upcomingEvents.length} priradení udalostí v najbližšej hodine`);
|
||||||
|
|
||||||
// Group events by user to avoid duplicate notifications for same event
|
const userNotifications = groupEventsByUser(upcomingEvents);
|
||||||
const userNotifications = new Map();
|
|
||||||
|
|
||||||
for (const row of upcomingEvents) {
|
|
||||||
const key = `${row.userId}-${row.eventId}`;
|
|
||||||
if (!userNotifications.has(key)) {
|
|
||||||
userNotifications.set(key, {
|
|
||||||
userId: row.userId,
|
|
||||||
username: row.username,
|
|
||||||
firstName: row.firstName,
|
|
||||||
lastName: row.lastName,
|
|
||||||
event: {
|
|
||||||
id: row.eventId,
|
|
||||||
title: row.title,
|
|
||||||
description: row.description,
|
|
||||||
type: row.type,
|
|
||||||
start: row.start,
|
|
||||||
end: row.end,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);
|
logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);
|
||||||
|
|
||||||
// Send notifications
|
const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
|
||||||
for (const [key, data] of userNotifications) {
|
getSubject: (event) => {
|
||||||
const { userId, username, firstName, event } = data;
|
const typeLabel = event.type === 'meeting' ? 'Stretnutie' : (event.type === 'important' ? 'Dôležité' : 'Udalosť');
|
||||||
|
return `Pripomienka: ${typeLabel} - ${event.title} (o 1 hodinu)`;
|
||||||
// Get user's email
|
},
|
||||||
const userEmail = await getUserEmail(userId);
|
logPrefix: '1h ',
|
||||||
|
});
|
||||||
if (!userEmail) {
|
|
||||||
stats.skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate email content with "1 hour before" subject
|
|
||||||
const typeLabel = event.type === 'meeting' ? 'Stretnutie' : (event.type === 'important' ? 'Dôležité' : 'Udalosť');
|
|
||||||
const subject = `Pripomienka: ${typeLabel} - ${event.title} (o 1 hodinu)`;
|
|
||||||
const htmlBody = generateEventNotificationHtml({ firstName, username, event });
|
|
||||||
const textBody = generateEventNotificationText({ firstName, username, event });
|
|
||||||
|
|
||||||
// Send email
|
|
||||||
logger.info(`Odosielam 1h notifikáciu pre ${username} - udalosť: ${event.title}`);
|
|
||||||
|
|
||||||
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
logger.success(`Email úspešne odoslaný na ${userEmail}`);
|
|
||||||
stats.sent++;
|
|
||||||
} else {
|
|
||||||
logger.error(`Nepodarilo sa odoslať email na ${userEmail}`);
|
|
||||||
stats.failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`=== Hotovo (1h notifikácie): odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
logger.info(`=== Hotovo (1h notifikácie): odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user