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