Agenda Sync refactor Mig Graph
This commit is contained in:
parent
60c5ce8e1a
commit
a6b63d723a
@ -989,12 +989,12 @@ export async function getEmails(
|
|||||||
folder,
|
folder,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
});
|
});
|
||||||
// Fall back to IMAP if Graph API fails
|
// Don't fall back to IMAP for Microsoft accounts - the token doesn't have IMAP permissions
|
||||||
logger.debug('[EMAIL] Falling back to IMAP after Graph API error');
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use IMAP for non-Microsoft accounts or as fallback
|
// Use IMAP for non-Microsoft accounts only
|
||||||
// The getImapConnection function already handles 'default' accountId by finding the first available account
|
// The getImapConnection function already handles 'default' accountId by finding the first available account
|
||||||
const client = await getImapConnection(userId, accountId);
|
const client = await getImapConnection(userId, accountId);
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { Notification, NotificationCount } from '@/lib/types/notification';
|
|||||||
import { getRedisClient } from '@/lib/redis';
|
import { getRedisClient } from '@/lib/redis';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { getImapConnection } from '@/lib/services/email-service';
|
import { getImapConnection } from '@/lib/services/email-service';
|
||||||
|
import { getGraphUnreadCount, fetchGraphEmails } from '@/lib/services/microsoft-graph-mail';
|
||||||
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
export class EmailAdapter implements NotificationAdapter {
|
export class EmailAdapter implements NotificationAdapter {
|
||||||
readonly sourceName = 'email';
|
readonly sourceName = 'email';
|
||||||
@ -44,53 +46,92 @@ export class EmailAdapter implements NotificationAdapter {
|
|||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
const accountId = account.id;
|
const accountId = account.id;
|
||||||
try {
|
try {
|
||||||
// Get IMAP connection for this account
|
|
||||||
logger.debug('[EMAIL_ADAPTER] Processing account', {
|
logger.debug('[EMAIL_ADAPTER] Processing account', {
|
||||||
userId,
|
userId,
|
||||||
accountId,
|
accountId,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
});
|
});
|
||||||
const client = await getImapConnection(userId, accountId);
|
|
||||||
|
// Check if this is a Microsoft account that should use Graph API
|
||||||
|
const mailCredential = await prisma.mailCredentials.findUnique({
|
||||||
|
where: { id: accountId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
host: true,
|
||||||
|
use_oauth: true,
|
||||||
|
refresh_token: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMicrosoftAccount = mailCredential &&
|
||||||
|
mailCredential.host === 'outlook.office365.com' &&
|
||||||
|
mailCredential.use_oauth &&
|
||||||
|
mailCredential.refresh_token;
|
||||||
|
|
||||||
unreadCounts[accountId] = {};
|
unreadCounts[accountId] = {};
|
||||||
|
|
||||||
// Standard folders to check (focus on INBOX for notifications)
|
if (isMicrosoftAccount) {
|
||||||
const standardFolders = ['INBOX'];
|
// Use Graph API for Microsoft accounts
|
||||||
|
|
||||||
// Get mailboxes for this account to check if folders exist
|
|
||||||
const mailboxes = await client.list();
|
|
||||||
const availableFolders = mailboxes.map(mb => mb.path);
|
|
||||||
|
|
||||||
// Check each standard folder if it exists
|
|
||||||
for (const folder of standardFolders) {
|
|
||||||
// Skip if folder doesn't exist in this account
|
|
||||||
if (!availableFolders.includes(folder) &&
|
|
||||||
!availableFolders.some(f => f.toLowerCase() === folder.toLowerCase())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check folder status without opening it (more efficient)
|
const unreadCount = await getGraphUnreadCount(mailCredential.id, 'Inbox');
|
||||||
const status = await client.status(folder, { unseen: true });
|
unreadCounts[accountId]['INBOX'] = unreadCount;
|
||||||
|
|
||||||
if (status && typeof status.unseen === 'number') {
|
logger.debug('[EMAIL_ADAPTER] Unread count (Graph API)', {
|
||||||
// Store the unread count
|
userId,
|
||||||
unreadCounts[accountId][folder] = status.unseen;
|
accountId,
|
||||||
|
folder: 'INBOX',
|
||||||
|
unread: unreadCount,
|
||||||
|
});
|
||||||
|
} catch (graphError) {
|
||||||
|
logger.error('[EMAIL_ADAPTER] Error getting unread count via Graph API', {
|
||||||
|
userId,
|
||||||
|
accountId,
|
||||||
|
error: graphError instanceof Error ? graphError.message : String(graphError),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use IMAP for non-Microsoft accounts
|
||||||
|
const client = await getImapConnection(userId, accountId);
|
||||||
|
|
||||||
|
// Standard folders to check (focus on INBOX for notifications)
|
||||||
|
const standardFolders = ['INBOX'];
|
||||||
|
|
||||||
|
// Get mailboxes for this account to check if folders exist
|
||||||
|
const mailboxes = await client.list();
|
||||||
|
const availableFolders = mailboxes.map(mb => mb.path);
|
||||||
|
|
||||||
|
// Check each standard folder if it exists
|
||||||
|
for (const folder of standardFolders) {
|
||||||
|
// Skip if folder doesn't exist in this account
|
||||||
|
if (!availableFolders.includes(folder) &&
|
||||||
|
!availableFolders.some(f => f.toLowerCase() === folder.toLowerCase())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check folder status without opening it (more efficient)
|
||||||
|
const status = await client.status(folder, { unseen: true });
|
||||||
|
|
||||||
logger.debug('[EMAIL_ADAPTER] Unread count', {
|
if (status && typeof status.unseen === 'number') {
|
||||||
|
// Store the unread count
|
||||||
|
unreadCounts[accountId][folder] = status.unseen;
|
||||||
|
|
||||||
|
logger.debug('[EMAIL_ADAPTER] Unread count (IMAP)', {
|
||||||
|
userId,
|
||||||
|
accountId,
|
||||||
|
folder,
|
||||||
|
unread: status.unseen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (folderError) {
|
||||||
|
logger.error('[EMAIL_ADAPTER] Error getting unread count for folder', {
|
||||||
userId,
|
userId,
|
||||||
accountId,
|
accountId,
|
||||||
folder,
|
folder,
|
||||||
unread: status.unseen,
|
error: folderError instanceof Error ? folderError.message : String(folderError),
|
||||||
});
|
});
|
||||||
|
// Continue to next folder even if this one fails
|
||||||
}
|
}
|
||||||
} catch (folderError) {
|
|
||||||
logger.error('[EMAIL_ADAPTER] Error getting unread count for folder', {
|
|
||||||
userId,
|
|
||||||
accountId,
|
|
||||||
folder,
|
|
||||||
error: folderError instanceof Error ? folderError.message : String(folderError),
|
|
||||||
});
|
|
||||||
// Continue to next folder even if this one fails
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (accountError) {
|
} catch (accountError) {
|
||||||
@ -227,6 +268,68 @@ export class EmailAdapter implements NotificationAdapter {
|
|||||||
// Use the same flow as getEmails() but filter for unread only
|
// Use the same flow as getEmails() but filter for unread only
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
try {
|
try {
|
||||||
|
// Check if this is a Microsoft account that should use Graph API
|
||||||
|
const mailCredential = await prisma.mailCredentials.findUnique({
|
||||||
|
where: { id: account.id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
host: true,
|
||||||
|
use_oauth: true,
|
||||||
|
refresh_token: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMicrosoftAccount = mailCredential &&
|
||||||
|
mailCredential.host === 'outlook.office365.com' &&
|
||||||
|
mailCredential.use_oauth &&
|
||||||
|
mailCredential.refresh_token;
|
||||||
|
|
||||||
|
if (isMicrosoftAccount) {
|
||||||
|
// Use Graph API for Microsoft accounts
|
||||||
|
try {
|
||||||
|
const graphResult = await fetchGraphEmails(
|
||||||
|
mailCredential.id,
|
||||||
|
'Inbox',
|
||||||
|
limit * 3, // Get more than limit to have enough after filtering
|
||||||
|
0,
|
||||||
|
'isRead eq false' // Filter for unread only
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert Graph messages to notifications
|
||||||
|
for (const graphMessage of graphResult.value) {
|
||||||
|
if (graphMessage.isRead) continue; // Double-check unread status
|
||||||
|
|
||||||
|
notifications.push({
|
||||||
|
id: `email-${graphMessage.id}`,
|
||||||
|
source: 'email',
|
||||||
|
title: graphMessage.subject || '(No subject)',
|
||||||
|
message: graphMessage.bodyPreview || '',
|
||||||
|
timestamp: new Date(graphMessage.receivedDateTime),
|
||||||
|
read: false,
|
||||||
|
link: `/courrier/${account.id}?email=${graphMessage.id}`,
|
||||||
|
metadata: {
|
||||||
|
accountId: account.id,
|
||||||
|
accountEmail: account.email,
|
||||||
|
emailId: graphMessage.id,
|
||||||
|
folder: 'INBOX',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (notifications.length >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (graphError) {
|
||||||
|
logger.error('[EMAIL_ADAPTER] Error fetching notifications via Graph API', {
|
||||||
|
userId,
|
||||||
|
accountId: account.id,
|
||||||
|
error: graphError instanceof Error ? graphError.message : String(graphError),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue; // Skip IMAP processing for Microsoft accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use IMAP for non-Microsoft accounts
|
||||||
const client = await getImapConnection(userId, account.id);
|
const client = await getImapConnection(userId, account.id);
|
||||||
|
|
||||||
// Use the same approach as getEmails() - open mailbox first
|
// Use the same approach as getEmails() - open mailbox first
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user