feat(mvp): phase 2 - database schema & services layer
Define full Prisma schema (10 models), run initial migration, build core services (auth, user, group, app, board, permission), Zod validators, type definitions, API response envelope, constants, and seed script.
This commit is contained in:
+164
-2
@@ -1,5 +1,3 @@
|
||||
// Prisma schema — models added in Phase 2
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
@@ -8,3 +6,167 @@ 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?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
groups UserGroup[]
|
||||
createdApps App[]
|
||||
boards Board[]
|
||||
|
||||
@@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[]
|
||||
|
||||
@@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
|
||||
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)
|
||||
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
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user