// modules/helpdesk/helpdesk.inbound.routes.ts import { FastifyPluginAsync } from 'fastify' import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js' import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js' import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js' /** * Öffentliche Route zum Empfang eingehender Kontaktformular-Nachrichten. * Authentifizierung: über `public_token` aus helpdesk_channel_instances */ function extractDomain(email) { if (!email) return null const parts = email.split("@") return parts.length === 2 ? parts[1].toLowerCase() : null } async function findCustomerOrContactByEmailOrDomain(server,fromMail, tenantId) { const sender = fromMail const senderDomain = extractDomain(sender) if (!senderDomain) return null // 1️⃣ Direkter Match über contacts const { data: contactMatch } = await server.supabase .from("contacts") .select("id, customer") .eq("email", sender) .eq("tenant", tenantId) .maybeSingle() if (contactMatch?.customer_id) return { customer: contactMatch.customer, contact: contactMatch.id } // 2️⃣ Kunden laden, bei denen E-Mail oder Rechnungsmail passt const { data: customers, error } = await server.supabase .from("customers") .select("id, infoData") .eq("tenant", tenantId) if (error) { console.error(`[Helpdesk] Fehler beim Laden der Kunden:`, error.message) return null } // 3️⃣ Durch Kunden iterieren und prüfen for (const c of customers || []) { const info = c.infoData || {} const email = info.email?.toLowerCase() const invoiceEmail = info.invoiceEmail?.toLowerCase() const emailDomain = extractDomain(email) const invoiceDomain = extractDomain(invoiceEmail) // exakter Match oder Domain-Match if ( sender === email || sender === invoiceEmail || senderDomain === emailDomain || senderDomain === invoiceDomain ) { return {customer: c.id, contact:null} } } return null } const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => { // Öffentliche POST-Route server.post('/helpdesk/inbound/:public_token', async (req, res) => { const { public_token } = req.params as { public_token: string } const { email, phone, display_name, subject, message } = req.body as { email: string, phone: string, display_name: string subject: string message: string } if (!message) { return res.status(400).send({ error: 'Message content required' }) } // 1️⃣ Kanalinstanz anhand des Tokens ermitteln const { data: channel, error: channelError } = await server.supabase .from('helpdesk_channel_instances') .select('*') .eq('public_token', public_token) .single() if (channelError || !channel) { return res.status(404).send({ error: 'Invalid channel token' }) } const tenant_id = channel.tenant_id const channel_instance_id = channel.id // @ts-ignore const {customer, contact: contactPerson} = await findCustomerOrContactByEmailOrDomain(server,email, tenant_id ) // 2️⃣ Kontakt finden oder anlegen const contact = await getOrCreateContact(server, tenant_id, { email, phone, display_name, customer_id: customer, contact_id: contactPerson, }) // 3️⃣ Konversation erstellen const conversation = await createConversation(server, { tenant_id, contact, channel_instance_id, subject: subject ?? 'Kontaktformular Anfrage', customer_id: customer, contact_person_id: contactPerson }) // 4️⃣ Erste Nachricht hinzufügen await addMessage(server, { tenant_id, conversation_id: conversation.id, direction: 'incoming', payload: { type: 'text', text: message }, raw_meta: { source: 'contact_form' }, }) // (optional) Auto-Antwort oder Event hier ergänzen return res.status(201).send({ success: true, conversation_id: conversation.id, }) }) } export default helpdeskInboundRoutes