add searching, total unread message, create user

This commit is contained in:
richardtekula
2025-11-19 08:45:37 +01:00
parent da01d586fc
commit 97f437c1c4
8 changed files with 1338 additions and 90 deletions

View File

@@ -193,10 +193,148 @@ export const discoverContactsFromJMAP = async (jmapConfig, userId, searchTerm =
}
};
/**
* Search emails using JMAP full-text search
* Searches in: from, to, subject, and email body
* Returns list of unique senders grouped by email address
*/
export const searchEmailsJMAP = async (jmapConfig, userId, query, limit = 50, offset = 0) => {
try {
logger.info(`Searching emails in JMAP (query: "${query}", limit: ${limit}, offset: ${offset})`);
if (!query || query.trim().length < 1) {
return [];
}
// Use JMAP search with wildcards for substring matching
// Add wildcards (*) to enable partial matching: "ander" -> "*ander*"
const wildcardQuery = query.includes('*') ? query : `*${query}*`;
let queryResponse;
try {
// Try with 'text' filter first (full-text search if supported)
queryResponse = await jmapRequest(jmapConfig, [
[
'Email/query',
{
accountId: jmapConfig.accountId,
filter: {
text: wildcardQuery, // Full-text search with wildcards
},
sort: [{ property: 'receivedAt', isAscending: false }],
position: offset,
limit: 200,
},
'query1',
],
]);
} catch (textFilterError) {
// If 'text' filter fails, fall back to OR conditions with wildcards
logger.warn('Text filter failed, falling back to OR conditions', textFilterError);
queryResponse = await jmapRequest(jmapConfig, [
[
'Email/query',
{
accountId: jmapConfig.accountId,
filter: {
operator: 'OR',
conditions: [
{ from: wildcardQuery },
{ to: wildcardQuery },
{ subject: wildcardQuery },
],
},
sort: [{ property: 'receivedAt', isAscending: false }],
position: offset,
limit: 200,
},
'query1',
],
]);
}
const emailIds = queryResponse.methodResponses?.[0]?.[1]?.ids;
if (!emailIds || emailIds.length === 0) {
logger.info('No emails found matching search query');
return [];
}
logger.info(`Found ${emailIds.length} emails matching query`);
// Fetch email metadata
const getResponse = await jmapRequest(jmapConfig, [
[
'Email/get',
{
accountId: jmapConfig.accountId,
ids: emailIds,
properties: ['from', 'to', 'subject', 'receivedAt', 'preview'],
},
'get1',
],
]);
const emailsList = getResponse.methodResponses[0][1].list;
// Get existing contacts for this user
const existingContacts = await db
.select()
.from(contacts)
.where(eq(contacts.userId, userId));
const contactEmailsSet = new Set(existingContacts.map((c) => c.email.toLowerCase()));
// Group by sender (unique senders)
const sendersMap = new Map();
const myEmail = jmapConfig.username.toLowerCase();
emailsList.forEach((email) => {
const fromEmail = email.from?.[0]?.email;
if (!fromEmail || fromEmail.toLowerCase() === myEmail) {
return; // Skip my own emails
}
// Keep only the most recent email from each sender
if (!sendersMap.has(fromEmail)) {
sendersMap.set(fromEmail, {
email: fromEmail,
name: email.from?.[0]?.name || fromEmail.split('@')[0],
latestSubject: email.subject || '(No Subject)',
latestDate: email.receivedAt,
snippet: email.preview || '',
isContact: contactEmailsSet.has(fromEmail.toLowerCase()),
});
}
});
// Convert to array, sort by date, and apply limit
const senders = Array.from(sendersMap.values())
.sort((a, b) => new Date(b.latestDate) - new Date(a.latestDate))
.slice(0, limit);
logger.success(`Found ${senders.length} unique senders matching query`);
return senders;
} catch (error) {
logger.error('Failed to search emails in JMAP', error);
throw error;
}
};
/**
* Sync emails from a specific sender (when adding as contact)
*/
export const syncEmailsFromSender = async (jmapConfig, userId, contactId, senderEmail) => {
export const syncEmailsFromSender = async (
jmapConfig,
userId,
contactId,
senderEmail,
options = {}
) => {
const { limit = 500 } = options;
try {
logger.info(`Syncing emails from sender: ${senderEmail}`);
@@ -211,7 +349,7 @@ export const syncEmailsFromSender = async (jmapConfig, userId, contactId, sender
conditions: [{ from: senderEmail }, { to: senderEmail }],
},
sort: [{ property: 'receivedAt', isAscending: false }],
limit: 500,
limit,
},
'query1',
],