Files
crm-server/src/services/crm-email.service.js

235 lines
6.4 KiB
JavaScript

import { db } from '../config/database.js';
import { emails, contacts } from '../db/schema.js';
import { eq, and, or, desc, like, sql } from 'drizzle-orm';
import { NotFoundError } from '../utils/errors.js';
/**
* Get all emails for a user (only from added contacts)
* If emailAccountId is provided, filter by that account
*/
export const getUserEmails = async (userId, emailAccountId = null) => {
const conditions = [eq(emails.userId, userId)];
if (emailAccountId) {
conditions.push(eq(emails.emailAccountId, emailAccountId));
}
const userEmails = await db
.select({
id: emails.id,
jmapId: emails.jmapId,
messageId: emails.messageId,
threadId: emails.threadId,
inReplyTo: emails.inReplyTo,
from: emails.from,
to: emails.to,
subject: emails.subject,
body: emails.body,
isRead: emails.isRead,
date: emails.date,
createdAt: emails.createdAt,
emailAccountId: emails.emailAccountId,
contact: {
id: contacts.id,
email: contacts.email,
name: contacts.name,
},
})
.from(emails)
.leftJoin(contacts, eq(emails.contactId, contacts.id))
.where(and(...conditions))
.orderBy(desc(emails.date));
return userEmails;
};
/**
* Get emails by thread ID
*/
export const getEmailThread = async (userId, threadId) => {
const thread = await db
.select()
.from(emails)
.where(and(eq(emails.userId, userId), eq(emails.threadId, threadId)))
.orderBy(emails.date);
if (thread.length === 0) {
throw new NotFoundError('Thread nenájdený');
}
return thread;
};
/**
* Search emails (from, to, subject)
* If emailAccountId is provided, filter by that account
*/
export const searchEmails = async (userId, query, emailAccountId = null) => {
if (!query || query.trim().length < 2) {
throw new Error('Search term must be at least 2 characters');
}
const searchPattern = `%${query}%`;
const conditions = [
eq(emails.userId, userId),
or(
like(emails.from, searchPattern),
like(emails.to, searchPattern),
like(emails.subject, searchPattern)
),
];
if (emailAccountId) {
conditions.push(eq(emails.emailAccountId, emailAccountId));
}
const results = await db
.select()
.from(emails)
.where(and(...conditions))
.orderBy(desc(emails.date))
.limit(50);
return results;
};
/**
* Get unread email count
* Returns total count and counts per email account
*/
export const getUnreadCount = async (userId) => {
// Get total unread count
const totalResult = await db
.select({ count: sql`count(*)::int` })
.from(emails)
.where(and(eq(emails.userId, userId), eq(emails.isRead, false)));
const totalUnread = totalResult[0]?.count || 0;
// Get unread count per email account
const accountCounts = await db
.select({
emailAccountId: emails.emailAccountId,
count: sql`count(*)::int`,
})
.from(emails)
.where(and(eq(emails.userId, userId), eq(emails.isRead, false)))
.groupBy(emails.emailAccountId);
return {
totalUnread,
accounts: accountCounts.map((ac) => ({
emailAccountId: ac.emailAccountId,
unreadCount: ac.count,
})),
};
};
/**
* 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 });
// Get the contact info first
const [contact] = await db
.select()
.from(contacts)
.where(eq(contacts.id, contactId))
.limit(1);
console.log('👤 Contact info:', {
id: contact?.id,
email: contact?.email,
name: contact?.name,
});
// 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 (by contactId):', {
total: allContactEmails.length,
unread: allContactEmails.filter(e => !e.isRead).length,
read: allContactEmails.filter(e => e.isRead).length,
sampleEmails: allContactEmails.slice(0, 3),
});
// Check if there are emails from this sender but with NULL or different contactId
if (contact) {
const emailsFromSender = 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),
or(
eq(emails.from, contact.email),
like(emails.from, `%<${contact.email}>%`)
)
))
.limit(10);
console.log('📨 Emails from this sender email (any contactId):', {
total: emailsFromSender.length,
withContactId: emailsFromSender.filter(e => e.contactId === contactId).length,
withNullContactId: emailsFromSender.filter(e => e.contactId === null).length,
withDifferentContactId: emailsFromSender.filter(e => e.contactId && e.contactId !== contactId).length,
sampleEmails: emailsFromSender.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 };
};
/**
* Get emails for a specific contact
*/
export const getContactEmails = async (userId, contactId) => {
const contactEmails = await db
.select()
.from(emails)
.where(and(eq(emails.userId, userId), eq(emails.contactId, contactId)))
.orderBy(desc(emails.date));
return contactEmails;
};