feat: Implement initial with admin and mobile clients, authentication, data models, and lead generation scripts.

This commit is contained in:
2026-02-19 16:18:34 +01:00
parent c53a71a5f9
commit 5e2d5fb3ae
32 changed files with 2283 additions and 420 deletions

View File

@@ -1,12 +1,14 @@
// InnungsApp — Prisma Schema
// Stack: PostgreSQL + Prisma ORM + better-auth
// Stack: SQLite + Prisma ORM + better-auth
// Note: SQLite has no native enum support — enum fields are stored as String.
// Valid values are enforced at the application layer (Zod).
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
provider = "sqlite"
url = env("DATABASE_URL")
}
@@ -87,16 +89,16 @@ model Verification {
// =============================================
model Organization {
id String @id @default(uuid())
id String @id @default(uuid())
name String
slug String @unique
plan Plan @default(pilot)
logoUrl String? @map("logo_url")
primaryColor String @default("#E63946") @map("primary_color")
contactEmail String? @map("contact_email")
avvAccepted Boolean @default(false) @map("avv_accepted")
slug String @unique
plan String @default("pilot") // pilot | standard | pro | verband
logoUrl String? @map("logo_url")
primaryColor String @default("#E63946") @map("primary_color")
contactEmail String? @map("contact_email")
avvAccepted Boolean @default(false) @map("avv_accepted")
avvAcceptedAt DateTime? @map("avv_accepted_at")
createdAt DateTime @default(now()) @map("created_at")
createdAt DateTime @default(now()) @map("created_at")
members Member[]
userRoles UserRole[]
@@ -107,62 +109,49 @@ model Organization {
@@map("organizations")
}
enum Plan {
pilot
standard
pro
verband
}
// =============================================
// MEMBERS
// =============================================
model Member {
id String @id @default(uuid())
orgId String @map("org_id")
userId String? @unique @map("user_id") // NULL until magic-link clicked
name String
betrieb String
sparte String
ort String
telefon String?
email String
status MemberStatus @default(aktiv)
istAusbildungsbetrieb Boolean @default(false) @map("ist_ausbildungsbetrieb")
seit Int?
avatarUrl String? @map("avatar_url")
pushToken String? @map("push_token")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
id String @id @default(uuid())
orgId String @map("org_id")
userId String? @unique @map("user_id") // NULL until magic-link clicked
name String
betrieb String
sparte String
ort String
telefon String?
email String
status String @default("aktiv") // aktiv | ruhend | ausgetreten
istAusbildungsbetrieb Boolean @default(false) @map("ist_ausbildungsbetrieb")
seit Int?
avatarUrl String? @map("avatar_url")
pushToken String? @map("push_token")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
newsAuthored News[] @relation("NewsAuthor")
stellen Stelle[]
terminAnmeldungen TerminAnmeldung[]
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
newsAuthored News[] @relation("NewsAuthor")
stellen Stelle[]
terminAnmeldungen TerminAnmeldung[]
@@index([orgId])
@@index([status])
@@map("members")
}
enum MemberStatus {
aktiv
ruhend
ausgetreten
}
// =============================================
// USER ROLES (multi-tenancy)
// =============================================
model UserRole {
id String @id @default(uuid())
orgId String @map("org_id")
userId String @map("user_id")
role OrgRole
createdAt DateTime @default(now()) @map("created_at")
id String @id @default(uuid())
orgId String @map("org_id")
userId String @map("user_id")
role String // admin | member
createdAt DateTime @default(now()) @map("created_at")
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -171,24 +160,19 @@ model UserRole {
@@map("user_roles")
}
enum OrgRole {
admin
member
}
// =============================================
// NEWS
// =============================================
model News {
id String @id @default(uuid())
orgId String @map("org_id")
authorId String? @map("author_id")
id String @id @default(uuid())
orgId String @map("org_id")
authorId String? @map("author_id")
title String
body String // Markdown
kategorie NewsKategorie
publishedAt DateTime? @map("published_at") // NULL = Entwurf
createdAt DateTime @default(now()) @map("created_at")
body String // Markdown
kategorie String // Wichtig | Pruefung | Foerderung | Veranstaltung | Allgemein
publishedAt DateTime? @map("published_at") // NULL = Entwurf
createdAt DateTime @default(now()) @map("created_at")
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
author Member? @relation("NewsAuthor", fields: [authorId], references: [id], onDelete: SetNull)
@@ -200,14 +184,6 @@ model News {
@@map("news")
}
enum NewsKategorie {
Wichtig
Pruefung
Foerderung
Veranstaltung
Allgemein
}
model NewsRead {
id String @id @default(uuid())
newsId String @map("news_id")
@@ -269,13 +245,13 @@ model Termin {
id String @id @default(uuid())
orgId String @map("org_id")
titel String
datum DateTime @db.Date
datum DateTime
uhrzeit String? // stored as "HH:MM"
endeDatum DateTime? @map("ende_datum") @db.Date
endeDatum DateTime? @map("ende_datum")
endeUhrzeit String? @map("ende_uhrzeit")
ort String?
adresse String?
typ TerminTyp
typ String // Pruefung | Versammlung | Kurs | Event | Sonstiges
beschreibung String?
maxTeilnehmer Int? @map("max_teilnehmer") // NULL = unbegrenzt
createdAt DateTime @default(now()) @map("created_at")
@@ -288,14 +264,6 @@ model Termin {
@@map("termine")
}
enum TerminTyp {
Pruefung
Versammlung
Kurs
Event
Sonstiges
}
model TerminAnmeldung {
id String @id @default(uuid())
terminId String @map("termin_id")