mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat(dbal): generate Prisma schema for email models
- Updated gen_prisma_schema.js to parse YAML entity schemas - Generated Prisma models for EmailClient, EmailFolder, EmailMessage, EmailAttachment - All email models include proper indexes and constraints from YAML definitions - Schema generation now supports dynamic YAML parsing with fallback to core models
This commit is contained in:
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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}` : ''
|
||||
|
||||
Reference in New Issue
Block a user