updates
This commit is contained in:
@@ -90,8 +90,11 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
for (const attachment of attachments) {
|
||||
try {
|
||||
// Determine which bucket to use
|
||||
const bucket = attachment.bucket || 'client-portal'; // Default to client-portal
|
||||
|
||||
// Download file from MinIO
|
||||
const stream = await client.getObject('portnimaradev', attachment.path);
|
||||
const stream = await client.getObject(bucket, attachment.path);
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -107,7 +110,7 @@ export default defineEventHandler(async (event) => {
|
||||
content: content
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to attach file ${attachment.name}:`, error);
|
||||
console.error(`Failed to attach file ${attachment.name} from bucket ${attachment.bucket}:`, error);
|
||||
// Continue with other attachments
|
||||
}
|
||||
}
|
||||
|
||||
263
server/api/files/list-with-attachments.ts
Normal file
263
server/api/files/list-with-attachments.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { Client } from 'minio';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const xTagHeader = getRequestHeader(event, "x-tag");
|
||||
|
||||
if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
|
||||
throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
|
||||
}
|
||||
|
||||
try {
|
||||
const query = getQuery(event);
|
||||
const prefix = (query.prefix as string) || '';
|
||||
const recursive = query.recursive === 'true';
|
||||
const includeEmailAttachments = query.includeEmailAttachments === 'true';
|
||||
const currentUserEmail = query.userEmail as string;
|
||||
|
||||
// Get files from main bucket (client-portal)
|
||||
const mainFiles = await listFilesFromBucket('client-portal', prefix, recursive);
|
||||
|
||||
let allFiles = [...mainFiles];
|
||||
|
||||
// If requested, also include email attachments from client-emails bucket
|
||||
if (includeEmailAttachments && currentUserEmail) {
|
||||
try {
|
||||
// Get the user's full name from their interests
|
||||
const { getInterests } = await import('~/server/utils/nocodb');
|
||||
const interests = await getInterests();
|
||||
const userInterest = interests.list?.find((interest: any) =>
|
||||
interest['Email Address']?.toLowerCase() === currentUserEmail.toLowerCase()
|
||||
);
|
||||
|
||||
if (userInterest && userInterest['Full Name']) {
|
||||
// Create the folder name from the user's name
|
||||
const clientName = userInterest['Full Name']
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '');
|
||||
|
||||
const attachmentFolder = `${clientName}-attachments`;
|
||||
|
||||
// List files from the user's attachment folder
|
||||
const attachmentFiles = await listFilesFromBucket('client-emails', attachmentFolder + '/', true);
|
||||
|
||||
// Add these files with a special flag to identify them as email attachments
|
||||
const formattedAttachmentFiles = attachmentFiles.map((file: any) => ({
|
||||
...file,
|
||||
isEmailAttachment: true,
|
||||
displayPath: `Email Attachments/${file.name.replace(attachmentFolder + '/', '')}`,
|
||||
bucket: 'client-emails'
|
||||
}));
|
||||
|
||||
// Create a virtual folder for email attachments
|
||||
if (formattedAttachmentFiles.length > 0 && !prefix) {
|
||||
allFiles.push({
|
||||
name: 'Email Attachments/',
|
||||
size: 0,
|
||||
lastModified: new Date(),
|
||||
etag: '',
|
||||
isFolder: true,
|
||||
isVirtualFolder: true,
|
||||
icon: 'mdi-email-multiple'
|
||||
});
|
||||
}
|
||||
|
||||
// If we're inside the Email Attachments folder, show the files
|
||||
if (prefix === 'Email Attachments/') {
|
||||
allFiles = formattedAttachmentFiles.map((file: any) => ({
|
||||
...file,
|
||||
name: file.name.replace(attachmentFolder + '/', '')
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching email attachments:', error);
|
||||
// Continue without email attachments if there's an error
|
||||
}
|
||||
}
|
||||
|
||||
// Format file list with additional metadata
|
||||
const formattedFiles = allFiles.map(file => ({
|
||||
...file,
|
||||
sizeFormatted: file.isFolder ? '-' : formatFileSize(file.size),
|
||||
extension: file.isFolder ? 'folder' : getFileExtension(file.name),
|
||||
icon: file.icon || (file.isFolder ? 'mdi-folder' : getFileIcon(file.name)),
|
||||
displayName: file.displayPath || getDisplayName(file.name),
|
||||
}));
|
||||
|
||||
// Sort folders first, then files
|
||||
formattedFiles.sort((a, b) => {
|
||||
if (a.isFolder && !b.isFolder) return -1;
|
||||
if (!a.isFolder && b.isFolder) return 1;
|
||||
return a.displayName.localeCompare(b.displayName);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files: formattedFiles,
|
||||
count: formattedFiles.length,
|
||||
currentPath: prefix,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Failed to list files:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Failed to list files',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List files from a specific bucket
|
||||
async function listFilesFromBucket(bucketName: string, prefix: string = '', recursive: boolean = false): Promise<any[]> {
|
||||
const config = useRuntimeConfig().minio;
|
||||
|
||||
const client = new Client({
|
||||
endPoint: config.endPoint,
|
||||
port: config.port,
|
||||
useSSL: config.useSSL,
|
||||
accessKey: config.accessKey,
|
||||
secretKey: config.secretKey,
|
||||
});
|
||||
|
||||
const files: any[] = [];
|
||||
const folders = new Set<string>();
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const stream = client.listObjectsV2(bucketName, prefix, recursive);
|
||||
|
||||
stream.on('data', (obj) => {
|
||||
if (obj && obj.prefix) {
|
||||
folders.add(obj.prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obj || typeof obj.name !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recursive) {
|
||||
if (prefix) {
|
||||
const relativePath = obj.name.substring(prefix.length);
|
||||
if (!relativePath) return;
|
||||
|
||||
const firstSlash = relativePath.indexOf('/');
|
||||
|
||||
if (firstSlash > -1) {
|
||||
const folderName = relativePath.substring(0, firstSlash);
|
||||
folders.add(prefix + folderName + '/');
|
||||
} else if (relativePath && !obj.name.endsWith('/')) {
|
||||
files.push({
|
||||
name: obj.name,
|
||||
size: obj.size || 0,
|
||||
lastModified: obj.lastModified || new Date(),
|
||||
etag: obj.etag || '',
|
||||
isFolder: false,
|
||||
bucket: bucketName
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const firstSlash = obj.name.indexOf('/');
|
||||
|
||||
if (obj.name.endsWith('/')) {
|
||||
folders.add(obj.name);
|
||||
} else if (firstSlash > -1) {
|
||||
const folderName = obj.name.substring(0, firstSlash);
|
||||
folders.add(folderName + '/');
|
||||
} else {
|
||||
files.push({
|
||||
name: obj.name,
|
||||
size: obj.size || 0,
|
||||
lastModified: obj.lastModified || new Date(),
|
||||
etag: obj.etag || '',
|
||||
isFolder: false,
|
||||
bucket: bucketName
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!obj.name.endsWith('/')) {
|
||||
files.push({
|
||||
name: obj.name,
|
||||
size: obj.size || 0,
|
||||
lastModified: obj.lastModified || new Date(),
|
||||
etag: obj.etag || '',
|
||||
isFolder: false,
|
||||
bucket: bucketName
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', (error) => {
|
||||
console.error('Stream error:', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
const folderItems = Array.from(folders).map(folder => ({
|
||||
name: folder,
|
||||
size: 0,
|
||||
lastModified: new Date(),
|
||||
etag: '',
|
||||
isFolder: true,
|
||||
bucket: bucketName
|
||||
}));
|
||||
|
||||
resolve([...folderItems, ...files]);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in listFilesFromBucket:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function formatFileSize(bytes: number): string {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function getFileExtension(filename: string): string {
|
||||
const parts = filename.split('.');
|
||||
return parts.length > 1 ? parts.pop()?.toLowerCase() || '' : '';
|
||||
}
|
||||
|
||||
function getFileIcon(filename: string): string {
|
||||
const ext = getFileExtension(filename);
|
||||
const iconMap: Record<string, string> = {
|
||||
pdf: 'mdi-file-pdf-box',
|
||||
doc: 'mdi-file-document',
|
||||
docx: 'mdi-file-document',
|
||||
xls: 'mdi-file-excel',
|
||||
xlsx: 'mdi-file-excel',
|
||||
jpg: 'mdi-file-image',
|
||||
jpeg: 'mdi-file-image',
|
||||
png: 'mdi-file-image',
|
||||
gif: 'mdi-file-image',
|
||||
svg: 'mdi-file-image',
|
||||
zip: 'mdi-folder-zip',
|
||||
rar: 'mdi-folder-zip',
|
||||
txt: 'mdi-file-document-outline',
|
||||
csv: 'mdi-file-delimited',
|
||||
mp4: 'mdi-file-video',
|
||||
mp3: 'mdi-file-music',
|
||||
};
|
||||
return iconMap[ext] || 'mdi-file';
|
||||
}
|
||||
|
||||
function getDisplayName(filepath: string): string {
|
||||
if (filepath.endsWith('/')) {
|
||||
const parts = filepath.slice(0, -1).split('/');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
const parts = filepath.split('/');
|
||||
const filename = parts[parts.length - 1];
|
||||
|
||||
const match = filename.match(/^\d{10,}-(.+)$/);
|
||||
return match ? match[1] : filename;
|
||||
}
|
||||
@@ -12,24 +12,19 @@ export async function scheduleEOIReminders() {
|
||||
|
||||
console.log('[EOI Reminders] Scheduling reminder tasks...');
|
||||
|
||||
// Dynamic import for node-cron to avoid ESM issues
|
||||
const cron = await import('node-cron');
|
||||
// Check every hour if it's time to send reminders
|
||||
setInterval(async () => {
|
||||
const now = new Date();
|
||||
const parisTime = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Paris' }));
|
||||
const hour = parisTime.getHours();
|
||||
const minute = parisTime.getMinutes();
|
||||
|
||||
// Schedule for 9am daily
|
||||
cron.schedule('0 9 * * *', async () => {
|
||||
console.log('[EOI Reminders] Running 9am reminder check...');
|
||||
await processReminders();
|
||||
}, {
|
||||
timezone: 'Europe/Paris'
|
||||
});
|
||||
|
||||
// Schedule for 4pm daily
|
||||
cron.schedule('0 16 * * *', async () => {
|
||||
console.log('[EOI Reminders] Running 4pm reminder check...');
|
||||
await processReminders();
|
||||
}, {
|
||||
timezone: 'Europe/Paris'
|
||||
});
|
||||
// Check if it's 9am or 4pm (16:00)
|
||||
if ((hour === 9 || hour === 16) && minute === 0) {
|
||||
console.log(`[EOI Reminders] Running ${hour === 9 ? '9am' : '4pm'} reminder check...`);
|
||||
await processReminders();
|
||||
}
|
||||
}, 60000); // Check every minute
|
||||
|
||||
tasksScheduled = true;
|
||||
console.log('[EOI Reminders] Tasks scheduled successfully');
|
||||
@@ -78,12 +73,14 @@ async function processReminders() {
|
||||
|
||||
async function sendReminder(interest: any) {
|
||||
try {
|
||||
// Use a full URL for the fetch since we're in a background task
|
||||
const baseUrl = process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||
const response = await $fetch<{
|
||||
success: boolean;
|
||||
remindersSent: number;
|
||||
results: any[];
|
||||
message?: string;
|
||||
}>('/api/eoi/send-reminders', {
|
||||
}>(`${baseUrl}/api/eoi/send-reminders`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-tag': '094ut234' // System tag for automated processes
|
||||
|
||||
@@ -28,7 +28,9 @@ export function scheduleEmailProcessing() {
|
||||
|
||||
async function processEmails() {
|
||||
try {
|
||||
const response = await $fetch('/api/email/process-sales-eois', {
|
||||
// Use a full URL for the fetch since we're in a background task
|
||||
const baseUrl = process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||
const response = await $fetch(`${baseUrl}/api/email/process-sales-eois`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-tag': '094ut234' // System tag for automated processes
|
||||
|
||||
Reference in New Issue
Block a user