From 970db890eb5192a5ed1e6c05547de13b237218ac Mon Sep 17 00:00:00 2001 From: sujit Date: Tue, 5 Aug 2025 06:47:51 +0545 Subject: [PATCH] update --- examples/app/server.go | 5 +- examples/email/contact-form.html | 190 ++++++++ examples/email/error.html | 134 +++++ examples/email/success.html | 210 ++++++++ examples/email_notification_dag.go | 461 +----------------- .../schema.go | 54 +- 6 files changed, 588 insertions(+), 466 deletions(-) create mode 100644 examples/email/contact-form.html create mode 100644 examples/email/error.html create mode 100644 examples/email/success.html rename examples/app/jsonschema_renderer.go => renderer/schema.go (86%) diff --git a/examples/app/server.go b/examples/app/server.go index e204230..c2dd27f 100644 --- a/examples/app/server.go +++ b/examples/app/server.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" "os" + + "github.com/oarkflow/mq/renderer" ) func main() { @@ -18,6 +20,7 @@ func main() { fmt.Printf("Error parsing schema: %v\n", err) return } + http.Handle("/form.css", http.FileServer(http.Dir("templates"))) http.HandleFunc("/render", func(w http.ResponseWriter, r *http.Request) { templateName := r.URL.Query().Get("template") @@ -32,7 +35,7 @@ func main() { return } - renderer := NewJSONSchemaRenderer(schema, string(htmlLayout)) + renderer := renderer.NewJSONSchemaRenderer(schema, string(htmlLayout)) // Set template data for dynamic interpolation templateData := map[string]interface{}{ diff --git a/examples/email/contact-form.html b/examples/email/contact-form.html new file mode 100644 index 0000000..d74180d --- /dev/null +++ b/examples/email/contact-form.html @@ -0,0 +1,190 @@ + + + + + Contact Us - Email Notification System + + + + +
+

📧 Contact Us

+
Advanced Email Notification System with DAG Workflow
+ +
+

🔄 Smart Routing: Our system automatically routes your message based on your user type + and preferences.

+
+ +
+
+ 📱 Instant Notifications
+ Real-time email delivery +
+
+ 🎯 Smart Targeting
+ User-specific content +
+
+ 🔒 Secure Processing
+ Enterprise-grade security +
+
+ +
+
+ {{form_fields}} +
+ + +
+
+ + + diff --git a/examples/email/error.html b/examples/email/error.html new file mode 100644 index 0000000..9935457 --- /dev/null +++ b/examples/email/error.html @@ -0,0 +1,134 @@ + + + + + Email Error + + + + +
+
+

Email Processing Error

+ +
+ {{error_message}} +
+ + {{if error_field}} +
+ 🎯 Error Field: {{error_field}}
+ ⚡ Action Required: Please correct the highlighted field and try again.
+ 💡 Tip: Make sure all required fields are properly filled out. +
+ {{end}} + + {{if retry_suggested}} +
+ ⚠️ Temporary Issue: This appears to be a temporary system issue. + Please try sending your message again in a few moments.
+ 🔄 Auto-Retry: Our system will automatically retry failed deliveries. +
+ {{end}} + + + +
+ 🔄 DAG Error Handler | Email Notification Workflow Failed
+ Our advanced routing system ensures reliable message delivery. +
+
+ + + diff --git a/examples/email/success.html b/examples/email/success.html new file mode 100644 index 0000000..f26e89d --- /dev/null +++ b/examples/email/success.html @@ -0,0 +1,210 @@ + + + + + Message Sent Successfully + + + + +
+
+

Message Sent Successfully!

+ +
{{email_status}}
+ +
+
+
👤 Recipient
+
{{full_name}}
+
+
+
📧 Email Address
+
{{email}}
+
+
+
🆔 Email ID
+
{{email_id}}
+
+
+
⏰ Sent At
+
{{sent_at}}
+
+
+
📨 Email Type
+
{{email_type}}
+
+
+
👥 User Type
+
{{user_type}}
+
+
+
🚨 Priority
+
{{priority}}
+
+
+
🚚 Delivery
+
{{delivery_estimate}}
+
+
+ +
+
📋 Subject:
+
+ {{subject}} +
+
💬 Message ({{message_length}} chars):
+
+ "{{message}}" +
+
+ + + +
+ 🔄 Workflow Details:
+ Gateway: {{gateway}} | Template: {{email_template}} | Processed: {{processed_at}}
+ This message was processed through our advanced DAG workflow system with conditional routing. +
+
+ + + diff --git a/examples/email_notification_dag.go b/examples/email_notification_dag.go index 72cff23..7a6dfb4 100644 --- a/examples/email_notification_dag.go +++ b/examples/email_notification_dag.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "os" "regexp" "sort" "strings" @@ -20,145 +21,19 @@ import ( ) func main() { - contactFormSchema := map[string]any{ - "properties": map[string]any{ - "first_name": map[string]any{ - "type": "string", - "title": "👤 First Name", - "order": 1, - "ui": map[string]any{ - "control": "input", - "class": "form-group", - "name": "first_name", - }, - }, - "last_name": map[string]any{ - "type": "string", - "title": "👤 Last Name", - "order": 2, - "ui": map[string]any{ - "control": "input", - "class": "form-group", - "name": "last_name", - }, - }, - "email": map[string]any{ - "type": "email", - "title": "📧 Email Address", - "order": 3, - "ui": map[string]any{ - "control": "input", - "class": "form-group", - "name": "email", - }, - }, - "user_type": map[string]any{ - "type": "string", - "title": "👥 User Type", - "order": 4, - "ui": map[string]any{ - "control": "select", - "class": "form-group", - "name": "user_type", - "options": []any{"new", "premium", "standard"}, - }, - }, - "priority": map[string]any{ - "type": "string", - "title": "🚨 Priority Level", - "order": 5, - "ui": map[string]any{ - "control": "select", - "class": "form-group", - "name": "priority", - "options": []any{"low", "medium", "high", "urgent"}, - }, - }, - "subject": map[string]any{ - "type": "string", - "title": "📋 Subject", - "order": 6, - "ui": map[string]any{ - "control": "input", - "class": "form-group", - "name": "subject", - }, - }, - "message": map[string]any{ - "type": "textarea", - "title": "💬 Message", - "order": 7, - "ui": map[string]any{ - "control": "textarea", - "class": "form-group", - "name": "message", - }, - }, - }, - "required": []any{"first_name", "last_name", "email", "user_type", "priority", "subject", "message"}, + var contactFormSchema = map[string]any{} + content, err := os.ReadFile("app/schema.json") + if err != nil { + panic(err) + } + if err := json.Unmarshal(content, &contactFormSchema); err != nil { + panic(fmt.Errorf("failed to parse JSON schema: %w", err)) } - contactFormLayout := ` - - - - Contact Us - Email Notification System - - - -
-

📧 Contact Us

-
Advanced Email Notification System with DAG Workflow
- -
-

🔄 Smart Routing: Our system automatically routes your message based on your user type and preferences.

-
- -
-
- 📱 Instant Notifications
- Real-time email delivery -
-
- 🎯 Smart Targeting
- User-specific content -
-
- 🔒 Secure Processing
- Enterprise-grade security -
-
- -
-
- {{form_fields}} -
- - -
-
- -` + contactFormLayout, err := os.ReadFile("email/contact-form.html") + if err != nil { + panic(err) + } flow := dag.NewDAG("Email Notification System", "email-notification", func(taskID string, result mq.Result) { fmt.Printf("Email notification workflow completed for task %s: %s\n", taskID, string(utils.RemoveRecursiveFromJSON(result.Payload, "html_content"))) @@ -166,7 +41,7 @@ func main() { // Add workflow nodes // Note: Page nodes have no timeout by default, allowing users unlimited time for form input - flow.AddNode(dag.Page, "Contact Form", "ContactForm", &ConfigurableFormNode{Schema: contactFormSchema, HTMLLayout: contactFormLayout}, true) + flow.AddNode(dag.Page, "Contact Form", "ContactForm", &ConfigurableFormNode{Schema: contactFormSchema, HTMLLayout: string(contactFormLayout)}, true) flow.AddNode(dag.Function, "Validate Contact Data", "ValidateContact", &ValidateContactNode{}) flow.AddNode(dag.Function, "Check User Type", "CheckUserType", &CheckUserTypeNode{}) flow.AddNode(dag.Function, "Send Welcome Email", "SendWelcomeEmail", &SendWelcomeEmailNode{}) @@ -631,190 +506,13 @@ func (s *SuccessPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq.Res return mq.Result{Error: err, Ctx: ctx} } - htmlTemplate := ` - - - - Message Sent Successfully - - - -
-
-

Message Sent Successfully!

- -
{{email_status}}
- -
-
-
👤 Recipient
-
{{full_name}}
-
-
-
📧 Email Address
-
{{email}}
-
-
-
🆔 Email ID
-
{{email_id}}
-
-
-
⏰ Sent At
-
{{sent_at}}
-
-
-
📨 Email Type
-
{{email_type}}
-
-
-
👥 User Type
-
{{user_type}}
-
-
-
🚨 Priority
-
{{priority}}
-
-
-
🚚 Delivery
-
{{delivery_estimate}}
-
-
- -
-
📋 Subject:
-
- {{subject}} -
-
💬 Message ({{message_length}} chars):
-
- "{{message}}" -
-
- - - -
- 🔄 Workflow Details:
- Gateway: {{gateway}} | Template: {{email_template}} | Processed: {{processed_at}}
- This message was processed through our advanced DAG workflow system with conditional routing. -
-
- -` + htmlTemplate, err := os.ReadFile("email/success.html") + if err != nil { + return mq.Result{Error: fmt.Errorf("failed to read success template: %v", err)} + } parser := jet.NewWithMemory(jet.WithDelims("{{", "}}")) - rs, err := parser.ParseTemplate(htmlTemplate, inputData) + rs, err := parser.ParseTemplate(string(htmlTemplate), inputData) if err != nil { return mq.Result{Error: err, Ctx: ctx} } @@ -853,123 +551,10 @@ func (e *EmailErrorPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq. errorMessage = "An unknown error occurred" } - htmlTemplate := ` - - - - Email Error - - - -
-
-

Email Processing Error

- -
- {{error_message}} -
- - {{if error_field}} -
- 🎯 Error Field: {{error_field}}
- ⚡ Action Required: Please correct the highlighted field and try again.
- 💡 Tip: Make sure all required fields are properly filled out. -
- {{end}} - - {{if retry_suggested}} -
- ⚠️ Temporary Issue: This appears to be a temporary system issue. - Please try sending your message again in a few moments.
- 🔄 Auto-Retry: Our system will automatically retry failed deliveries. -
- {{end}} - - - -
- 🔄 DAG Error Handler | Email Notification Workflow Failed
- Our advanced routing system ensures reliable message delivery. -
-
- -` + htmlTemplate, err := os.ReadFile("email/error.html") + if err != nil { + return mq.Result{Error: fmt.Errorf("failed to read error template: %v", err)} + } parser := jet.NewWithMemory(jet.WithDelims("{{", "}}")) templateData := map[string]any{ @@ -978,7 +563,7 @@ func (e *EmailErrorPageNode) ProcessTask(ctx context.Context, task *mq.Task) mq. "retry_suggested": inputData["retry_suggested"], } - rs, err := parser.ParseTemplate(htmlTemplate, templateData) + rs, err := parser.ParseTemplate(string(htmlTemplate), templateData) if err != nil { return mq.Result{Error: err, Ctx: ctx} } diff --git a/examples/app/jsonschema_renderer.go b/renderer/schema.go similarity index 86% rename from examples/app/jsonschema_renderer.go rename to renderer/schema.go index 3972acf..d1f07dc 100644 --- a/examples/app/jsonschema_renderer.go +++ b/renderer/schema.go @@ -1,4 +1,4 @@ -package main +package renderer import ( "bytes" @@ -11,7 +11,7 @@ import ( type FieldInfo struct { Name string Order int - Definition map[string]interface{} + Definition map[string]any } // GroupInfo represents metadata for a group extracted from JSONSchema @@ -29,23 +29,23 @@ type GroupTitle struct { // JSONSchemaRenderer is responsible for rendering HTML fields based on JSONSchema type JSONSchemaRenderer struct { - Schema map[string]interface{} + Schema map[string]any HTMLLayout string - TemplateData map[string]interface{} // Data for template interpolation - cachedHTML string // Cached rendered HTML + TemplateData map[string]any // Data for template interpolation + cachedHTML string // Cached rendered HTML } // NewJSONSchemaRenderer creates a new instance of JSONSchemaRenderer -func NewJSONSchemaRenderer(schema map[string]interface{}, htmlLayout string) *JSONSchemaRenderer { +func NewJSONSchemaRenderer(schema map[string]any, htmlLayout string) *JSONSchemaRenderer { return &JSONSchemaRenderer{ Schema: schema, HTMLLayout: htmlLayout, - TemplateData: make(map[string]interface{}), + TemplateData: make(map[string]any), } } // SetTemplateData sets the data used for template interpolation -func (r *JSONSchemaRenderer) SetTemplateData(data map[string]interface{}) { +func (r *JSONSchemaRenderer) SetTemplateData(data map[string]any) { r.TemplateData = data } @@ -93,7 +93,7 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) { groupHTML.WriteString(renderGroup(group)) } - formConfig := r.Schema["form"].(map[string]interface{}) + formConfig := r.Schema["form"].(map[string]any) formClass, _ := formConfig["class"].(string) formAction, _ := formConfig["action"].(string) formMethod, _ := formConfig["method"].(string) @@ -128,20 +128,20 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) { } // parseGroupsFromSchema extracts and sorts groups and fields from schema -func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo { - formConfig, ok := schema["form"].(map[string]interface{}) +func parseGroupsFromSchema(schema map[string]any) []GroupInfo { + formConfig, ok := schema["form"].(map[string]any) if !ok { return nil } - properties, ok := schema["properties"].(map[string]interface{}) + properties, ok := schema["properties"].(map[string]any) if !ok { return nil } // Get required fields from schema root var requiredFields map[string]bool = make(map[string]bool) - if reqFields, ok := schema["required"].([]interface{}); ok { + if reqFields, ok := schema["required"].([]any); ok { for _, field := range reqFields { if fieldName, ok := field.(string); ok { requiredFields[fieldName] = true @@ -150,12 +150,12 @@ func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo { } var groups []GroupInfo - for _, group := range formConfig["groups"].([]interface{}) { - groupMap := group.(map[string]interface{}) + for _, group := range formConfig["groups"].([]any) { + groupMap := group.(map[string]any) // Parse group title var groupTitle GroupTitle - if titleMap, ok := groupMap["title"].(map[string]interface{}); ok { + if titleMap, ok := groupMap["title"].(map[string]any); ok { if text, ok := titleMap["text"].(string); ok { groupTitle.Text = text } @@ -168,15 +168,15 @@ func parseGroupsFromSchema(schema map[string]interface{}) []GroupInfo { groupClass, _ := groupMap["class"].(string) var fields []FieldInfo - for _, fieldName := range groupMap["fields"].([]interface{}) { - fieldDef := properties[fieldName.(string)].(map[string]interface{}) + for _, fieldName := range groupMap["fields"].([]any) { + fieldDef := properties[fieldName.(string)].(map[string]any) order := 0 if ord, exists := fieldDef["order"].(int); exists { order = ord } // Add required field info to field definition - fieldDefCopy := make(map[string]interface{}) + fieldDefCopy := make(map[string]any) for k, v := range fieldDef { fieldDefCopy[k] = v } @@ -243,7 +243,7 @@ var fieldTemplates = map[string]string{ } func renderField(field FieldInfo) string { - ui, ok := field.Definition["ui"].(map[string]interface{}) + ui, ok := field.Definition["ui"].(map[string]any) if !ok { return "" } @@ -279,7 +279,7 @@ func renderField(field FieldInfo) string { } } - data := map[string]interface{}{ + data := map[string]any{ "Class": class, "Name": name, "Title": template.HTML(titleHTML), @@ -302,7 +302,7 @@ func renderField(field FieldInfo) string { tmpl.Execute(&buf, data) return buf.String() case "select": - options, _ := ui["options"].([]interface{}) + options, _ := ui["options"].([]any) var optionsHTML bytes.Buffer for _, option := range options { optionsHTML.WriteString(fmt.Sprintf(``, option, option)) @@ -338,10 +338,10 @@ func renderField(field FieldInfo) string { // Template for buttons var buttonTemplate = `` -func renderButtons(formConfig map[string]interface{}) string { +func renderButtons(formConfig map[string]any) string { var buttonsHTML bytes.Buffer - if submitConfig, ok := formConfig["submit"].(map[string]interface{}); ok { - data := map[string]interface{}{ + if submitConfig, ok := formConfig["submit"].(map[string]any); ok { + data := map[string]any{ "Type": submitConfig["type"], "Class": submitConfig["class"], "Label": submitConfig["label"], @@ -349,8 +349,8 @@ func renderButtons(formConfig map[string]interface{}) string { tmpl := template.Must(template.New("button").Parse(buttonTemplate)) tmpl.Execute(&buttonsHTML, data) } - if resetConfig, ok := formConfig["reset"].(map[string]interface{}); ok { - data := map[string]interface{}{ + if resetConfig, ok := formConfig["reset"].(map[string]any); ok { + data := map[string]any{ "Type": resetConfig["type"], "Class": resetConfig["class"], "Label": resetConfig["label"],