import fs from 'fs/promises';
import path from 'path';
import { db } from '../../config/database.js';
import { prilohy, registracie, ucastnici, kurzy, emailAccounts, userEmailAccounts } from '../../db/schema.js';
import { eq, and } from 'drizzle-orm';
import { NotFoundError, BadRequestError } from '../../utils/errors.js';
import { logger } from '../../utils/logger.js';
import { jmapRequest, getMailboxes, getIdentities } from '../jmap/client.js';
import { getJmapConfig } from '../jmap/config.js';
/**
* Format date to Slovak locale
*/
const formatDate = (date) => {
if (!date) return '';
return new Intl.DateTimeFormat('sk-SK', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(new Date(date));
};
/**
* Generate HTML email template for certificate notification
*/
export const generateCertificateEmailHtml = ({ participantName, courseName, courseDate, courseType }) => {
return `
Certifikát o absolvovaní kurzu
|
🎓
Gratulujeme k úspešnému absolvovaniu!
|
|
Vážený/á ${participantName},
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
|
${courseType || 'Kurz'}
|
${courseName}
📅
${courseDate}
|
|
📎 Príloha: Váš certifikát nájdete v prílohe tohto emailu vo formáte PDF.
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
S pozdravom,
Tím AI Kurzov
|
|
Táto správa bola automaticky vygenerovaná.
V prípade otázok nás neváhajte kontaktovať.
|
|
`.trim();
};
/**
* Generate plain text version of the certificate email
*/
export const generateCertificateEmailText = ({ participantName, courseName, courseDate, courseType }) => {
return `
Vážený/á ${participantName},
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
KURZ: ${courseName}
TYP: ${courseType || 'Kurz'}
DÁTUM: ${courseDate}
Váš certifikát nájdete v prílohe tohto emailu vo formáte PDF.
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
S pozdravom,
Tím AI Kurzov
---
Táto správa bola automaticky vygenerovaná.
V prípade otázok nás neváhajte kontaktovať.
`.trim();
};
/**
* Send email with HTML content and PDF attachment via JMAP
*/
const sendEmailWithAttachment = async (jmapConfig, to, subject, htmlBody, textBody, attachment) => {
logger.info(`Odosielam certifikát emailom na: ${to}`);
// Get mailboxes
const mailboxes = await getMailboxes(jmapConfig);
const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');
if (!sentMailbox) {
throw new Error('Priečinok Odoslané nebol nájdený');
}
// Read attachment file and encode to base64
const fileBuffer = await fs.readFile(attachment.path);
const base64Content = fileBuffer.toString('base64');
// Create email with attachment
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' }],
attachments: [
{
blobId: null, // Will be set via bodyValues
type: attachment.mimeType || 'application/pdf',
name: attachment.filename,
disposition: 'attachment',
},
],
bodyValues: {
html: {
value: htmlBody,
},
text: {
value: textBody,
},
},
},
},
},
'set1',
],
]);
// Check if we need to upload blob first (for some JMAP servers)
const createResult = createResponse.methodResponses[0][1];
if (createResult.notCreated?.draft) {
// Try alternative approach - upload blob first, then create email
logger.info('Skúšam alternatívny prístup s nahraním prílohy');
// Upload blob
const uploadResponse = await jmapRequest(jmapConfig, [
[
'Blob/upload',
{
accountId: jmapConfig.accountId,
create: {
attachment1: {
data: [{ 'data:asBase64': base64Content }],
type: attachment.mimeType || 'application/pdf',
},
},
},
'blob1',
],
]);
const blobId = uploadResponse.methodResponses[0][1]?.created?.attachment1?.blobId;
if (!blobId) {
// Try yet another approach - inline base64
logger.info('Skúšam inline base64 prílohu');
const inlineResponse = 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,
bodyStructure: {
type: 'multipart/mixed',
subParts: [
{
type: 'multipart/alternative',
subParts: [
{
type: 'text/plain',
partId: 'text',
},
{
type: 'text/html',
partId: 'html',
},
],
},
{
type: attachment.mimeType || 'application/pdf',
name: attachment.filename,
disposition: 'attachment',
partId: 'attachment',
},
],
},
bodyValues: {
html: { value: htmlBody },
text: { value: textBody },
attachment: { value: base64Content, isEncodingProblem: false },
},
},
},
},
'set2',
],
]);
const inlineResult = inlineResponse.methodResponses[0][1];
const createdEmailId = inlineResult.created?.draft?.id;
if (!createdEmailId) {
logger.error('Nepodarilo sa vytvoriť email s prílohou', inlineResult.notCreated?.draft);
throw new Error('Nepodarilo sa vytvoriť email s prílohou');
}
// Submit the email
return await submitEmail(jmapConfig, createdEmailId);
}
// Create email with uploaded blob
const emailWithBlobResponse = await jmapRequest(jmapConfig, [
[
'Email/set',
{
accountId: jmapConfig.accountId,
create: {
draft: {
mailboxIds: {
[sentMailbox.id]: true,
},
from: [{ email: jmapConfig.username }],
to: [{ email: to }],
subject: subject,
htmlBody: [{ partId: 'html', type: 'text/html' }],
textBody: [{ partId: 'text', type: 'text/plain' }],
attachments: [
{
blobId: blobId,
type: attachment.mimeType || 'application/pdf',
name: attachment.filename,
disposition: 'attachment',
},
],
bodyValues: {
html: { value: htmlBody },
text: { value: textBody },
},
},
},
},
'set3',
],
]);
const createdEmailId = emailWithBlobResponse.methodResponses[0][1].created?.draft?.id;
if (!createdEmailId) {
throw new Error('Nepodarilo sa vytvoriť email s prílohou');
}
return await submitEmail(jmapConfig, createdEmailId);
}
const createdEmailId = createResult.created?.draft?.id;
if (!createdEmailId) {
throw new Error('Nepodarilo sa vytvoriť email');
}
return await submitEmail(jmapConfig, createdEmailId);
};
/**
* Submit email for sending
*/
const submitEmail = async (jmapConfig, emailId) => {
// Get user identity
const identities = await getIdentities(jmapConfig);
const identity = identities.find((i) => i.email === jmapConfig.username) || identities[0];
if (!identity) {
throw new Error('Nenašla sa identita pre odosielanie emailov');
}
// Submit the email
const submitResponse = await jmapRequest(jmapConfig, [
[
'EmailSubmission/set',
{
accountId: jmapConfig.accountId,
create: {
submission: {
emailId: emailId,
identityId: identity.id,
},
},
},
'submit1',
],
]);
const submissionId = submitResponse.methodResponses[0][1].created?.submission?.id;
if (!submissionId) {
const error = submitResponse.methodResponses[0][1].notCreated?.submission;
logger.error('Nepodarilo sa odoslať email', error);
throw new Error('Nepodarilo sa odoslať email');
}
logger.success(`Email s certifikátom úspešne odoslaný`);
return { success: true, submissionId };
};
/**
* Send certificate email to participant
* @param {number} prilohaId - ID of the certificate attachment
* @param {number} userId - ID of the user sending the email (for getting email account)
*/
export const sendCertificateEmail = async (prilohaId, userId) => {
// Get priloha with registration and participant info
const [priloha] = await db
.select({
id: prilohy.id,
nazovSuboru: prilohy.nazovSuboru,
typPrilohy: prilohy.typPrilohy,
cestaKSuboru: prilohy.cestaKSuboru,
mimeType: prilohy.mimeType,
registraciaId: prilohy.registraciaId,
})
.from(prilohy)
.where(eq(prilohy.id, prilohaId))
.limit(1);
if (!priloha) {
throw new NotFoundError('Príloha nenájdená');
}
if (priloha.typPrilohy !== 'certifikat') {
throw new BadRequestError('Táto príloha nie je certifikát');
}
// Check if file exists
try {
await fs.access(priloha.cestaKSuboru);
} catch {
throw new NotFoundError('Súbor certifikátu nebol nájdený na serveri');
}
// Get registration with participant and course info
const [registration] = await db
.select({
id: registracie.id,
ucastnikMeno: ucastnici.meno,
ucastnikPriezvisko: ucastnici.priezvisko,
ucastnikTitul: ucastnici.titul,
ucastnikEmail: ucastnici.email,
kurzNazov: kurzy.nazov,
kurzTyp: kurzy.typKurzu,
kurzDatumOd: kurzy.datumOd,
kurzDatumDo: kurzy.datumDo,
})
.from(registracie)
.leftJoin(ucastnici, eq(registracie.ucastnikId, ucastnici.id))
.leftJoin(kurzy, eq(registracie.kurzId, kurzy.id))
.where(eq(registracie.id, priloha.registraciaId))
.limit(1);
if (!registration) {
throw new NotFoundError('Registrácia nenájdená');
}
if (!registration.ucastnikEmail) {
throw new BadRequestError('Účastník nemá zadaný email');
}
// Get email account for sending (through userEmailAccounts join table)
const [emailAccountResult] = await db
.select({
id: emailAccounts.id,
email: emailAccounts.email,
emailPassword: emailAccounts.emailPassword,
jmapAccountId: emailAccounts.jmapAccountId,
isActive: emailAccounts.isActive,
})
.from(userEmailAccounts)
.innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
.where(and(eq(userEmailAccounts.userId, userId), eq(emailAccounts.isActive, true)))
.limit(1);
if (!emailAccountResult) {
throw new BadRequestError('Nemáte nastavený aktívny emailový účet pre odosielanie');
}
const emailAccount = emailAccountResult;
// Build participant name
const participantName = [
registration.ucastnikTitul,
registration.ucastnikMeno,
registration.ucastnikPriezvisko,
]
.filter(Boolean)
.join(' ');
// Build course date string
const courseDate = registration.kurzDatumOd
? registration.kurzDatumDo && registration.kurzDatumDo !== registration.kurzDatumOd
? `${formatDate(registration.kurzDatumOd)} - ${formatDate(registration.kurzDatumDo)}`
: formatDate(registration.kurzDatumOd)
: '';
// Generate email content
const emailData = {
participantName,
courseName: registration.kurzNazov || 'Kurz',
courseDate,
courseType: registration.kurzTyp,
};
const htmlBody = generateCertificateEmailHtml(emailData);
const textBody = generateCertificateEmailText(emailData);
const subject = `Certifikát o absolvovaní kurzu: ${registration.kurzNazov}`;
// Get JMAP config
const jmapConfig = getJmapConfig(emailAccount, true);
// Send email with attachment
await sendEmailWithAttachment(
jmapConfig,
registration.ucastnikEmail,
subject,
htmlBody,
textBody,
{
path: priloha.cestaKSuboru,
filename: priloha.nazovSuboru,
mimeType: priloha.mimeType || 'application/pdf',
}
);
logger.success(`Certifikát odoslaný na ${registration.ucastnikEmail}`);
return {
success: true,
message: `Certifikát bol odoslaný na ${registration.ucastnikEmail}`,
recipientEmail: registration.ucastnikEmail,
};
};