feat: initial SchwarmbLog monorepo setup
- React + Vite + TanStack Router/Query frontend - Hono + Prisma + MySQL backend - Prisma schema mit allen Entitäten - Docker + docker-compose Setup - Tailwind mit Salm/Ozean Farbpalette
This commit is contained in:
260
apps/api/prisma/schema.prisma
Normal file
260
apps/api/prisma/schema.prisma
Normal file
@@ -0,0 +1,260 @@
|
||||
// SchwarmbLog – Prisma Schema
|
||||
// Stack: Hono + Prisma + MySQL
|
||||
// Conventions: camelCase in Prisma, snake_case in DB, cuid() for IDs
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// USERS & AUTH
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
email String? @unique
|
||||
role Role @default(STUDENT)
|
||||
|
||||
// Auth
|
||||
passwordHash String?
|
||||
qrToken String? @unique @map("qr_token")
|
||||
qrTokenCreated DateTime? @map("qr_token_created")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
classMemberships ClassMembership[]
|
||||
swarmMemberships SwarmMembership[]
|
||||
blog Blog?
|
||||
assignedTodos Todo[] @relation("AssignedTodos")
|
||||
reactions Reaction[]
|
||||
comments Comment[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
TEACHER
|
||||
STUDENT
|
||||
PARENT
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// CLASSES & SWARMS
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
model Class {
|
||||
id String @id @default(cuid())
|
||||
name String // z.B. "7a"
|
||||
schoolYear String @map("school_year") // z.B. "2025/26"
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
memberships ClassMembership[]
|
||||
swarms Swarm[]
|
||||
blogs Blog[]
|
||||
|
||||
@@map("classes")
|
||||
}
|
||||
|
||||
model ClassMembership {
|
||||
id String @id @default(cuid())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @map("user_id")
|
||||
class Class @relation(fields: [classId], references: [id])
|
||||
classId String @map("class_id")
|
||||
role ClassRole @default(STUDENT)
|
||||
|
||||
@@unique([userId, classId])
|
||||
@@map("class_memberships")
|
||||
}
|
||||
|
||||
enum ClassRole {
|
||||
TEACHER
|
||||
STUDENT
|
||||
}
|
||||
|
||||
// Schwarm = Sichtbarkeitsgruppe (Klasse oder ganzer Jahrgang)
|
||||
model Swarm {
|
||||
id String @id @default(cuid())
|
||||
name String // z.B. "Schwarm 7a" oder "Schwarm Jahrgang 7"
|
||||
scope SwarmScope
|
||||
class Class? @relation(fields: [classId], references: [id])
|
||||
classId String? @map("class_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
memberships SwarmMembership[]
|
||||
|
||||
@@map("swarms")
|
||||
}
|
||||
|
||||
enum SwarmScope {
|
||||
CLASS // nur eine Klasse
|
||||
YEAR // ganzer Jahrgang
|
||||
}
|
||||
|
||||
model SwarmMembership {
|
||||
id String @id @default(cuid())
|
||||
swarm Swarm @relation(fields: [swarmId], references: [id])
|
||||
swarmId String @map("swarm_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @map("user_id")
|
||||
|
||||
@@unique([swarmId, userId])
|
||||
@@map("swarm_memberships")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// STATIONEN & TEMPLATES
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
model Station {
|
||||
id String @id @default(cuid())
|
||||
name String // z.B. "Lagos"
|
||||
country String
|
||||
latitude Float
|
||||
longitude Float
|
||||
bneTopic String @map("bne_topic") // z.B. "Elektroschrott"
|
||||
description String? @db.Text
|
||||
orderIndex Int @map("order_index") // optimale Reihenfolge
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
templates EntryTemplate[]
|
||||
blogEntries BlogEntry[]
|
||||
|
||||
@@map("stations")
|
||||
}
|
||||
|
||||
// Vorstrukturierter Eintrag (Lückentext, Bildblock, etc.)
|
||||
// blocks: JSON-Array von Block-Objekten
|
||||
// Beispiel: [{ type: "gap_text", content: "Ich bin in ___ angekommen..." }, { type: "image" }, { type: "free_text" }]
|
||||
model EntryTemplate {
|
||||
id String @id @default(cuid())
|
||||
station Station @relation(fields: [stationId], references: [id])
|
||||
stationId String @map("station_id")
|
||||
title String
|
||||
blocks Json // Block-Struktur des Templates
|
||||
isOptional Boolean @default(false) @map("is_optional")
|
||||
orderIndex Int @default(0) @map("order_index")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
todos Todo[]
|
||||
blogEntries BlogEntry[]
|
||||
|
||||
@@map("entry_templates")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// BLOG & EINTRÄGE
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
model Blog {
|
||||
id String @id @default(cuid())
|
||||
student User @relation(fields: [studentId], references: [id])
|
||||
studentId String @unique @map("student_id")
|
||||
class Class @relation(fields: [classId], references: [id])
|
||||
classId String @map("class_id")
|
||||
title String @default("Mein Reiseblog")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
entries BlogEntry[]
|
||||
todos Todo[]
|
||||
|
||||
@@map("blogs")
|
||||
}
|
||||
|
||||
model BlogEntry {
|
||||
id String @id @default(cuid())
|
||||
blog Blog @relation(fields: [blogId], references: [id])
|
||||
blogId String @map("blog_id")
|
||||
station Station? @relation(fields: [stationId], references: [id])
|
||||
stationId String? @map("station_id")
|
||||
template EntryTemplate? @relation(fields: [templateId], references: [id])
|
||||
templateId String? @map("template_id") // null = freier Eintrag
|
||||
title String
|
||||
blocks Json // Ausgefüllte Block-Inhalte (Text, Bildpfad, etc.)
|
||||
status EntryStatus @default(DRAFT)
|
||||
visibility Visibility @default(PRIVATE)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
reactions Reaction[]
|
||||
comments Comment[]
|
||||
|
||||
@@map("blog_entries")
|
||||
}
|
||||
|
||||
enum EntryStatus {
|
||||
DRAFT
|
||||
PUBLISHED
|
||||
}
|
||||
|
||||
enum Visibility {
|
||||
PRIVATE // nur Schüler + Lehrkraft
|
||||
CLASS // Klassen-Schwarm
|
||||
YEAR // Jahrgangs-Schwarm
|
||||
PARENTS // zusätzlich Eltern
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// TODOS
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
// Lehrkraft weist Schüler einen vorstrukturierten Eintrag zu
|
||||
model Todo {
|
||||
id String @id @default(cuid())
|
||||
blog Blog @relation(fields: [blogId], references: [id])
|
||||
blogId String @map("blog_id")
|
||||
template EntryTemplate @relation(fields: [templateId], references: [id])
|
||||
templateId String @map("template_id")
|
||||
assignedBy User @relation("AssignedTodos", fields: [assignedById], references: [id])
|
||||
assignedById String @map("assigned_by")
|
||||
dueDate DateTime? @map("due_date")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("todos")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// SOZIALES
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
model Reaction {
|
||||
id String @id @default(cuid())
|
||||
entry BlogEntry @relation(fields: [entryId], references: [id])
|
||||
entryId String @map("entry_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @map("user_id")
|
||||
type ReactionType @default(LIKE)
|
||||
|
||||
@@unique([entryId, userId]) // pro Eintrag nur eine Reaktion pro User
|
||||
@@map("reactions")
|
||||
}
|
||||
|
||||
enum ReactionType {
|
||||
LIKE
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id String @id @default(cuid())
|
||||
entry BlogEntry @relation(fields: [entryId], references: [id])
|
||||
entryId String @map("entry_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @map("user_id")
|
||||
text String @db.Text
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("comments")
|
||||
}
|
||||
Reference in New Issue
Block a user