Files
lernbar/src/lib/db/schema.ts
T
Florian WildeandClaude Opus 5 e856349cd4 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>
2026-09-10 00:52:44 +02:00

60 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
/** Rollen – bewusst nur zwei. */
export const rollen = ['ausbilder', 'azubi'] as const;
export type Rolle = (typeof rollen)[number];
export const benutzer = sqliteTable(
'benutzer',
{
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull(),
passwortHash: text('passwort_hash').notNull(),
rolle: text('rolle', { enum: rollen }).notNull(),
aktiv: integer('aktiv').notNull().default(1),
erstelltAm: text('erstellt_am').notNull(),
letzteAnmeldungAm: text('letzte_anmeldung_am'),
},
(t) => [uniqueIndex('benutzer_email_idx').on(t.email)],
);
export const sitzung = sqliteTable(
'sitzung',
{
id: text('id').primaryKey(),
benutzerId: text('benutzer_id')
.notNull()
.references(() => benutzer.id, { onDelete: 'cascade' }),
erstelltAm: text('erstellt_am').notNull(),
laeuftAbAm: text('laeuft_ab_am').notNull(),
},
(t) => [index('sitzung_benutzer_idx').on(t.benutzerId), index('sitzung_ablauf_idx').on(t.laeuftAbAm)],
);
export const ausbildung = sqliteTable(
'ausbildung',
{
id: text('id').primaryKey(),
azubiId: text('azubi_id')
.notNull()
.references(() => benutzer.id, { onDelete: 'cascade' }),
ausbilderId: text('ausbilder_id')
.notNull()
.references(() => benutzer.id),
beruf: text('beruf').notNull().default('Fachinformatiker/-in für Systemintegration'),
beginn: text('beginn').notNull(),
ende: text('ende'),
probezeitBis: text('probezeit_bis'),
erstelltAm: text('erstellt_am').notNull(),
},
(t) => [uniqueIndex('ausbildung_azubi_idx').on(t.azubiId), index('ausbildung_ausbilder_idx').on(t.ausbilderId)],
);
export type Benutzer = typeof benutzer.$inferSelect;
export type Sitzung = typeof sitzung.$inferSelect;
export type Ausbildung = typeof ausbildung.$inferSelect;
/** Wird von der Schema-Abgleichprüfung im Test verwendet. */
export const alleTabellen = { benutzer, sitzung, ausbildung };