feat(kalender): Seite für die Kalenderkonten
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9946e7c308
commit
0c67b73a62
@@ -0,0 +1,126 @@
|
||||
'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.`,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user