mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
code: packages,operations,lua (4 files)
This commit is contained in:
358
packages/css_designer/seed/scripts/db/operations.lua
Normal file
358
packages/css_designer/seed/scripts/db/operations.lua
Normal file
@@ -0,0 +1,358 @@
|
||||
-- css_designer/seed/scripts/db/operations.lua
|
||||
-- DBAL operations for Theme and Style entities
|
||||
-- @module css_designer.db.operations
|
||||
|
||||
local M = {}
|
||||
local json = require('json')
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- THEME OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class ThemeCreateParams
|
||||
---@field tenantId string
|
||||
---@field name string
|
||||
---@field baseTheme string|nil ('light', 'dark', or parent theme ID)
|
||||
---@field variables table CSS variable definitions
|
||||
---@field metadata table|nil
|
||||
|
||||
---Create a new theme
|
||||
---@param dbal table DBAL client instance
|
||||
---@param params ThemeCreateParams
|
||||
---@return table Created theme
|
||||
function M.createTheme(dbal, params)
|
||||
return dbal:create('Theme', {
|
||||
tenantId = params.tenantId,
|
||||
name = params.name,
|
||||
baseTheme = params.baseTheme or 'light',
|
||||
variables = json.encode(params.variables),
|
||||
metadata = params.metadata and json.encode(params.metadata) or nil,
|
||||
isActive = true,
|
||||
version = 1,
|
||||
createdAt = os.time() * 1000,
|
||||
updatedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Get theme by ID
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@return table|nil Theme with decoded variables
|
||||
function M.getTheme(dbal, themeId)
|
||||
local theme = dbal:read('Theme', themeId)
|
||||
if theme then
|
||||
theme.variables = json.decode(theme.variables or '{}')
|
||||
if theme.metadata then
|
||||
theme.metadata = json.decode(theme.metadata)
|
||||
end
|
||||
end
|
||||
return theme
|
||||
end
|
||||
|
||||
---Get theme by name
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param name string
|
||||
---@return table|nil Theme
|
||||
function M.getThemeByName(dbal, tenantId, name)
|
||||
local theme = dbal:findFirst('Theme', {
|
||||
where = { tenantId = tenantId, name = name },
|
||||
})
|
||||
if theme then
|
||||
theme.variables = json.decode(theme.variables or '{}')
|
||||
end
|
||||
return theme
|
||||
end
|
||||
|
||||
---List themes for tenant
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param activeOnly boolean|nil
|
||||
---@return table[] Themes
|
||||
function M.listThemes(dbal, tenantId, activeOnly)
|
||||
local where = { tenantId = tenantId }
|
||||
if activeOnly then
|
||||
where.isActive = true
|
||||
end
|
||||
|
||||
local result = dbal:list('Theme', {
|
||||
where = where,
|
||||
orderBy = { name = 'asc' },
|
||||
take = 50,
|
||||
})
|
||||
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Update theme
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@param updates table
|
||||
---@return table Updated theme
|
||||
function M.updateTheme(dbal, themeId, updates)
|
||||
if updates.variables and type(updates.variables) == 'table' then
|
||||
updates.variables = json.encode(updates.variables)
|
||||
-- Increment version on variable changes
|
||||
local theme = M.getTheme(dbal, themeId)
|
||||
if theme then
|
||||
updates.version = (theme.version or 1) + 1
|
||||
end
|
||||
end
|
||||
if updates.metadata and type(updates.metadata) == 'table' then
|
||||
updates.metadata = json.encode(updates.metadata)
|
||||
end
|
||||
updates.updatedAt = os.time() * 1000
|
||||
return dbal:update('Theme', themeId, updates)
|
||||
end
|
||||
|
||||
---Set theme variable
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@param varName string CSS variable name
|
||||
---@param value string Value
|
||||
function M.setVariable(dbal, themeId, varName, value)
|
||||
local theme = M.getTheme(dbal, themeId)
|
||||
if not theme then
|
||||
error('Theme not found: ' .. themeId)
|
||||
end
|
||||
|
||||
local vars = theme.variables or {}
|
||||
vars[varName] = value
|
||||
|
||||
return M.updateTheme(dbal, themeId, { variables = vars })
|
||||
end
|
||||
|
||||
---Clone theme
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@param newName string
|
||||
---@return table Cloned theme
|
||||
function M.cloneTheme(dbal, themeId, newName)
|
||||
local original = M.getTheme(dbal, themeId)
|
||||
if not original then
|
||||
error('Theme not found: ' .. themeId)
|
||||
end
|
||||
|
||||
return M.createTheme(dbal, {
|
||||
tenantId = original.tenantId,
|
||||
name = newName,
|
||||
baseTheme = original.baseTheme,
|
||||
variables = original.variables,
|
||||
metadata = original.metadata,
|
||||
})
|
||||
end
|
||||
|
||||
---Delete theme
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
function M.deleteTheme(dbal, themeId)
|
||||
return dbal:delete('Theme', themeId)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- STYLE COMPONENT OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class StyleComponentParams
|
||||
---@field tenantId string
|
||||
---@field themeId string|nil
|
||||
---@field name string
|
||||
---@field selector string CSS selector
|
||||
---@field styles table CSS properties
|
||||
---@field variants table|nil Style variants
|
||||
|
||||
---Create style component
|
||||
---@param dbal table
|
||||
---@param params StyleComponentParams
|
||||
---@return table Created component
|
||||
function M.createComponent(dbal, params)
|
||||
return dbal:create('StyleComponent', {
|
||||
tenantId = params.tenantId,
|
||||
themeId = params.themeId,
|
||||
name = params.name,
|
||||
selector = params.selector,
|
||||
styles = json.encode(params.styles),
|
||||
variants = params.variants and json.encode(params.variants) or nil,
|
||||
createdAt = os.time() * 1000,
|
||||
updatedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Get component by ID
|
||||
---@param dbal table
|
||||
---@param componentId string
|
||||
---@return table|nil Component
|
||||
function M.getComponent(dbal, componentId)
|
||||
local comp = dbal:read('StyleComponent', componentId)
|
||||
if comp then
|
||||
comp.styles = json.decode(comp.styles or '{}')
|
||||
if comp.variants then
|
||||
comp.variants = json.decode(comp.variants)
|
||||
end
|
||||
end
|
||||
return comp
|
||||
end
|
||||
|
||||
---List components for theme
|
||||
---@param dbal table
|
||||
---@param themeId string|nil
|
||||
---@param tenantId string
|
||||
---@return table[] Components
|
||||
function M.listComponents(dbal, tenantId, themeId)
|
||||
local where = { tenantId = tenantId }
|
||||
if themeId then
|
||||
where.themeId = themeId
|
||||
end
|
||||
|
||||
local result = dbal:list('StyleComponent', {
|
||||
where = where,
|
||||
orderBy = { name = 'asc' },
|
||||
take = 200,
|
||||
})
|
||||
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Update component styles
|
||||
---@param dbal table
|
||||
---@param componentId string
|
||||
---@param styles table CSS properties
|
||||
function M.updateStyles(dbal, componentId, styles)
|
||||
return dbal:update('StyleComponent', componentId, {
|
||||
styles = json.encode(styles),
|
||||
updatedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Delete component
|
||||
---@param dbal table
|
||||
---@param componentId string
|
||||
function M.deleteComponent(dbal, componentId)
|
||||
return dbal:delete('StyleComponent', componentId)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- CSS GENERATION
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---Generate CSS variables from theme
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@return string CSS custom properties
|
||||
function M.generateVariablesCSS(dbal, themeId)
|
||||
local theme = M.getTheme(dbal, themeId)
|
||||
if not theme then
|
||||
return ''
|
||||
end
|
||||
|
||||
local css = ':root {\n'
|
||||
for name, value in pairs(theme.variables or {}) do
|
||||
css = css .. ' --' .. name .. ': ' .. value .. ';\n'
|
||||
end
|
||||
css = css .. '}\n'
|
||||
|
||||
return css
|
||||
end
|
||||
|
||||
---Generate CSS for all components
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param themeId string|nil
|
||||
---@return string Generated CSS
|
||||
function M.generateComponentsCSS(dbal, tenantId, themeId)
|
||||
local components = M.listComponents(dbal, tenantId, themeId)
|
||||
local css = ''
|
||||
|
||||
for _, comp in ipairs(components) do
|
||||
local styles = json.decode(comp.styles or '{}')
|
||||
css = css .. comp.selector .. ' {\n'
|
||||
for prop, value in pairs(styles) do
|
||||
css = css .. ' ' .. prop .. ': ' .. value .. ';\n'
|
||||
end
|
||||
css = css .. '}\n\n'
|
||||
|
||||
-- Add variants if present
|
||||
local variants = comp.variants and json.decode(comp.variants) or nil
|
||||
if variants then
|
||||
for variant, varStyles in pairs(variants) do
|
||||
css = css .. comp.selector .. ':' .. variant .. ' {\n'
|
||||
for prop, value in pairs(varStyles) do
|
||||
css = css .. ' ' .. prop .. ': ' .. value .. ';\n'
|
||||
end
|
||||
css = css .. '}\n\n'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return css
|
||||
end
|
||||
|
||||
---Generate complete CSS bundle
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param themeId string
|
||||
---@return string Complete CSS
|
||||
function M.generateFullCSS(dbal, tenantId, themeId)
|
||||
local css = '/* Generated Theme CSS */\n\n'
|
||||
css = css .. M.generateVariablesCSS(dbal, themeId)
|
||||
css = css .. '\n/* Component Styles */\n\n'
|
||||
css = css .. M.generateComponentsCSS(dbal, tenantId, themeId)
|
||||
return css
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- PRESET MANAGEMENT
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---Save theme as preset
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@param presetName string
|
||||
---@param description string|nil
|
||||
function M.saveAsPreset(dbal, themeId, presetName, description)
|
||||
local theme = M.getTheme(dbal, themeId)
|
||||
if not theme then
|
||||
error('Theme not found: ' .. themeId)
|
||||
end
|
||||
|
||||
return dbal:create('ThemePreset', {
|
||||
tenantId = theme.tenantId,
|
||||
name = presetName,
|
||||
description = description,
|
||||
variables = json.encode(theme.variables),
|
||||
baseTheme = theme.baseTheme,
|
||||
createdAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---List available presets
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@return table[] Presets
|
||||
function M.listPresets(dbal, tenantId)
|
||||
local result = dbal:list('ThemePreset', {
|
||||
where = { tenantId = tenantId },
|
||||
orderBy = { name = 'asc' },
|
||||
take = 50,
|
||||
})
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Apply preset to theme
|
||||
---@param dbal table
|
||||
---@param themeId string
|
||||
---@param presetId string
|
||||
function M.applyPreset(dbal, themeId, presetId)
|
||||
local preset = dbal:read('ThemePreset', presetId)
|
||||
if not preset then
|
||||
error('Preset not found: ' .. presetId)
|
||||
end
|
||||
|
||||
local variables = json.decode(preset.variables or '{}')
|
||||
return M.updateTheme(dbal, themeId, {
|
||||
variables = variables,
|
||||
baseTheme = preset.baseTheme,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
368
packages/form_builder/seed/scripts/db/operations.lua
Normal file
368
packages/form_builder/seed/scripts/db/operations.lua
Normal file
@@ -0,0 +1,368 @@
|
||||
-- form_builder/seed/scripts/db/operations.lua
|
||||
-- DBAL operations for Form and Submission entities
|
||||
-- @module form_builder.db.operations
|
||||
|
||||
local M = {}
|
||||
local json = require('json')
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- FORM OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class FormCreateParams
|
||||
---@field tenantId string
|
||||
---@field name string
|
||||
---@field description string|nil
|
||||
---@field fields table[] Form field definitions
|
||||
---@field settings table|nil Form settings
|
||||
---@field validationRules table|nil Validation rules
|
||||
|
||||
---Create a new form
|
||||
---@param dbal table DBAL client instance
|
||||
---@param params FormCreateParams
|
||||
---@return table Created form
|
||||
function M.createForm(dbal, params)
|
||||
return dbal:create('Form', {
|
||||
tenantId = params.tenantId,
|
||||
name = params.name,
|
||||
description = params.description,
|
||||
fields = json.encode(params.fields),
|
||||
settings = params.settings and json.encode(params.settings) or '{}',
|
||||
validationRules = params.validationRules and json.encode(params.validationRules) or nil,
|
||||
isActive = true,
|
||||
version = 1,
|
||||
submissionCount = 0,
|
||||
createdAt = os.time() * 1000,
|
||||
updatedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Get form by ID
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@return table|nil Form with decoded fields
|
||||
function M.getForm(dbal, formId)
|
||||
local form = dbal:read('Form', formId)
|
||||
if form then
|
||||
form.fields = json.decode(form.fields or '[]')
|
||||
form.settings = json.decode(form.settings or '{}')
|
||||
if form.validationRules then
|
||||
form.validationRules = json.decode(form.validationRules)
|
||||
end
|
||||
end
|
||||
return form
|
||||
end
|
||||
|
||||
---List forms for tenant
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param activeOnly boolean|nil
|
||||
---@return table[] Forms
|
||||
function M.listForms(dbal, tenantId, activeOnly)
|
||||
local where = { tenantId = tenantId }
|
||||
if activeOnly then
|
||||
where.isActive = true
|
||||
end
|
||||
|
||||
local result = dbal:list('Form', {
|
||||
where = where,
|
||||
orderBy = { createdAt = 'desc' },
|
||||
take = 100,
|
||||
})
|
||||
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Update form definition
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param updates table
|
||||
---@return table Updated form
|
||||
function M.updateForm(dbal, formId, updates)
|
||||
-- Encode complex fields
|
||||
if updates.fields and type(updates.fields) == 'table' then
|
||||
updates.fields = json.encode(updates.fields)
|
||||
end
|
||||
if updates.settings and type(updates.settings) == 'table' then
|
||||
updates.settings = json.encode(updates.settings)
|
||||
end
|
||||
if updates.validationRules and type(updates.validationRules) == 'table' then
|
||||
updates.validationRules = json.encode(updates.validationRules)
|
||||
end
|
||||
|
||||
-- Increment version on field changes
|
||||
if updates.fields then
|
||||
local form = M.getForm(dbal, formId)
|
||||
if form then
|
||||
updates.version = (form.version or 1) + 1
|
||||
end
|
||||
end
|
||||
|
||||
updates.updatedAt = os.time() * 1000
|
||||
return dbal:update('Form', formId, updates)
|
||||
end
|
||||
|
||||
---Clone form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param newName string
|
||||
---@return table Cloned form
|
||||
function M.cloneForm(dbal, formId, newName)
|
||||
local original = M.getForm(dbal, formId)
|
||||
if not original then
|
||||
error('Form not found: ' .. formId)
|
||||
end
|
||||
|
||||
return M.createForm(dbal, {
|
||||
tenantId = original.tenantId,
|
||||
name = newName,
|
||||
description = original.description,
|
||||
fields = original.fields, -- Already decoded
|
||||
settings = original.settings, -- Already decoded
|
||||
validationRules = original.validationRules,
|
||||
})
|
||||
end
|
||||
|
||||
---Delete form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@return boolean
|
||||
function M.deleteForm(dbal, formId)
|
||||
return dbal:delete('Form', formId)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- FORM FIELD OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class FormField
|
||||
---@field id string Unique field ID
|
||||
---@field type string Field type (text, select, checkbox, etc.)
|
||||
---@field name string Field name/key
|
||||
---@field label string Display label
|
||||
---@field required boolean
|
||||
---@field options table|nil For select/radio fields
|
||||
---@field validation table|nil Validation config
|
||||
---@field order number Display order
|
||||
|
||||
---Add field to form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param field FormField
|
||||
---@return table Updated form
|
||||
function M.addField(dbal, formId, field)
|
||||
local form = M.getForm(dbal, formId)
|
||||
if not form then
|
||||
error('Form not found: ' .. formId)
|
||||
end
|
||||
|
||||
local fields = form.fields or {}
|
||||
|
||||
-- Generate ID if not provided
|
||||
if not field.id then
|
||||
field.id = 'field_' .. os.time() .. '_' .. math.random(1000, 9999)
|
||||
end
|
||||
|
||||
-- Set order to end if not specified
|
||||
if not field.order then
|
||||
field.order = #fields + 1
|
||||
end
|
||||
|
||||
table.insert(fields, field)
|
||||
|
||||
return M.updateForm(dbal, formId, { fields = fields })
|
||||
end
|
||||
|
||||
---Update field in form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param fieldId string
|
||||
---@param updates table
|
||||
---@return table Updated form
|
||||
function M.updateField(dbal, formId, fieldId, updates)
|
||||
local form = M.getForm(dbal, formId)
|
||||
if not form then
|
||||
error('Form not found: ' .. formId)
|
||||
end
|
||||
|
||||
local fields = form.fields or {}
|
||||
|
||||
for i, field in ipairs(fields) do
|
||||
if field.id == fieldId then
|
||||
for key, value in pairs(updates) do
|
||||
fields[i][key] = value
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return M.updateForm(dbal, formId, { fields = fields })
|
||||
end
|
||||
|
||||
---Remove field from form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param fieldId string
|
||||
---@return table Updated form
|
||||
function M.removeField(dbal, formId, fieldId)
|
||||
local form = M.getForm(dbal, formId)
|
||||
if not form then
|
||||
error('Form not found: ' .. formId)
|
||||
end
|
||||
|
||||
local fields = form.fields or {}
|
||||
local newFields = {}
|
||||
|
||||
for _, field in ipairs(fields) do
|
||||
if field.id ~= fieldId then
|
||||
table.insert(newFields, field)
|
||||
end
|
||||
end
|
||||
|
||||
return M.updateForm(dbal, formId, { fields = newFields })
|
||||
end
|
||||
|
||||
---Reorder fields
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param fieldIds table[] Ordered array of field IDs
|
||||
---@return table Updated form
|
||||
function M.reorderFields(dbal, formId, fieldIds)
|
||||
local form = M.getForm(dbal, formId)
|
||||
if not form then
|
||||
error('Form not found: ' .. formId)
|
||||
end
|
||||
|
||||
local fieldMap = {}
|
||||
for _, field in ipairs(form.fields or {}) do
|
||||
fieldMap[field.id] = field
|
||||
end
|
||||
|
||||
local orderedFields = {}
|
||||
for order, fieldId in ipairs(fieldIds) do
|
||||
local field = fieldMap[fieldId]
|
||||
if field then
|
||||
field.order = order
|
||||
table.insert(orderedFields, field)
|
||||
end
|
||||
end
|
||||
|
||||
return M.updateForm(dbal, formId, { fields = orderedFields })
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- SUBMISSION OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class SubmissionParams
|
||||
---@field tenantId string
|
||||
---@field formId string
|
||||
---@field userId string|nil Submitter user ID
|
||||
---@field data table Form data
|
||||
---@field metadata table|nil Additional metadata
|
||||
|
||||
---Submit form response
|
||||
---@param dbal table
|
||||
---@param params SubmissionParams
|
||||
---@return table Created submission
|
||||
function M.submitForm(dbal, params)
|
||||
-- Increment form submission count
|
||||
local form = dbal:read('Form', params.formId)
|
||||
if form then
|
||||
dbal:update('Form', params.formId, {
|
||||
submissionCount = (form.submissionCount or 0) + 1,
|
||||
})
|
||||
end
|
||||
|
||||
return dbal:create('FormSubmission', {
|
||||
tenantId = params.tenantId,
|
||||
formId = params.formId,
|
||||
userId = params.userId,
|
||||
data = json.encode(params.data),
|
||||
metadata = params.metadata and json.encode(params.metadata) or nil,
|
||||
status = 'submitted',
|
||||
submittedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Get submission by ID
|
||||
---@param dbal table
|
||||
---@param submissionId string
|
||||
---@return table|nil Submission
|
||||
function M.getSubmission(dbal, submissionId)
|
||||
local sub = dbal:read('FormSubmission', submissionId)
|
||||
if sub then
|
||||
sub.data = json.decode(sub.data or '{}')
|
||||
if sub.metadata then
|
||||
sub.metadata = json.decode(sub.metadata)
|
||||
end
|
||||
end
|
||||
return sub
|
||||
end
|
||||
|
||||
---List submissions for a form
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@param options table|nil Filtering options
|
||||
---@return table List result
|
||||
function M.listSubmissions(dbal, formId, options)
|
||||
options = options or {}
|
||||
|
||||
local result = dbal:list('FormSubmission', {
|
||||
where = { formId = formId },
|
||||
orderBy = { submittedAt = 'desc' },
|
||||
take = options.take or 50,
|
||||
skip = options.skip or 0,
|
||||
})
|
||||
|
||||
return {
|
||||
items = result.items or {},
|
||||
total = result.total or 0,
|
||||
}
|
||||
end
|
||||
|
||||
---Update submission status
|
||||
---@param dbal table
|
||||
---@param submissionId string
|
||||
---@param status string (submitted, reviewed, approved, rejected)
|
||||
---@param reviewNotes string|nil
|
||||
function M.updateSubmissionStatus(dbal, submissionId, status, reviewNotes)
|
||||
return dbal:update('FormSubmission', submissionId, {
|
||||
status = status,
|
||||
reviewNotes = reviewNotes,
|
||||
reviewedAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
---Delete submission
|
||||
---@param dbal table
|
||||
---@param submissionId string
|
||||
function M.deleteSubmission(dbal, submissionId)
|
||||
return dbal:delete('FormSubmission', submissionId)
|
||||
end
|
||||
|
||||
---Export submissions as table
|
||||
---@param dbal table
|
||||
---@param formId string
|
||||
---@return table[] Array of submission data objects
|
||||
function M.exportSubmissions(dbal, formId)
|
||||
local result = dbal:list('FormSubmission', {
|
||||
where = { formId = formId },
|
||||
orderBy = { submittedAt = 'asc' },
|
||||
take = 10000,
|
||||
})
|
||||
|
||||
local exports = {}
|
||||
for _, sub in ipairs(result.items or {}) do
|
||||
local data = json.decode(sub.data or '{}')
|
||||
data._submissionId = sub.id
|
||||
data._submittedAt = sub.submittedAt
|
||||
data._userId = sub.userId
|
||||
data._status = sub.status
|
||||
table.insert(exports, data)
|
||||
end
|
||||
|
||||
return exports
|
||||
end
|
||||
|
||||
return M
|
||||
426
packages/schema_editor/seed/scripts/db/operations.lua
Normal file
426
packages/schema_editor/seed/scripts/db/operations.lua
Normal file
@@ -0,0 +1,426 @@
|
||||
-- schema_editor/seed/scripts/db/operations.lua
|
||||
-- DBAL operations for Schema Registry entities
|
||||
-- @module schema_editor.db.operations
|
||||
|
||||
local M = {}
|
||||
local json = require('json')
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- ENTITY SCHEMA OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class EntitySchemaParams
|
||||
---@field tenantId string
|
||||
---@field name string Entity name
|
||||
---@field fields table[] Field definitions
|
||||
---@field indexes table[]|nil Index definitions
|
||||
---@field relations table[]|nil Relation definitions
|
||||
---@field acl table|nil Access control rules
|
||||
|
||||
---Create or update entity schema
|
||||
---@param dbal table DBAL client instance
|
||||
---@param params EntitySchemaParams
|
||||
---@return table Created/updated schema
|
||||
function M.saveEntitySchema(dbal, params)
|
||||
local existing = dbal:findFirst('EntitySchema', {
|
||||
where = { tenantId = params.tenantId, name = params.name },
|
||||
})
|
||||
|
||||
local data = {
|
||||
fields = json.encode(params.fields),
|
||||
indexes = params.indexes and json.encode(params.indexes) or nil,
|
||||
relations = params.relations and json.encode(params.relations) or nil,
|
||||
acl = params.acl and json.encode(params.acl) or nil,
|
||||
updatedAt = os.time() * 1000,
|
||||
}
|
||||
|
||||
if existing then
|
||||
data.version = (existing.version or 1) + 1
|
||||
return dbal:update('EntitySchema', existing.id, data)
|
||||
end
|
||||
|
||||
data.tenantId = params.tenantId
|
||||
data.name = params.name
|
||||
data.version = 1
|
||||
data.createdAt = os.time() * 1000
|
||||
return dbal:create('EntitySchema', data)
|
||||
end
|
||||
|
||||
---Get entity schema by name
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param name string
|
||||
---@return table|nil Schema with decoded fields
|
||||
function M.getEntitySchema(dbal, tenantId, name)
|
||||
local schema = dbal:findFirst('EntitySchema', {
|
||||
where = { tenantId = tenantId, name = name },
|
||||
})
|
||||
|
||||
if schema then
|
||||
schema.fields = json.decode(schema.fields or '[]')
|
||||
schema.indexes = schema.indexes and json.decode(schema.indexes) or {}
|
||||
schema.relations = schema.relations and json.decode(schema.relations) or {}
|
||||
schema.acl = schema.acl and json.decode(schema.acl) or nil
|
||||
end
|
||||
|
||||
return schema
|
||||
end
|
||||
|
||||
---List all entity schemas
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@return table[] Schemas
|
||||
function M.listEntitySchemas(dbal, tenantId)
|
||||
local result = dbal:list('EntitySchema', {
|
||||
where = { tenantId = tenantId },
|
||||
orderBy = { name = 'asc' },
|
||||
take = 200,
|
||||
})
|
||||
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Delete entity schema
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param name string
|
||||
function M.deleteEntitySchema(dbal, tenantId, name)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, name)
|
||||
if schema then
|
||||
return dbal:delete('EntitySchema', schema.id)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- FIELD OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class FieldDefinition
|
||||
---@field name string
|
||||
---@field type string (String, Int, Float, Boolean, DateTime, Json, etc.)
|
||||
---@field required boolean|nil
|
||||
---@field unique boolean|nil
|
||||
---@field default any|nil
|
||||
---@field validation table|nil
|
||||
|
||||
---Add field to entity schema
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param field FieldDefinition
|
||||
---@return table Updated schema
|
||||
function M.addField(dbal, tenantId, entityName, field)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local fields = schema.fields or {}
|
||||
|
||||
-- Check for duplicate field name
|
||||
for _, f in ipairs(fields) do
|
||||
if f.name == field.name then
|
||||
error('Field already exists: ' .. field.name)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(fields, field)
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = fields,
|
||||
indexes = schema.indexes,
|
||||
relations = schema.relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---Update field definition
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param fieldName string
|
||||
---@param updates table
|
||||
---@return table Updated schema
|
||||
function M.updateField(dbal, tenantId, entityName, fieldName, updates)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local fields = schema.fields or {}
|
||||
local found = false
|
||||
|
||||
for i, field in ipairs(fields) do
|
||||
if field.name == fieldName then
|
||||
for key, value in pairs(updates) do
|
||||
fields[i][key] = value
|
||||
end
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
error('Field not found: ' .. fieldName)
|
||||
end
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = fields,
|
||||
indexes = schema.indexes,
|
||||
relations = schema.relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---Remove field from entity
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param fieldName string
|
||||
---@return table Updated schema
|
||||
function M.removeField(dbal, tenantId, entityName, fieldName)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local fields = schema.fields or {}
|
||||
local newFields = {}
|
||||
|
||||
for _, field in ipairs(fields) do
|
||||
if field.name ~= fieldName then
|
||||
table.insert(newFields, field)
|
||||
end
|
||||
end
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = newFields,
|
||||
indexes = schema.indexes,
|
||||
relations = schema.relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- INDEX OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class IndexDefinition
|
||||
---@field name string
|
||||
---@field fields table[] Field names
|
||||
---@field unique boolean|nil
|
||||
|
||||
---Add index to entity
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param index IndexDefinition
|
||||
---@return table Updated schema
|
||||
function M.addIndex(dbal, tenantId, entityName, index)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local indexes = schema.indexes or {}
|
||||
table.insert(indexes, index)
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = schema.fields,
|
||||
indexes = indexes,
|
||||
relations = schema.relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---Remove index from entity
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param indexName string
|
||||
---@return table Updated schema
|
||||
function M.removeIndex(dbal, tenantId, entityName, indexName)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local indexes = schema.indexes or {}
|
||||
local newIndexes = {}
|
||||
|
||||
for _, idx in ipairs(indexes) do
|
||||
if idx.name ~= indexName then
|
||||
table.insert(newIndexes, idx)
|
||||
end
|
||||
end
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = schema.fields,
|
||||
indexes = newIndexes,
|
||||
relations = schema.relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- RELATION OPERATIONS
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---@class RelationDefinition
|
||||
---@field name string
|
||||
---@field type string (hasOne, hasMany, belongsTo, manyToMany)
|
||||
---@field target string Target entity name
|
||||
---@field foreignKey string|nil
|
||||
---@field through string|nil For manyToMany
|
||||
|
||||
---Add relation to entity
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param relation RelationDefinition
|
||||
---@return table Updated schema
|
||||
function M.addRelation(dbal, tenantId, entityName, relation)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local relations = schema.relations or {}
|
||||
table.insert(relations, relation)
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = schema.fields,
|
||||
indexes = schema.indexes,
|
||||
relations = relations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---Remove relation from entity
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param relationName string
|
||||
---@return table Updated schema
|
||||
function M.removeRelation(dbal, tenantId, entityName, relationName)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
error('Entity schema not found: ' .. entityName)
|
||||
end
|
||||
|
||||
local relations = schema.relations or {}
|
||||
local newRelations = {}
|
||||
|
||||
for _, rel in ipairs(relations) do
|
||||
if rel.name ~= relationName then
|
||||
table.insert(newRelations, rel)
|
||||
end
|
||||
end
|
||||
|
||||
return M.saveEntitySchema(dbal, {
|
||||
tenantId = tenantId,
|
||||
name = entityName,
|
||||
fields = schema.fields,
|
||||
indexes = schema.indexes,
|
||||
relations = newRelations,
|
||||
acl = schema.acl,
|
||||
})
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- SCHEMA VALIDATION
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---Validate entity schema
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@return table Validation result
|
||||
function M.validateSchema(dbal, tenantId, entityName)
|
||||
local schema = M.getEntitySchema(dbal, tenantId, entityName)
|
||||
if not schema then
|
||||
return { valid = false, errors = { 'Entity schema not found' } }
|
||||
end
|
||||
|
||||
local errors = {}
|
||||
|
||||
-- Validate fields
|
||||
for _, field in ipairs(schema.fields or {}) do
|
||||
if not field.name or field.name == '' then
|
||||
table.insert(errors, 'Field must have a name')
|
||||
end
|
||||
if not field.type or field.type == '' then
|
||||
table.insert(errors, 'Field must have a type: ' .. (field.name or 'unknown'))
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate indexes reference valid fields
|
||||
local fieldNames = {}
|
||||
for _, field in ipairs(schema.fields or {}) do
|
||||
fieldNames[field.name] = true
|
||||
end
|
||||
|
||||
for _, idx in ipairs(schema.indexes or {}) do
|
||||
for _, fieldName in ipairs(idx.fields or {}) do
|
||||
if not fieldNames[fieldName] then
|
||||
table.insert(errors, 'Index references non-existent field: ' .. fieldName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
valid = #errors == 0,
|
||||
errors = errors,
|
||||
}
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- SCHEMA VERSION HISTORY
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
---Get schema version history
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@return table[] Version history
|
||||
function M.getVersionHistory(dbal, tenantId, entityName)
|
||||
local result = dbal:list('SchemaVersion', {
|
||||
where = { tenantId = tenantId, entityName = entityName },
|
||||
orderBy = { version = 'desc' },
|
||||
take = 50,
|
||||
})
|
||||
|
||||
return result.items or {}
|
||||
end
|
||||
|
||||
---Save schema version snapshot
|
||||
---@param dbal table
|
||||
---@param tenantId string
|
||||
---@param entityName string
|
||||
---@param schema table Current schema
|
||||
---@param changeNote string|nil
|
||||
function M.saveVersion(dbal, tenantId, entityName, schema, changeNote)
|
||||
return dbal:create('SchemaVersion', {
|
||||
tenantId = tenantId,
|
||||
entityName = entityName,
|
||||
version = schema.version or 1,
|
||||
snapshot = json.encode(schema),
|
||||
changeNote = changeNote,
|
||||
createdAt = os.time() * 1000,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -16,6 +16,7 @@ import { autoRegisterPackages } from '../auto-loader'
|
||||
// Packages known to have good components.json files
|
||||
const AUTO_LOAD_PACKAGES = [
|
||||
'arcade_lobby',
|
||||
'audit_log',
|
||||
'dashboard',
|
||||
'data_table',
|
||||
'form_builder',
|
||||
|
||||
Reference in New Issue
Block a user