Baustein 1: Grundgeruest, Datenbank, Anmeldung, zwei Rollen
Legt das Fundament der Lern-Bar an: - Next.js 15 (App Router), React 19, TypeScript, ESLint, Vitest - SQLite ueber Drizzle hinter einer Repository-Schicht - Eigener Migrationslauf mit Pruefsummen, beim Start angewendet - Argon2id, serverseitige Sitzungen, HttpOnly-Cookie, SameSite=Lax - Rollen Ausbilder und Azubi, Rechteregeln als reine Funktionen - Verwaltung: Azubikonto und Ausbildung anlegen, Passwort, Sperren - Gestaltung Petrol/Bernstein, helle und dunkle Darstellung - Dockerfile, docker-compose, Lebenszeichen, update.sh mit Rueckfall - Sicherung und Wiederherstellung als Shell-Skripte - 29 Tests fuer Rechte, Zeitrechnung und Migrationslauf Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { erfordereAusbilder } from '@/lib/auth/rechte';
|
||||
import { passwortMangel } from '@/lib/auth/passwort';
|
||||
import { aktivSetzen, benutzerAnlegen, benutzerNachEmail, benutzerNachId, passwortSetzen } from '@/lib/repos/benutzer';
|
||||
import { ausbildungAendern, ausbildungAnlegen, ausbildungNachAzubi, ausbildungNachId } from '@/lib/repos/ausbildung';
|
||||
|
||||
export type Rueckmeldung = { fehler?: string; erfolg?: string };
|
||||
|
||||
const datumMuster = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
export async function azubiAnlegen(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||||
const ausbilder = await erfordereAusbilder();
|
||||
|
||||
const name = String(formular.get('name') ?? '').trim();
|
||||
const email = String(formular.get('email') ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const passwort = String(formular.get('passwort') ?? '');
|
||||
const beginn = String(formular.get('beginn') ?? '').trim();
|
||||
const probezeitBis = String(formular.get('probezeitBis') ?? '').trim();
|
||||
const ende = String(formular.get('ende') ?? '').trim();
|
||||
|
||||
if (!name) return { fehler: 'Bitte einen Namen eintragen.' };
|
||||
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return { fehler: 'Die E-Mail-Adresse sieht nicht gültig aus.' };
|
||||
const mangel = passwortMangel(passwort);
|
||||
if (mangel) return { fehler: mangel };
|
||||
if (!datumMuster.test(beginn)) return { fehler: 'Bitte ein Beginndatum wählen.' };
|
||||
if (benutzerNachEmail(email)) return { fehler: 'Diese E-Mail-Adresse ist schon vergeben.' };
|
||||
|
||||
const azubi = await benutzerAnlegen({ name, email, passwort, rolle: 'azubi' });
|
||||
ausbildungAnlegen({
|
||||
azubiId: azubi.id,
|
||||
ausbilderId: ausbilder.id,
|
||||
beginn,
|
||||
probezeitBis: datumMuster.test(probezeitBis) ? probezeitBis : null,
|
||||
ende: datumMuster.test(ende) ? ende : null,
|
||||
});
|
||||
|
||||
revalidatePath('/verwaltung');
|
||||
return { erfolg: `Konto für ${name} angelegt und Ausbildung ab ${beginn} eingerichtet.` };
|
||||
}
|
||||
|
||||
export async function ausbildungBearbeiten(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||||
const ausbilder = await erfordereAusbilder();
|
||||
const id = String(formular.get('ausbildungId') ?? '');
|
||||
const bestand = ausbildungNachId(id);
|
||||
|
||||
if (!bestand || bestand.ausbilderId !== ausbilder.id) {
|
||||
return { fehler: 'Diese Ausbildung gehört nicht zu dir.' };
|
||||
}
|
||||
|
||||
const beginn = String(formular.get('beginn') ?? '').trim();
|
||||
const ende = String(formular.get('ende') ?? '').trim();
|
||||
const probezeitBis = String(formular.get('probezeitBis') ?? '').trim();
|
||||
if (!datumMuster.test(beginn)) return { fehler: 'Bitte ein gültiges Beginndatum wählen.' };
|
||||
|
||||
ausbildungAendern(id, {
|
||||
beginn,
|
||||
ende: datumMuster.test(ende) ? ende : null,
|
||||
probezeitBis: datumMuster.test(probezeitBis) ? probezeitBis : null,
|
||||
});
|
||||
|
||||
revalidatePath('/verwaltung');
|
||||
return { erfolg: 'Ausbildung aktualisiert.' };
|
||||
}
|
||||
|
||||
export async function passwortZuruecksetzen(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||||
const ausbilder = await erfordereAusbilder();
|
||||
const benutzerId = String(formular.get('benutzerId') ?? '');
|
||||
const neu = String(formular.get('passwort') ?? '');
|
||||
|
||||
const ziel = benutzerNachId(benutzerId);
|
||||
if (!ziel) return { fehler: 'Konto nicht gefunden.' };
|
||||
|
||||
const bezug = ausbildungNachAzubi(benutzerId);
|
||||
const eigenesKonto = ziel.id === ausbilder.id;
|
||||
const eigenerAzubi = ziel.rolle === 'azubi' && bezug?.ausbilderId === ausbilder.id;
|
||||
if (!eigenesKonto && !eigenerAzubi) return { fehler: 'Dieses Konto darfst du nicht ändern.' };
|
||||
|
||||
const mangel = passwortMangel(neu);
|
||||
if (mangel) return { fehler: mangel };
|
||||
|
||||
await passwortSetzen(benutzerId, neu);
|
||||
revalidatePath('/verwaltung');
|
||||
return { erfolg: `Neues Passwort für ${ziel.name} gesetzt. Alle offenen Sitzungen wurden beendet.` };
|
||||
}
|
||||
|
||||
export async function kontoAktivSetzen(formular: FormData): Promise<void> {
|
||||
const ausbilder = await erfordereAusbilder();
|
||||
const benutzerId = String(formular.get('benutzerId') ?? '');
|
||||
const aktiv = String(formular.get('aktiv') ?? '') === '1';
|
||||
|
||||
const ziel = benutzerNachId(benutzerId);
|
||||
if (!ziel || ziel.id === ausbilder.id) return;
|
||||
|
||||
const bezug = ausbildungNachAzubi(benutzerId);
|
||||
if (ziel.rolle !== 'azubi' || bezug?.ausbilderId !== ausbilder.id) return;
|
||||
|
||||
aktivSetzen(benutzerId, aktiv);
|
||||
revalidatePath('/verwaltung');
|
||||
}
|
||||
Reference in New Issue
Block a user