port-nimara-client-portal/server/api/files/list-with-attachments.ts

256 lines
8.1 KiB
TypeScript

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 {
// Create the folder name from the user's email
const username = currentUserEmail.split('@')[0]
.toLowerCase()
.replace(/[^a-z0-9-]/g, '');
const attachmentFolder = `${username}-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,
displayName: file.name.replace(attachmentFolder + '/', ''),
// Keep the full path in 'name' for operations like delete
name: file.name
}));
}
} 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;
}