import * as crmEmailService from '../services/crm-email.service.js'; import * as contactService from '../services/contact.service.js'; import * as emailAccountService from '../services/email-account.service.js'; import { markEmailAsRead, sendEmail, getJmapConfig, getJmapConfigFromAccount, syncEmailsFromSender, searchEmailsJMAP as searchEmailsJMAPService } from '../services/jmap/index.js'; import { getUserById } from '../services/auth.service.js'; import { logger } from '../utils/logger.js'; /** * Get all emails for authenticated user * GET /api/emails?accountId=xxx (REQUIRED) */ export const getEmails = async (req, res, next) => { try { const userId = req.userId; const { accountId } = req; // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); const emails = await crmEmailService.getEmailsForAccount(accountId); res.status(200).json({ success: true, count: emails.length, data: emails, }); } catch (error) { next(error); } }; /** * Get emails by thread (conversation) * GET /api/emails/thread/:threadId?accountId=xxx (accountId required) */ export const getThread = async (req, res, next) => { try { const userId = req.userId; const { threadId } = req.params; const { accountId } = req; // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); const thread = await crmEmailService.getEmailThread(accountId, threadId); res.status(200).json({ success: true, count: thread.length, data: thread, }); } catch (error) { next(error); } }; /** * Search emails * GET /api/emails/search?q=query&accountId=xxx (accountId required) */ export const searchEmails = async (req, res, next) => { try { const userId = req.userId; const { q } = req.query; const { accountId } = req; // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); const results = await crmEmailService.searchEmails(accountId, q); res.status(200).json({ success: true, count: results.length, data: results, }); } catch (error) { next(error); } }; /** * Get unread count * GET /api/emails/unread-count * Returns total unread count and per-account counts */ export const getUnreadCount = async (req, res, next) => { try { const userId = req.userId; // Get all user's email accounts const userAccounts = await emailAccountService.getUserEmailAccounts(userId); const emailAccountIds = userAccounts.map((account) => account.id); // Get unread count summary for all accounts const unreadData = await crmEmailService.getUnreadCountSummary(emailAccountIds); res.status(200).json({ success: true, data: { count: unreadData.totalUnread, totalUnread: unreadData.totalUnread, accounts: unreadData.byAccount, lastUpdatedAt: new Date().toISOString(), }, }); } catch (error) { logger.error('Chyba v getUnreadCount', { error: error.message }); next(error); } }; /** * Sync latest emails for all contacts from JMAP * POST /api/emails/sync * Body: { accountId } (optional - defaults to primary account) */ export const syncEmails = async (req, res, next) => { try { const userId = req.userId; const { accountId } = req.body; // Get email account (or primary if not specified) let emailAccount; if (accountId) { emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); } else { const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId); if (!primaryAccount) { return res.status(400).json({ success: false, error: { message: 'Najprv musíš pripojiť email účet v Profile', statusCode: 400, }, }); } emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId); } // Get contacts for this email account const contacts = await contactService.getContactsForEmailAccount(emailAccount.id); if (!contacts.length) { return res.status(200).json({ success: true, message: 'Žiadne kontakty na synchronizáciu', data: { contacts: 0, synced: 0, newEmails: 0 }, }); } const jmapConfig = getJmapConfigFromAccount(emailAccount); let totalSynced = 0; let totalNew = 0; for (const contact of contacts) { try { const { total, saved } = await syncEmailsFromSender( jmapConfig, emailAccount.id, contact.id, contact.email, { limit: 50 } ); totalSynced += total; totalNew += saved; } catch (syncError) { logger.error('Nepodarilo sa synchronizovať emaily pre kontakt', { contactEmail: contact.email, error: syncError.message }); } } res.status(200).json({ success: true, message: 'Emaily synchronizované', data: { contacts: contacts.length, synced: totalSynced, newEmails: totalNew, }, }); } catch (error) { next(error); } }; /** * Mark email as read/unread * PATCH /api/emails/:jmapId/read?accountId=xxx */ export const markAsRead = async (req, res, next) => { try { const userId = req.userId; const { jmapId } = req.params; const { isRead } = req.body; const accountId = req.accountId || req.body.accountId; if (!accountId) { return res.status(400).json({ success: false, error: { message: 'accountId je povinný parameter', statusCode: 400, }, }); } // Verify user has access to this email account const emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); const jmapConfig = getJmapConfigFromAccount(emailAccount); await markEmailAsRead(jmapConfig, userId, jmapId, isRead); res.status(200).json({ success: true, message: `Email označený ako ${isRead ? 'prečítaný' : 'neprečítaný'}`, }); } catch (error) { next(error); } }; /** * Mark all emails from contact as read * POST /api/emails/contact/:contactId/read?accountId=xxx */ export const markContactEmailsRead = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; const { accountId } = req; // Verify user has access to this email account const emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); const jmapConfig = getJmapConfigFromAccount(emailAccount); // Mark emails as read in database const count = await crmEmailService.markContactEmailsAsRead(contactId, accountId); // Get the emails that were marked as read to sync with JMAP const contactEmails = await crmEmailService.getContactEmailsWithUnread(accountId, contactId); // Also mark emails as read on JMAP server if (contactEmails && contactEmails.length > 0) { logger.info(`Marking ${contactEmails.length} emails as read on JMAP server`); for (const email of contactEmails) { if (!email.jmapId || email.isRead) { continue; } try { await markEmailAsRead(jmapConfig, userId, email.jmapId, true); } catch (jmapError) { logger.error('Nepodarilo sa označiť JMAP email ako prečítaný', { jmapId: email.jmapId, error: jmapError.message }); } } } res.status(200).json({ success: true, message: `Označených ${count} emailov ako prečítaných`, data: { count }, }); } catch (error) { logger.error('Chyba v markContactEmailsRead', { error: error.message }); next(error); } }; /** * Mark entire thread as read * POST /api/emails/thread/:threadId/read?accountId=xxx */ export const markThreadRead = async (req, res, next) => { try { const userId = req.userId; const { threadId } = req.params; const { accountId } = req; // Verify user has access to this email account const emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); const jmapConfig = getJmapConfigFromAccount(emailAccount); const threadEmails = await crmEmailService.getEmailThread(accountId, threadId); const unreadEmails = threadEmails.filter((email) => !email.isRead); // Mark emails as read on JMAP server for (const email of unreadEmails) { if (!email.jmapId) { continue; } try { await markEmailAsRead(jmapConfig, userId, email.jmapId, true); } catch (jmapError) { logger.error('Nepodarilo sa označiť JMAP email ako prečítaný', { jmapId: email.jmapId, error: jmapError.message }); } } // Mark thread as read in database const count = await crmEmailService.markThreadAsRead(accountId, threadId); res.status(200).json({ success: true, message: 'Konverzácia označená ako prečítaná', count, }); } catch (error) { next(error); } }; /** * Send email reply * POST /api/emails/reply * Body: { to, subject, body, inReplyTo, threadId, accountId } */ export const replyToEmail = async (req, res, next) => { try { const userId = req.userId; const { to, subject, body, inReplyTo = null, threadId = null, accountId } = req.body; if (!to || !subject || !body) { return res.status(400).json({ success: false, error: { message: 'Chýbajúce povinné polia: to, subject, body', statusCode: 400, }, }); } // Get email account (or primary if not specified) let emailAccount; if (accountId) { emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); } else { const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId); if (!primaryAccount) { return res.status(400).json({ success: false, error: { message: 'Najprv musíš pripojiť email účet v Profile', statusCode: 400, }, }); } emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId); } const jmapConfig = getJmapConfigFromAccount(emailAccount); const result = await sendEmail(jmapConfig, userId, emailAccount.id, to, subject, body, inReplyTo, threadId); res.status(200).json({ success: true, message: 'Email odoslaný', data: result, }); } catch (error) { next(error); } }; /** * Get emails for a specific contact * GET /api/emails/contact/:contactId?accountId=xxx */ export const getContactEmails = async (req, res, next) => { try { const userId = req.userId; const { contactId } = req.params; const accountId = req.accountId || req.query.accountId; if (!accountId) { return res.status(400).json({ success: false, error: { message: 'accountId je povinný parameter', statusCode: 400, }, }); } // Verify user has access to this email account await emailAccountService.getEmailAccountById(accountId, userId); const emails = await crmEmailService.getContactEmailsWithUnread(accountId, contactId); res.status(200).json({ success: true, count: emails.length, data: emails, }); } catch (error) { next(error); } }; /** * Search emails using JMAP full-text search * GET /api/emails/search-jmap?query=text&limit=50&offset=0&accountId=xxx * Searches in: from, to, subject, and email body */ export const searchEmailsJMAP = async (req, res, next) => { try { const userId = req.userId; const { query = '', limit = 50, offset = 0, accountId } = req.query; logger.debug('searchEmailsJMAP called', { userId, query, limit, offset, accountId }); // Get email account (or primary if not specified) let emailAccount; if (accountId) { logger.debug('Using provided accountId', { accountId }); emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId); } else { logger.debug('No accountId provided, using primary account'); const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId); if (!primaryAccount) { return res.status(400).json({ success: false, error: { message: 'Najprv musíš pripojiť email účet v Profile', statusCode: 400, }, }); } emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId); logger.debug('Using primary account', { accountId: primaryAccount.id }); } const jmapConfig = getJmapConfigFromAccount(emailAccount); const results = await searchEmailsJMAPService( jmapConfig, emailAccount.id, query, parseInt(limit), parseInt(offset) ); res.status(200).json({ success: true, count: results.length, data: results, }); } catch (error) { logger.error('Chyba v searchEmailsJMAP', { error: error.message }); next(error); } };