import axios from "axios" import dayjs from "dayjs" import { ImapFlow } from "imapflow" import { simpleParser } from "mailparser" import { FastifyInstance } from "fastify" import {saveFile} from "../../utils/files"; import { secrets } from "../../utils/secrets" // Drizzle Imports import { tenants, folders, filetags, } from "../../../db/schema" import { eq, and, } from "drizzle-orm" export function syncDokuboxService (server: FastifyInstance) { let badMessageDetected = false let badMessageMessageSent = false let client: ImapFlow | null = null async function initDokuboxClient() { client = new ImapFlow({ host: secrets.DOKUBOX_IMAP_HOST, port: secrets.DOKUBOX_IMAP_PORT, secure: secrets.DOKUBOX_IMAP_SECURE, auth: { user: secrets.DOKUBOX_IMAP_USER, pass: secrets.DOKUBOX_IMAP_PASSWORD }, logger: false }) console.log("Dokubox E-Mail Client Initialized") await client.connect() } const syncDokubox = async () => { console.log("Perform Dokubox Sync") await initDokuboxClient() if (!client?.usable) { throw new Error("E-Mail Client not usable") } // ------------------------------- // TENANTS LADEN (DRIZZLE) // ------------------------------- const tenantList = await server.db .select({ id: tenants.id, name: tenants.name, emailAddresses: tenants.dokuboxEmailAddresses, key: tenants.dokuboxkey }) .from(tenants) const lock = await client.getMailboxLock("INBOX") try { for await (let msg of client.fetch({ seen: false }, { envelope: true, source: true })) { const parsed = await simpleParser(msg.source) const message = { id: msg.uid, subject: parsed.subject, to: parsed.to?.value || [], cc: parsed.cc?.value || [], attachments: parsed.attachments || [] } // ------------------------------------------------- // MAPPING / FIND TENANT // ------------------------------------------------- const config = await getMessageConfigDrizzle(server, message, tenantList) if (!config) { badMessageDetected = true if (!badMessageMessageSent) { badMessageMessageSent = true } return } if (message.attachments.length > 0) { for (const attachment of message.attachments) { await saveFile( server, config.tenant, message.id, attachment, config.folder, config.filetype ) } } } if (!badMessageDetected) { badMessageDetected = false badMessageMessageSent = false } await client.messageFlagsAdd({ seen: false }, ["\\Seen"]) await client.messageDelete({ seen: true }) } finally { lock.release() client.close() } } const getMessageConfigDrizzle = async ( server: FastifyInstance, message, tenantsList: any[] ) => { let possibleKeys: string[] = [] if (message.to) { message.to.forEach((item) => possibleKeys.push(item.address.split("@")[0].toLowerCase()) ) } if (message.cc) { message.cc.forEach((item) => possibleKeys.push(item.address.split("@")[0].toLowerCase()) ) } // ------------------------------------------- // TENANT IDENTIFY // ------------------------------------------- let tenant = tenantsList.find((t) => possibleKeys.includes(t.key)) if (!tenant && message.to?.length) { const address = message.to[0].address.toLowerCase() tenant = tenantsList.find((t) => (t.emailAddresses || []).map((m) => m.toLowerCase()).includes(address) ) } if (!tenant) return null // ------------------------------------------- // FOLDER + FILETYPE VIA SUBJECT // ------------------------------------------- let folderId = null let filetypeId = null // ------------------------------------------- // Rechnung / Invoice // ------------------------------------------- if (message.subject?.match(/(Rechnung|Beleg|Invoice|Quittung)/gi)) { const folder = await server.db .select({ id: folders.id }) .from(folders) .where( and( eq(folders.tenant, tenant.id), and( eq(folders.function, "incomingInvoices"), //@ts-ignore eq(folders.year, dayjs().format("YYYY")) ) ) ) .limit(1) folderId = folder[0]?.id ?? null const tag = await server.db .select({ id: filetags.id }) .from(filetags) .where( and( eq(filetags.tenant, tenant.id), eq(filetags.incomingDocumentType, "invoices") ) ) .limit(1) filetypeId = tag[0]?.id ?? null } // ------------------------------------------- // Mahnung // ------------------------------------------- else if (message.subject?.match(/(Mahnung|Zahlungsaufforderung|Zahlungsverzug)/gi)) { const tag = await server.db .select({ id: filetags.id }) .from(filetags) .where( and( eq(filetags.tenant, tenant.id), eq(filetags.incomingDocumentType, "reminders") ) ) .limit(1) filetypeId = tag[0]?.id ?? null } // ------------------------------------------- // Sonstige Dokumente → Deposit Folder // ------------------------------------------- else { const folder = await server.db .select({ id: folders.id }) .from(folders) .where( and( eq(folders.tenant, tenant.id), eq(folders.function, "deposit") ) ) .limit(1) folderId = folder[0]?.id ?? null } return { tenant: tenant.id, folder: folderId, filetype: filetypeId } } return { run: async () => { await initDokuboxClient() await syncDokubox() console.log("Service: Dokubox sync finished") } } }