Files
metabuilder/prisma/schema.prisma
T
2025-12-30 20:40:29 +00:00

904 lines
26 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
}
// =============================================================================
// CORE IDENTITY & AUTH
// =============================================================================
model User {
id String @id
username String @unique
email String @unique
role String // public|user|moderator|admin|god|supergod
profilePicture String?
bio String?
createdAt BigInt
tenantId String?
isInstanceOwner Boolean @default(false)
tenant Tenant? @relation(fields: [tenantId], references: [id])
ownedTenants Tenant[] @relation("TenantOwner")
comments Comment[]
powerTransfersFrom PowerTransferRequest[] @relation("PowerTransferFrom")
powerTransfersTo PowerTransferRequest[] @relation("PowerTransferTo")
passwordChangeTimestamp BigInt?
firstLogin Boolean @default(false)
sessions Session[]
notifications Notification[]
auditLogs AuditLog[]
mediaAssets MediaAsset[]
forumPosts ForumPost[]
forumThreads ForumThread[]
@@index([tenantId])
@@index([role])
}
model Credential {
username String @id
passwordHash String
}
model Session {
id String @id
userId String
token String @unique
expiresAt BigInt
createdAt BigInt
lastActivity BigInt
ipAddress String?
userAgent String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([expiresAt])
@@index([token])
}
// =============================================================================
// MULTI-TENANCY
// =============================================================================
model Tenant {
id String @id
name String
slug String @unique
ownerId String
createdAt BigInt
homepageConfig String?
settings String? // JSON: theme, features, limits
owner User @relation("TenantOwner", fields: [ownerId], references: [id], onDelete: Cascade)
users User[]
workflows Workflow[]
luaScripts LuaScript[]
pages PageConfig[]
schemas ModelSchema[]
notifications Notification[]
auditLogs AuditLog[]
mediaAssets MediaAsset[]
forumCategories ForumCategory[]
@@index([slug])
@@index([ownerId])
}
// =============================================================================
// ROUTING & PAGES
// =============================================================================
model PageConfig {
id String @id
tenantId String?
packageId String? // Which package defined this route
path String // Route pattern: /media/jobs, /forum/:id
title String
description String? // SEO meta description
icon String? // Icon for navigation menus
component String? // Named component reference
componentTree String // JSON: full component tree
level Int // Required permission level 1-6
requiresAuth Boolean
requiredRole String?
parentPath String? // Parent route for hierarchy
sortOrder Int @default(0)
isPublished Boolean @default(true)
params String? // JSON: route parameter definitions
meta String? // JSON: additional metadata (og tags, etc.)
createdAt BigInt?
updatedAt BigInt?
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@unique([tenantId, path])
@@index([tenantId])
@@index([packageId])
@@index([level])
@@index([parentPath])
}
// =============================================================================
// WORKFLOWS & SCRIPTING
// =============================================================================
model Workflow {
id String @id
tenantId String?
name String
description String?
nodes String // JSON: WorkflowNode[]
edges String // JSON: WorkflowEdge[]
enabled Boolean
version Int @default(1)
createdAt BigInt?
updatedAt BigInt?
createdBy String?
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([enabled])
}
model LuaScript {
id String @id
tenantId String?
name String
description String?
code String
parameters String // JSON: Array<{name, type}>
returnType String?
isSandboxed Boolean @default(true)
allowedGlobals String @default("[]")
timeoutMs Int @default(5000)
version Int @default(1)
createdAt BigInt?
updatedAt BigInt?
createdBy String?
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([name])
}
// =============================================================================
// DATA SCHEMAS
// =============================================================================
model ModelSchema {
id String @id @default(cuid())
tenantId String?
name String
label String?
labelPlural String?
icon String?
fields String // JSON: field definitions
listDisplay String? // JSON: columns to show in list
listFilter String? // JSON: filterable fields
searchFields String? // JSON: searchable fields
ordering String? // JSON: default sort order
validations String? // JSON: validation rules
hooks String? // JSON: lifecycle hooks (Lua script refs)
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@unique([tenantId, name])
@@index([tenantId])
}
model DynamicData {
id String @id @default(cuid())
tenantId String
schemaId String // Reference to ModelSchema
data String // JSON: actual record data
createdAt BigInt
updatedAt BigInt?
createdBy String?
updatedBy String?
@@index([tenantId, schemaId])
@@index([createdAt])
}
// =============================================================================
// APP CONFIGURATION
// =============================================================================
model AppConfiguration {
id String @id
name String
schemas String // JSON
workflows String // JSON
luaScripts String // JSON
pages String // JSON
theme String // JSON
}
model SystemConfig {
key String @id
value String
}
model SMTPConfig {
id String @id @default("default")
host String
port Int
secure Boolean
username String
password String
fromEmail String
fromName String
}
// =============================================================================
// GENERIC KEY-VALUE STORAGE
// =============================================================================
model KeyValue {
id String @id @default(cuid())
tenantId String?
namespace String // Logical grouping: 'settings', 'cache', 'state', 'user_prefs', etc.
key String
value String // JSON or plain text
type String @default("string") // string, json, number, boolean
expiresAt BigInt? // Optional TTL for cache-like usage
createdAt BigInt
updatedAt BigInt?
@@unique([tenantId, namespace, key])
@@index([tenantId])
@@index([namespace])
@@index([expiresAt])
}
model UserPreference {
id String @id @default(cuid())
userId String
tenantId String?
key String // theme, language, notifications_enabled, sidebar_collapsed, etc.
value String // JSON or plain value
createdAt BigInt
updatedAt BigInt?
@@unique([userId, tenantId, key])
@@index([userId])
@@index([tenantId])
}
model FeatureFlag {
id String @id @default(cuid())
tenantId String?
name String // dark_mode, beta_features, new_editor, etc.
enabled Boolean @default(false)
description String?
rules String? // JSON: targeting rules (user roles, percentages, etc.)
createdAt BigInt
updatedAt BigInt?
@@unique([tenantId, name])
@@index([tenantId])
@@index([enabled])
}
// =============================================================================
// COMPONENTS & UI
// =============================================================================
model ComponentNode {
id String @id
type String
parentId String?
childIds String // JSON: string[]
order Int
pageId String
@@index([pageId])
@@index([parentId])
}
model ComponentConfig {
id String @id
componentId String
props String // JSON
styles String // JSON
events String // JSON
conditionalRendering String? // JSON
@@index([componentId])
}
model CssCategory {
id String @id @default(cuid())
name String @unique
classes String // JSON: string[]
}
model DropdownConfig {
id String @id
name String @unique
label String
options String // JSON: Array<{value, label}>
}
// =============================================================================
// PACKAGES
// =============================================================================
model InstalledPackage {
packageId String @id
tenantId String?
installedAt BigInt
version String
enabled Boolean
config String? // JSON: package-specific configuration
@@index([tenantId])
}
model PackageData {
packageId String @id
data String // JSON
}
// =============================================================================
// CONTENT: COMMENTS
// =============================================================================
model Comment {
id String @id
tenantId String?
userId String
content String
createdAt BigInt
updatedAt BigInt?
parentId String?
entityType String? // What this comment is on: 'post', 'thread', 'media', etc.
entityId String? // ID of the entity being commented on
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([userId])
@@index([parentId])
@@index([entityType, entityId])
}
// =============================================================================
// CONTENT: FORUM
// =============================================================================
model ForumCategory {
id String @id @default(cuid())
tenantId String
name String
description String?
icon String?
slug String
sortOrder Int @default(0)
parentId String? // For nested categories
createdAt BigInt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
threads ForumThread[]
@@unique([tenantId, slug])
@@index([tenantId])
@@index([parentId])
}
model ForumThread {
id String @id @default(cuid())
tenantId String
categoryId String
authorId String
title String
content String
slug String
isPinned Boolean @default(false)
isLocked Boolean @default(false)
viewCount Int @default(0)
replyCount Int @default(0)
lastReplyAt BigInt?
lastReplyBy String?
createdAt BigInt
updatedAt BigInt?
category ForumCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
posts ForumPost[]
@@unique([tenantId, slug])
@@index([tenantId])
@@index([categoryId])
@@index([authorId])
@@index([isPinned, lastReplyAt])
}
model ForumPost {
id String @id @default(cuid())
tenantId String
threadId String
authorId String
content String
likes Int @default(0)
isEdited Boolean @default(false)
createdAt BigInt
updatedAt BigInt?
thread ForumThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([threadId])
@@index([authorId])
}
// =============================================================================
// MEDIA
// =============================================================================
model MediaAsset {
id String @id @default(cuid())
tenantId String
userId String
filename String
originalName String
mimeType String
size BigInt
path String
thumbnailPath String?
width Int?
height Int?
duration Int? // For video/audio in seconds
metadata String? // JSON: EXIF, tags, etc.
createdAt BigInt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([userId])
@@index([mimeType])
}
model MediaJob {
id String @id @default(cuid())
tenantId String
userId String
type String // transcode, thumbnail, convert, etc.
status String // pending, processing, completed, failed
priority Int @default(0)
inputPath String
outputPath String?
params String // JSON: job-specific parameters
progress Int @default(0)
error String?
startedAt BigInt?
completedAt BigInt?
createdAt BigInt
@@index([tenantId])
@@index([status, priority])
@@index([userId])
}
// =============================================================================
// NOTIFICATIONS
// =============================================================================
model Notification {
id String @id @default(cuid())
tenantId String
userId String
type String // info, warning, success, error, mention, reply, etc.
title String
message String
icon String?
read Boolean @default(false)
data String? // JSON: action URLs, entity references, etc.
createdAt BigInt
expiresAt BigInt?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([userId, read])
@@index([createdAt])
}
// =============================================================================
// AUDIT & LOGGING
// =============================================================================
model AuditLog {
id String @id @default(cuid())
tenantId String
userId String?
username String?
action String // create, update, delete, login, logout, etc.
entity String // User, Workflow, Page, etc.
entityId String?
oldValue String? // JSON: previous state
newValue String? // JSON: new state
ipAddress String?
userAgent String?
details String? // JSON: additional context
timestamp BigInt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([tenantId])
@@index([userId])
@@index([entity, entityId])
@@index([action])
@@index([timestamp])
}
model ErrorLog {
id String @id @default(cuid())
timestamp BigInt
level String // error, warning, info
message String
stack String?
context String? // JSON: additional context
userId String?
username String?
tenantId String?
source String? // Component/file where error occurred
resolved Boolean @default(false)
resolvedAt BigInt?
resolvedBy String?
@@index([tenantId])
@@index([level])
@@index([timestamp])
@@index([resolved])
}
// =============================================================================
// AUTH TOKENS & SECURITY
// =============================================================================
model PasswordResetToken {
username String @id
token String
expiresAt BigInt?
createdAt BigInt?
}
model PowerTransferRequest {
id String @id
fromUserId String
toUserId String
status String // pending, accepted, rejected
createdAt BigInt
expiresAt BigInt
fromUser User @relation("PowerTransferFrom", fields: [fromUserId], references: [id], onDelete: Cascade)
toUser User @relation("PowerTransferTo", fields: [toUserId], references: [id], onDelete: Cascade)
@@index([status])
@@index([expiresAt])
}
// =============================================================================
// GENERIC ENTITIES FOR PACKAGES
// =============================================================================
// Tags - Attach labels to any entity (posts, media, threads, etc.)
model Tag {
id String @id @default(cuid())
tenantId String
name String
slug String
color String? // Hex color for UI
icon String?
category String? // Group tags: 'topic', 'status', 'priority', etc.
createdAt BigInt
@@unique([tenantId, slug])
@@index([tenantId])
@@index([category])
}
// Tagging - Many-to-many junction for tags on any entity
model Tagging {
id String @id @default(cuid())
tenantId String
tagId String
entityType String // 'post', 'media', 'thread', 'user', etc.
entityId String
createdAt BigInt
createdBy String?
@@unique([tagId, entityType, entityId])
@@index([tenantId])
@@index([tagId])
@@index([entityType, entityId])
}
// Relationships - Generic many-to-many between any entities
model EntityRelation {
id String @id @default(cuid())
tenantId String
sourceType String // 'user', 'post', 'product', etc.
sourceId String
targetType String
targetId String
relationType String // 'follows', 'blocks', 'related_to', 'parent_of', etc.
metadata String? // JSON: additional relation data
createdAt BigInt
createdBy String?
@@unique([sourceType, sourceId, targetType, targetId, relationType])
@@index([tenantId])
@@index([sourceType, sourceId])
@@index([targetType, targetId])
@@index([relationType])
}
// Generic Task Queue - Background jobs for any package
model Task {
id String @id @default(cuid())
tenantId String
queue String // 'email', 'export', 'import', 'cleanup', etc.
type String // Specific task type within queue
status String @default("pending") // pending, running, completed, failed, cancelled
priority Int @default(0)
payload String // JSON: task input data
result String? // JSON: task output/result
error String?
attempts Int @default(0)
maxAttempts Int @default(3)
runAt BigInt? // Scheduled execution time
startedAt BigInt?
completedAt BigInt?
createdAt BigInt
createdBy String?
@@index([tenantId])
@@index([queue, status])
@@index([status, priority])
@@index([runAt])
}
// Webhooks - Outbound event notifications
model Webhook {
id String @id @default(cuid())
tenantId String
name String
url String
secret String? // For HMAC signing
events String // JSON: array of event types to trigger on
headers String? // JSON: custom headers to include
enabled Boolean @default(true)
lastTriggered BigInt?
failCount Int @default(0)
createdAt BigInt
updatedAt BigInt?
@@index([tenantId])
@@index([enabled])
}
// Webhook Deliveries - Log of webhook attempts
model WebhookDelivery {
id String @id @default(cuid())
webhookId String
event String
payload String // JSON: what was sent
response String? // JSON: response received
statusCode Int?
success Boolean
duration Int? // ms
error String?
createdAt BigInt
@@index([webhookId])
@@index([createdAt])
@@index([success])
}
// Attachments - Generic file attachments to any entity
model Attachment {
id String @id @default(cuid())
tenantId String
entityType String // 'comment', 'post', 'message', etc.
entityId String
filename String
originalName String
mimeType String
size BigInt
path String
metadata String? // JSON: dimensions, duration, etc.
sortOrder Int @default(0)
createdAt BigInt
createdBy String?
@@index([tenantId])
@@index([entityType, entityId])
}
// Reactions - Likes, emoji reactions on any entity
model Reaction {
id String @id @default(cuid())
tenantId String
userId String
entityType String // 'post', 'comment', 'media', etc.
entityId String
type String // 'like', 'love', 'laugh', 'wow', 'sad', 'angry', '+1', etc.
createdAt BigInt
@@unique([userId, entityType, entityId, type])
@@index([tenantId])
@@index([entityType, entityId])
@@index([userId])
}
// Bookmarks/Favorites - Users saving any entity
model Bookmark {
id String @id @default(cuid())
tenantId String
userId String
entityType String // 'post', 'thread', 'media', 'product', etc.
entityId String
folder String? // Optional folder/collection
note String? // User's note about the bookmark
createdAt BigInt
@@unique([userId, entityType, entityId])
@@index([tenantId])
@@index([userId])
@@index([entityType, entityId])
@@index([folder])
}
// Activity Feed - Timeline events
model Activity {
id String @id @default(cuid())
tenantId String
userId String?
action String // 'created', 'updated', 'deleted', 'shared', 'mentioned', etc.
entityType String
entityId String
targetType String? // Secondary entity (e.g., 'user' mentioned in 'post')
targetId String?
metadata String? // JSON: extra context
isPublic Boolean @default(true)
createdAt BigInt
@@index([tenantId])
@@index([userId])
@@index([entityType, entityId])
@@index([createdAt])
@@index([isPublic, createdAt])
}
// Counters - Denormalized counts for any entity
model Counter {
id String @id @default(cuid())
tenantId String
entityType String
entityId String
name String // 'views', 'likes', 'shares', 'downloads', etc.
value BigInt @default(0)
updatedAt BigInt
@@unique([entityType, entityId, name])
@@index([tenantId])
@@index([entityType, entityId])
}
// Versions - Version history for any entity
model Version {
id String @id @default(cuid())
tenantId String
entityType String
entityId String
version Int
data String // JSON: snapshot of entity state
changes String? // JSON: diff from previous version
message String? // Commit message
createdAt BigInt
createdBy String?
@@unique([entityType, entityId, version])
@@index([tenantId])
@@index([entityType, entityId])
@@index([createdAt])
}
// Templates - Reusable content templates
model Template {
id String @id @default(cuid())
tenantId String
name String
description String?
category String // 'email', 'page', 'component', 'workflow', etc.
content String // JSON or template string
variables String? // JSON: list of variable placeholders
isPublic Boolean @default(false)
createdAt BigInt
updatedAt BigInt?
createdBy String?
@@unique([tenantId, category, name])
@@index([tenantId])
@@index([category])
@@index([isPublic])
}
// Scheduled Jobs - Cron-like recurring tasks
model ScheduledJob {
id String @id @default(cuid())
tenantId String
name String
description String?
schedule String // Cron expression: "0 0 * * *"
taskType String // What to run
taskPayload String // JSON: parameters
enabled Boolean @default(true)
lastRunAt BigInt?
nextRunAt BigInt?
lastStatus String? // success, failed
lastError String?
runCount Int @default(0)
createdAt BigInt
updatedAt BigInt?
@@index([tenantId])
@@index([enabled, nextRunAt])
}
// Locks - Distributed locking for concurrency control
model Lock {
id String @id @default(cuid())
tenantId String?
resource String // What's being locked
owner String // Who holds the lock (session ID, worker ID, etc.)
expiresAt BigInt
acquiredAt BigInt
metadata String? // JSON: additional context
@@unique([tenantId, resource])
@@index([expiresAt])
}
// Messages - Generic messaging/inbox system
model Message {
id String @id @default(cuid())
tenantId String
senderId String?
recipientId String
threadId String? // For threaded conversations
subject String?
body String
isRead Boolean @default(false)
isArchived Boolean @default(false)
isDeleted Boolean @default(false)
metadata String? // JSON: attachments, flags, etc.
createdAt BigInt
readAt BigInt?
@@index([tenantId])
@@index([recipientId, isRead])
@@index([senderId])
@@index([threadId])
@@index([createdAt])
}
// Invitations - Generic invite system
model Invitation {
id String @id @default(cuid())
tenantId String
type String // 'team', 'project', 'event', etc.
targetId String? // What they're being invited to
email String
token String @unique
role String? // Role to assign on acceptance
invitedBy String
status String @default("pending") // pending, accepted, declined, expired
expiresAt BigInt
acceptedAt BigInt?
createdAt BigInt
@@index([tenantId])
@@index([email])
@@index([token])
@@index([status])
}