This commit is contained in:
Matt 2025-06-10 01:57:19 +02:00
parent b4fe29413b
commit 662f22a58f
3 changed files with 115 additions and 37 deletions

View File

@ -1085,35 +1085,48 @@ const updateBerthRecommendations = async (newRecommendations: number[]) => {
// Format date helper function
const formatDate = (dateString: string | null | undefined) => {
if (!dateString) return "";
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
if (dateString.includes("-")) {
const parts = dateString.split("-");
if (parts.length === 3) {
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",
});
}
else if (dateString.match(/^\d{2}-\d{2}-\d{4}$/)) {
const [day, month, year] = dateString.split("-");
date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
// Fallback to direct date parsing
const date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
// Handle YYYY-MM-DD format
else if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) {
date = new Date(dateString);
}
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) {
console.error('Date formatting error:', error, dateString);
return dateString;
}
};

View File

@ -162,6 +162,29 @@ NUXT_DOCUMENSO_BASE_URL=https://signatures.portnimara.dev
- **Cause**: BCC search criteria not supported by all IMAP servers
- **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
1. **Email Session Management**:

View File

@ -1,7 +1,7 @@
import Imap from 'imap';
import { simpleParser } from 'mailparser';
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 {
id: string;
@ -72,12 +72,25 @@ export default defineEventHandler(async (event) => {
for (const file of files) {
if (file.name.endsWith('.json') && !file.isFolder) {
try {
// Use the getDownloadUrl function to get a proper presigned URL
const { getDownloadUrl } = await import('~/server/utils/minio');
const downloadUrl = await getDownloadUrl(file.name);
// Read file directly on server using MinIO client (works with private buckets)
const client = getMinioClient();
const bucketName = useRuntimeConfig().minio.bucketName;
const response = await fetch(downloadUrl);
const emailData = await response.json();
// Get object as stream
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);
} catch (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
};
// Fetch emails from IMAP with timeout
// Fetch emails from IMAP with timeout (increased to 30 seconds)
let imapEmails: EmailMessage[] = [];
const timeoutPromise = new Promise<EmailMessage[]>((_, reject) =>
setTimeout(() => reject(new Error('IMAP connection timeout')), 15000)
setTimeout(() => reject(new Error('IMAP connection timeout')), 30000)
);
try {
imapEmails = await Promise.race([
fetchImapEmails(imapConfig, userEmail, clientEmail, limit),
fetchImapEmails(imapConfig, userEmail, clientEmail, limit, interestId),
timeoutPromise
]);
} catch (imapError) {
@ -159,7 +172,8 @@ async function fetchImapEmails(
imapConfig: any,
userEmail: string,
clientEmail: string,
limit: number
limit: number,
interestId?: string
): Promise<EmailMessage[]> {
return new Promise((resolve, reject) => {
const emails: EmailMessage[] = [];
@ -210,9 +224,16 @@ async function fetchImapEmails(
return;
}
// Use ALL to get all messages, then filter manually
// This avoids the complex search criteria issues
imap.search(['ALL'], (err, results) => {
// Use date-based search to reduce the number of emails fetched
// Search for emails from the last 30 days
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) {
console.error(`Search error in ${folderName}:`, err);
searchNextFolder();
@ -286,6 +307,27 @@ async function fetchImapEmails(
}
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++;
if (messagesProcessed === messagesToFetch.length) {