Verbinden, umbenennen, von Hand abgleichen und trennen. Alle Aktionen arbeiten nur auf Konten des angemeldeten Benutzers — die Kennung geht in die where-Bedingung, nicht in einen nachträglichen Vergleich. Ein Anbieter erscheint nur, wenn die Instanz ihn erlaubt *und* er konfiguriert ist. Ist er erlaubt, aber nicht konfiguriert, sagt die Seite das ausdrücklich, statt den Knopf wortlos wegzulassen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
'use server'
|
|
|
|
import { revalidatePath } from 'next/cache'
|
|
import { z } from 'zod'
|
|
import { auditSchreiben } from '@/server/audit'
|
|
import { NichtErlaubt, pruefeRolle } from '@/server/berechtigung'
|
|
import { db } from '@/server/db'
|
|
import { kontoAbgleichen } from '@/server/kalender/abgleich'
|
|
|
|
export type Rueckmeldung = { ok: true; meldung: string } | { ok: false; fehler: string }
|
|
|
|
const eingabe = z.object({ id: z.string().min(1) })
|
|
|
|
/**
|
|
* Alle Aktionen arbeiten nur auf Konten des angemeldeten Benutzers — die
|
|
* Kennung geht in die where-Bedingung, nicht in einen nachträglichen
|
|
* Vergleich.
|
|
*/
|
|
async function eigenesKonto(id: string, benutzerId: string) {
|
|
return db.kalenderkonto.findFirst({
|
|
where: { id, benutzerId },
|
|
select: { id: true, anzeigename: true, anbieter: true },
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
export async function jetztAbgleichen(daten: FormData): Promise<Rueckmeldung> {
|
|
const geprueft = await akteurOderFehler()
|
|
if ('antwort' in geprueft) return geprueft.antwort
|
|
|
|
const gelesen = eingabe.safeParse({ id: daten.get('id') })
|
|
if (!gelesen.success) return { ok: false, fehler: 'Ungültige Anfrage.' }
|
|
|
|
const konto = await eigenesKonto(gelesen.data.id, geprueft.akteur.id)
|
|
if (!konto) return { ok: false, fehler: 'Dieses Kalenderkonto gibt es nicht.' }
|
|
|
|
const bericht = await kontoAbgleichen(konto.id)
|
|
revalidatePath('/kalenderkonten')
|
|
|
|
if (!bericht.ok) {
|
|
return { ok: false, fehler: bericht.fehler ?? 'Der Abgleich schlug fehl.' }
|
|
}
|
|
return {
|
|
ok: true,
|
|
meldung: `„${konto.anzeigename}": ${bericht.geaendert} Termin(e) gelesen, ${bericht.entfernt} entfernt.`,
|
|
}
|
|
}
|
|
|
|
const umbenennen = z.object({
|
|
id: z.string().min(1),
|
|
anzeigename: z.string().trim().min(2, 'Mindestens zwei Zeichen.').max(60),
|
|
})
|
|
|
|
export async function kontoUmbenennen(daten: FormData): Promise<Rueckmeldung> {
|
|
const geprueft = await akteurOderFehler()
|
|
if ('antwort' in geprueft) return geprueft.antwort
|
|
|
|
const gelesen = umbenennen.safeParse({
|
|
id: daten.get('id'),
|
|
anzeigename: daten.get('anzeigename'),
|
|
})
|
|
if (!gelesen.success) {
|
|
return { ok: false, fehler: 'Bitte einen Namen mit mindestens zwei Zeichen angeben.' }
|
|
}
|
|
|
|
const konto = await eigenesKonto(gelesen.data.id, geprueft.akteur.id)
|
|
if (!konto) return { ok: false, fehler: 'Dieses Kalenderkonto gibt es nicht.' }
|
|
|
|
await db.kalenderkonto.update({
|
|
where: { id: konto.id },
|
|
data: { anzeigename: gelesen.data.anzeigename },
|
|
})
|
|
|
|
await auditSchreiben({
|
|
akteur: { id: geprueft.akteur.id, email: geprueft.akteur.email },
|
|
aktion: 'kalenderkonto.umbenannt',
|
|
objektTyp: 'Kalenderkonto',
|
|
objektId: konto.id,
|
|
details: { vorher: konto.anzeigename, nachher: gelesen.data.anzeigename },
|
|
})
|
|
|
|
revalidatePath('/kalenderkonten')
|
|
return { ok: true, meldung: 'Name gespeichert.' }
|
|
}
|
|
|
|
/**
|
|
* Trennt ein Kalenderkonto. Die gespiegelten Termine verschwinden mit ihm —
|
|
* sie stammen aus dem Kalender und sind dort weiterhin vorhanden.
|
|
*/
|
|
export async function kontoTrennen(daten: FormData): Promise<Rueckmeldung> {
|
|
const geprueft = await akteurOderFehler()
|
|
if ('antwort' in geprueft) return geprueft.antwort
|
|
|
|
const gelesen = eingabe.safeParse({ id: daten.get('id') })
|
|
if (!gelesen.success) return { ok: false, fehler: 'Ungültige Anfrage.' }
|
|
|
|
const konto = await eigenesKonto(gelesen.data.id, geprueft.akteur.id)
|
|
if (!konto) return { ok: false, fehler: 'Dieses Kalenderkonto gibt es nicht.' }
|
|
|
|
await db.kalenderkonto.delete({ where: { id: konto.id } })
|
|
|
|
await auditSchreiben({
|
|
akteur: { id: geprueft.akteur.id, email: geprueft.akteur.email },
|
|
aktion: 'kalenderkonto.getrennt',
|
|
objektTyp: 'Kalenderkonto',
|
|
objektId: konto.id,
|
|
details: { anbieter: konto.anbieter, anzeigename: konto.anzeigename },
|
|
})
|
|
|
|
revalidatePath('/kalenderkonten')
|
|
revalidatePath('/')
|
|
return {
|
|
ok: true,
|
|
meldung: `„${konto.anzeigename}" getrennt. Die Termine bleiben im Kalender selbst erhalten.`,
|
|
}
|
|
}
|