Telegram alle zwei Minuten, Zammad und Spiegel alle fuenfzehn. Wer dem Bot etwas schickt, erwartet die Aufgabe gleich in der Liste; in Zammad aendert sich dagegen nichts im Minutentakt. Meldungen gehen jetzt auch per Telegram hinaus — mit einem Knopf zum Abhaken, wenn es um genau eine Aufgabe geht. Eine klemmende Integration meldet sich wie ein klemmender Kalender: erst nach dem dritten Fehlversuch, dann aber deutlich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
166 lines
5.1 KiB
TypeScript
166 lines
5.1 KiB
TypeScript
import 'server-only'
|
|
import cron from 'node-cron'
|
|
import { db } from '@/server/db'
|
|
import { abgleichFenster, kontoAbgleichen } from '@/server/kalender/abgleich'
|
|
import { abgelaufeneChallengesEntfernen } from '@/server/auth/passkey'
|
|
import {
|
|
alteNachweiseEntfernen,
|
|
benachrichtigungenVerschicken,
|
|
} from '@/server/benachrichtigung/versand'
|
|
import { zammadAbgleichen } from '@/server/integration/zammad'
|
|
import { alteKopplungenEntfernen, telegramAbholen } from '@/server/integration/telegram'
|
|
import { spiegelSenden } from '@/server/integration/spiegel'
|
|
import { umgebung } from '@/server/umgebung'
|
|
import { jetzt } from '@/server/zeitgeber'
|
|
|
|
/**
|
|
* Hintergrundjobs.
|
|
*
|
|
* Werden einmal je Prozess gestartet. Im Entwicklungsbetrieb lädt Next Module
|
|
* mehrfach neu — deshalb der Riegel über globalThis, sonst liefen nach ein
|
|
* paar Änderungen ein Dutzend Zeitpläne parallel.
|
|
*/
|
|
|
|
const globalerBereich = globalThis as unknown as { planerJobs?: boolean }
|
|
|
|
/** Alle 15 Minuten abgleichen; öfter bringt nichts und kostet nur Aufrufe. */
|
|
const ABGLEICH_PLAN = '*/15 * * * *'
|
|
/** Einmal nachts aufräumen. */
|
|
const AUFRAEUM_PLAN = '17 3 * * *'
|
|
/**
|
|
* Alle zehn Minuten nachsehen, ob etwas zu melden ist.
|
|
*
|
|
* Der Takt passt zu den Toleranzfenstern in src/lib/benachrichtigung.ts: Ein
|
|
* Anlass, der in einem 15-Minuten-Fenster liegt, wird sicher getroffen, aber
|
|
* dank Wiederholungssperre nur einmal gemeldet.
|
|
*/
|
|
const MELDUNGS_PLAN = '*/10 * * * *'
|
|
/**
|
|
* Integrationen laufen im eigenen Takt.
|
|
*
|
|
* Telegram oefter als der Rest: Wer dem Bot etwas schickt, erwartet die
|
|
* Aufgabe gleich in der Liste, nicht in einer Viertelstunde. Zammad und der
|
|
* Spiegel duerfen traeger sein — dort aendert sich nichts im Minutentakt.
|
|
*/
|
|
const TELEGRAM_PLAN = '*/2 * * * *'
|
|
const ZAMMAD_PLAN = '*/15 * * * *'
|
|
const SPIEGEL_PLAN = '*/15 * * * *'
|
|
|
|
/**
|
|
* Wer im Protokoll steht, wenn niemand geklickt hat.
|
|
*
|
|
* Ein Job hat keinen Benutzer. Statt den Eintrag wegzulassen, traegt er
|
|
* sichtbar den Dienst ein — sonst sieht es aus, als sei nichts passiert.
|
|
*/
|
|
const DIENST = { id: null, email: 'dienst@intern' }
|
|
|
|
async function alleKontenAbgleichen(): Promise<void> {
|
|
const zeitpunkt = jetzt()
|
|
const konten = await db.kalenderkonto.findMany({
|
|
where: { aktiv: true },
|
|
select: { id: true },
|
|
})
|
|
|
|
let ok = 0
|
|
let gescheitert = 0
|
|
for (const konto of konten) {
|
|
const bericht = await kontoAbgleichen(konto.id, zeitpunkt)
|
|
if (bericht.ok) ok++
|
|
else gescheitert++
|
|
}
|
|
|
|
if (gescheitert > 0) {
|
|
// Nur Zahlen, keine Namen und keine Termininhalte.
|
|
console.warn(
|
|
`Kalenderabgleich: ${ok} Konto/Konten erfolgreich, ${gescheitert} fehlgeschlagen.`,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Entfernt Termine, die aus dem Abgleichfenster herausgewandert sind.
|
|
* Ohne das bliebe der Spiegel für immer stehen.
|
|
*/
|
|
async function alteTermineEntfernen(): Promise<void> {
|
|
const fenster = abgleichFenster(jetzt())
|
|
await db.termin.deleteMany({ where: { ende: { lt: fenster.von } } })
|
|
}
|
|
|
|
export function jobsStarten(): void {
|
|
if (globalerBereich.planerJobs) return
|
|
globalerBereich.planerJobs = true
|
|
|
|
cron.schedule(ABGLEICH_PLAN, () => {
|
|
void alleKontenAbgleichen().catch((fehler: unknown) => {
|
|
console.error('Kalenderabgleich abgebrochen:', fehler)
|
|
})
|
|
})
|
|
|
|
cron.schedule(MELDUNGS_PLAN, () => {
|
|
void benachrichtigungenVerschicken()
|
|
.then((bericht) => {
|
|
// Nur Zahlen, nie Empfänger oder Inhalte.
|
|
if (bericht.gesendet > 0) {
|
|
console.warn(`Benachrichtigungen: ${bericht.gesendet} verschickt.`)
|
|
}
|
|
})
|
|
.catch((fehler: unknown) => {
|
|
console.error('Benachrichtigungen abgebrochen:', fehler)
|
|
})
|
|
})
|
|
|
|
const einst = umgebung()
|
|
|
|
if (einst.zammadAktiv) {
|
|
cron.schedule(ZAMMAD_PLAN, () => {
|
|
void zammadAbgleichen(DIENST)
|
|
.then((bericht) => {
|
|
if (bericht.angelegt > 0 || bericht.geschlossen > 0) {
|
|
console.warn(
|
|
`Zammad: ${bericht.angelegt} neu, ${bericht.geschlossen} geschlossen.`,
|
|
)
|
|
}
|
|
})
|
|
.catch((fehler: unknown) => {
|
|
console.error('Zammad-Abgleich abgebrochen:', fehler)
|
|
})
|
|
})
|
|
}
|
|
|
|
if (einst.telegramAktiv) {
|
|
cron.schedule(TELEGRAM_PLAN, () => {
|
|
void telegramAbholen(DIENST)
|
|
.then((bericht) => {
|
|
if (bericht.verworfen > 0) {
|
|
// Nie die Chatkennung: Sie gehoert zu einer Person.
|
|
console.warn(`Telegram: ${bericht.verworfen} Nachricht(en) verworfen.`)
|
|
}
|
|
})
|
|
.catch((fehler: unknown) => {
|
|
console.error('Telegram-Abholen abgebrochen:', fehler)
|
|
})
|
|
})
|
|
}
|
|
|
|
if (einst.spiegelSendenAktiv) {
|
|
cron.schedule(SPIEGEL_PLAN, () => {
|
|
void spiegelSenden().catch((fehler: unknown) => {
|
|
console.error('Spiegel-Versand abgebrochen:', fehler)
|
|
})
|
|
})
|
|
}
|
|
|
|
cron.schedule(AUFRAEUM_PLAN, () => {
|
|
void Promise.all([
|
|
alteTermineEntfernen(),
|
|
abgelaufeneChallengesEntfernen(jetzt()),
|
|
alteNachweiseEntfernen(jetzt()),
|
|
alteKopplungenEntfernen(jetzt()),
|
|
])
|
|
.then(() => undefined)
|
|
.catch((fehler: unknown) => {
|
|
console.error('Aufräumen abgebrochen:', fehler)
|
|
})
|
|
})
|
|
}
|