Add debug logging for markContactEmailsAsRead and remove password change restriction
This commit is contained in:
@@ -85,9 +85,10 @@ export const setNewPassword = async (userId, newPassword) => {
|
||||
throw new NotFoundError('Používateľ nenájdený');
|
||||
}
|
||||
|
||||
if (user.changedPassword) {
|
||||
throw new ValidationError('Heslo už bolo zmenené');
|
||||
}
|
||||
// Allow users to change password anytime (removed restriction)
|
||||
// if (user.changedPassword) {
|
||||
// throw new ValidationError('Heslo už bolo zmenené');
|
||||
// }
|
||||
|
||||
// Hash nového hesla
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { contacts, emails } from '../db/schema.js';
|
||||
import { eq, and, desc } from 'drizzle-orm';
|
||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||
import { syncEmailsFromSender } from './jmap.service.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Get all contacts for a user
|
||||
@@ -61,7 +62,7 @@ export const addContact = async (userId, emailAccountId, jmapConfig, email, name
|
||||
try {
|
||||
await syncEmailsFromSender(jmapConfig, userId, emailAccountId, newContact.id, email);
|
||||
} catch (error) {
|
||||
console.error('Failed to sync emails for new contact:', error);
|
||||
logger.error('Failed to sync emails for new contact', { error: error.message });
|
||||
// Don't throw - contact was created successfully
|
||||
}
|
||||
|
||||
|
||||
@@ -129,12 +129,52 @@ export const getUnreadCount = async (userId) => {
|
||||
* Mark thread as read
|
||||
*/
|
||||
export const markThreadAsRead = async (userId, threadId) => {
|
||||
console.log('🟦 markThreadAsRead called:', { userId, threadId });
|
||||
|
||||
const result = await db
|
||||
.update(emails)
|
||||
.set({ isRead: true, updatedAt: new Date() })
|
||||
.where(and(eq(emails.userId, userId), eq(emails.threadId, threadId), eq(emails.isRead, false)))
|
||||
.returning();
|
||||
|
||||
console.log('✅ markThreadAsRead result:', { count: result.length, threadId });
|
||||
|
||||
return { success: true, count: result.length };
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all emails from a contact as read
|
||||
*/
|
||||
export const markContactEmailsAsRead = async (userId, contactId) => {
|
||||
console.log('🟦 markContactEmailsAsRead called:', { userId, contactId });
|
||||
|
||||
// First, check what emails exist for this contact (including already read ones)
|
||||
const allContactEmails = await db
|
||||
.select({
|
||||
id: emails.id,
|
||||
contactId: emails.contactId,
|
||||
isRead: emails.isRead,
|
||||
from: emails.from,
|
||||
subject: emails.subject,
|
||||
})
|
||||
.from(emails)
|
||||
.where(and(eq(emails.userId, userId), eq(emails.contactId, contactId)));
|
||||
|
||||
console.log('📧 All emails for this contact:', {
|
||||
total: allContactEmails.length,
|
||||
unread: allContactEmails.filter(e => !e.isRead).length,
|
||||
read: allContactEmails.filter(e => e.isRead).length,
|
||||
sampleEmails: allContactEmails.slice(0, 3),
|
||||
});
|
||||
|
||||
const result = await db
|
||||
.update(emails)
|
||||
.set({ isRead: true, updatedAt: new Date() })
|
||||
.where(and(eq(emails.userId, userId), eq(emails.contactId, contactId), eq(emails.isRead, false)))
|
||||
.returning();
|
||||
|
||||
console.log('✅ markContactEmailsAsRead result:', { count: result.length, contactId });
|
||||
|
||||
return { success: true, count: result.length };
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ConflictError,
|
||||
AuthenticationError,
|
||||
} from '../utils/errors.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Get all email accounts for a user
|
||||
@@ -200,22 +201,27 @@ export const toggleEmailAccountStatus = async (accountId, userId, isActive) => {
|
||||
export const setPrimaryEmailAccount = async (accountId, userId) => {
|
||||
const account = await getEmailAccountById(accountId, userId);
|
||||
|
||||
// Remove primary flag from all accounts
|
||||
await db
|
||||
.update(emailAccounts)
|
||||
.set({ isPrimary: false, updatedAt: new Date() })
|
||||
.where(eq(emailAccounts.userId, userId));
|
||||
// Use transaction to prevent race conditions
|
||||
const updated = await db.transaction(async (tx) => {
|
||||
// Remove primary flag from all accounts
|
||||
await tx
|
||||
.update(emailAccounts)
|
||||
.set({ isPrimary: false, updatedAt: new Date() })
|
||||
.where(eq(emailAccounts.userId, userId));
|
||||
|
||||
// Set new primary account
|
||||
const [updated] = await db
|
||||
.update(emailAccounts)
|
||||
.set({
|
||||
isPrimary: true,
|
||||
isActive: true, // Primary account must be active
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(emailAccounts.id, accountId))
|
||||
.returning();
|
||||
// Set new primary account
|
||||
const [updatedAccount] = await tx
|
||||
.update(emailAccounts)
|
||||
.set({
|
||||
isPrimary: true,
|
||||
isActive: true, // Primary account must be active
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(emailAccounts.id, accountId))
|
||||
.returning();
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
|
||||
return {
|
||||
id: updated.id,
|
||||
@@ -260,9 +266,9 @@ export const deleteEmailAccount = async (accountId, userId) => {
|
||||
* Get email account with decrypted password (for JMAP operations)
|
||||
*/
|
||||
export const getEmailAccountWithCredentials = async (accountId, userId) => {
|
||||
console.log('🔐 getEmailAccountWithCredentials called:', { accountId, userId });
|
||||
logger.debug('getEmailAccountWithCredentials called', { accountId, userId });
|
||||
const account = await getEmailAccountById(accountId, userId);
|
||||
console.log('📦 Account retrieved:', {
|
||||
logger.debug('Account retrieved', {
|
||||
id: account.id,
|
||||
email: account.email,
|
||||
hasPassword: !!account.emailPassword,
|
||||
@@ -270,7 +276,7 @@ export const getEmailAccountWithCredentials = async (accountId, userId) => {
|
||||
});
|
||||
|
||||
const decryptedPassword = decryptPassword(account.emailPassword);
|
||||
console.log('🔓 Password decrypted, length:', decryptedPassword?.length);
|
||||
logger.debug('Password decrypted', { passwordLength: decryptedPassword?.length });
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
|
||||
@@ -1,31 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
const JMAP_CONFIG = {
|
||||
server: process.env.JMAP_SERVER || 'https://mail.truemail.sk/jmap/',
|
||||
username: process.env.JMAP_USERNAME || 'info1_test@truemail.sk',
|
||||
password: process.env.JMAP_PASSWORD || 'info1',
|
||||
accountId: process.env.JMAP_ACCOUNT_ID || 'ba',
|
||||
};
|
||||
|
||||
/**
|
||||
* Získa JMAP session
|
||||
*/
|
||||
const getJmapSession = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${JMAP_CONFIG.server}session`, {
|
||||
auth: {
|
||||
username: JMAP_CONFIG.username,
|
||||
password: JMAP_CONFIG.password,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
logger.error('Failed to get JMAP session', error);
|
||||
throw new Error('Email service nedostupný');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validuje JMAP credentials a vráti account ID
|
||||
* @param {string} email - Email address
|
||||
@@ -33,8 +8,14 @@ const getJmapSession = async () => {
|
||||
* @returns {Promise<{accountId: string, session: object}>}
|
||||
*/
|
||||
export const validateJmapCredentials = async (email, password) => {
|
||||
const jmapServer = process.env.JMAP_SERVER;
|
||||
|
||||
if (!jmapServer) {
|
||||
throw new Error('JMAP_SERVER environment variable is not configured');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${JMAP_CONFIG.server}session`, {
|
||||
const response = await axios.get(`${jmapServer}session`, {
|
||||
auth: {
|
||||
username: email,
|
||||
password: password,
|
||||
@@ -68,188 +49,3 @@ export const validateJmapCredentials = async (email, password) => {
|
||||
throw new Error('Nepodarilo sa overiť emailový účet');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pošle email pomocou JMAP
|
||||
*/
|
||||
const sendJmapEmail = async ({ to, subject, htmlBody, textBody }) => {
|
||||
try {
|
||||
const session = await getJmapSession();
|
||||
const apiUrl = session.apiUrl;
|
||||
|
||||
const emailObject = {
|
||||
from: [{ email: JMAP_CONFIG.username }],
|
||||
to: [{ email: to }],
|
||||
subject,
|
||||
htmlBody: [
|
||||
{
|
||||
partId: '1',
|
||||
type: 'text/html',
|
||||
value: htmlBody,
|
||||
},
|
||||
],
|
||||
textBody: [
|
||||
{
|
||||
partId: '2',
|
||||
type: 'text/plain',
|
||||
value: textBody || subject,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await axios.post(
|
||||
apiUrl,
|
||||
{
|
||||
using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'],
|
||||
methodCalls: [
|
||||
[
|
||||
'Email/set',
|
||||
{
|
||||
accountId: JMAP_CONFIG.accountId,
|
||||
create: {
|
||||
draft: emailObject,
|
||||
},
|
||||
},
|
||||
'0',
|
||||
],
|
||||
[
|
||||
'EmailSubmission/set',
|
||||
{
|
||||
accountId: JMAP_CONFIG.accountId,
|
||||
create: {
|
||||
submission: {
|
||||
emailId: '#draft',
|
||||
envelope: {
|
||||
mailFrom: { email: JMAP_CONFIG.username },
|
||||
rcptTo: [{ email: to }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'1',
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
auth: {
|
||||
username: JMAP_CONFIG.username,
|
||||
password: JMAP_CONFIG.password,
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
logger.success(`Email sent to ${to}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send email to ${to}`, error);
|
||||
throw new Error('Nepodarilo sa odoslať email');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Email templates
|
||||
*/
|
||||
|
||||
export const sendVerificationEmail = async (to, username, verificationToken) => {
|
||||
const verificationUrl = `${process.env.BETTER_AUTH_URL}/api/auth/verify-email?token=${verificationToken}`;
|
||||
|
||||
const htmlBody = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.footer { margin-top: 30px; font-size: 12px; color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Vitajte v CRM systéme, ${username}!</h2>
|
||||
<p>Prosím, verifikujte svoju emailovú adresu kliknutím na tlačidlo nižšie:</p>
|
||||
<a href="${verificationUrl}" class="button">Verifikovať email</a>
|
||||
<p>Alebo skopírujte tento link do prehliadača:</p>
|
||||
<p>${verificationUrl}</p>
|
||||
<p class="footer">
|
||||
Tento link vyprší za 24 hodín.<br>
|
||||
Ak ste tento email neočakávali, môžete ho ignorovať.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const textBody = `
|
||||
Vitajte v CRM systéme, ${username}!
|
||||
|
||||
Prosím, verifikujte svoju emailovú adresu kliknutím na tento link:
|
||||
${verificationUrl}
|
||||
|
||||
Tento link vyprší za 24 hodín.
|
||||
Ak ste tento email neočakávali, môžete ho ignorovať.
|
||||
`;
|
||||
|
||||
return sendJmapEmail({
|
||||
to,
|
||||
subject: 'Verifikácia emailu - CRM systém',
|
||||
htmlBody,
|
||||
textBody,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendWelcomeEmail = async (to, username) => {
|
||||
const htmlBody = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Vitajte v CRM systéme, ${username}!</h2>
|
||||
<p>Váš účet bol úspešne vytvorený a nastavený.</p>
|
||||
<p>Môžete sa prihlásiť a začať používať systém.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const textBody = `
|
||||
Vitajte v CRM systéme, ${username}!
|
||||
|
||||
Váš účet bol úspešne vytvorený a nastavený.
|
||||
Môžete sa prihlásiť a začať používať systém.
|
||||
`;
|
||||
|
||||
return sendJmapEmail({
|
||||
to,
|
||||
subject: 'Vitajte v CRM systéme',
|
||||
htmlBody,
|
||||
textBody,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchrónne posielanie emailov (non-blocking)
|
||||
*/
|
||||
export const sendEmailAsync = (emailFunction, ...args) => {
|
||||
// Spustí email sending v pozadí
|
||||
emailFunction(...args).catch((error) => {
|
||||
logger.error('Async email sending failed', error);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user