import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/app/db/drizzle'; import { domains, emails } from '@/app/db/schema'; import { authenticate } from '@/app/lib/utils'; import { eq, sql } from 'drizzle-orm'; export async function GET(req: NextRequest) { if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { searchParams } = new URL(req.url); const bucket = searchParams.get('bucket'); const mailbox = searchParams.get('mailbox')?.toLowerCase(); if (!bucket || !mailbox) return NextResponse.json({ error: 'Missing params' }, { status: 400 }); const [domain] = await db.select().from(domains).where(eq(domains.bucket, bucket)); if (!domain) return NextResponse.json({ error: 'Domain not found' }, { status: 404 }); const emailList = await db.select({ key: emails.s3Key, subject: emails.subject, from: emails.from, to: emails.to, date: emails.date, html: emails.html, raw: emails.raw, processed: emails.processed, processedAt: emails.processedAt, processedBy: emails.processedBy, queuedTo: emails.queuedTo, status: emails.status, }).from(emails).where(sql`${mailbox} = ANY(${emails.to}) AND ${emails.domainId} = ${domain.id}`); return NextResponse.json(emailList.map(e => { let preview = ''; // Check both HTML and raw content for images const htmlContent = e.html || ''; const rawContent = e.raw || ''; // Check for images in HTML const hasImgTags = /]+>/i.test(htmlContent); const imageCount = (htmlContent.match(/]+>/gi) || []).length; // Check for base64 encoded images const hasBase64Images = /data:image\//i.test(htmlContent); // Check for image attachments in raw content const hasImageAttachments = /Content-Type:\s*image\//i.test(rawContent); const attachmentCount = (rawContent.match(/Content-Type:\s*image\//gi) || []).length; // Check for multipart/related (usually contains embedded images) const isMultipartRelated = /Content-Type:\s*multipart\/related/i.test(rawContent); // Extract text content let textContent = ''; if (htmlContent) { textContent = htmlContent .replace(/]*>[\s\S]*?<\/style>/gi, '') // Remove style blocks .replace(/]*>[\s\S]*?<\/script>/gi, '') // Remove script blocks .replace(/<[^>]*>/g, '') // Remove HTML tags .replace(/@font-face\s*\{[^}]*\}/gi, '') // Remove @font-face definitions .replace(/@media[^{]*\{[\s\S]*?\}/gi, '') // Remove @media queries .replace(/@import[^;]*;/gi, '') // Remove @import statements .replace(/font-family:\s*[^;]+;?/gi, '') // Remove font-family CSS .replace(/\s+/g, ' ') // Normalize whitespace .trim(); } // Determine preview const totalImages = imageCount + attachmentCount + (hasBase64Images ? 1 : 0); const hasImages = hasImgTags || hasBase64Images || hasImageAttachments || isMultipartRelated; if (hasImages && textContent.length < 50) { // Mostly images, little text if (totalImages > 1) { preview = `📷 ${totalImages} Bilder`; } else if (totalImages === 1) { preview = '📷 Bild'; } else { preview = '📷 Bild'; } } else if (textContent.length > 0) { // Has text content preview = textContent.substring(0, 150); if (textContent.length > 150) preview += '...'; } else if (hasImages) { // Has images but couldn't count them properly preview = '📷 Bild(er)'; } else if (!htmlContent && rawContent) { // No HTML, check if it's plain text const plainText = rawContent .split('\n\n') .find(line => !line.startsWith('Content-') && !line.startsWith('MIME-') && line.length > 10); if (plainText) { preview = plainText.substring(0, 150); if (plainText.length > 150) preview += '...'; } } return { key: e.key, subject: e.subject, from: e.from, to: e.to, preview, date: e.date?.toISOString(), processed: e.processed ? 'true' : 'false', processedAt: e.processedAt?.toISOString() || null, processedBy: e.processedBy, queuedTo: e.queuedTo, status: e.status, }; })); }