import { index, integer, primaryKey, real, 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 const handoutGelesen = sqliteTable( 'handout_gelesen', { benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), handoutId: text('handout_id').notNull(), gelesenAm: text('gelesen_am').notNull(), }, (t) => [primaryKey({ columns: [t.benutzerId, t.handoutId] }), index('handout_gelesen_benutzer_idx').on(t.benutzerId)], ); /** Zustände einer eingeplanten Woche – als Aufzählung, nicht als freier Text. */ export const wochenStatus = ['geplant', 'laufend', 'abgeschlossen'] as const; export type WochenStatus = (typeof wochenStatus)[number]; export const wochenplanung = sqliteTable( 'wochenplanung', { id: text('id').primaryKey(), ausbildungId: text('ausbildung_id') .notNull() .references(() => ausbildung.id, { onDelete: 'cascade' }), wochenNummer: integer('wochen_nummer').notNull(), geplantVon: text('geplant_von').notNull(), geplantBis: text('geplant_bis').notNull(), tatsaechlichVon: text('tatsaechlich_von'), status: text('status', { enum: wochenStatus }).notNull().default('geplant'), notiz: text('notiz'), geaendertAm: text('geaendert_am').notNull(), }, (t) => [ uniqueIndex('wochenplanung_woche_idx').on(t.ausbildungId, t.wochenNummer), index('wochenplanung_termin_idx').on(t.ausbildungId, t.geplantVon), index('wochenplanung_status_idx').on(t.ausbildungId, t.status), ], ); export const kartenBewertungen = ['sitzt', 'unsicher', 'nochmal'] as const; export type KartenBewertung = (typeof kartenBewertungen)[number]; export const karte = sqliteTable( 'karte', { benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), karteId: text('karte_id').notNull(), fach: integer('fach').notNull().default(1), faelligAm: text('faellig_am').notNull(), letzteBewertung: text('letzte_bewertung', { enum: kartenBewertungen }), wiederholungen: integer('wiederholungen').notNull().default(0), abstandTage: integer('abstand_tage').notNull().default(0), leichtigkeit: real('leichtigkeit').notNull().default(2.5), zuletztGelerntAm: text('zuletzt_gelernt_am'), }, (t) => [primaryKey({ columns: [t.benutzerId, t.karteId] }), index('karte_faellig_idx').on(t.benutzerId, t.faelligAm)], ); export const kartenVerlauf = sqliteTable( 'karten_verlauf', { id: text('id').primaryKey(), benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), karteId: text('karte_id').notNull(), bewertung: text('bewertung', { enum: kartenBewertungen }).notNull(), bewertetAm: text('bewertet_am').notNull(), }, (t) => [index('karten_verlauf_benutzer_idx').on(t.benutzerId, t.bewertetAm)], ); export const aufgabenStatus = ['offen', 'in_arbeit', 'zur_abnahme', 'abgenommen'] as const; export type AufgabenStatus = (typeof aufgabenStatus)[number]; export const aufgabeStatus = sqliteTable( 'aufgabe_status', { id: text('id').primaryKey(), benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), aufgabeId: text('aufgabe_id').notNull(), status: text('status', { enum: aufgabenStatus }).notNull().default('offen'), notiz: text('notiz').notNull().default(''), kriterienErfuellt: text('kriterien_erfuellt').notNull().default('[]'), bewertung: text('bewertung'), rueckmeldung: text('rueckmeldung'), begonnenAm: text('begonnen_am'), abgegebenAm: text('abgegeben_am'), abgenommenVon: text('abgenommen_von').references(() => benutzer.id), abgenommenAm: text('abgenommen_am'), geaendertAm: text('geaendert_am').notNull(), }, (t) => [ uniqueIndex('aufgabe_status_idx').on(t.benutzerId, t.aufgabeId), index('aufgabe_status_status_idx').on(t.status), ], ); export const bezugTypen = ['aufgabe', 'checkliste', 'bericht'] as const; export type BezugTyp = (typeof bezugTypen)[number]; export const anhang = sqliteTable( 'anhang', { id: text('id').primaryKey(), bezugTyp: text('bezug_typ', { enum: bezugTypen }).notNull(), bezugId: text('bezug_id').notNull(), benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), dateiname: text('dateiname').notNull(), mimeTyp: text('mime_typ').notNull(), groesse: integer('groesse').notNull(), pfad: text('pfad').notNull(), beschriftung: text('beschriftung'), hochgeladenAm: text('hochgeladen_am').notNull(), }, (t) => [index('anhang_bezug_idx').on(t.bezugTyp, t.bezugId), index('anhang_benutzer_idx').on(t.benutzerId)], ); export const checklisteLauf = sqliteTable( 'checkliste_lauf', { id: text('id').primaryKey(), benutzerId: text('benutzer_id') .notNull() .references(() => benutzer.id, { onDelete: 'cascade' }), checklisteId: text('checkliste_id').notNull(), titel: text('titel').notNull(), datum: text('datum').notNull(), ticket: text('ticket'), kunde: text('kunde'), bemerkung: text('bemerkung'), gestartetAm: text('gestartet_am').notNull(), abgeschlossenAm: text('abgeschlossen_am'), }, (t) => [ index('checkliste_lauf_benutzer_idx').on(t.benutzerId, t.datum), index('checkliste_lauf_vorlage_idx').on(t.checklisteId), ], ); export const checklisteSchritt = sqliteTable( 'checkliste_schritt', { id: text('id').primaryKey(), laufId: text('lauf_id') .notNull() .references(() => checklisteLauf.id, { onDelete: 'cascade' }), gruppeId: text('gruppe_id').notNull(), schrittId: text('schritt_id').notNull(), text: text('text').notNull(), reihenfolge: integer('reihenfolge').notNull(), erledigt: integer('erledigt').notNull().default(0), bemerkung: text('bemerkung'), erledigtAm: text('erledigt_am'), }, (t) => [ uniqueIndex('checkliste_schritt_idx').on(t.laufId, t.schrittId), index('checkliste_schritt_lauf_idx').on(t.laufId, t.reihenfolge), ], ); export type Benutzer = typeof benutzer.$inferSelect; export type Sitzung = typeof sitzung.$inferSelect; export type Ausbildung = typeof ausbildung.$inferSelect; export type HandoutGelesen = typeof handoutGelesen.$inferSelect; export type Wochenplanung = typeof wochenplanung.$inferSelect; export type Karte = typeof karte.$inferSelect; export type KartenVerlauf = typeof kartenVerlauf.$inferSelect; export type AufgabeStatus = typeof aufgabeStatus.$inferSelect; export type Anhang = typeof anhang.$inferSelect; export type ChecklisteLauf = typeof checklisteLauf.$inferSelect; export type ChecklisteSchritt = typeof checklisteSchritt.$inferSelect; /** Wird von der Schema-Abgleichprüfung im Test verwendet. */ export const alleTabellen = { benutzer, sitzung, ausbildung, handoutGelesen, wochenplanung, karte, kartenVerlauf, aufgabeStatus, anhang, checklisteLauf, checklisteSchritt, };