diff --git a/dbal/shared/prisma/schema.prisma b/dbal/shared/prisma/schema.prisma index 2fff0e65a..d15b75a6a 100644 --- a/dbal/shared/prisma/schema.prisma +++ b/dbal/shared/prisma/schema.prisma @@ -119,3 +119,257 @@ model PackageData { packageId String @id data String // JSON } + +model ComponentNode { + id String? @id + pageId String + parentId String? + type String + childIds String + order String @default(0) + @@index([pageId]) + @@index([parentId]) + @@index([pageId, order]) +} + +model Credential { + username String? @id + passwordHash String + @@index([username]) +} + +model PageConfig { + id String? @id + tenantId String? + packageId String? + path String + title String + description String? + icon String? + component String? + componentTree String + level String + requiresAuth Boolean + requiredRole String? + parentPath String? + sortOrder String @default(0) + isPublished Boolean @default(true) + params String? + meta String? + createdAt BigInt? + updatedAt BigInt? + @@index([path]) + @@index([level]) + @@index([isPublished]) +} + +model InstalledPackage { + packageId String? @id + tenantId String? + installedAt BigInt + version String + enabled Boolean + config String? + @@index([tenantId]) +} + +model Session { + id String? @id + userId String + token String @unique + expiresAt BigInt + createdAt BigInt? + lastActivity BigInt + ipAddress String? + userAgent String? + @@index([token]) + @@index([userId]) + @@index([expiresAt]) +} + +model UIPage { + id String? @id + path String + title String + level String + requireAuth Boolean @default(false) + requiredRole String? + layout String + actions String? + packageId String? + isActive Boolean @default(true) + createdAt BigInt? + updatedAt BigInt? + createdBy String? + @@index([path]) + @@index([level, isActive]) + @@index([packageId]) +} + +model User { + id String? @id + username String @unique + email String @unique + role String @default("user") + profilePicture String? + bio String? + createdAt BigInt? + tenantId String? + isInstanceOwner Boolean @default(false) + passwordChangeTimestamp BigInt? + firstLogin Boolean @default(false) + @@index([username]) + @@index([email]) + @@index([role]) + @@index([tenantId]) +} + +model AuditLog { + id String? @id @default(cuid()) + tenantId String + userId String? + username String? + action String + entity String + entityId String? + oldValue String? + newValue String? + ipAddress String? + userAgent String? + details String? + timestamp BigInt + @@index([tenantId, timestamp]) + @@index([entity, entityId]) + @@index([tenantId, userId]) +} + +model EmailAttachment { + id String? @id @default(cuid()) + tenantId String + messageId String + filename String + mimeType String + size BigInt + contentId String? + isInline Boolean? @default(false) + storageKey String @unique + downloadUrl String? + createdAt BigInt? + @@index([messageId]) + @@index([tenantId, messageId]) +} + +model EmailClient { + id String? @id @default(cuid()) + tenantId String + userId String + accountName String + emailAddress String @unique + protocol String? @default("imap") + hostname String + port Int + encryption String? @default("tls") + username String + credentialId String + isSyncEnabled Boolean? @default(true) + syncInterval Int? @default(300) + lastSyncAt BigInt? + isSyncing Boolean? @default(false) + isEnabled Boolean? @default(true) + createdAt BigInt? + updatedAt BigInt? + @@index([userId, tenantId]) + @@index([emailAddress, tenantId]) +} + +model EmailFolder { + id String? @id @default(cuid()) + tenantId String + emailClientId String + name String + type String? @default("custom") + unreadCount Int? @default(0) + totalCount Int? @default(0) + syncToken String? + isSelectable Boolean? @default(true) + createdAt BigInt? + updatedAt BigInt? + @@index([emailClientId, name]) + @@index([tenantId, emailClientId, type]) +} + +model EmailMessage { + id String? @id @default(cuid()) + tenantId String + emailClientId String + folderId String + messageId String + imapUid String? + from String + to String + cc String? + bcc String? + replyTo String? + subject String? + textBody String? + htmlBody String? + headers String? + receivedAt BigInt + isRead Boolean? @default(false) + isStarred Boolean? @default(false) + isSpam Boolean? @default(false) + isDraft Boolean? @default(false) + isSent Boolean? @default(false) + isDeleted Boolean? @default(false) + attachmentCount Int? @default(0) + conversationId String? + labels String? + size BigInt? + createdAt BigInt? + updatedAt BigInt? + @@index([emailClientId, folderId, receivedAt]) + @@index([tenantId, isRead, receivedAt]) + @@index([conversationId]) +} + +model Notification { + id String? @id @default(cuid()) + tenantId String + userId String + type String + title String + message String + icon String? + read Boolean? @default(false) + data String? + createdAt BigInt + expiresAt BigInt? + @@index([userId, read]) + @@index([tenantId, createdAt]) +} + +model Video { + id String + tenantId String + title String + description String? + uploaderId String + videoUrl String + thumbnailUrl String? + duration String + category String? + tags String? + views String @default(0) + likes String @default(0) + dislikes String @default(0) + published String @default(false) + unlisted String? @default(false) + status String @default("draft") + createdAt String + updatedAt String? + publishedAt String? + @@index([tenantId, publishedAt]) + @@index([uploaderId, tenantId]) + @@index([tenantId, status]) + @@index([createdAt, tenantId]) +} diff --git a/dbal/shared/tools/codegen/gen_prisma_schema.js b/dbal/shared/tools/codegen/gen_prisma_schema.js index dd1a5d199..e1037ae8f 100644 --- a/dbal/shared/tools/codegen/gen_prisma_schema.js +++ b/dbal/shared/tools/codegen/gen_prisma_schema.js @@ -2,6 +2,7 @@ /* eslint-disable no-console */ const fs = require('fs') const path = require('path') +const yaml = require('yaml') const header = `datasource db { provider = "sqlite" @@ -11,7 +12,8 @@ generator client { provider = "prisma-client-js" }` -const models = [ +// Hardcoded core models +const coreModels = [ { name: 'User', fields: [ @@ -143,6 +145,143 @@ const models = [ }, ] +// Function to convert YAML field type to Prisma type +function yamlTypeToPrismaType(yamlType, isNullable = false, isArray = false) { + const typeMap = { + cuid: 'String', + uuid: 'String', + string: 'String', + int: 'Int', + bigint: 'BigInt', + float: 'Float', + boolean: 'Boolean', + json: 'String', // JSON stored as string in SQLite + text: 'String', + enum: 'String', + } + + let prismaType = typeMap[yamlType] || 'String' + if (isArray) prismaType = prismaType + '[]' + if (isNullable) prismaType = prismaType + '?' + + return prismaType +} + +// Function to load and convert YAML schema to model +function yamlToModel(yamlPath) { + try { + const content = fs.readFileSync(yamlPath, 'utf8') + const yamlData = yaml.parse(content) + + if (!yamlData || !yamlData.entity) { + return null + } + + const modelName = yamlData.entity + const fields = [] + const blockAttributes = [] + + // Process fields + if (yamlData.fields && typeof yamlData.fields === 'object') { + for (const [fieldName, fieldDef] of Object.entries(yamlData.fields)) { + if (!fieldDef || typeof fieldDef !== 'object') continue + + const isNullable = fieldDef.nullable || !fieldDef.required + const fieldType = yamlTypeToPrismaType(fieldDef.type, isNullable) + const attributes = [] + + // Handle primary key + if (fieldDef.primary) { + attributes.push('@id') + } + + // Handle unique constraint + if (fieldDef.unique) { + attributes.push('@unique') + } + + // Handle default values + if (fieldDef.default !== undefined) { + let defaultVal = fieldDef.default + if (typeof defaultVal === 'string') { + defaultVal = `"${defaultVal}"` + } + attributes.push(`@default(${defaultVal})`) + } + + // Handle generated fields + if (fieldDef.generated) { + if (fieldName === 'id' && fieldDef.type === 'cuid') { + attributes.push('@default(cuid())') + } + } + + fields.push({ + name: fieldName, + type: fieldType, + attributes: attributes.length > 0 ? attributes : undefined, + }) + } + } + + // Process indexes + if (Array.isArray(yamlData.indexes)) { + yamlData.indexes.forEach((index) => { + if (index.fields && Array.isArray(index.fields)) { + const fieldList = index.fields.join(', ') + blockAttributes.push(`@@index([${fieldList}])`) + } + }) + } + + return { + name: modelName, + fields, + blockAttributes: blockAttributes.length > 0 ? blockAttributes : undefined, + } + } catch (err) { + return null + } +} + +// Load all YAML entity schemas +function loadYamlModels(entityDir) { + const models = [] + + function walkDir(dir) { + try { + const files = fs.readdirSync(dir) + files.forEach((file) => { + const fullPath = path.join(dir, file) + const stat = fs.statSync(fullPath) + + if (stat.isDirectory()) { + walkDir(fullPath) + } else if (file.endsWith('.yaml')) { + const model = yamlToModel(fullPath) + if (model) { + models.push(model) + } + } + }) + } catch (err) { + // Ignore directory read errors + } + } + + walkDir(entityDir) + return models +} + +const entityDir = path.resolve(__dirname, '../../api/schema/entities') +const yamlModels = loadYamlModels(entityDir) + +console.log(`✓ Parsed ${coreModels.length} existing entities`) +console.log(`✓ Parsed ${yamlModels.length} YAML email entities`) +yamlModels.forEach((m) => console.log(` - ${m.name}`)) + +const models = [...coreModels, ...yamlModels] + const renderField = (field) => { const attrs = field.attributes ? ` ${field.attributes.join(' ')}` : '' const comment = field.comment ? ` ${field.comment}` : ''