generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique password String? displayName String avatarUrl String? authProvider String @default("local") // local | oauth role String @default("user") // admin | user refreshToken String? refreshTokenExpiresAt DateTime? onboardingComplete Boolean @default(false) trackRecentApps Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt themeMode String? primaryHue Int? primarySaturation Int? backgroundType String? locale String? groups UserGroup[] createdApps App[] boards Board[] favorites UserFavorite[] clicks AppClick[] notificationChannels NotificationChannel[] notifications Notification[] apiTokens ApiToken[] auditLogs AuditLog[] boardTemplates BoardTemplate[] @@index([email]) } model Group { id String @id @default(cuid()) name String @unique description String? isDefault Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users UserGroup[] } model UserGroup { id String @id @default(cuid()) userId String groupId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) @@unique([userId, groupId]) @@index([userId]) @@index([groupId]) } model App { id String @id @default(cuid()) name String url String icon String? iconType String @default("lucide") // lucide | simple | url | emoji description String? category String? tags String @default("") // comma-separated healthcheckEnabled Boolean @default(false) healthcheckInterval Int @default(300) // seconds healthcheckMethod String @default("GET") healthcheckExpectedStatus Int @default(200) healthcheckTimeout Int @default(5000) // milliseconds createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) statuses AppStatus[] widgets Widget[] appTags AppTag[] links AppLink[] clicks AppClick[] notifications Notification[] favorites UserFavorite[] @@index([name]) @@index([category]) @@index([createdById]) } model AppStatus { id String @id @default(cuid()) appId String status String @default("unknown") // online | offline | degraded | unknown responseTime Int? // milliseconds checkedAt DateTime @default(now()) app App @relation(fields: [appId], references: [id], onDelete: Cascade) @@index([appId]) @@index([checkedAt]) } model Board { id String @id @default(cuid()) name String icon String? description String? isDefault Boolean @default(false) isGuestAccessible Boolean @default(false) backgroundConfig String? // JSON stored as string for SQLite themeHue Int? themeSaturation Int? backgroundType String? cardSize String? wallpaperUrl String? wallpaperBlur Int? wallpaperOverlay Float? customCss String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) sections Section[] @@index([createdById]) } model Section { id String @id @default(cuid()) boardId String title String icon String? order Int @default(0) isExpandedByDefault Boolean @default(true) cardSize String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) widgets Widget[] @@index([boardId]) } model Widget { id String @id @default(cuid()) sectionId String type String // app | bookmark | note | embed | status order Int @default(0) config String @default("{}") // JSON stored as string for SQLite appId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt section Section @relation(fields: [sectionId], references: [id], onDelete: Cascade) app App? @relation(fields: [appId], references: [id], onDelete: SetNull) @@index([sectionId]) @@index([appId]) } model Permission { id String @id @default(cuid()) entityType String // board | app entityId String targetType String // user | group targetId String level String // view | edit | admin createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([entityType, entityId, targetType, targetId]) @@index([entityType, entityId]) @@index([targetType, targetId]) } model SystemSettings { id String @id @default("singleton") authMode String @default("local") // local | oauth | both registrationEnabled Boolean @default(true) oauthClientId String? oauthClientSecret String? oauthDiscoveryUrl String? defaultTheme String @default("dark") defaultPrimaryColor String @default("#6366f1") healthcheckDefaults String @default("{}") // JSON stored as string for SQLite customCss String? onboardingComplete Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // --- New models for Phases 4-7 --- model Tag { id String @id @default(cuid()) name String @unique color String? createdAt DateTime @default(now()) appTags AppTag[] @@index([name]) } model AppTag { id String @id @default(cuid()) appId String tagId String app App @relation(fields: [appId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@unique([appId, tagId]) @@index([appId]) @@index([tagId]) } model AppLink { id String @id @default(cuid()) appId String label String url String icon String? order Int @default(0) app App @relation(fields: [appId], references: [id], onDelete: Cascade) @@index([appId]) } model UserFavorite { id String @id @default(cuid()) userId String appId String order Int @default(0) user User @relation(fields: [userId], references: [id], onDelete: Cascade) app App @relation(fields: [appId], references: [id], onDelete: Cascade) @@unique([userId, appId]) @@index([userId]) @@index([appId]) } model AppClick { id String @id @default(cuid()) userId String appId String clickedAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) app App @relation(fields: [appId], references: [id], onDelete: Cascade) @@index([userId]) @@index([appId]) @@index([clickedAt]) } model NotificationChannel { id String @id @default(cuid()) userId String type String // discord | slack | telegram | http config String @default("{}") // JSON stored as string for SQLite enabled Boolean @default(true) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) } model Notification { id String @id @default(cuid()) userId String appId String? event String // app_online | app_offline | app_degraded message String sentAt DateTime @default(now()) readAt DateTime? user User @relation(fields: [userId], references: [id], onDelete: Cascade) app App? @relation(fields: [appId], references: [id], onDelete: SetNull) @@index([userId]) @@index([appId]) @@index([sentAt]) } model ApiToken { id String @id @default(cuid()) userId String name String tokenHash String @unique scope String // read | write | admin lastUsedAt DateTime? expiresAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([tokenHash]) } model AuditLog { id String @id @default(cuid()) userId String? action String // user_created | user_deleted | etc. entityType String entityId String details String @default("{}") // JSON stored as string for SQLite createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([action]) @@index([entityType, entityId]) @@index([createdAt]) } model BoardTemplate { id String @id @default(cuid()) name String description String? icon String? config String @default("{}") // JSON stored as string for SQLite isBuiltin Boolean @default(false) createdById String? createdAt DateTime @default(now()) createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) @@index([createdById]) }