Add debug logging for markContactEmailsAsRead and remove password change restriction
This commit is contained in:
@@ -15,13 +15,9 @@ const pool = new Pool({
|
||||
connectionTimeoutMillis: 2000,
|
||||
});
|
||||
|
||||
// Test database connection
|
||||
pool.on('connect', () => {
|
||||
console.log('✅ Database connected successfully');
|
||||
});
|
||||
|
||||
// Note: Connection logging handled in index.js to avoid circular dependencies
|
||||
pool.on('error', (err) => {
|
||||
console.error('❌ Unexpected database error:', err);
|
||||
console.error('Unexpected database error:', err);
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ export const getAllUsers = async (req, res) => {
|
||||
* Získanie konkrétneho usera (admin only)
|
||||
* GET /api/admin/users/:userId
|
||||
*/
|
||||
export const getUserById = async (req, res) => {
|
||||
export const getUser = async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as contactService from '../services/contact.service.js';
|
||||
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap.service.js';
|
||||
import { formatErrorResponse } from '../utils/errors.js';
|
||||
import * as emailAccountService from '../services/email-account.service.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Get all contacts for authenticated user
|
||||
@@ -34,18 +35,18 @@ export const discoverContacts = async (req, res) => {
|
||||
const userId = req.userId;
|
||||
const { accountId, search = '', limit = 50 } = req.query;
|
||||
|
||||
console.log('🔍 discoverContacts called:', { userId, accountId, search, limit });
|
||||
logger.debug('discoverContacts called', { userId, accountId, search, limit });
|
||||
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Getting email account by ID:', accountId);
|
||||
logger.debug('Getting email account by ID', { accountId });
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
console.log('✅ Email account retrieved:', { id: emailAccount.id, email: emailAccount.email });
|
||||
logger.debug('Email account retrieved', { id: emailAccount.id, email: emailAccount.email });
|
||||
} else {
|
||||
console.log('📧 No accountId provided, getting primary account for user:', userId);
|
||||
logger.debug('No accountId provided, getting primary account', { userId });
|
||||
const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId);
|
||||
console.log('🔑 Primary account:', primaryAccount ? { id: primaryAccount.id, email: primaryAccount.email } : 'NOT FOUND');
|
||||
logger.debug('Primary account', primaryAccount ? { id: primaryAccount.id, email: primaryAccount.email } : { found: false });
|
||||
if (!primaryAccount) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -56,11 +57,11 @@ export const discoverContacts = async (req, res) => {
|
||||
});
|
||||
}
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId);
|
||||
console.log('✅ Email account retrieved from primary:', { id: emailAccount.id, email: emailAccount.email });
|
||||
logger.debug('Email account retrieved from primary', { id: emailAccount.id, email: emailAccount.email });
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
console.log('🔧 JMAP Config created:', {
|
||||
logger.debug('JMAP Config created', {
|
||||
server: jmapConfig.server,
|
||||
username: jmapConfig.username,
|
||||
accountId: jmapConfig.accountId,
|
||||
@@ -80,8 +81,7 @@ export const discoverContacts = async (req, res) => {
|
||||
data: potentialContacts,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in discoverContacts:', error);
|
||||
console.error('Error stack:', error.stack);
|
||||
logger.error('ERROR in discoverContacts', { error: error.message, stack: error.stack });
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
@@ -95,10 +95,10 @@ export const discoverContacts = async (req, res) => {
|
||||
export const addContact = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
console.log('📦 Full req.body:', JSON.stringify(req.body, null, 2));
|
||||
logger.debug('Full req.body', { body: req.body });
|
||||
const { email, name = '', notes = '', accountId } = req.body;
|
||||
|
||||
console.log('➕ addContact called:', { userId, email, name, accountId });
|
||||
logger.debug('addContact called', { userId, email, name, accountId });
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({
|
||||
@@ -113,10 +113,10 @@ export const addContact = async (req, res) => {
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Using provided accountId:', accountId);
|
||||
logger.debug('Using provided accountId', { accountId });
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
} else {
|
||||
console.log('📧 No accountId provided, using primary account');
|
||||
logger.debug('No accountId provided, using primary account');
|
||||
const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId);
|
||||
if (!primaryAccount) {
|
||||
return res.status(400).json({
|
||||
@@ -128,7 +128,7 @@ export const addContact = async (req, res) => {
|
||||
});
|
||||
}
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId);
|
||||
console.log('📧 Using primary account:', primaryAccount.id);
|
||||
logger.debug('Using primary account', { accountId: primaryAccount.id });
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Get all emails for authenticated user
|
||||
@@ -91,7 +92,7 @@ export const getUnreadCount = async (req, res) => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in getUnreadCount:', error);
|
||||
logger.error('ERROR in getUnreadCount', { error: error.message });
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
@@ -153,7 +154,7 @@ export const syncEmails = async (req, res) => {
|
||||
totalSynced += total;
|
||||
totalNew += saved;
|
||||
} catch (syncError) {
|
||||
console.error(`Failed to sync emails for contact ${contact.email}`, syncError);
|
||||
logger.error('Failed to sync emails for contact', { contactEmail: contact.email, error: syncError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +199,29 @@ export const markAsRead = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all emails from contact as read
|
||||
* POST /api/emails/contact/:contactId/read
|
||||
*/
|
||||
export const markContactEmailsRead = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { contactId } = req.params;
|
||||
|
||||
const result = await crmEmailService.markContactEmailsAsRead(userId, contactId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Označených ${result.count} emailov ako prečítaných`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('ERROR in markContactEmailsRead', { error: error.message });
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark entire thread as read
|
||||
* POST /api/emails/thread/:threadId/read
|
||||
@@ -224,7 +248,7 @@ export const markThreadRead = async (req, res) => {
|
||||
try {
|
||||
await markEmailAsRead(jmapConfig, userId, email.jmapId, true);
|
||||
} catch (jmapError) {
|
||||
console.error(`Failed to mark JMAP email ${email.jmapId} as read`, jmapError);
|
||||
logger.error('Failed to mark JMAP email as read', { jmapId: email.jmapId, error: jmapError.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,15 +351,15 @@ export const searchEmailsJMAP = async (req, res) => {
|
||||
const userId = req.userId;
|
||||
const { query = '', limit = 50, offset = 0, accountId } = req.query;
|
||||
|
||||
console.log('🔍 searchEmailsJMAP called:', { userId, query, limit, offset, accountId });
|
||||
logger.debug('searchEmailsJMAP called', { userId, query, limit, offset, accountId });
|
||||
|
||||
// Get email account (or primary if not specified)
|
||||
let emailAccount;
|
||||
if (accountId) {
|
||||
console.log('📧 Using provided accountId:', accountId);
|
||||
logger.debug('Using provided accountId', { accountId });
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(accountId, userId);
|
||||
} else {
|
||||
console.log('📧 No accountId provided, using primary account');
|
||||
logger.debug('No accountId provided, using primary account');
|
||||
const primaryAccount = await emailAccountService.getPrimaryEmailAccount(userId);
|
||||
if (!primaryAccount) {
|
||||
return res.status(400).json({
|
||||
@@ -347,7 +371,7 @@ export const searchEmailsJMAP = async (req, res) => {
|
||||
});
|
||||
}
|
||||
emailAccount = await emailAccountService.getEmailAccountWithCredentials(primaryAccount.id, userId);
|
||||
console.log('📧 Using primary account:', primaryAccount.id);
|
||||
logger.debug('Using primary account', { accountId: primaryAccount.id });
|
||||
}
|
||||
|
||||
const jmapConfig = getJmapConfigFromAccount(emailAccount);
|
||||
@@ -366,7 +390,7 @@ export const searchEmailsJMAP = async (req, res) => {
|
||||
data: results,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ ERROR in searchEmailsJMAP:', error);
|
||||
logger.error('ERROR in searchEmailsJMAP', { error: error.message });
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
|
||||
@@ -57,34 +57,3 @@ export const authenticate = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Optional authentication - nepovinnné overenie
|
||||
* Ak je token poskytnutý, overí ho, ale nehodí error ak nie je
|
||||
*/
|
||||
export const optionalAuthenticate = async (req, res, next) => {
|
||||
try {
|
||||
let token = null;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
}
|
||||
|
||||
if (!token && req.cookies && req.cookies.accessToken) {
|
||||
token = req.cookies.accessToken;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
const decoded = verifyAccessToken(token);
|
||||
const user = await getUserById(decoded.id);
|
||||
req.user = user;
|
||||
req.userId = user.id;
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// Ignoruj chyby, len pokračuj bez user objektu
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
export function validateBody(req, res, next) {
|
||||
const data = JSON.stringify({ body: req.body, query: req.query, params: req.params });
|
||||
const dangerousPatterns = [
|
||||
@@ -10,8 +12,8 @@ export function validateBody(req, res, next) {
|
||||
];
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(data)) {
|
||||
console.warn(`❌ Suspicious input detected: ${data}`);
|
||||
return res.status(400).json({ message: '🚨 Malicious content detected in request data' });
|
||||
logger.warn('Suspicious input detected', { data: data.substring(0, 100) });
|
||||
return res.status(400).json({ message: 'Malicious content detected in request data' });
|
||||
}
|
||||
}
|
||||
next();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ZodError } from 'zod';
|
||||
import { ValidationError } from '../../utils/errors.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Middleware na validáciu request body pomocou Zod schema
|
||||
@@ -34,7 +35,7 @@ export const validateBody = (schema) => {
|
||||
}
|
||||
|
||||
// Log unexpected errors
|
||||
console.error('Validation error:', error);
|
||||
logger.error('Validation error', { error: error.message });
|
||||
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -74,7 +75,7 @@ export const validateQuery = (schema) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.error('Query validation error:', error);
|
||||
logger.error('Query validation error', { error: error.message });
|
||||
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -114,7 +115,7 @@ export const validateParams = (schema) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.error('Params validation error:', error);
|
||||
logger.error('Params validation error', { error: error.message });
|
||||
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ router.get('/users', adminController.getAllUsers);
|
||||
router.get(
|
||||
'/users/:userId',
|
||||
validateParams(z.object({ userId: z.string().uuid() })),
|
||||
adminController.getUserById
|
||||
adminController.getUser
|
||||
);
|
||||
|
||||
// Zmena role usera
|
||||
|
||||
@@ -49,6 +49,13 @@ router.get(
|
||||
crmEmailController.getContactEmails
|
||||
);
|
||||
|
||||
// Mark all emails from contact as read
|
||||
router.post(
|
||||
'/contact/:contactId/read',
|
||||
validateParams(z.object({ contactId: z.string().uuid() })),
|
||||
crmEmailController.markContactEmailsRead
|
||||
);
|
||||
|
||||
// Mark email as read/unread
|
||||
router.patch(
|
||||
'/:jmapId/read',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -67,8 +67,12 @@ export const generateVerificationToken = () => {
|
||||
* @returns {string} Encrypted password in format: iv:authTag:encrypted
|
||||
*/
|
||||
export const encryptPassword = (text) => {
|
||||
if (!process.env.JWT_SECRET) {
|
||||
throw new Error('JWT_SECRET environment variable is required for password encryption');
|
||||
}
|
||||
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32);
|
||||
const iv = crypto.randomBytes(16);
|
||||
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
@@ -86,8 +90,12 @@ export const encryptPassword = (text) => {
|
||||
* @returns {string} Plain text password
|
||||
*/
|
||||
export const decryptPassword = (encryptedText) => {
|
||||
if (!process.env.JWT_SECRET) {
|
||||
throw new Error('JWT_SECRET environment variable is required for password decryption');
|
||||
}
|
||||
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32);
|
||||
|
||||
const parts = encryptedText.split(':');
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
|
||||
Reference in New Issue
Block a user