generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } /// A Konfi-Castle event; the top-level tenant. One instance manages many KCs. model Kc { id String @id @default(cuid()) name String inviteCode String @unique isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt gemeinden Gemeinde[] memberships Membership[] wahlen Wahl[] files File[] channels ChatChannel[] guests GuestAccount[] } /// A local congregation/community participating in one Kc. model Gemeinde { id String @id @default(cuid()) name String kcId String createdAt DateTime @default(now()) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) memberships Membership[] guests GuestAccount[] @@unique([kcId, name]) } enum Role { LEITUNGSTEAM GEMEINDE_VERANTWORTLICHER GEMEINDE_TEAMER } /// Authentik-backed user (team member with elevated rights). model User { id String @id @default(cuid()) authentikSub String @unique email String @unique firstName String lastName String createdAt DateTime @default(now()) memberships Membership[] messages ChatMessage[] } /// Scopes a User's role to a specific Kc (and Gemeinde, if applicable). /// LEITUNGSTEAM memberships apply to all Kcs implicitly and omit gemeindeId. model Membership { id String @id @default(cuid()) userId String kcId String gemeindeId String? role Role createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) gemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id], onDelete: Cascade) @@unique([userId, kcId, gemeindeId]) } /// Local, non-Authentik account for Konfis/guests, scoped to one Kc/event. model GuestAccount { id String @id @default(cuid()) kcId String gemeindeId String? firstName String lastName String createdAt DateTime @default(now()) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) gemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id], onDelete: Cascade) messages ChatMessage[] teilnehmer Teilnehmer[] } /// A workshop election, scoped to a Kc; name carries a date key + "Teil". model Wahl { id String @id @default(cuid()) kcId String name String datumsSchluessel String teil String isOpen Boolean @default(true) createdAt DateTime @default(now()) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) workshops Workshop[] teilnehmer Teilnehmer[] } model Workshop { id String @id @default(cuid()) wahlId String name String kapazitaet Int wahl Wahl @relation(fields: [wahlId], references: [id], onDelete: Cascade) zuteilungen Zuteilung[] } /// A participant's submitted choices for a Wahl. model Teilnehmer { id String @id @default(cuid()) wahlId String guestAccountId String prioritaeten Json createdAt DateTime @default(now()) wahl Wahl @relation(fields: [wahlId], references: [id], onDelete: Cascade) guestAccount GuestAccount @relation(fields: [guestAccountId], references: [id], onDelete: Cascade) zuteilung Zuteilung? @@unique([wahlId, guestAccountId]) } /// Result of the assignment algorithm (or a manual force-assignment) for one Teilnehmer. model Zuteilung { id String @id @default(cuid()) teilnehmerId String @unique workshopId String isForced Boolean @default(false) createdAt DateTime @default(now()) teilnehmer Teilnehmer @relation(fields: [teilnehmerId], references: [id], onDelete: Cascade) workshop Workshop @relation(fields: [workshopId], references: [id], onDelete: Cascade) } enum FileVisibility { ALLE ALLE_AUSSER_KONFIS NUR_LT } model File { id String @id @default(cuid()) kcId String storageKey String filename String visibility FileVisibility uploadedById String createdAt DateTime @default(now()) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) } enum ChatChannelType { GEMEINDE_GRUPPE DIREKT LT_UEBERGREIFEND BROADCAST } model ChatChannel { id String @id @default(cuid()) kcId String type ChatChannelType gemeindeId String? createdAt DateTime @default(now()) kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade) messages ChatMessage[] } model ChatMessage { id String @id @default(cuid()) channelId String senderUserId String? senderGuestId String? body String createdAt DateTime @default(now()) channel ChatChannel @relation(fields: [channelId], references: [id], onDelete: Cascade) senderUser User? @relation(fields: [senderUserId], references: [id]) senderGuest GuestAccount? @relation(fields: [senderGuestId], references: [id]) }