add searching, total unread message, create user
This commit is contained in:
@@ -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',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user