mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-08 09:20:10 +08:00
update
This commit is contained in:
261
workflow/json-engine/blog-engine.json
Normal file
261
workflow/json-engine/blog-engine.json
Normal file
File diff suppressed because one or more lines are too long
276
workflow/json-engine/email-campaign.json
Normal file
276
workflow/json-engine/email-campaign.json
Normal file
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
htmlTemplate "text/template"
|
htmlTemplate "text/template"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,36 +18,104 @@ import (
|
|||||||
"github.com/oarkflow/mq/workflow"
|
"github.com/oarkflow/mq/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Helper functions for default values
|
||||||
|
func getDefaultInt(value, defaultValue int) int {
|
||||||
|
if value != 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultString(value, defaultValue string) string {
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultBool(value, defaultValue bool) bool {
|
||||||
|
if value {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultStringSlice(value, defaultValue []string) []string {
|
||||||
|
if value != nil && len(value) > 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultDuration(value string, defaultValue time.Duration) time.Duration {
|
||||||
|
if value != "" {
|
||||||
|
if d, err := time.ParseDuration(value); err == nil {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSONEngine creates a new JSON-driven workflow engine
|
// NewJSONEngine creates a new JSON-driven workflow engine
|
||||||
func NewJSONEngine() *JSONEngine {
|
func NewJSONEngine(config *AppConfiguration) *JSONEngine {
|
||||||
// Initialize real workflow engine with default config
|
// Use configuration from JSON or defaults
|
||||||
workflowConfig := &workflow.Config{
|
var workflowEngineConfig *WorkflowEngineConfig
|
||||||
|
if config.WorkflowEngine != nil {
|
||||||
|
workflowEngineConfig = config.WorkflowEngine
|
||||||
|
} else {
|
||||||
|
// Default configuration
|
||||||
|
workflowEngineConfig = &WorkflowEngineConfig{
|
||||||
MaxWorkers: 4,
|
MaxWorkers: 4,
|
||||||
ExecutionTimeout: 30 * time.Second,
|
ExecutionTimeout: "30s",
|
||||||
EnableMetrics: true,
|
EnableMetrics: true,
|
||||||
EnableAudit: true,
|
EnableAudit: true,
|
||||||
EnableTracing: true,
|
EnableTracing: true,
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
Storage: workflow.StorageConfig{
|
Storage: StorageConfig{
|
||||||
Type: "memory",
|
Type: "memory",
|
||||||
MaxConnections: 10,
|
MaxConnections: 10,
|
||||||
},
|
},
|
||||||
Security: workflow.SecurityConfig{
|
Security: SecurityConfig{
|
||||||
EnableAuth: false,
|
EnableAuth: false,
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowConfig := &workflow.Config{
|
||||||
|
MaxWorkers: workflowEngineConfig.MaxWorkers,
|
||||||
|
ExecutionTimeout: getDefaultDuration(workflowEngineConfig.ExecutionTimeout, 30*time.Second),
|
||||||
|
EnableMetrics: workflowEngineConfig.EnableMetrics,
|
||||||
|
EnableAudit: workflowEngineConfig.EnableAudit,
|
||||||
|
EnableTracing: workflowEngineConfig.EnableTracing,
|
||||||
|
LogLevel: getDefaultString(workflowEngineConfig.LogLevel, "info"),
|
||||||
|
Storage: workflow.StorageConfig{
|
||||||
|
Type: getDefaultString(workflowEngineConfig.Storage.Type, "memory"),
|
||||||
|
MaxConnections: getDefaultInt(workflowEngineConfig.Storage.MaxConnections, 10),
|
||||||
|
},
|
||||||
|
Security: workflow.SecurityConfig{
|
||||||
|
EnableAuth: workflowEngineConfig.Security.EnableAuth,
|
||||||
|
AllowedOrigins: getDefaultStringSlice(workflowEngineConfig.Security.AllowedOrigins, []string{"*"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
workflowEngine := workflow.NewWorkflowEngine(workflowConfig)
|
workflowEngine := workflow.NewWorkflowEngine(workflowConfig)
|
||||||
|
|
||||||
return &JSONEngine{
|
engine := &JSONEngine{
|
||||||
workflowEngine: workflowEngine,
|
workflowEngine: workflowEngine,
|
||||||
|
workflowEngineConfig: workflowEngineConfig,
|
||||||
templates: make(map[string]*Template),
|
templates: make(map[string]*Template),
|
||||||
workflows: make(map[string]*Workflow),
|
workflows: make(map[string]*Workflow),
|
||||||
functions: make(map[string]*Function),
|
functions: make(map[string]*Function),
|
||||||
validators: make(map[string]*Validator),
|
validators: make(map[string]*Validator),
|
||||||
middleware: make(map[string]*Middleware),
|
middleware: make(map[string]*Middleware),
|
||||||
data: make(map[string]interface{}),
|
data: make(map[string]interface{}),
|
||||||
|
genericData: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the configuration
|
||||||
|
engine.config = config
|
||||||
|
|
||||||
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfiguration loads and parses JSON configuration
|
// LoadConfiguration loads and parses JSON configuration
|
||||||
@@ -69,6 +138,19 @@ func (e *JSONEngine) LoadConfiguration(configPath string) error {
|
|||||||
func (e *JSONEngine) Compile() error {
|
func (e *JSONEngine) Compile() error {
|
||||||
log.Println("🔨 Compiling JSON configuration...")
|
log.Println("🔨 Compiling JSON configuration...")
|
||||||
|
|
||||||
|
// Store global data and merge with genericData
|
||||||
|
e.data = e.config.Data
|
||||||
|
|
||||||
|
// Initialize genericData with config data for backward compatibility
|
||||||
|
if e.genericData == nil {
|
||||||
|
e.genericData = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge config data into genericData
|
||||||
|
for key, value := range e.config.Data {
|
||||||
|
e.genericData[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Compile templates
|
// 1. Compile templates
|
||||||
if err := e.compileTemplates(); err != nil {
|
if err := e.compileTemplates(); err != nil {
|
||||||
return fmt.Errorf("template compilation failed: %v", err)
|
return fmt.Errorf("template compilation failed: %v", err)
|
||||||
@@ -94,9 +176,19 @@ func (e *JSONEngine) Compile() error {
|
|||||||
return fmt.Errorf("middleware compilation failed: %v", err)
|
return fmt.Errorf("middleware compilation failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Store global data
|
// 6. Store global data and merge with genericData
|
||||||
e.data = e.config.Data
|
e.data = e.config.Data
|
||||||
|
|
||||||
|
// Initialize genericData with config data for backward compatibility
|
||||||
|
if e.genericData == nil {
|
||||||
|
e.genericData = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge config data into genericData
|
||||||
|
for key, value := range e.config.Data {
|
||||||
|
e.genericData[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("✅ JSON configuration compiled successfully")
|
log.Println("✅ JSON configuration compiled successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -171,7 +263,7 @@ func (e *JSONEngine) compileTemplates() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileFunctions compiles all function definitions
|
// compileFunctions compiles all function definitions dynamically
|
||||||
func (e *JSONEngine) compileFunctions() error {
|
func (e *JSONEngine) compileFunctions() error {
|
||||||
for id, functionConfig := range e.config.Functions {
|
for id, functionConfig := range e.config.Functions {
|
||||||
log.Printf("Compiling function: %s", id)
|
log.Printf("Compiling function: %s", id)
|
||||||
@@ -179,9 +271,10 @@ func (e *JSONEngine) compileFunctions() error {
|
|||||||
function := &Function{
|
function := &Function{
|
||||||
ID: id,
|
ID: id,
|
||||||
Config: functionConfig,
|
Config: functionConfig,
|
||||||
|
Runtime: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile function based on type
|
// Compile function based on type - completely generic approach
|
||||||
switch functionConfig.Type {
|
switch functionConfig.Type {
|
||||||
case "http":
|
case "http":
|
||||||
function.Handler = e.createHTTPFunction(functionConfig)
|
function.Handler = e.createHTTPFunction(functionConfig)
|
||||||
@@ -189,10 +282,15 @@ func (e *JSONEngine) compileFunctions() error {
|
|||||||
function.Handler = e.createExpressionFunction(functionConfig)
|
function.Handler = e.createExpressionFunction(functionConfig)
|
||||||
case "template":
|
case "template":
|
||||||
function.Handler = e.createTemplateFunction(functionConfig)
|
function.Handler = e.createTemplateFunction(functionConfig)
|
||||||
case "js":
|
case "js", "javascript":
|
||||||
function.Handler = e.createJSFunction(functionConfig)
|
function.Handler = e.createJSFunction(functionConfig)
|
||||||
|
case "builtin":
|
||||||
|
function.Handler = e.createBuiltinFunction(functionConfig)
|
||||||
|
case "custom":
|
||||||
|
function.Handler = e.createCustomFunction(functionConfig)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown function type: %s", functionConfig.Type)
|
// For unknown types, create a generic function that can be extended
|
||||||
|
function.Handler = e.createGenericFunction(functionConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.functions[id] = function
|
e.functions[id] = function
|
||||||
@@ -200,7 +298,7 @@ func (e *JSONEngine) compileFunctions() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileValidators compiles all validators
|
// compileValidators compiles all validators with generic approach
|
||||||
func (e *JSONEngine) compileValidators() error {
|
func (e *JSONEngine) compileValidators() error {
|
||||||
for id, validatorConfig := range e.config.Validators {
|
for id, validatorConfig := range e.config.Validators {
|
||||||
log.Printf("Compiling validator: %s", id)
|
log.Printf("Compiling validator: %s", id)
|
||||||
@@ -208,7 +306,8 @@ func (e *JSONEngine) compileValidators() error {
|
|||||||
e.validators[id] = &Validator{
|
e.validators[id] = &Validator{
|
||||||
ID: id,
|
ID: id,
|
||||||
Config: validatorConfig,
|
Config: validatorConfig,
|
||||||
Rules: validatorConfig.Rules,
|
Rules: validatorConfig.Rules, // Now using generic map
|
||||||
|
Runtime: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -357,7 +456,7 @@ func (e *JSONEngine) createRouteHandler(routeConfig RouteConfig) fiber.Handler {
|
|||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
log.Printf("Handler called for route: %s %s", routeConfig.Method, routeConfig.Path)
|
log.Printf("Handler called for route: %s %s", routeConfig.Method, routeConfig.Path)
|
||||||
|
|
||||||
// Create execution context
|
// Create execution context with enhanced generic data
|
||||||
ctx := &ExecutionContext{
|
ctx := &ExecutionContext{
|
||||||
Request: c,
|
Request: c,
|
||||||
Data: make(map[string]interface{}),
|
Data: make(map[string]interface{}),
|
||||||
@@ -366,9 +465,18 @@ func (e *JSONEngine) createRouteHandler(routeConfig RouteConfig) fiber.Handler {
|
|||||||
User: make(map[string]interface{}),
|
User: make(map[string]interface{}),
|
||||||
Functions: e.functions,
|
Functions: e.functions,
|
||||||
Validators: e.validators,
|
Validators: e.validators,
|
||||||
|
Config: e.config,
|
||||||
|
Runtime: make(map[string]interface{}),
|
||||||
|
Context: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply route middleware - skip auth middleware as we handle it below
|
// Add global and generic data to context
|
||||||
|
for k, v := range e.data {
|
||||||
|
ctx.Data[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range e.genericData {
|
||||||
|
ctx.Data[k] = v
|
||||||
|
} // Apply route middleware - skip auth middleware as we handle it below
|
||||||
for _, middlewareID := range routeConfig.Middleware {
|
for _, middlewareID := range routeConfig.Middleware {
|
||||||
if middlewareID == "auth" {
|
if middlewareID == "auth" {
|
||||||
continue // Skip auth middleware, handle it in route
|
continue // Skip auth middleware, handle it in route
|
||||||
@@ -391,7 +499,7 @@ func (e *JSONEngine) createRouteHandler(routeConfig RouteConfig) fiber.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute handler based on type
|
// Execute handler based on type - with generic fallback
|
||||||
switch routeConfig.Handler.Type {
|
switch routeConfig.Handler.Type {
|
||||||
case "template":
|
case "template":
|
||||||
return e.handleTemplate(ctx, routeConfig)
|
return e.handleTemplate(ctx, routeConfig)
|
||||||
@@ -403,8 +511,13 @@ func (e *JSONEngine) createRouteHandler(routeConfig RouteConfig) fiber.Handler {
|
|||||||
return c.Redirect(routeConfig.Handler.Target)
|
return c.Redirect(routeConfig.Handler.Target)
|
||||||
case "static":
|
case "static":
|
||||||
return e.handleStatic(ctx, routeConfig)
|
return e.handleStatic(ctx, routeConfig)
|
||||||
|
case "json":
|
||||||
|
return e.handleJSON(ctx, routeConfig)
|
||||||
|
case "api":
|
||||||
|
return e.handleAPI(ctx, routeConfig)
|
||||||
default:
|
default:
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Unknown handler type: " + routeConfig.Handler.Type})
|
// Generic handler for unknown types
|
||||||
|
return e.handleGeneric(ctx, routeConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,6 +548,37 @@ func (e *JSONEngine) handleTemplate(ctx *ExecutionContext, routeConfig RouteConf
|
|||||||
data[k] = v
|
data[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For employee_form template, ensure proper employee data structure
|
||||||
|
if templateID == "employee_form" {
|
||||||
|
if emp, exists := data["employee"]; !exists || emp == nil {
|
||||||
|
// For add mode: provide empty employee object and set isEditMode to false
|
||||||
|
data["employee"] = map[string]interface{}{
|
||||||
|
"id": "",
|
||||||
|
"name": "",
|
||||||
|
"email": "",
|
||||||
|
"department": "",
|
||||||
|
"position": "",
|
||||||
|
"salary": "",
|
||||||
|
"hire_date": "",
|
||||||
|
"status": "active",
|
||||||
|
}
|
||||||
|
data["isEditMode"] = false
|
||||||
|
} else {
|
||||||
|
// For edit mode: ensure employee has all required fields and set isEditMode to true
|
||||||
|
if empMap, ok := emp.(map[string]interface{}); ok {
|
||||||
|
// Fill in any missing fields with empty values
|
||||||
|
fields := []string{"id", "name", "email", "department", "position", "salary", "hire_date", "status"}
|
||||||
|
for _, field := range fields {
|
||||||
|
if _, exists := empMap[field]; !exists {
|
||||||
|
empMap[field] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data["employee"] = empMap
|
||||||
|
data["isEditMode"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add request data
|
// Add request data
|
||||||
data["request"] = map[string]interface{}{
|
data["request"] = map[string]interface{}{
|
||||||
"method": ctx.Request.Method(),
|
"method": ctx.Request.Method(),
|
||||||
@@ -452,7 +596,17 @@ func (e *JSONEngine) handleTemplate(ctx *ExecutionContext, routeConfig RouteConf
|
|||||||
// Execute template
|
// Execute template
|
||||||
tmpl := template.Compiled.(*htmlTemplate.Template)
|
tmpl := template.Compiled.(*htmlTemplate.Template)
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
|
|
||||||
|
log.Printf("DEBUG: About to execute template %s with data keys: %v", templateID, func() []string {
|
||||||
|
keys := make([]string, 0, len(data))
|
||||||
|
for k := range data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}())
|
||||||
|
|
||||||
if err := tmpl.Execute(&buf, data); err != nil {
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
log.Printf("DEBUG: Template execution error: %v", err)
|
||||||
return ctx.Request.Status(500).JSON(fiber.Map{"error": "Template execution failed: " + err.Error()})
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": "Template execution failed: " + err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +624,16 @@ func (e *JSONEngine) handleTemplate(ctx *ExecutionContext, routeConfig RouteConf
|
|||||||
return ctx.Request.Send([]byte(buf.String()))
|
return ctx.Request.Send([]byte(buf.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderTemplate renders a template with data
|
||||||
|
func (e *JSONEngine) renderTemplate(template *Template, data map[string]interface{}) (string, error) {
|
||||||
|
tmpl := template.Compiled.(*htmlTemplate.Template)
|
||||||
|
var buf strings.Builder
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
workflowID := routeConfig.Handler.Target
|
workflowID := routeConfig.Handler.Target
|
||||||
log.Printf("Looking for workflow: %s", workflowID)
|
log.Printf("Looking for workflow: %s", workflowID)
|
||||||
@@ -521,24 +685,92 @@ func (e *JSONEngine) handleWorkflow(ctx *ExecutionContext, routeConfig RouteConf
|
|||||||
func (e *JSONEngine) handleFunction(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
func (e *JSONEngine) handleFunction(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
functionID := routeConfig.Handler.Target
|
functionID := routeConfig.Handler.Target
|
||||||
|
|
||||||
// Handle special built-in functions
|
// Look up the function in our compiled functions
|
||||||
switch functionID {
|
|
||||||
case "authenticate_user":
|
|
||||||
return e.handleAuthFunction(ctx)
|
|
||||||
default:
|
|
||||||
function, exists := e.functions[functionID]
|
function, exists := e.functions[functionID]
|
||||||
if !exists {
|
if !exists {
|
||||||
return ctx.Request.Status(404).JSON(fiber.Map{"error": "Function not found: " + functionID})
|
return ctx.Request.Status(404).JSON(fiber.Map{"error": "Function not found: " + functionID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare input data
|
||||||
|
input := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Add handler input if specified
|
||||||
|
if routeConfig.Handler.Input != nil {
|
||||||
|
for k, v := range routeConfig.Handler.Input {
|
||||||
|
input[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add request body for POST/PUT requests
|
||||||
|
if ctx.Request.Method() == "POST" || ctx.Request.Method() == "PUT" {
|
||||||
|
var body map[string]interface{}
|
||||||
|
if err := ctx.Request.BodyParser(&body); err == nil {
|
||||||
|
for k, v := range body {
|
||||||
|
input[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
for k, v := range ctx.Request.Queries() {
|
||||||
|
input[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add route parameters (if any) - specifically handle common parameter names
|
||||||
|
if id := ctx.Request.Params("id"); id != "" {
|
||||||
|
input["id"] = id
|
||||||
|
}
|
||||||
|
if userId := ctx.Request.Params("userId"); userId != "" {
|
||||||
|
input["userId"] = userId
|
||||||
|
}
|
||||||
|
if employeeId := ctx.Request.Params("employeeId"); employeeId != "" {
|
||||||
|
input["employeeId"] = employeeId
|
||||||
|
}
|
||||||
|
// Add any other route parameters that might exist
|
||||||
|
// Note: Fiber v2 doesn't have a direct AllParams() method, so we handle common cases
|
||||||
|
|
||||||
// Execute function
|
// Execute function
|
||||||
result, err := e.executeFunction(ctx, function, routeConfig.Handler.Input)
|
result, err := e.executeFunction(ctx, function, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.Request.Status(500).JSON(fiber.Map{"error": "Function execution failed: " + err.Error()})
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": "Function execution failed: " + err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Request.JSON(result)
|
// Check if we should render a template with the result
|
||||||
|
if routeConfig.Handler.Template != "" {
|
||||||
|
templateID := routeConfig.Handler.Template
|
||||||
|
template, exists := e.templates[templateID]
|
||||||
|
if !exists {
|
||||||
|
return ctx.Request.Status(404).JSON(fiber.Map{"error": "Template not found: " + templateID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge function result with context data
|
||||||
|
templateData := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Add global data first
|
||||||
|
for k, v := range ctx.Data {
|
||||||
|
templateData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all function result data directly (this includes the employee field)
|
||||||
|
for k, v := range result {
|
||||||
|
templateData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// For employee_form template, ensure employee field exists (even if nil)
|
||||||
|
if templateID == "employee_form" && templateData["employee"] == nil {
|
||||||
|
templateData["employee"] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render template
|
||||||
|
rendered, err := e.renderTemplate(template, templateData)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": "Template rendering failed: " + err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Request.Type("html").Send([]byte(rendered))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Request.JSON(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *JSONEngine) handleStatic(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
func (e *JSONEngine) handleStatic(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
@@ -557,13 +789,21 @@ func (e *JSONEngine) handleAuthFunction(ctx *ExecutionContext) error {
|
|||||||
return ctx.Request.Status(400).JSON(fiber.Map{"error": "Invalid request body"})
|
return ctx.Request.Status(400).JSON(fiber.Map{"error": "Invalid request body"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple authentication using demo users from data
|
// Generic authentication using user data from configuration
|
||||||
demoUsers, ok := e.data["demo_users"].([]interface{})
|
// Look for users in multiple possible data keys for flexibility
|
||||||
if !ok {
|
var users []interface{}
|
||||||
return ctx.Request.Status(500).JSON(fiber.Map{"error": "User data not configured"})
|
|
||||||
|
if demoUsers, ok := e.data["demo_users"].([]interface{}); ok {
|
||||||
|
users = demoUsers
|
||||||
|
} else if configUsers, ok := e.data["users"].([]interface{}); ok {
|
||||||
|
users = configUsers
|
||||||
|
} else if authUsers, ok := e.data["auth_users"].([]interface{}); ok {
|
||||||
|
users = authUsers
|
||||||
|
} else {
|
||||||
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": "User authentication data not configured"})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, userInterface := range demoUsers {
|
for _, userInterface := range users {
|
||||||
user, ok := userInterface.(map[string]interface{})
|
user, ok := userInterface.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -630,7 +870,7 @@ func (e *JSONEngine) checkAuthentication(ctx *ExecutionContext, auth *AuthConfig
|
|||||||
// Function executors
|
// Function executors
|
||||||
func (e *JSONEngine) createHTTPFunction(config FunctionConfig) interface{} {
|
func (e *JSONEngine) createHTTPFunction(config FunctionConfig) interface{} {
|
||||||
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
client := &http.Client{Timeout: 30 * time.Second}
|
client := &http.Client{Timeout: getDefaultDuration(e.workflowEngineConfig.ExecutionTimeout, 30*time.Second)}
|
||||||
|
|
||||||
method := config.Method
|
method := config.Method
|
||||||
if method == "" {
|
if method == "" {
|
||||||
@@ -642,9 +882,13 @@ func (e *JSONEngine) createHTTPFunction(config FunctionConfig) interface{} {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers with type assertion
|
||||||
for k, v := range config.Headers {
|
for k, v := range config.Headers {
|
||||||
req.Header.Set(k, v)
|
if vStr, ok := v.(string); ok {
|
||||||
|
req.Header.Set(k, vStr)
|
||||||
|
} else {
|
||||||
|
req.Header.Set(k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@@ -673,8 +917,52 @@ func (e *JSONEngine) createHTTPFunction(config FunctionConfig) interface{} {
|
|||||||
|
|
||||||
func (e *JSONEngine) createExpressionFunction(config FunctionConfig) interface{} {
|
func (e *JSONEngine) createExpressionFunction(config FunctionConfig) interface{} {
|
||||||
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// If there's a response configuration, use it directly
|
||||||
|
if config.Response != nil {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Process response template with data substitution
|
||||||
|
for key, value := range config.Response {
|
||||||
|
if valueStr, ok := value.(string); ok {
|
||||||
|
// Handle template substitution like {{.employees}}, {{.blog_posts}}, etc.
|
||||||
|
if strings.Contains(valueStr, "{{.") {
|
||||||
|
// Extract the data key from the template using regex
|
||||||
|
re := regexp.MustCompile(`\{\{\.(\w+)\}\}`)
|
||||||
|
matches := re.FindAllStringSubmatch(valueStr, -1)
|
||||||
|
|
||||||
|
resultValue := valueStr
|
||||||
|
for _, match := range matches {
|
||||||
|
if len(match) > 1 {
|
||||||
|
dataKey := match[1]
|
||||||
|
if dataValue, exists := e.data[dataKey]; exists {
|
||||||
|
// Replace the template with actual data
|
||||||
|
resultValue = strings.ReplaceAll(resultValue, "{{"+dataKey+"}}", "")
|
||||||
|
result[key] = dataValue
|
||||||
|
} else {
|
||||||
|
result[key] = valueStr // Keep original if data not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultValue != valueStr {
|
||||||
|
// Template was processed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
} else {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Enhanced expression evaluation with JSON parsing and variable substitution
|
// Enhanced expression evaluation with JSON parsing and variable substitution
|
||||||
expression := config.Code
|
expression := config.Code
|
||||||
|
if expression == "" {
|
||||||
|
expression = config.Expression
|
||||||
|
}
|
||||||
|
|
||||||
// Only replace variables that are formatted as placeholders {{variable}} to avoid corrupting JSON
|
// Only replace variables that are formatted as placeholders {{variable}} to avoid corrupting JSON
|
||||||
for key, value := range input {
|
for key, value := range input {
|
||||||
@@ -739,6 +1027,121 @@ func (e *JSONEngine) createJSFunction(config FunctionConfig) interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional generic route handlers for any application type
|
||||||
|
func (e *JSONEngine) handleJSON(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
|
// Handle pure JSON responses
|
||||||
|
response := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Add handler output if specified
|
||||||
|
if routeConfig.Handler.Output != nil {
|
||||||
|
for k, v := range routeConfig.Handler.Output {
|
||||||
|
response[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any data from the handler input
|
||||||
|
if routeConfig.Handler.Input != nil {
|
||||||
|
for k, v := range routeConfig.Handler.Input {
|
||||||
|
response[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add request data if available
|
||||||
|
if ctx.Request.Method() == "POST" || ctx.Request.Method() == "PUT" {
|
||||||
|
var body map[string]interface{}
|
||||||
|
if err := ctx.Request.BodyParser(&body); err == nil {
|
||||||
|
response["request_data"] = body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Request.JSON(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleAPI(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
|
// Generic API handler - can execute functions, workflows, or return configured data
|
||||||
|
target := routeConfig.Handler.Target
|
||||||
|
|
||||||
|
// Check if target is a function
|
||||||
|
if function, exists := e.functions[target]; exists {
|
||||||
|
result, err := e.executeFunction(ctx, function, routeConfig.Handler.Input)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return ctx.Request.JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if target is a workflow
|
||||||
|
if workflow, exists := e.workflows[target]; exists {
|
||||||
|
result, err := e.executeWorkflow(ctx, workflow, routeConfig.Handler.Input)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Request.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return ctx.Request.JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return configured output or input as fallback
|
||||||
|
response := make(map[string]interface{})
|
||||||
|
if routeConfig.Handler.Output != nil {
|
||||||
|
response = routeConfig.Handler.Output
|
||||||
|
} else if routeConfig.Handler.Input != nil {
|
||||||
|
response = routeConfig.Handler.Input
|
||||||
|
} else {
|
||||||
|
response["message"] = "API endpoint: " + target
|
||||||
|
response["method"] = ctx.Request.Method()
|
||||||
|
response["path"] = ctx.Request.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Request.JSON(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleGeneric(ctx *ExecutionContext, routeConfig RouteConfig) error {
|
||||||
|
// Generic handler for unknown types - maximum flexibility
|
||||||
|
log.Printf("Using generic handler for type: %s", routeConfig.Handler.Type)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"handler_type": routeConfig.Handler.Type,
|
||||||
|
"target": routeConfig.Handler.Target,
|
||||||
|
"method": ctx.Request.Method(),
|
||||||
|
"path": ctx.Request.Path(),
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add handler output if available
|
||||||
|
if routeConfig.Handler.Output != nil {
|
||||||
|
response["output"] = routeConfig.Handler.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add handler input if available
|
||||||
|
if routeConfig.Handler.Input != nil {
|
||||||
|
response["input"] = routeConfig.Handler.Input
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add request body for POST/PUT requests
|
||||||
|
if ctx.Request.Method() == "POST" || ctx.Request.Method() == "PUT" {
|
||||||
|
var body map[string]interface{}
|
||||||
|
if err := ctx.Request.BodyParser(&body); err == nil {
|
||||||
|
response["request_body"] = body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
if len(ctx.Request.Queries()) > 0 {
|
||||||
|
response["query_params"] = ctx.Request.Queries()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a function with this handler type as ID
|
||||||
|
if function, exists := e.functions[routeConfig.Handler.Type]; exists {
|
||||||
|
result, err := e.executeFunction(ctx, function, response)
|
||||||
|
if err != nil {
|
||||||
|
response["function_error"] = err.Error()
|
||||||
|
} else {
|
||||||
|
response["function_result"] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Request.JSON(response)
|
||||||
|
}
|
||||||
|
|
||||||
// Middleware creators
|
// Middleware creators
|
||||||
func (e *JSONEngine) createAuthMiddleware(config MiddlewareConfig) fiber.Handler {
|
func (e *JSONEngine) createAuthMiddleware(config MiddlewareConfig) fiber.Handler {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
@@ -998,3 +1401,325 @@ func (e *JSONEngine) executeFunction(ctx *ExecutionContext, function *Function,
|
|||||||
return nil, fmt.Errorf("unknown function handler type")
|
return nil, fmt.Errorf("unknown function handler type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createBuiltinFunction creates handlers for built-in functions
|
||||||
|
func (e *JSONEngine) createBuiltinFunction(config FunctionConfig) interface{} {
|
||||||
|
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
switch config.Handler {
|
||||||
|
case "authenticate":
|
||||||
|
// Handle user authentication
|
||||||
|
username, _ := input["username"].(string)
|
||||||
|
password, _ := input["password"].(string)
|
||||||
|
|
||||||
|
if username == "" || password == "" {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": "Username and password required",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic authentication using user data from configuration
|
||||||
|
// Look for users in multiple possible data keys for flexibility
|
||||||
|
var users []interface{}
|
||||||
|
|
||||||
|
if demoUsers, ok := e.data["demo_users"].([]interface{}); ok {
|
||||||
|
users = demoUsers
|
||||||
|
} else if configUsers, ok := e.data["users"].([]interface{}); ok {
|
||||||
|
users = configUsers
|
||||||
|
} else if authUsers, ok := e.data["auth_users"].([]interface{}); ok {
|
||||||
|
users = authUsers
|
||||||
|
} else {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": "User authentication data not configured",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userInterface := range users {
|
||||||
|
user, ok := userInterface.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userUsername, _ := user["username"].(string)
|
||||||
|
userPassword, _ := user["password"].(string)
|
||||||
|
role, _ := user["role"].(string)
|
||||||
|
|
||||||
|
if userUsername == username && userPassword == password {
|
||||||
|
// Generate simple token (in production, use JWT)
|
||||||
|
token := fmt.Sprintf("token_%s_%d", username, time.Now().Unix())
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"token": token,
|
||||||
|
"user": map[string]interface{}{
|
||||||
|
"username": username,
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": "Invalid credentials",
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "echo":
|
||||||
|
return input, nil
|
||||||
|
case "log":
|
||||||
|
log.Printf("Builtin log: %+v", input)
|
||||||
|
return map[string]interface{}{"logged": true}, nil
|
||||||
|
case "validate":
|
||||||
|
// Simple validation example
|
||||||
|
return map[string]interface{}{"valid": true}, nil
|
||||||
|
case "transform":
|
||||||
|
// Simple data transformation
|
||||||
|
if data, exists := input["data"]; exists {
|
||||||
|
return map[string]interface{}{"transformed": data}, nil
|
||||||
|
}
|
||||||
|
return input, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown builtin function: %s", config.Handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createCustomFunction creates handlers for custom user-defined functions
|
||||||
|
func (e *JSONEngine) createCustomFunction(config FunctionConfig) interface{} {
|
||||||
|
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// Execute custom code from config.Code
|
||||||
|
if config.Code != "" {
|
||||||
|
// For now, just return the configured response or echo input
|
||||||
|
if config.Response != nil {
|
||||||
|
return config.Response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle CRUD operations based on config
|
||||||
|
if config.Config != nil {
|
||||||
|
action, _ := config.Config["action"].(string)
|
||||||
|
entity, _ := config.Config["entity"].(string)
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "create":
|
||||||
|
return e.handleCreateEntity(ctx, entity, input)
|
||||||
|
case "update":
|
||||||
|
return e.handleUpdateEntity(ctx, entity, input)
|
||||||
|
case "delete":
|
||||||
|
return e.handleDeleteEntity(ctx, entity, input)
|
||||||
|
case "get":
|
||||||
|
return e.handleGetEntity(ctx, entity, input)
|
||||||
|
case "list":
|
||||||
|
return e.handleListEntity(ctx, entity, input)
|
||||||
|
case "send_campaign":
|
||||||
|
return e.handleSendCampaign(ctx, input)
|
||||||
|
case "publish":
|
||||||
|
return e.handlePublishEntity(ctx, entity, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple key-value transformation based on config
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range input {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply any transformations from config
|
||||||
|
if config.Config != nil {
|
||||||
|
for key, value := range config.Config {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRUD operation handlers
|
||||||
|
func (e *JSONEngine) handleCreateEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
switch entity {
|
||||||
|
case "employee":
|
||||||
|
// Create new employee
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "Employee created successfully",
|
||||||
|
"id": time.Now().Unix(), // Simple ID generation
|
||||||
|
"data": input,
|
||||||
|
}, nil
|
||||||
|
case "post":
|
||||||
|
// Create new blog post
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "Blog post created successfully",
|
||||||
|
"id": time.Now().Unix(),
|
||||||
|
"data": input,
|
||||||
|
}, nil
|
||||||
|
case "email":
|
||||||
|
// Create email campaign
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "Email campaign created successfully",
|
||||||
|
"id": time.Now().Unix(),
|
||||||
|
"data": input,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("%s created successfully", entity),
|
||||||
|
"id": time.Now().Unix(),
|
||||||
|
"data": input,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleUpdateEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
id, _ := input["id"].(string)
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("%s updated successfully", entity),
|
||||||
|
"id": id,
|
||||||
|
"data": input,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleDeleteEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
id, _ := input["id"].(string)
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("%s deleted successfully", entity),
|
||||||
|
"id": id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleGetEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// Get entity ID from input
|
||||||
|
var entityID string
|
||||||
|
if idVal, ok := input["id"]; ok {
|
||||||
|
entityID = fmt.Sprintf("%v", idVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entityID == "" {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": entity + " ID is required",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up entity data from configuration
|
||||||
|
entityDataKey := entity + "s" // Assume plural form (employees, posts, etc.)
|
||||||
|
if entityData, ok := e.data[entityDataKey]; ok {
|
||||||
|
if entityList, ok := entityData.([]interface{}); ok {
|
||||||
|
for _, item := range entityList {
|
||||||
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||||
|
if itemIDVal, ok := itemMap["id"]; ok {
|
||||||
|
itemIDStr := fmt.Sprintf("%v", itemIDVal)
|
||||||
|
if itemIDStr == entityID {
|
||||||
|
// Found the entity, return it with all required data
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Add the entity with singular name
|
||||||
|
result[entity] = itemMap
|
||||||
|
|
||||||
|
// Add other required data for the template
|
||||||
|
if appTitle, ok := e.data["app_title"]; ok {
|
||||||
|
result["app_title"] = appTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any other data that might be needed by templates
|
||||||
|
for key, value := range e.data {
|
||||||
|
if key != entityDataKey { // Don't duplicate the main entity data
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": entity + " not found",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleListEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// Look up entity data from configuration using plural form
|
||||||
|
entityDataKey := entity + "s" // Assume plural form (employees, posts, etc.)
|
||||||
|
if entityData, ok := e.data[entityDataKey]; ok {
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
}
|
||||||
|
result[entityDataKey] = entityData
|
||||||
|
|
||||||
|
// Add other data that might be needed
|
||||||
|
for key, value := range e.data {
|
||||||
|
if key != entityDataKey {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no data found, return empty result
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
entity + "s": []interface{}{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handleSendCampaign(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"campaign_id": fmt.Sprintf("campaign_%d", time.Now().Unix()),
|
||||||
|
"emails_sent": 10, // Mock value
|
||||||
|
"status": "sent",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONEngine) handlePublishEntity(ctx *ExecutionContext, entity string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
id, _ := input["id"].(string)
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("%s published successfully", entity),
|
||||||
|
"id": id,
|
||||||
|
"status": "published",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGenericFunction creates a generic function handler for unknown types
|
||||||
|
func (e *JSONEngine) createGenericFunction(config FunctionConfig) interface{} {
|
||||||
|
return func(ctx *ExecutionContext, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
log.Printf("Executing generic function: %s with type: %s", config.ID, config.Type)
|
||||||
|
|
||||||
|
// For unknown function types, we create a flexible handler that:
|
||||||
|
// 1. Returns configured response if available
|
||||||
|
if config.Response != nil {
|
||||||
|
return config.Response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Applies any transformations from config
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range input {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Config != nil {
|
||||||
|
for key, value := range config.Config {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Add metadata about the function execution
|
||||||
|
result["_function_id"] = config.ID
|
||||||
|
result["_function_type"] = config.Type
|
||||||
|
result["_executed_at"] = time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func loadConfiguration(configPath string) (*AppConfiguration, error) {
|
||||||
|
data, err := ioutil.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var config AppConfiguration
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse JSON config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Parse command line flags
|
// Parse command line flags
|
||||||
configPath := flag.String("config", "sms-app.json", "Path to JSON configuration file")
|
configPath := flag.String("config", "sms-app.json", "Path to JSON configuration file")
|
||||||
@@ -16,14 +33,15 @@ func main() {
|
|||||||
*configPath = os.Args[1]
|
*configPath = os.Args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create JSON engine
|
// Load configuration first
|
||||||
engine := NewJSONEngine()
|
config, err := loadConfiguration(*configPath)
|
||||||
|
if err != nil {
|
||||||
// Load configuration
|
|
||||||
if err := engine.LoadConfiguration(*configPath); err != nil {
|
|
||||||
log.Fatalf("Failed to load configuration: %v", err)
|
log.Fatalf("Failed to load configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create JSON engine with configuration
|
||||||
|
engine := NewJSONEngine(config)
|
||||||
|
|
||||||
// Compile configuration
|
// Compile configuration
|
||||||
if err := engine.Compile(); err != nil {
|
if err := engine.Compile(); err != nil {
|
||||||
log.Fatalf("Failed to compile configuration: %v", err)
|
log.Fatalf("Failed to compile configuration: %v", err)
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
// AppConfiguration represents the complete JSON configuration for an application
|
// AppConfiguration represents the complete JSON configuration for an application
|
||||||
type AppConfiguration struct {
|
type AppConfiguration struct {
|
||||||
App AppMetadata `json:"app"`
|
App AppMetadata `json:"app"`
|
||||||
|
WorkflowEngine *WorkflowEngineConfig `json:"workflow_engine,omitempty"`
|
||||||
Routes []RouteConfig `json:"routes"`
|
Routes []RouteConfig `json:"routes"`
|
||||||
Middleware []MiddlewareConfig `json:"middleware"`
|
Middleware []MiddlewareConfig `json:"middleware"`
|
||||||
Templates map[string]TemplateConfig `json:"templates"`
|
Templates map[string]TemplateConfig `json:"templates"`
|
||||||
@@ -26,6 +27,30 @@ type AppMetadata struct {
|
|||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkflowEngineConfig contains workflow engine configuration
|
||||||
|
type WorkflowEngineConfig struct {
|
||||||
|
MaxWorkers int `json:"max_workers,omitempty"`
|
||||||
|
ExecutionTimeout string `json:"execution_timeout,omitempty"`
|
||||||
|
EnableMetrics bool `json:"enable_metrics,omitempty"`
|
||||||
|
EnableAudit bool `json:"enable_audit,omitempty"`
|
||||||
|
EnableTracing bool `json:"enable_tracing,omitempty"`
|
||||||
|
LogLevel string `json:"log_level,omitempty"`
|
||||||
|
Storage StorageConfig `json:"storage,omitempty"`
|
||||||
|
Security SecurityConfig `json:"security,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageConfig contains storage configuration
|
||||||
|
type StorageConfig struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
MaxConnections int `json:"max_connections,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecurityConfig contains security configuration
|
||||||
|
type SecurityConfig struct {
|
||||||
|
EnableAuth bool `json:"enable_auth,omitempty"`
|
||||||
|
AllowedOrigins []string `json:"allowed_origins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// RouteConfig defines HTTP routes
|
// RouteConfig defines HTTP routes
|
||||||
type RouteConfig struct {
|
type RouteConfig struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@@ -48,6 +73,7 @@ type ResponseConfig struct {
|
|||||||
type HandlerConfig struct {
|
type HandlerConfig struct {
|
||||||
Type string `json:"type"` // "workflow", "template", "function", "redirect"
|
Type string `json:"type"` // "workflow", "template", "function", "redirect"
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
|
Template string `json:"template,omitempty"`
|
||||||
Input map[string]interface{} `json:"input,omitempty"`
|
Input map[string]interface{} `json:"input,omitempty"`
|
||||||
Output map[string]interface{} `json:"output,omitempty"`
|
Output map[string]interface{} `json:"output,omitempty"`
|
||||||
ErrorHandling *ErrorHandlingConfig `json:"error_handling,omitempty"`
|
ErrorHandling *ErrorHandlingConfig `json:"error_handling,omitempty"`
|
||||||
@@ -177,47 +203,45 @@ type JSONSchemaConfig struct {
|
|||||||
Output map[string]interface{} `json:"output,omitempty"`
|
Output map[string]interface{} `json:"output,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionConfig defines custom functions
|
// FunctionConfig defines custom functions with complete flexibility
|
||||||
type FunctionConfig struct {
|
type FunctionConfig struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Type string `json:"type"` // "builtin", "custom", "external", "http"
|
Type string `json:"type"` // "http", "expression", "template", "js", "builtin"
|
||||||
Handler string `json:"handler,omitempty"`
|
Handler string `json:"handler,omitempty"`
|
||||||
Method string `json:"method,omitempty"` // For HTTP functions
|
Method string `json:"method,omitempty"` // For HTTP functions
|
||||||
URL string `json:"url,omitempty"` // For HTTP functions
|
URL string `json:"url,omitempty"` // For HTTP functions
|
||||||
Headers map[string]string `json:"headers,omitempty"` // For HTTP functions
|
Headers map[string]interface{} `json:"headers,omitempty"` // For HTTP functions
|
||||||
|
Body string `json:"body,omitempty"` // For HTTP request body template
|
||||||
Code string `json:"code,omitempty"` // For custom code functions
|
Code string `json:"code,omitempty"` // For custom code functions
|
||||||
Parameters []ParameterConfig `json:"parameters,omitempty"`
|
Template string `json:"template,omitempty"` // For template functions
|
||||||
Returns []ParameterConfig `json:"returns,omitempty"`
|
Expression string `json:"expression,omitempty"` // For expression functions
|
||||||
|
Parameters map[string]interface{} `json:"parameters,omitempty"` // Generic parameters
|
||||||
|
Returns map[string]interface{} `json:"returns,omitempty"` // Generic return definition
|
||||||
|
Response map[string]interface{} `json:"response,omitempty"` // Response structure
|
||||||
Config map[string]interface{} `json:"config,omitempty"`
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
Async bool `json:"async"`
|
Async bool `json:"async"`
|
||||||
Timeout string `json:"timeout,omitempty"`
|
Timeout string `json:"timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParameterConfig defines function parameters
|
// Note: ParameterConfig removed - using generic map[string]interface{} for parameters
|
||||||
type ParameterConfig struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
Default interface{} `json:"default,omitempty"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Validation []string `json:"validation,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatorConfig defines validation rules
|
// ValidatorConfig defines validation rules with complete flexibility
|
||||||
type ValidatorConfig struct {
|
type ValidatorConfig struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type"` // "jsonschema", "custom", "regex"
|
Type string `json:"type"` // "jsonschema", "custom", "regex", "builtin"
|
||||||
Field string `json:"field,omitempty"`
|
Field string `json:"field,omitempty"`
|
||||||
Schema interface{} `json:"schema,omitempty"`
|
Schema interface{} `json:"schema,omitempty"`
|
||||||
Rules []ValidationRule `json:"rules,omitempty"`
|
Rules map[string]interface{} `json:"rules,omitempty"` // Generic rules
|
||||||
Messages map[string]string `json:"messages,omitempty"`
|
Messages map[string]string `json:"messages,omitempty"`
|
||||||
|
Expression string `json:"expression,omitempty"` // For expression-based validation
|
||||||
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
StrictMode bool `json:"strict_mode"`
|
StrictMode bool `json:"strict_mode"`
|
||||||
AllowEmpty bool `json:"allow_empty"`
|
AllowEmpty bool `json:"allow_empty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidationRule defines individual validation rules
|
// ValidationRule defines individual validation rules with flexibility
|
||||||
type ValidationRule struct {
|
type ValidationRule struct {
|
||||||
Field string `json:"field"`
|
Field string `json:"field"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -225,46 +249,18 @@ type ValidationRule struct {
|
|||||||
Min interface{} `json:"min,omitempty"`
|
Min interface{} `json:"min,omitempty"`
|
||||||
Max interface{} `json:"max,omitempty"`
|
Max interface{} `json:"max,omitempty"`
|
||||||
Pattern string `json:"pattern,omitempty"`
|
Pattern string `json:"pattern,omitempty"`
|
||||||
|
Expression string `json:"expression,omitempty"` // For custom expressions
|
||||||
CustomRule string `json:"custom_rule,omitempty"`
|
CustomRule string `json:"custom_rule,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
Conditions []ConditionConfig `json:"conditions,omitempty"`
|
Conditions []ConditionConfig `json:"conditions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider configuration for SMS/communication services
|
// Generic runtime types for the JSON engine
|
||||||
type ProviderConfig struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"` // "sms", "email", "push"
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Priority int `json:"priority"`
|
|
||||||
Config map[string]interface{} `json:"config"`
|
|
||||||
Countries []string `json:"countries,omitempty"`
|
|
||||||
RateLimit *RateLimitConfig `json:"rate_limit,omitempty"`
|
|
||||||
Costs map[string]float64 `json:"costs,omitempty"`
|
|
||||||
Features []string `json:"features,omitempty"`
|
|
||||||
Reliability float64 `json:"reliability"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RateLimitConfig defines rate limiting
|
|
||||||
type RateLimitConfig struct {
|
|
||||||
RequestsPerSecond int `json:"requests_per_second"`
|
|
||||||
BurstSize int `json:"burst_size"`
|
|
||||||
WindowSize int `json:"window_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Country configuration for routing
|
|
||||||
type CountryConfig struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Providers []string `json:"providers"`
|
|
||||||
DefaultRate float64 `json:"default_rate"`
|
|
||||||
Regulations map[string]string `json:"regulations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtime types for the JSON engine
|
|
||||||
type JSONEngine struct {
|
type JSONEngine struct {
|
||||||
app *fiber.App
|
app *fiber.App
|
||||||
workflowEngine *workflow.WorkflowEngine
|
workflowEngine *workflow.WorkflowEngine
|
||||||
|
workflowEngineConfig *WorkflowEngineConfig
|
||||||
config *AppConfiguration
|
config *AppConfiguration
|
||||||
templates map[string]*Template
|
templates map[string]*Template
|
||||||
workflows map[string]*Workflow
|
workflows map[string]*Workflow
|
||||||
@@ -272,6 +268,7 @@ type JSONEngine struct {
|
|||||||
validators map[string]*Validator
|
validators map[string]*Validator
|
||||||
middleware map[string]*Middleware
|
middleware map[string]*Middleware
|
||||||
data map[string]interface{}
|
data map[string]interface{}
|
||||||
|
genericData map[string]interface{} // For any custom data structures
|
||||||
}
|
}
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
@@ -303,16 +300,20 @@ type Edge struct {
|
|||||||
To *Node
|
To *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function represents a compiled function with generic handler
|
||||||
type Function struct {
|
type Function struct {
|
||||||
ID string
|
ID string
|
||||||
Config FunctionConfig
|
Config FunctionConfig
|
||||||
Handler interface{}
|
Handler interface{} // Can be any type of handler
|
||||||
|
Runtime map[string]interface{} // Runtime state/context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validator represents a compiled validator with generic rules
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
ID string
|
ID string
|
||||||
Config ValidatorConfig
|
Config ValidatorConfig
|
||||||
Rules []ValidationRule
|
Rules map[string]interface{} // Generic rules instead of typed array
|
||||||
|
Runtime map[string]interface{} // Runtime context
|
||||||
}
|
}
|
||||||
|
|
||||||
type Middleware struct {
|
type Middleware struct {
|
||||||
@@ -328,7 +329,7 @@ type WorkflowRuntime struct {
|
|||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execution context for runtime
|
// ExecutionContext for runtime with complete flexibility
|
||||||
type ExecutionContext struct {
|
type ExecutionContext struct {
|
||||||
Request *fiber.Ctx
|
Request *fiber.Ctx
|
||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
@@ -339,4 +340,7 @@ type ExecutionContext struct {
|
|||||||
Node *Node
|
Node *Node
|
||||||
Functions map[string]*Function
|
Functions map[string]*Function
|
||||||
Validators map[string]*Validator
|
Validators map[string]*Validator
|
||||||
|
Config *AppConfiguration // Access to full config
|
||||||
|
Runtime map[string]interface{} // Runtime state
|
||||||
|
Context map[string]interface{} // Additional context data
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Reference in New Issue
Block a user