141 lines
4.9 KiB
TypeScript
141 lines
4.9 KiB
TypeScript
// /opt/erp-system/app/api/cron/imap/route.ts
|
|
import { NextResponse } from 'next/server';
|
|
import prisma from '../../../../lib/prisma';
|
|
import { ImapFlow } from 'imapflow';
|
|
import { simpleParser } from 'mailparser';
|
|
import { writeFile } from 'fs/promises';
|
|
import { join } from 'path';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
const UPLOAD_DIR = join(process.cwd(), 'uploads');
|
|
|
|
export async function GET() {
|
|
try {
|
|
const settings = await prisma.systemSettings.findFirst({ where: { id: 1 } });
|
|
if (!settings || !settings.imapHost || !settings.imapUser || !settings.imapPass) {
|
|
return NextResponse.json({ error: 'IMAP Zugangsdaten fehlen.' }, { status: 400 });
|
|
}
|
|
|
|
const client = new ImapFlow({
|
|
host: settings.imapHost,
|
|
port: settings.imapPort,
|
|
secure: settings.imapPort === 993,
|
|
auth: { user: settings.imapUser, pass: settings.imapPass },
|
|
logger: false
|
|
});
|
|
|
|
await client.connect();
|
|
let lock = await client.getMailboxLock('INBOX');
|
|
let processedCounter = 0;
|
|
|
|
try {
|
|
for await (let message of client.fetch({ seen: false }, { source: true, uid: true })) {
|
|
const parsed = await simpleParser(message.source);
|
|
|
|
const fromEmail = parsed.from?.value[0]?.address;
|
|
const fromName = parsed.from?.value[0]?.name || 'Unbekannt';
|
|
const subject = parsed.subject || 'Kein Betreff';
|
|
const textContent = parsed.text || parsed.textAsHtml || '(Kein Inhalt)';
|
|
|
|
if (!fromEmail) continue;
|
|
|
|
// 1. Kunden-Matching (Haupt-Email, alternative E-Mails oder Mitarbeiter)
|
|
let targetCustomerId = null;
|
|
|
|
const directCustomer = await prisma.customer.findFirst({
|
|
where: {
|
|
OR: [
|
|
{ email: fromEmail },
|
|
{ additionalEmails: { has: fromEmail } }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (directCustomer) {
|
|
targetCustomerId = directCustomer.id;
|
|
} else {
|
|
const contact = await prisma.customerContact.findFirst({
|
|
where: { email: fromEmail }
|
|
});
|
|
if (contact) {
|
|
targetCustomerId = contact.customerId;
|
|
}
|
|
}
|
|
|
|
// Falls komplett unbekannt, neuen Kunden anlegen
|
|
if (!targetCustomerId) {
|
|
const newCustomer = await prisma.customer.create({
|
|
data: {
|
|
email: fromEmail,
|
|
firstName: fromName,
|
|
lastName: '(Auto-Erfasst)',
|
|
companyName: 'Aus E-Mail Anfrage'
|
|
}
|
|
});
|
|
targetCustomerId = newCustomer.id;
|
|
}
|
|
|
|
// 2. Ticket Zuordnung
|
|
const ticketMatch = subject.match(/Ticket #(\d+)/i);
|
|
let ticketId = ticketMatch ? parseInt(ticketMatch[1]) : null;
|
|
let activeTicketId = null;
|
|
|
|
if (ticketId) {
|
|
const existingTicket = await prisma.ticket.findUnique({ where: { id: ticketId } });
|
|
if (existingTicket) {
|
|
activeTicketId = existingTicket.id;
|
|
await prisma.ticketMessage.create({
|
|
data: { content: textContent, isFromCustomer: true, ticketId: existingTicket.id }
|
|
});
|
|
|
|
if (existingTicket.status !== 'OPEN' && existingTicket.status !== 'IN_PROGRESS') {
|
|
await prisma.ticket.update({ where: { id: existingTicket.id }, data: { status: 'OPEN' } });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!activeTicketId) {
|
|
const newTicket = await prisma.ticket.create({
|
|
data: { title: subject, description: textContent, customerId: targetCustomerId, status: 'OPEN' }
|
|
});
|
|
activeTicketId = newTicket.id;
|
|
}
|
|
|
|
// 3. Dateianhänge verarbeiten
|
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
for (const att of parsed.attachments) {
|
|
// Buffer in Datei schreiben
|
|
const safeOriginalName = (att.filename || 'unbekannt.dat').replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
const savedName = `${Date.now()}-${safeOriginalName}`;
|
|
const filepath = join(UPLOAD_DIR, savedName);
|
|
|
|
await writeFile(filepath, att.content);
|
|
|
|
// DB Eintrag
|
|
await prisma.attachment.create({
|
|
data: {
|
|
fileName: att.filename || 'unbekannt.dat',
|
|
savedName: savedName,
|
|
fileSize: att.size || 0,
|
|
fileType: att.contentType || 'application/octet-stream',
|
|
ticketId: activeTicketId
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
await client.messageFlagsAdd(message.uid, ['\\Seen'], { uid: true });
|
|
processedCounter++;
|
|
}
|
|
} finally {
|
|
lock.release();
|
|
}
|
|
await client.logout();
|
|
return NextResponse.json({ success: true, processedTickets: processedCounter });
|
|
|
|
} catch (error: any) {
|
|
console.error('IMAP Error:', error);
|
|
return NextResponse.json({ error: 'Verarbeitungsfehler', details: error.message }, { status: 500 });
|
|
}
|
|
}
|