update: packages,operations,lua (3 files)

This commit is contained in:
Richard Ward
2025-12-30 21:59:30 +00:00
parent fcc593c155
commit b807de3f34
3 changed files with 1082 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
-- role_editor/seed/scripts/db/operations.lua
-- DBAL operations for Role management
-- Uses Permission entity from Prisma schema
-- @module role_editor.db.operations
local M = {}
local json = require('json')
-- Permission levels
M.LEVELS = {
PUBLIC = 0,
USER = 1,
MODERATOR = 2,
ADMIN = 3,
GOD = 4,
SUPERGOD = 5,
SYSTEM = 6,
}
M.LEVEL_NAMES = {
[0] = 'Public',
[1] = 'User',
[2] = 'Moderator',
[3] = 'Admin',
[4] = 'God',
[5] = 'Supergod',
[6] = 'System',
}
---------------------------------------------------------------------------
-- ROLE/PERMISSION OPERATIONS
---------------------------------------------------------------------------
---@class RoleCreateParams
---@field tenantId string
---@field name string
---@field level number 0-6
---@field description string|nil
---@field permissions table|nil Array of permission strings
---@field color string|nil Display color
---Create a new role
---@param dbal table DBAL client instance
---@param params RoleCreateParams
---@return table Created role
function M.createRole(dbal, params)
return dbal:create('Role', {
tenantId = params.tenantId,
name = params.name,
level = params.level or 1,
description = params.description,
permissions = params.permissions and json.encode(params.permissions) or '[]',
color = params.color or '#808080',
isDefault = false,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get role by ID
---@param dbal table
---@param roleId string
---@return table|nil Role
function M.getRole(dbal, roleId)
local role = dbal:read('Role', roleId)
if role and role.permissions then
role.permissions = json.decode(role.permissions)
end
return role
end
---Get role by name
---@param dbal table
---@param tenantId string
---@param name string
---@return table|nil Role
function M.getRoleByName(dbal, tenantId, name)
local role = dbal:findFirst('Role', {
where = { tenantId = tenantId, name = name },
})
if role and role.permissions then
role.permissions = json.decode(role.permissions)
end
return role
end
---List all roles for a tenant
---@param dbal table
---@param tenantId string
---@return table[] Roles sorted by level
function M.listRoles(dbal, tenantId)
local result = dbal:list('Role', {
where = { tenantId = tenantId },
orderBy = { level = 'asc' },
take = 100,
})
local roles = result.items or {}
-- Parse permissions JSON
for _, role in ipairs(roles) do
if role.permissions and type(role.permissions) == 'string' then
role.permissions = json.decode(role.permissions)
end
end
return roles
end
---Update role
---@param dbal table
---@param roleId string
---@param updates table
---@return table Updated role
function M.updateRole(dbal, roleId, updates)
if updates.permissions and type(updates.permissions) == 'table' then
updates.permissions = json.encode(updates.permissions)
end
updates.updatedAt = os.time() * 1000
return dbal:update('Role', roleId, updates)
end
---Add permission to role
---@param dbal table
---@param roleId string
---@param permission string
---@return table Updated role
function M.addPermission(dbal, roleId, permission)
local role = M.getRole(dbal, roleId)
if not role then
error('Role not found: ' .. roleId)
end
local perms = role.permissions or {}
-- Check if already has permission
for _, p in ipairs(perms) do
if p == permission then
return role
end
end
table.insert(perms, permission)
return M.updateRole(dbal, roleId, { permissions = perms })
end
---Remove permission from role
---@param dbal table
---@param roleId string
---@param permission string
---@return table Updated role
function M.removePermission(dbal, roleId, permission)
local role = M.getRole(dbal, roleId)
if not role then
error('Role not found: ' .. roleId)
end
local perms = role.permissions or {}
local newPerms = {}
for _, p in ipairs(perms) do
if p ~= permission then
table.insert(newPerms, p)
end
end
return M.updateRole(dbal, roleId, { permissions = newPerms })
end
---Check if role has permission
---@param dbal table
---@param roleId string
---@param permission string
---@return boolean
function M.hasPermission(dbal, roleId, permission)
local role = M.getRole(dbal, roleId)
if not role then
return false
end
for _, p in ipairs(role.permissions or {}) do
if p == permission or p == '*' then
return true
end
end
return false
end
---Delete role
---@param dbal table
---@param roleId string
---@return boolean Success
function M.deleteRole(dbal, roleId)
return dbal:delete('Role', roleId)
end
---------------------------------------------------------------------------
-- USER ROLE ASSIGNMENT
---------------------------------------------------------------------------
---Assign role to user
---@param dbal table
---@param userId string
---@param roleId string
---@return table Created assignment
function M.assignRoleToUser(dbal, userId, roleId)
-- Check if already assigned
local existing = dbal:findFirst('UserRole', {
where = { userId = userId, roleId = roleId },
})
if existing then
return existing
end
return dbal:create('UserRole', {
userId = userId,
roleId = roleId,
createdAt = os.time() * 1000,
})
end
---Remove role from user
---@param dbal table
---@param userId string
---@param roleId string
---@return boolean Success
function M.removeRoleFromUser(dbal, userId, roleId)
local assignment = dbal:findFirst('UserRole', {
where = { userId = userId, roleId = roleId },
})
if assignment then
return dbal:delete('UserRole', assignment.id)
end
return false
end
---Get user's roles
---@param dbal table
---@param userId string
---@return table[] Roles
function M.getUserRoles(dbal, userId)
local assignments = dbal:list('UserRole', {
where = { userId = userId },
take = 100,
})
local roles = {}
for _, assignment in ipairs(assignments.items or {}) do
local role = M.getRole(dbal, assignment.roleId)
if role then
table.insert(roles, role)
end
end
return roles
end
---Get effective permission level for user
---@param dbal table
---@param userId string
---@return number Maximum level from all roles
function M.getEffectiveLevel(dbal, userId)
local roles = M.getUserRoles(dbal, userId)
local maxLevel = 0
for _, role in ipairs(roles) do
if (role.level or 0) > maxLevel then
maxLevel = role.level
end
end
return maxLevel
end
---Get all permissions for user
---@param dbal table
---@param userId string
---@return table Unique permissions
function M.getAllUserPermissions(dbal, userId)
local roles = M.getUserRoles(dbal, userId)
local permSet = {}
for _, role in ipairs(roles) do
for _, perm in ipairs(role.permissions or {}) do
permSet[perm] = true
end
end
local perms = {}
for perm, _ in pairs(permSet) do
table.insert(perms, perm)
end
table.sort(perms)
return perms
end
---Check if user has specific permission
---@param dbal table
---@param userId string
---@param permission string
---@return boolean
function M.userHasPermission(dbal, userId, permission)
local perms = M.getAllUserPermissions(dbal, userId)
for _, p in ipairs(perms) do
if p == permission or p == '*' then
return true
end
end
return false
end
---------------------------------------------------------------------------
-- DEFAULT ROLES
---------------------------------------------------------------------------
---Create default roles for a tenant
---@param dbal table
---@param tenantId string
---@return table[] Created roles
function M.createDefaultRoles(dbal, tenantId)
local roles = {}
-- Public role
table.insert(roles, M.createRole(dbal, {
tenantId = tenantId,
name = 'Guest',
level = 0,
description = 'Unauthenticated visitor',
permissions = {'view:public'},
color = '#9E9E9E',
}))
-- User role
table.insert(roles, M.createRole(dbal, {
tenantId = tenantId,
name = 'User',
level = 1,
description = 'Registered user',
permissions = {'view:public', 'view:private', 'edit:own'},
color = '#2196F3',
}))
-- Moderator role
table.insert(roles, M.createRole(dbal, {
tenantId = tenantId,
name = 'Moderator',
level = 2,
description = 'Content moderator',
permissions = {'view:public', 'view:private', 'edit:own', 'moderate:content'},
color = '#4CAF50',
}))
-- Admin role
table.insert(roles, M.createRole(dbal, {
tenantId = tenantId,
name = 'Admin',
level = 3,
description = 'Administrator',
permissions = {'view:*', 'edit:*', 'manage:users', 'manage:content'},
color = '#FF9800',
}))
-- God role
table.insert(roles, M.createRole(dbal, {
tenantId = tenantId,
name = 'God',
level = 4,
description = 'Full access',
permissions = {'*'},
color = '#F44336',
}))
return roles
end
return M

View File

@@ -0,0 +1,338 @@
-- user_manager/seed/scripts/db/operations.lua
-- DBAL operations for User management
-- Uses existing User entity from Prisma schema
-- @module user_manager.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- USER CRUD OPERATIONS
---------------------------------------------------------------------------
---@class UserCreateParams
---@field tenantId string
---@field email string
---@field username string
---@field password string Hashed password
---@field displayName string|nil
---@field avatar string|nil
---@field level number 0-6 permission level
---@field status string active|inactive|banned
---Create a new user
---@param dbal table DBAL client instance
---@param params UserCreateParams
---@return table Created user
function M.createUser(dbal, params)
return dbal:create('User', {
tenantId = params.tenantId,
email = params.email,
username = params.username,
password = params.password,
displayName = params.displayName or params.username,
avatar = params.avatar,
level = params.level or 1,
status = params.status or 'active',
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get user by ID
---@param dbal table
---@param userId string
---@return table|nil User
function M.getUser(dbal, userId)
return dbal:read('User', userId)
end
---Get user by email
---@param dbal table
---@param tenantId string
---@param email string
---@return table|nil User
function M.getUserByEmail(dbal, tenantId, email)
return dbal:findFirst('User', {
where = { tenantId = tenantId, email = email },
})
end
---Get user by username
---@param dbal table
---@param tenantId string
---@param username string
---@return table|nil User
function M.getUserByUsername(dbal, tenantId, username)
return dbal:findFirst('User', {
where = { tenantId = tenantId, username = username },
})
end
---List users
---@param dbal table
---@param tenantId string
---@param status string|nil Filter by status
---@param minLevel number|nil Minimum permission level
---@param take number|nil
---@param skip number|nil
---@return table List result
function M.listUsers(dbal, tenantId, status, minLevel, take, skip)
local where = { tenantId = tenantId }
if status then
where.status = status
end
local result = dbal:list('User', {
where = where,
orderBy = { createdAt = 'desc' },
take = take or 50,
skip = skip or 0,
})
-- Filter by minLevel if specified
if minLevel and result.items then
local filtered = {}
for _, user in ipairs(result.items) do
if (user.level or 0) >= minLevel then
table.insert(filtered, user)
end
end
result.items = filtered
end
return result
end
---Update user
---@param dbal table
---@param userId string
---@param updates table
---@return table Updated user
function M.updateUser(dbal, userId, updates)
updates.updatedAt = os.time() * 1000
return dbal:update('User', userId, updates)
end
---Update user profile
---@param dbal table
---@param userId string
---@param displayName string|nil
---@param avatar string|nil
---@param bio string|nil
---@return table Updated user
function M.updateProfile(dbal, userId, displayName, avatar, bio)
local updates = { updatedAt = os.time() * 1000 }
if displayName ~= nil then
updates.displayName = displayName
end
if avatar ~= nil then
updates.avatar = avatar
end
if bio ~= nil then
updates.bio = bio
end
return dbal:update('User', userId, updates)
end
---Change user password
---@param dbal table
---@param userId string
---@param hashedPassword string
---@return table Updated user
function M.changePassword(dbal, userId, hashedPassword)
return M.updateUser(dbal, userId, {
password = hashedPassword,
})
end
---Set user status
---@param dbal table
---@param userId string
---@param status string active|inactive|banned
---@return table Updated user
function M.setStatus(dbal, userId, status)
return M.updateUser(dbal, userId, { status = status })
end
---Activate user
---@param dbal table
---@param userId string
function M.activateUser(dbal, userId)
return M.setStatus(dbal, userId, 'active')
end
---Deactivate user
---@param dbal table
---@param userId string
function M.deactivateUser(dbal, userId)
return M.setStatus(dbal, userId, 'inactive')
end
---Ban user
---@param dbal table
---@param userId string
---@param reason string|nil
function M.banUser(dbal, userId, reason)
return M.updateUser(dbal, userId, {
status = 'banned',
banReason = reason,
bannedAt = os.time() * 1000,
})
end
---Unban user
---@param dbal table
---@param userId string
function M.unbanUser(dbal, userId)
return M.updateUser(dbal, userId, {
status = 'active',
banReason = nil,
bannedAt = nil,
})
end
---------------------------------------------------------------------------
-- PERMISSION OPERATIONS
---------------------------------------------------------------------------
---Set user permission level
---@param dbal table
---@param userId string
---@param level number 0-6
---@return table Updated user
function M.setLevel(dbal, userId, level)
if level < 0 or level > 6 then
error('Invalid permission level: ' .. tostring(level))
end
return M.updateUser(dbal, userId, { level = level })
end
---Promote user by one level
---@param dbal table
---@param userId string
---@param maxLevel number|nil Maximum level to promote to
---@return table Updated user
function M.promoteUser(dbal, userId, maxLevel)
local user = M.getUser(dbal, userId)
if not user then
error('User not found: ' .. userId)
end
local newLevel = math.min((user.level or 0) + 1, maxLevel or 6)
return M.setLevel(dbal, userId, newLevel)
end
---Demote user by one level
---@param dbal table
---@param userId string
---@return table Updated user
function M.demoteUser(dbal, userId)
local user = M.getUser(dbal, userId)
if not user then
error('User not found: ' .. userId)
end
local newLevel = math.max((user.level or 0) - 1, 0)
return M.setLevel(dbal, userId, newLevel)
end
---Check if user has minimum permission level
---@param dbal table
---@param userId string
---@param requiredLevel number
---@return boolean
function M.hasPermission(dbal, userId, requiredLevel)
local user = M.getUser(dbal, userId)
if not user then
return false
end
return (user.level or 0) >= requiredLevel
end
---------------------------------------------------------------------------
-- BULK OPERATIONS
---------------------------------------------------------------------------
---List admins (level >= 3)
---@param dbal table
---@param tenantId string
---@return table[] Admin users
function M.listAdmins(dbal, tenantId)
local result = M.listUsers(dbal, tenantId, 'active', 3, 100, 0)
return result.items or {}
end
---List moderators (level >= 2)
---@param dbal table
---@param tenantId string
---@return table[] Moderator users
function M.listModerators(dbal, tenantId)
local result = M.listUsers(dbal, tenantId, 'active', 2, 100, 0)
return result.items or {}
end
---Count users by status
---@param dbal table
---@param tenantId string
---@return table Counts by status
function M.countByStatus(dbal, tenantId)
local all = M.listUsers(dbal, tenantId, nil, nil, 10000, 0)
local counts = {
active = 0,
inactive = 0,
banned = 0,
total = 0,
}
for _, user in ipairs(all.items or {}) do
counts.total = counts.total + 1
local status = user.status or 'active'
counts[status] = (counts[status] or 0) + 1
end
return counts
end
---Search users by username or email
---@param dbal table
---@param tenantId string
---@param query string
---@param take number|nil
---@return table[] Matching users
function M.searchUsers(dbal, tenantId, query, take)
local all = M.listUsers(dbal, tenantId, nil, nil, 1000, 0)
local matches = {}
local lowerQuery = query:lower()
for _, user in ipairs(all.items or {}) do
local username = (user.username or ''):lower()
local email = (user.email or ''):lower()
local displayName = (user.displayName or ''):lower()
if username:find(lowerQuery, 1, true) or
email:find(lowerQuery, 1, true) or
displayName:find(lowerQuery, 1, true) then
table.insert(matches, user)
if #matches >= (take or 20) then
break
end
end
end
return matches
end
---Delete user
---@param dbal table
---@param userId string
---@return boolean Success
function M.deleteUser(dbal, userId)
return dbal:delete('User', userId)
end
return M

View File

@@ -0,0 +1,361 @@
-- workflow_editor/seed/scripts/db/operations.lua
-- DBAL operations for Workflow entities
-- @module workflow_editor.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- WORKFLOW OPERATIONS
---------------------------------------------------------------------------
---@class WorkflowCreateParams
---@field tenantId string
---@field name string
---@field description string|nil
---@field trigger table Trigger configuration
---@field nodes table[] Workflow nodes
---@field edges table[] Node connections
---@field createdBy string
---Create a new workflow
---@param dbal table DBAL client instance
---@param params WorkflowCreateParams
---@return table Created workflow
function M.createWorkflow(dbal, params)
return dbal:create('Workflow', {
tenantId = params.tenantId,
name = params.name,
description = params.description,
status = 'draft',
version = 1,
trigger = json.encode(params.trigger or {}),
nodes = json.encode(params.nodes or {}),
edges = json.encode(params.edges or {}),
createdBy = params.createdBy,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get workflow by ID
---@param dbal table
---@param workflowId string
---@return table|nil Workflow with parsed JSON
function M.getWorkflow(dbal, workflowId)
local workflow = dbal:read('Workflow', workflowId)
if workflow then
workflow.trigger = json.decode(workflow.trigger or '{}')
workflow.nodes = json.decode(workflow.nodes or '[]')
workflow.edges = json.decode(workflow.edges or '[]')
end
return workflow
end
---List workflows
---@param dbal table
---@param tenantId string
---@param status string|nil draft|active|paused|archived
---@param take number|nil
---@param skip number|nil
---@return table List result
function M.listWorkflows(dbal, tenantId, status, take, skip)
local where = { tenantId = tenantId }
if status then
where.status = status
end
local result = dbal:list('Workflow', {
where = where,
orderBy = { updatedAt = 'desc' },
take = take or 20,
skip = skip or 0,
})
return result
end
---Update workflow
---@param dbal table
---@param workflowId string
---@param updates table
---@return table Updated workflow
function M.updateWorkflow(dbal, workflowId, updates)
if updates.trigger and type(updates.trigger) == 'table' then
updates.trigger = json.encode(updates.trigger)
end
if updates.nodes and type(updates.nodes) == 'table' then
updates.nodes = json.encode(updates.nodes)
end
if updates.edges and type(updates.edges) == 'table' then
updates.edges = json.encode(updates.edges)
end
updates.updatedAt = os.time() * 1000
return dbal:update('Workflow', workflowId, updates)
end
---Save workflow nodes and edges
---@param dbal table
---@param workflowId string
---@param nodes table[]
---@param edges table[]
---@return table Updated workflow
function M.saveWorkflowGraph(dbal, workflowId, nodes, edges)
return M.updateWorkflow(dbal, workflowId, {
nodes = nodes,
edges = edges,
})
end
---Publish workflow (activate)
---@param dbal table
---@param workflowId string
---@return table Updated workflow
function M.publishWorkflow(dbal, workflowId)
local workflow = M.getWorkflow(dbal, workflowId)
if not workflow then
error('Workflow not found: ' .. workflowId)
end
return M.updateWorkflow(dbal, workflowId, {
status = 'active',
version = (workflow.version or 0) + 1,
publishedAt = os.time() * 1000,
})
end
---Pause workflow
---@param dbal table
---@param workflowId string
function M.pauseWorkflow(dbal, workflowId)
return M.updateWorkflow(dbal, workflowId, { status = 'paused' })
end
---Resume workflow
---@param dbal table
---@param workflowId string
function M.resumeWorkflow(dbal, workflowId)
return M.updateWorkflow(dbal, workflowId, { status = 'active' })
end
---Archive workflow
---@param dbal table
---@param workflowId string
function M.archiveWorkflow(dbal, workflowId)
return M.updateWorkflow(dbal, workflowId, { status = 'archived' })
end
---Delete workflow
---@param dbal table
---@param workflowId string
---@return boolean Success
function M.deleteWorkflow(dbal, workflowId)
return dbal:delete('Workflow', workflowId)
end
---------------------------------------------------------------------------
-- WORKFLOW EXECUTION OPERATIONS
---------------------------------------------------------------------------
---@class WorkflowExecutionParams
---@field tenantId string
---@field workflowId string
---@field triggeredBy string|nil User or system
---@field input table|nil Input data
---Start a workflow execution
---@param dbal table
---@param params WorkflowExecutionParams
---@return table Created execution
function M.startExecution(dbal, params)
return dbal:create('WorkflowExecution', {
tenantId = params.tenantId,
workflowId = params.workflowId,
status = 'running',
triggeredBy = params.triggeredBy or 'system',
input = params.input and json.encode(params.input) or nil,
startedAt = os.time() * 1000,
})
end
---Get execution by ID
---@param dbal table
---@param executionId string
---@return table|nil Execution
function M.getExecution(dbal, executionId)
local execution = dbal:read('WorkflowExecution', executionId)
if execution then
if execution.input then
execution.input = json.decode(execution.input)
end
if execution.output then
execution.output = json.decode(execution.output)
end
end
return execution
end
---List executions for a workflow
---@param dbal table
---@param workflowId string
---@param status string|nil running|completed|failed|cancelled
---@param take number|nil
---@return table List result
function M.listExecutions(dbal, workflowId, status, take)
local where = { workflowId = workflowId }
if status then
where.status = status
end
return dbal:list('WorkflowExecution', {
where = where,
orderBy = { startedAt = 'desc' },
take = take or 20,
})
end
---Update execution status
---@param dbal table
---@param executionId string
---@param status string
---@param output table|nil
---@param error string|nil
function M.updateExecution(dbal, executionId, status, output, error)
local updates = { status = status }
if status == 'completed' or status == 'failed' or status == 'cancelled' then
updates.completedAt = os.time() * 1000
end
if output then
updates.output = json.encode(output)
end
if error then
updates.error = error
end
return dbal:update('WorkflowExecution', executionId, updates)
end
---Complete execution
---@param dbal table
---@param executionId string
---@param output table|nil
function M.completeExecution(dbal, executionId, output)
return M.updateExecution(dbal, executionId, 'completed', output, nil)
end
---Fail execution
---@param dbal table
---@param executionId string
---@param error string
function M.failExecution(dbal, executionId, error)
return M.updateExecution(dbal, executionId, 'failed', nil, error)
end
---Cancel execution
---@param dbal table
---@param executionId string
function M.cancelExecution(dbal, executionId)
return M.updateExecution(dbal, executionId, 'cancelled', nil, nil)
end
---------------------------------------------------------------------------
-- NODE STEP LOGGING
---------------------------------------------------------------------------
---Log a node step
---@param dbal table
---@param executionId string
---@param nodeId string
---@param status string pending|running|completed|failed|skipped
---@param input table|nil
---@param output table|nil
---@param error string|nil
function M.logNodeStep(dbal, executionId, nodeId, status, input, output, error)
return dbal:create('WorkflowStep', {
executionId = executionId,
nodeId = nodeId,
status = status,
input = input and json.encode(input) or nil,
output = output and json.encode(output) or nil,
error = error,
timestamp = os.time() * 1000,
})
end
---Get steps for an execution
---@param dbal table
---@param executionId string
---@return table[] Steps in order
function M.getExecutionSteps(dbal, executionId)
local result = dbal:list('WorkflowStep', {
where = { executionId = executionId },
orderBy = { timestamp = 'asc' },
take = 1000,
})
return result.items or {}
end
---------------------------------------------------------------------------
-- WORKFLOW TEMPLATES
---------------------------------------------------------------------------
---List workflow templates
---@param dbal table
---@param category string|nil
---@return table[] Templates
function M.listTemplates(dbal, category)
local where = { isTemplate = true }
local result = dbal:list('Workflow', {
where = where,
orderBy = { name = 'asc' },
take = 50,
})
local templates = result.items or {}
-- Filter by category if specified
if category then
local filtered = {}
for _, t in ipairs(templates) do
if t.category == category then
table.insert(filtered, t)
end
end
templates = filtered
end
return templates
end
---Create workflow from template
---@param dbal table
---@param templateId string
---@param tenantId string
---@param name string
---@param createdBy string
---@return table Created workflow
function M.createFromTemplate(dbal, templateId, tenantId, name, createdBy)
local template = M.getWorkflow(dbal, templateId)
if not template then
error('Template not found: ' .. templateId)
end
return M.createWorkflow(dbal, {
tenantId = tenantId,
name = name,
description = 'Created from template: ' .. template.name,
trigger = template.trigger,
nodes = template.nodes,
edges = template.edges,
createdBy = createdBy,
})
end
return M