feat: Integrationen sichtbar und bedienbar machen
Eine Seite zeigt je Integration, ob sie vorgesehen, eingerichtet oder gestoert ist, wann sie zuletzt geglueckt ist und woran sie klemmt. Der Wartungsplaner steht sichtbar als offene Luecke darin, nicht als Leerstelle. Telegram-Chats werden hier gekoppelt und wieder getrennt, Meldungen je Chat einzeln geschaltet. Aufgaben bekommen einen Schalter zum Spiegeln — einzeln und je Aufgabe, denn wer versehentlich einen ganzen Bereich spiegelt, schickt Kundennamen nach Hause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bc60e5f864
commit
1281c3ea96
@@ -0,0 +1,191 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { auditSchreiben } from '@/server/audit'
|
||||
import { NichtErlaubt, pruefeRolle } from '@/server/berechtigung'
|
||||
import { db } from '@/server/db'
|
||||
import {
|
||||
KOPPLUNG_MINUTEN,
|
||||
kopplungscodeBauen,
|
||||
telegramAbholen,
|
||||
} from '@/server/integration/telegram'
|
||||
import { zammadAbgleichen } from '@/server/integration/zammad'
|
||||
import { spiegelSenden } from '@/server/integration/spiegel'
|
||||
import { umgebung } from '@/server/umgebung'
|
||||
import { jetzt } from '@/server/zeitgeber'
|
||||
|
||||
export type Rueckmeldung = { ok: true; meldung: string } | { ok: false; fehler: string }
|
||||
|
||||
async function akteurOderFehler() {
|
||||
try {
|
||||
return { akteur: await pruefeRolle('BENUTZER') }
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof NichtErlaubt) {
|
||||
return { antwort: { ok: false as const, fehler: fehler.message } }
|
||||
}
|
||||
throw fehler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Kopplungscode für einen Telegram-Chat.
|
||||
*
|
||||
* Alte Codes desselben Benutzers fallen dabei weg: Zwei gültige Codes
|
||||
* gleichzeitig sind einer zu viel.
|
||||
*/
|
||||
export async function kopplungAnfordern(): Promise<
|
||||
{ ok: true; code: string; minuten: number } | { ok: false; fehler: string }
|
||||
> {
|
||||
const geprueft = await akteurOderFehler()
|
||||
if ('antwort' in geprueft) return geprueft.antwort
|
||||
const { akteur } = geprueft
|
||||
|
||||
if (!umgebung().telegramAktiv) {
|
||||
return { ok: false, fehler: 'Telegram ist auf diesem Server nicht eingerichtet.' }
|
||||
}
|
||||
|
||||
await db.telegramKopplung.deleteMany({ where: { benutzerId: akteur.id } })
|
||||
|
||||
const code = kopplungscodeBauen()
|
||||
await db.telegramKopplung.create({
|
||||
data: {
|
||||
code,
|
||||
benutzerId: akteur.id,
|
||||
gueltigBis: new Date(jetzt().getTime() + KOPPLUNG_MINUTEN * 60_000),
|
||||
},
|
||||
})
|
||||
|
||||
await auditSchreiben({
|
||||
akteur: { id: akteur.id, email: akteur.email },
|
||||
aktion: 'telegram.kopplung-angefordert',
|
||||
objektTyp: 'TelegramKopplung',
|
||||
objektId: null,
|
||||
// Nie der Code selbst: Wer das Protokoll liest, könnte sonst koppeln.
|
||||
})
|
||||
|
||||
return { ok: true, code, minuten: KOPPLUNG_MINUTEN }
|
||||
}
|
||||
|
||||
/** Trennt einen Chat wieder. */
|
||||
export async function chatEntfernen(daten: FormData): Promise<Rueckmeldung> {
|
||||
const geprueft = await akteurOderFehler()
|
||||
if ('antwort' in geprueft) return geprueft.antwort
|
||||
const { akteur } = geprueft
|
||||
|
||||
const id = daten.get('id')
|
||||
if (typeof id !== 'string' || id.length === 0) {
|
||||
return { ok: false, fehler: 'Ungültige Anfrage.' }
|
||||
}
|
||||
|
||||
const chat = await db.telegramChat.findUnique({
|
||||
where: { id },
|
||||
select: { id: true, bezeichnung: true },
|
||||
})
|
||||
if (!chat) return { ok: false, fehler: 'Diesen Chat gibt es nicht.' }
|
||||
|
||||
await db.telegramChat.delete({ where: { id: chat.id } })
|
||||
|
||||
await auditSchreiben({
|
||||
akteur: { id: akteur.id, email: akteur.email },
|
||||
aktion: 'telegram.getrennt',
|
||||
objektTyp: 'TelegramChat',
|
||||
objektId: chat.id,
|
||||
})
|
||||
|
||||
revalidatePath('/integrationen')
|
||||
return { ok: true, meldung: 'Chat getrennt.' }
|
||||
}
|
||||
|
||||
/** Schaltet um, ob ein Chat Meldungen bekommt. */
|
||||
export async function chatMeldungenUmschalten(daten: FormData): Promise<Rueckmeldung> {
|
||||
const geprueft = await akteurOderFehler()
|
||||
if ('antwort' in geprueft) return geprueft.antwort
|
||||
const { akteur } = geprueft
|
||||
|
||||
const id = daten.get('id')
|
||||
if (typeof id !== 'string' || id.length === 0) {
|
||||
return { ok: false, fehler: 'Ungültige Anfrage.' }
|
||||
}
|
||||
|
||||
const chat = await db.telegramChat.findUnique({
|
||||
where: { id },
|
||||
select: { id: true, meldungen: true },
|
||||
})
|
||||
if (!chat) return { ok: false, fehler: 'Diesen Chat gibt es nicht.' }
|
||||
|
||||
const kuenftig = !chat.meldungen
|
||||
await db.telegramChat.update({
|
||||
where: { id: chat.id },
|
||||
data: { meldungen: kuenftig },
|
||||
})
|
||||
|
||||
await auditSchreiben({
|
||||
akteur: { id: akteur.id, email: akteur.email },
|
||||
aktion: kuenftig ? 'telegram.meldungen-an' : 'telegram.meldungen-aus',
|
||||
objektTyp: 'TelegramChat',
|
||||
objektId: chat.id,
|
||||
})
|
||||
|
||||
revalidatePath('/integrationen')
|
||||
return {
|
||||
ok: true,
|
||||
meldung: kuenftig
|
||||
? 'Dieser Chat bekommt Meldungen.'
|
||||
: 'Dieser Chat bekommt keine Meldungen mehr.',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stößt einen Lauf von Hand an.
|
||||
*
|
||||
* Nützlich nach dem Einrichten: Sonst wüsste man erst beim nächsten Takt, ob
|
||||
* die Zugangsdaten stimmen.
|
||||
*/
|
||||
export async function laufAnstossen(daten: FormData): Promise<Rueckmeldung> {
|
||||
const geprueft = await akteurOderFehler()
|
||||
if ('antwort' in geprueft) return geprueft.antwort
|
||||
const { akteur } = geprueft
|
||||
|
||||
const quelle = daten.get('quelle')
|
||||
const anstoss = { id: akteur.id, email: akteur.email }
|
||||
const einst = umgebung()
|
||||
|
||||
try {
|
||||
if (quelle === 'ZAMMAD' && einst.zammadAktiv) {
|
||||
const bericht = await zammadAbgleichen(anstoss)
|
||||
revalidatePath('/integrationen')
|
||||
revalidatePath('/aufgaben')
|
||||
return {
|
||||
ok: true,
|
||||
meldung: `${bericht.angelegt} neu, ${bericht.aktualisiert} geändert, ${bericht.geschlossen} geschlossen.`,
|
||||
}
|
||||
}
|
||||
|
||||
if (quelle === 'TELEGRAM' && einst.telegramAktiv) {
|
||||
const bericht = await telegramAbholen(anstoss)
|
||||
revalidatePath('/integrationen')
|
||||
revalidatePath('/aufgaben')
|
||||
return {
|
||||
ok: true,
|
||||
meldung: `${bericht.aufgaben} Aufgabe(n), ${bericht.gekoppelt} Kopplung(en), ${bericht.abgehakt} abgehakt.`,
|
||||
}
|
||||
}
|
||||
|
||||
if (quelle === 'SPIEGEL' && einst.spiegelSendenAktiv) {
|
||||
const bericht = await spiegelSenden()
|
||||
revalidatePath('/integrationen')
|
||||
return {
|
||||
ok: true,
|
||||
meldung: `${bericht.belegungen} Belegung(en) und ${bericht.aufgaben} Aufgabe(n) geschickt.`,
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: false, fehler: 'Diese Integration ist hier nicht eingerichtet.' }
|
||||
} catch (fehler) {
|
||||
// Die Meldung des fremden Systems, nicht dessen Antwort im Ganzen.
|
||||
return {
|
||||
ok: false,
|
||||
fehler: fehler instanceof Error ? fehler.message : 'Der Lauf ist gescheitert.',
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user