fixes
This commit is contained in:
parent
b4fe29413b
commit
662f22a58f
|
|
@ -1085,35 +1085,48 @@ const updateBerthRecommendations = async (newRecommendations: number[]) => {
|
||||||
// Format date helper function
|
// Format date helper function
|
||||||
const formatDate = (dateString: string | null | undefined) => {
|
const formatDate = (dateString: string | null | undefined) => {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let date: Date;
|
||||||
|
|
||||||
|
// Check if it's an ISO date string (e.g., "2025-06-09T22:58:47.731Z")
|
||||||
|
if (dateString.includes('T') || dateString.includes('Z')) {
|
||||||
|
date = new Date(dateString);
|
||||||
|
}
|
||||||
// Handle DD-MM-YYYY format
|
// Handle DD-MM-YYYY format
|
||||||
if (dateString.includes("-")) {
|
else if (dateString.match(/^\d{2}-\d{2}-\d{4}$/)) {
|
||||||
const parts = dateString.split("-");
|
const [day, month, year] = dateString.split("-");
|
||||||
if (parts.length === 3) {
|
date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
||||||
const [day, month, year] = parts;
|
|
||||||
const date = new Date(
|
|
||||||
parseInt(year),
|
|
||||||
parseInt(month) - 1,
|
|
||||||
parseInt(day)
|
|
||||||
);
|
|
||||||
return date.toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Fallback to direct date parsing
|
// Handle YYYY-MM-DD format
|
||||||
const date = new Date(dateString);
|
else if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||||
if (!isNaN(date.getTime())) {
|
date = new Date(dateString);
|
||||||
return date.toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return dateString;
|
// Fallback to direct parsing
|
||||||
|
else {
|
||||||
|
date = new Date(dateString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if date is valid
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format date in DD/MM/YYYY HH:mm format
|
||||||
|
const day = date.getDate().toString().padStart(2, '0');
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
// Include time if it's not midnight
|
||||||
|
if (hours !== '00' || minutes !== '00') {
|
||||||
|
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${day}/${month}/${year}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Date formatting error:', error, dateString);
|
||||||
return dateString;
|
return dateString;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,29 @@ NUXT_DOCUMENSO_BASE_URL=https://signatures.portnimara.dev
|
||||||
- **Cause**: BCC search criteria not supported by all IMAP servers
|
- **Cause**: BCC search criteria not supported by all IMAP servers
|
||||||
- **Solution**: Removed BCC from search criteria, now only searches FROM, TO, and CC fields
|
- **Solution**: Removed BCC from search criteria, now only searches FROM, TO, and CC fields
|
||||||
|
|
||||||
|
### 15. MinIO Private Bucket Authentication Fix
|
||||||
|
- **Problem**: Email caching failed when MinIO buckets were set to private
|
||||||
|
- **Cause**: Frontend was trying to fetch presigned URLs which failed with CORS/auth issues
|
||||||
|
- **Solution**:
|
||||||
|
- Now reading cached emails directly on server using MinIO client
|
||||||
|
- No presigned URLs needed - server has full authentication
|
||||||
|
- Works perfectly with private buckets
|
||||||
|
|
||||||
|
### 16. Email Fetching & Caching Improvements
|
||||||
|
- **Optimizations**:
|
||||||
|
- Date-based IMAP search (last 30 days) to reduce email count
|
||||||
|
- Increased timeout from 15s to 30s
|
||||||
|
- Now caching ALL emails (both sent and received)
|
||||||
|
- Fire-and-forget caching to avoid slowing down email fetch
|
||||||
|
- Proper IMAP date format (e.g., "1-Jan-2024")
|
||||||
|
|
||||||
|
### 17. Date Display Fix
|
||||||
|
- **Problem**: EOI dates showing wrong format/timezone
|
||||||
|
- **Solution**:
|
||||||
|
- Enhanced date parsing to handle ISO dates, DD-MM-YYYY, and YYYY-MM-DD
|
||||||
|
- Now displays in DD/MM/YYYY HH:mm format
|
||||||
|
- Properly handles timezones
|
||||||
|
|
||||||
## How It Works Now
|
## How It Works Now
|
||||||
|
|
||||||
1. **Email Session Management**:
|
1. **Email Session Management**:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Imap from 'imap';
|
import Imap from 'imap';
|
||||||
import { simpleParser } from 'mailparser';
|
import { simpleParser } from 'mailparser';
|
||||||
import { getCredentialsFromSession, decryptCredentials } from '~/server/utils/encryption';
|
import { getCredentialsFromSession, decryptCredentials } from '~/server/utils/encryption';
|
||||||
import { listFiles, getFileStats } from '~/server/utils/minio';
|
import { listFiles, getFileStats, getMinioClient, uploadFile } from '~/server/utils/minio';
|
||||||
|
|
||||||
interface EmailMessage {
|
interface EmailMessage {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -72,12 +72,25 @@ export default defineEventHandler(async (event) => {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.name.endsWith('.json') && !file.isFolder) {
|
if (file.name.endsWith('.json') && !file.isFolder) {
|
||||||
try {
|
try {
|
||||||
// Use the getDownloadUrl function to get a proper presigned URL
|
// Read file directly on server using MinIO client (works with private buckets)
|
||||||
const { getDownloadUrl } = await import('~/server/utils/minio');
|
const client = getMinioClient();
|
||||||
const downloadUrl = await getDownloadUrl(file.name);
|
const bucketName = useRuntimeConfig().minio.bucketName;
|
||||||
|
|
||||||
const response = await fetch(downloadUrl);
|
// Get object as stream
|
||||||
const emailData = await response.json();
|
const stream = await client.getObject(bucketName, file.name);
|
||||||
|
|
||||||
|
// Convert stream to string
|
||||||
|
let data = '';
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
stream.on('end', () => resolve(data));
|
||||||
|
stream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const emailData = JSON.parse(data);
|
||||||
cachedEmails.push(emailData);
|
cachedEmails.push(emailData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to read cached email:', file.name, err);
|
console.error('Failed to read cached email:', file.name, err);
|
||||||
|
|
@ -103,15 +116,15 @@ export default defineEventHandler(async (event) => {
|
||||||
authTimeout: 5000 // 5 seconds auth timeout
|
authTimeout: 5000 // 5 seconds auth timeout
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch emails from IMAP with timeout
|
// Fetch emails from IMAP with timeout (increased to 30 seconds)
|
||||||
let imapEmails: EmailMessage[] = [];
|
let imapEmails: EmailMessage[] = [];
|
||||||
const timeoutPromise = new Promise<EmailMessage[]>((_, reject) =>
|
const timeoutPromise = new Promise<EmailMessage[]>((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('IMAP connection timeout')), 15000)
|
setTimeout(() => reject(new Error('IMAP connection timeout')), 30000)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
imapEmails = await Promise.race([
|
imapEmails = await Promise.race([
|
||||||
fetchImapEmails(imapConfig, userEmail, clientEmail, limit),
|
fetchImapEmails(imapConfig, userEmail, clientEmail, limit, interestId),
|
||||||
timeoutPromise
|
timeoutPromise
|
||||||
]);
|
]);
|
||||||
} catch (imapError) {
|
} catch (imapError) {
|
||||||
|
|
@ -159,7 +172,8 @@ async function fetchImapEmails(
|
||||||
imapConfig: any,
|
imapConfig: any,
|
||||||
userEmail: string,
|
userEmail: string,
|
||||||
clientEmail: string,
|
clientEmail: string,
|
||||||
limit: number
|
limit: number,
|
||||||
|
interestId?: string
|
||||||
): Promise<EmailMessage[]> {
|
): Promise<EmailMessage[]> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const emails: EmailMessage[] = [];
|
const emails: EmailMessage[] = [];
|
||||||
|
|
@ -210,9 +224,16 @@ async function fetchImapEmails(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ALL to get all messages, then filter manually
|
// Use date-based search to reduce the number of emails fetched
|
||||||
// This avoids the complex search criteria issues
|
// Search for emails from the last 30 days
|
||||||
imap.search(['ALL'], (err, results) => {
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||||
|
|
||||||
|
// Format date for IMAP (e.g., "1-Jan-2024")
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
const searchDate = `${thirtyDaysAgo.getDate()}-${months[thirtyDaysAgo.getMonth()]}-${thirtyDaysAgo.getFullYear()}`;
|
||||||
|
|
||||||
|
imap.search(['SINCE', searchDate], (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(`Search error in ${folderName}:`, err);
|
console.error(`Search error in ${folderName}:`, err);
|
||||||
searchNextFolder();
|
searchNextFolder();
|
||||||
|
|
@ -286,6 +307,27 @@ async function fetchImapEmails(
|
||||||
}
|
}
|
||||||
|
|
||||||
allEmails.push(email);
|
allEmails.push(email);
|
||||||
|
|
||||||
|
// Cache this email if we have an interestId
|
||||||
|
if (interestId && involvesClient) {
|
||||||
|
try {
|
||||||
|
const emailData = {
|
||||||
|
...email,
|
||||||
|
interestId: interestId
|
||||||
|
};
|
||||||
|
|
||||||
|
const objectName = `client-emails/interest-${interestId}/${Date.now()}-${email.direction}.json`;
|
||||||
|
const buffer = Buffer.from(JSON.stringify(emailData, null, 2));
|
||||||
|
|
||||||
|
// Fire and forget - don't wait for upload
|
||||||
|
uploadFile(objectName, buffer, 'application/json').catch(err => {
|
||||||
|
console.error('Failed to cache email:', err);
|
||||||
|
});
|
||||||
|
} catch (cacheError) {
|
||||||
|
console.error('Failed to cache email:', cacheError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
messagesProcessed++;
|
messagesProcessed++;
|
||||||
|
|
||||||
if (messagesProcessed === messagesToFetch.length) {
|
if (messagesProcessed === messagesToFetch.length) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue