Added Dokubox Service & Mailparser
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"imapflow": "^1.1.1",
|
"imapflow": "^1.1.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mailparser": "^3.9.0",
|
||||||
"nodemailer": "^7.0.6",
|
"nodemailer": "^7.0.6",
|
||||||
"openai": "^6.10.0",
|
"openai": "^6.10.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
|||||||
259
src/modules/cron/dokuboximport.service.ts
Normal file
259
src/modules/cron/dokuboximport.service.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
let badMessageDetected = false
|
||||||
|
let badMessageMessageSent = false
|
||||||
|
|
||||||
|
let client: ImapFlow | null = null
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// IMAP CLIENT INITIALIZEN
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
export 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// MAIN SYNC FUNCTION (DRIZZLE VERSION)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
export const syncDokubox = (server: FastifyInstance) =>
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// TENANT ERKENNEN + FOLDER/FILETYPES (DRIZZLE VERSION)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user