option for more emails,fix jmap service,add table email accounts
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
import * as contactService from '../services/contact.service.js';
|
||||
import { discoverContactsFromJMAP, getJmapConfig } from '../services/jmap.service.js';
|
||||
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap.service.js';
|
||||
import { formatErrorResponse } from '../utils/errors.js';
|
||||
import { getUserById } from '../services/auth.service.js';
|
||||
import * as emailAccountService from '../services/email-account.service.js';
|
||||
|
||||
/**
|
||||
* Get all contacts for authenticated user
|
||||
* GET /api/contacts
|
||||
* GET /api/contacts?accountId=xxx (optional)
|
||||
*/
|
||||
export const getContacts = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const contacts = await contactService.getUserContacts(userId);
|
||||
const { accountId } = req.query;
|
||||
|
||||
const contacts = await contactService.getUserContacts(userId, accountId || null);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -25,28 +27,45 @@ export const getContacts = async (req, res) => {
|
||||
|
||||
/**
|
||||
* Discover potential contacts from JMAP (email senders)
|
||||
* GET /api/contacts/discover?search=query&limit=50
|
||||
* GET /api/contacts/discover?accountId=xxx&search=query&limit=50
|
||||
*/
|
||||
export const discoverContacts = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { search = '', limit = 50 } = req.query;
|
||||
const { accountId, search = '', limit = 50 } = req.query;
|
||||
|
||||
// Get user to access JMAP config
|
||||
const user = await getUserById(userId);
|
||||
console.log('🔍 discoverContacts called:', { userId, accountId, search, limit });
|
||||
|
||||
// Check if user has JMAP email configured
|
||||
if (!user.email || !user.emailPassword || !user.jmapAccountId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Najprv musíš pripojiť email účet v Profile',
|
||||
statusCode: 400,
|
||||
},
|
||||
});
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Getting email account by ID:', accountId);
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
console.log('✅ Email account retrieved:', { id: emailAccount.id, email: emailAccount.email });
|
||||
} else {
|
||||
console.log('📧 No accountId provided, getting primary account for user:', userId);
|
||||
const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId);
|
||||
console.log('🔑 Primary account:', primaryAccount ? { id: primaryAccount.id, email: primaryAccount.email } : 'NOT FOUND');
|
||||
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);
|
||||
console.log('✅ Email account retrieved from primary:', { id: emailAccount.id, email: emailAccount.email });
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfig(user);
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
console.log('🔧 JMAP Config created:', {
|
||||
server: jmapConfig.server,
|
||||
username: jmapConfig.username,
|
||||
accountId: jmapConfig.accountId,
|
||||
hasPassword: !!jmapConfig.password
|
||||
});
|
||||
|
||||
const potentialContacts = await discoverContactsFromJMAP(
|
||||
jmapConfig,
|
||||
@@ -61,6 +80,8 @@ export const discoverContacts = async (req, res) => {
|
||||
data: potentialContacts,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in discoverContacts:', error);
|
||||
console.error('Error stack:', error.stack);
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
@@ -69,11 +90,15 @@ export const discoverContacts = async (req, res) => {
|
||||
/**
|
||||
* Add a new contact
|
||||
* POST /api/contacts
|
||||
* Body: { email, name, notes, accountId }
|
||||
*/
|
||||
export const addContact = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { email, name = '', notes = '' } = req.body;
|
||||
console.log('📦 Full req.body:', JSON.stringify(req.body, null, 2));
|
||||
const { email, name = '', notes = '', accountId } = req.body;
|
||||
|
||||
console.log('➕ addContact called:', { userId, email, name, accountId });
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({
|
||||
@@ -85,11 +110,37 @@ export const addContact = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Get user to access JMAP config
|
||||
const user = await getUserById(userId);
|
||||
const jmapConfig = getJmapConfig(user);
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Using provided accountId:', accountId);
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
} else {
|
||||
console.log('📧 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);
|
||||
console.log('📧 Using primary account:', primaryAccount.id);
|
||||
}
|
||||
|
||||
const contact = await contactService.addContact(userId, jmapConfig, email, name, notes);
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
|
||||
const contact = await contactService.addContact(
|
||||
userId,
|
||||
emailAccount.id,
|
||||
jmapConfig,
|
||||
email,
|
||||
name,
|
||||
notes
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import * as crmEmailService from '../services/crm-email.service.js';
|
||||
import * as contactService from '../services/contact.service.js';
|
||||
import { markEmailAsRead, sendEmail, getJmapConfig, syncEmailsFromSender, searchEmailsJMAP as searchEmailsJMAPService } from '../services/jmap.service.js';
|
||||
import * as emailAccountService from '../services/email-account.service.js';
|
||||
import { markEmailAsRead, sendEmail, getJmapConfig, getJmapConfigFromAccount, syncEmailsFromSender, searchEmailsJMAP as searchEmailsJMAPService } from '../services/jmap.service.js';
|
||||
import { formatErrorResponse } from '../utils/errors.js';
|
||||
import { getUserById } from '../services/auth.service.js';
|
||||
|
||||
/**
|
||||
* Get all emails for authenticated user
|
||||
* GET /api/emails
|
||||
* GET /api/emails?accountId=xxx (optional)
|
||||
*/
|
||||
export const getEmails = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const emails = await crmEmailService.getUserEmails(userId);
|
||||
const { accountId } = req.query;
|
||||
|
||||
const emails = await crmEmailService.getUserEmails(userId, accountId || null);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -48,14 +51,14 @@ export const getThread = async (req, res) => {
|
||||
|
||||
/**
|
||||
* Search emails
|
||||
* GET /api/emails/search?q=query
|
||||
* GET /api/emails/search?q=query&accountId=xxx (accountId optional)
|
||||
*/
|
||||
export const searchEmails = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { q } = req.query;
|
||||
const { q, accountId } = req.query;
|
||||
|
||||
const results = await crmEmailService.searchEmails(userId, q);
|
||||
const results = await crmEmailService.searchEmails(userId, q, accountId || null);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -71,33 +74,24 @@ export const searchEmails = async (req, res) => {
|
||||
/**
|
||||
* Get unread count
|
||||
* GET /api/emails/unread-count
|
||||
* Returns total unread count and per-account counts
|
||||
*/
|
||||
export const getUnreadCount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const count = await crmEmailService.getUnreadCount(userId);
|
||||
|
||||
const accounts = [];
|
||||
|
||||
if (req.user?.email) {
|
||||
accounts.push({
|
||||
id: req.user.jmapAccountId || req.user.email,
|
||||
email: req.user.email,
|
||||
label: req.user.email,
|
||||
unread: count,
|
||||
});
|
||||
}
|
||||
const unreadData = await crmEmailService.getUnreadCount(userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
count,
|
||||
totalUnread: count,
|
||||
accounts,
|
||||
count: unreadData.totalUnread,
|
||||
totalUnread: unreadData.totalUnread,
|
||||
accounts: unreadData.accounts,
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in getUnreadCount:', error);
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
@@ -106,23 +100,33 @@ export const getUnreadCount = async (req, res) => {
|
||||
/**
|
||||
* 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) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const user = await getUserById(userId);
|
||||
const { accountId } = req.body;
|
||||
|
||||
if (!user.email || !user.emailPassword || !user.jmapAccountId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Najprv musíš pripojiť email účet v Profile',
|
||||
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 contacts = await contactService.getUserContacts(userId);
|
||||
// Get contacts for this email account
|
||||
const contacts = await contactService.getUserContacts(userId, emailAccount.id);
|
||||
|
||||
if (!contacts.length) {
|
||||
return res.status(200).json({
|
||||
@@ -132,7 +136,7 @@ export const syncEmails = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfig(user);
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
let totalSynced = 0;
|
||||
let totalNew = 0;
|
||||
|
||||
@@ -141,6 +145,7 @@ export const syncEmails = async (req, res) => {
|
||||
const { total, saved } = await syncEmailsFromSender(
|
||||
jmapConfig,
|
||||
userId,
|
||||
emailAccount.id,
|
||||
contact.id,
|
||||
contact.email,
|
||||
{ limit: 50 }
|
||||
@@ -240,11 +245,12 @@ export const markThreadRead = async (req, res) => {
|
||||
/**
|
||||
* Send email reply
|
||||
* POST /api/emails/reply
|
||||
* Body: { to, subject, body, inReplyTo, threadId, accountId }
|
||||
*/
|
||||
export const replyToEmail = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { to, subject, body, inReplyTo = null, threadId = null } = req.body;
|
||||
const { to, subject, body, inReplyTo = null, threadId = null, accountId } = req.body;
|
||||
|
||||
if (!to || !subject || !body) {
|
||||
return res.status(400).json({
|
||||
@@ -256,11 +262,27 @@ export const replyToEmail = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Get user to access JMAP config
|
||||
const user = await getUserById(userId);
|
||||
const jmapConfig = getJmapConfig(user);
|
||||
// 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 result = await sendEmail(jmapConfig, userId, to, subject, body, inReplyTo, threadId);
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
|
||||
const result = await sendEmail(jmapConfig, userId, emailAccount.id, to, subject, body, inReplyTo, threadId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -297,29 +319,38 @@ export const getContactEmails = async (req, res) => {
|
||||
|
||||
/**
|
||||
* Search emails using JMAP full-text search
|
||||
* GET /api/emails/search-jmap?query=text&limit=50&offset=0
|
||||
* 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) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { query = '', limit = 50, offset = 0 } = req.query;
|
||||
const { query = '', limit = 50, offset = 0, accountId } = req.query;
|
||||
|
||||
// Get user to access JMAP config
|
||||
const user = await getUserById(userId);
|
||||
console.log('🔍 searchEmailsJMAP called:', { userId, query, limit, offset, accountId });
|
||||
|
||||
// Check if user has JMAP email configured
|
||||
if (!user.email || !user.emailPassword || !user.jmapAccountId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Najprv musíš pripojiť email účet v Profile',
|
||||
statusCode: 400,
|
||||
},
|
||||
});
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Using provided accountId:', accountId);
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
} else {
|
||||
console.log('📧 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);
|
||||
console.log('📧 Using primary account:', primaryAccount.id);
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfig(user);
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
|
||||
const results = await searchEmailsJMAPService(
|
||||
jmapConfig,
|
||||
@@ -335,6 +366,7 @@ export const searchEmailsJMAP = async (req, res) => {
|
||||
data: results,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in searchEmailsJMAP:', error);
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
|
||||
174
src/controllers/email-account.controller.js
Normal file
174
src/controllers/email-account.controller.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import * as emailAccountService from '../services/email-account.service.js';
|
||||
import {
|
||||
logEmailLink,
|
||||
} from '../services/audit.service.js';
|
||||
import { formatErrorResponse } from '../utils/errors.js';
|
||||
|
||||
/**
|
||||
* Get all email accounts for logged-in user
|
||||
* GET /api/email-accounts
|
||||
*/
|
||||
export const getEmailAccounts = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const accounts = await emailAccountService.getUserEmailAccounts(userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: accounts,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a specific email account
|
||||
* GET /api/email-accounts/:id
|
||||
*/
|
||||
export const getEmailAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { id } = req.params;
|
||||
|
||||
const account = await emailAccountService.getEmailAccountById(id, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: account,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new email account
|
||||
* POST /api/email-accounts
|
||||
*/
|
||||
export const createEmailAccount = async (req, res) => {
|
||||
const { email, emailPassword } = req.body;
|
||||
const userId = req.userId;
|
||||
const ipAddress = req.ip || req.connection.remoteAddress;
|
||||
const userAgent = req.headers['user-agent'];
|
||||
|
||||
try {
|
||||
const account = await emailAccountService.createEmailAccount(
|
||||
userId,
|
||||
email,
|
||||
emailPassword
|
||||
);
|
||||
|
||||
// Log email account creation
|
||||
await logEmailLink(userId, email, ipAddress, userAgent);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: account,
|
||||
message: 'Email účet úspešne pripojený',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update email account password
|
||||
* PATCH /api/email-accounts/:id/password
|
||||
*/
|
||||
export const updateEmailAccountPassword = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { id } = req.params;
|
||||
const { emailPassword } = req.body;
|
||||
|
||||
const result = await emailAccountService.updateEmailAccountPassword(
|
||||
id,
|
||||
userId,
|
||||
emailPassword
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: 'Heslo k emailovému účtu bolo aktualizované',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle email account active status
|
||||
* PATCH /api/email-accounts/:id/status
|
||||
*/
|
||||
export const toggleEmailAccountStatus = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { id } = req.params;
|
||||
const { isActive } = req.body;
|
||||
|
||||
const result = await emailAccountService.toggleEmailAccountStatus(
|
||||
id,
|
||||
userId,
|
||||
isActive
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: `Email účet ${isActive ? 'aktivovaný' : 'deaktivovaný'}`,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set email account as primary
|
||||
* POST /api/email-accounts/:id/set-primary
|
||||
*/
|
||||
export const setPrimaryEmailAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await emailAccountService.setPrimaryEmailAccount(id, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: 'Primárny email účet bol nastavený',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete email account
|
||||
* DELETE /api/email-accounts/:id
|
||||
*/
|
||||
export const deleteEmailAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await emailAccountService.deleteEmailAccount(id, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: result.message,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user