Agenda Sync refactor Mig Graph

This commit is contained in:
alma 2026-01-14 16:15:23 +01:00
parent 60c5ce8e1a
commit a6b63d723a
2 changed files with 138 additions and 35 deletions

View File

@ -989,12 +989,12 @@ export async function getEmails(
folder,
error: error instanceof Error ? error.message : String(error),
});
// Fall back to IMAP if Graph API fails
logger.debug('[EMAIL] Falling back to IMAP after Graph API error');
// Don't fall back to IMAP for Microsoft accounts - the token doesn't have IMAP permissions
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
const client = await getImapConnection(userId, accountId);

View File

@ -4,6 +4,8 @@ import { Notification, NotificationCount } from '@/lib/types/notification';
import { getRedisClient } from '@/lib/redis';
import { prisma } from '@/lib/prisma';
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 {
readonly sourceName = 'email';
@ -44,53 +46,92 @@ export class EmailAdapter implements NotificationAdapter {
for (const account of accounts) {
const accountId = account.id;
try {
// Get IMAP connection for this account
logger.debug('[EMAIL_ADAPTER] Processing account', {
userId,
accountId,
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] = {};
// 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;
}
if (isMicrosoftAccount) {
// Use Graph API for Microsoft accounts
try {
// Check folder status without opening it (more efficient)
const status = await client.status(folder, { unseen: true });
const unreadCount = await getGraphUnreadCount(mailCredential.id, 'Inbox');
unreadCounts[accountId]['INBOX'] = unreadCount;
if (status && typeof status.unseen === 'number') {
// Store the unread count
unreadCounts[accountId][folder] = status.unseen;
logger.debug('[EMAIL_ADAPTER] Unread count (Graph API)', {
userId,
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,
accountId,
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) {
@ -227,6 +268,68 @@ export class EmailAdapter implements NotificationAdapter {
// Use the same flow as getEmails() but filter for unread only
for (const account of accounts) {
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);
// Use the same approach as getEmails() - open mailbox first