Files
crm-server/src/controllers/crm-email.controller.js
richardtekula 38e2c5970a Fix: Translate remaining English log messages to Slovak
- validateInput.js: validation error messages
- errorHandler.js: unhandled error message
- validateBody.js: suspicious input message
- crm-email.controller.js: error log messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:43:37 +01:00

451 lines
13 KiB
JavaScript

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);
}
};