package main
import (
"context"
"encoding/json"
"fmt"
"github.com/oarkflow/jet"
"log"
"math/rand"
"net/http"
"strings"
"time"
)
const (
PageType = "page"
FunctionType = "function"
)
type Node interface {
ProcessTask(ctx context.Context, task *Task) Result
GetNodeType() string
}
type Result struct {
ConditionStatus string
Payload json.RawMessage
Error error
}
type Task struct {
ID string
CurrentNodeID string
Payload json.RawMessage
FinalResult string
}
type PageNode struct {
ID string
Content string
}
func (n *PageNode) ProcessTask(ctx context.Context, task *Task) Result {
var data map[string]any
if task.Payload != nil {
err := json.Unmarshal(task.Payload, &data)
if err != nil {
return Result{Error: err}
}
}
if data == nil {
data = make(map[string]any)
}
parser := jet.NewWithMemory(jet.WithDelims("{{", "}}"))
data["taskID"] = task.ID
tmpl := fmt.Sprintf("%s
{{request_data|writeJson}}
", n.Content) rs, err := parser.ParseTemplate(tmpl, map[string]any{ "request_data": data, "taskID": task.ID, }) fmt.Println(rs, err, data) if err != nil { return Result{Error: err} } return Result{Payload: []byte(rs)} } func (n *PageNode) GetNodeType() string { return PageType } type FunctionNode struct { ID string Func func(task *Task) Result } func (n *FunctionNode) ProcessTask(ctx context.Context, task *Task) Result { return n.Func(task) } func (n *FunctionNode) GetNodeType() string { return FunctionType } type Graph struct { Nodes map[string]Node Edges map[string][]string } func NewGraph() *Graph { return &Graph{ Nodes: make(map[string]Node), Edges: make(map[string][]string), } } func (g *Graph) AddNode(node Node) { switch n := node.(type) { case *PageNode: g.Nodes[n.ID] = n case *FunctionNode: g.Nodes[n.ID] = n } } func (g *Graph) AddEdge(fromID, toID string) { g.Edges[fromID] = append(g.Edges[fromID], toID) } type TaskManager struct { tasks map[string]*Task graph *Graph } func NewTaskManager(graph *Graph) *TaskManager { return &TaskManager{ tasks: make(map[string]*Task), graph: graph, } } func (tm *TaskManager) GetTask(taskID string) (*Task, bool) { task, exists := tm.tasks[taskID] return task, exists } func (tm *TaskManager) StartTask() *Task { taskID := generateTaskID() task := &Task{ ID: taskID, CurrentNodeID: "customRegistration", } tm.tasks[taskID] = task return task } func (tm *TaskManager) UpdateTask(task *Task) { tm.tasks[task.ID] = task } func (tm *TaskManager) GetNextNode(task *Task) (Node, bool) { if task == nil { return nil, false } edges, _ := tm.graph.Edges[task.CurrentNodeID] if len(edges) > 0 { nextNodeID := edges[0] nextNode, exists := tm.graph.Nodes[nextNodeID] if exists { return nextNode, true } } return nil, false } func generateTaskID() string { rand.Seed(time.Now().UnixNano()) const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var result strings.Builder for i := 0; i < 8; i++ { result.WriteByte(charset[rand.Intn(len(charset))]) } return result.String() } func processNode(w http.ResponseWriter, r *http.Request, task *Task, tm *TaskManager) { for { log.Printf("Processing taskID: %s, Current Node: %s", task.ID, task.CurrentNodeID) node, exists := tm.graph.Nodes[task.CurrentNodeID] if !exists { http.Error(w, "Node not found", http.StatusInternalServerError) return } result := node.ProcessTask(context.Background(), task) log.Printf("Node %s processed. Result ConditionStatus: %s", task.CurrentNodeID, result.ConditionStatus) if node.GetNodeType() == PageType { fmt.Fprintf(w, string(result.Payload)) return } if result.Error != nil { http.Error(w, result.Error.Error(), http.StatusInternalServerError) return } nextNodeID := result.ConditionStatus if nextNodeID == "" { edges := tm.graph.Edges[task.CurrentNodeID] if len(edges) > 0 { nextNodeID = edges[0] log.Printf("No ConditionStatus found, following edge to next node: %s", nextNodeID) } else { log.Printf("Task %s completed. Final result: %s", task.ID, task.FinalResult) fmt.Fprintf(w, "%s
", task.FinalResult) return } } task.CurrentNodeID = nextNodeID tm.UpdateTask(task) if nextNode, nextExists := tm.graph.Nodes[nextNodeID]; nextExists && nextNode.GetNodeType() == PageType { log.Printf("Redirecting to next page: %s", nextNodeID) http.Redirect(w, r, fmt.Sprintf("/render?taskID=%s", task.ID), http.StatusFound) return } } } func renderHandler(w http.ResponseWriter, r *http.Request, tm *TaskManager) { taskID := r.URL.Query().Get("taskID") task, exists := tm.GetTask(taskID) if !exists { http.Error(w, "Task not found", http.StatusNotFound) return } processNode(w, r, task, tm) } func submitHandler(w http.ResponseWriter, r *http.Request, tm *TaskManager) { taskID := r.URL.Query().Get("taskID") task, exists := tm.GetTask(taskID) if !exists { http.Error(w, "Task not found", http.StatusNotFound) return } err := r.ParseForm() if err != nil { http.Error(w, "Failed to parse form data", http.StatusBadRequest) return } inputData := make(map[string]string) for key, values := range r.Form { if len(values) > 0 { inputData[key] = values[0] } } rawInputs, _ := json.Marshal(inputData) task.Payload = rawInputs nextNode, exists := tm.GetNextNode(task) if !exists { log.Printf("Task %s completed. Final result: %s", task.ID, task.FinalResult) fmt.Fprintf(w, "%s
", task.FinalResult) return } switch nextNode := nextNode.(type) { case *PageNode: task.CurrentNodeID = nextNode.ID case *FunctionNode: task.CurrentNodeID = nextNode.ID } processNode(w, r, task, tm) } func startTaskHandler(w http.ResponseWriter, r *http.Request, tm *TaskManager) { task := tm.StartTask() http.Redirect(w, r, fmt.Sprintf("/render?taskID=%s", task.ID), http.StatusFound) } func isValidEmail(email string) bool { return email != "" } func isValidPhone(phone string) bool { return phone != "" } func verifyHandler(w http.ResponseWriter, r *http.Request, tm *TaskManager) { taskID := r.URL.Query().Get("taskID") if taskID == "" { http.Error(w, "Missing taskID", http.StatusBadRequest) return } task, exists := tm.GetTask(taskID) if !exists { http.Error(w, "Task not found", http.StatusNotFound) return } data := map[string]any{ "email_verified": "true", } bt, _ := json.Marshal(data) task.Payload = bt log.Printf("Email for taskID %s successfully verified.", task.ID) nextNode, exists := tm.graph.Nodes["dashboard"] if !exists { http.Error(w, "Dashboard node not found", http.StatusInternalServerError) return } result := nextNode.ProcessTask(context.Background(), task) if result.Error != nil { http.Error(w, result.Error.Error(), http.StatusInternalServerError) return } fmt.Fprintf(w, string(result.Payload)) } func main() { graph := NewGraph() tm := NewTaskManager(graph) customRegistrationNode := &PageNode{ ID: "customRegistration", Content: `Click here to verify your email
Verify`, } dashboardNode := &PageNode{ ID: "dashboard", Content: `Welcome to your dashboard!
`, } manualVerificationNode := &PageNode{ ID: "manualVerificationPage", Content: `Please verify the user's information manually.
`, } verifyApprovedNode := &FunctionNode{ ID: "verifyApproved", Func: func(task *Task) Result { return Result{} }, } denyVerificationNode := &FunctionNode{ ID: "denyVerification", Func: func(task *Task) Result { task.FinalResult = "Verification Denied" return Result{} }, } graph.AddNode(customRegistrationNode) graph.AddNode(checkValidityNode) graph.AddNode(checkManualVerificationNode) graph.AddNode(approveCustomerNode) graph.AddNode(sendVerificationEmailNode) graph.AddNode(verificationLinkPageNode) graph.AddNode(dashboardNode) graph.AddNode(manualVerificationNode) graph.AddNode(verifyApprovedNode) graph.AddNode(denyVerificationNode) graph.AddEdge("customRegistration", "checkValidity") graph.AddEdge("checkValidity", "checkManualVerification") graph.AddEdge("checkManualVerification", "approveCustomer") graph.AddEdge("checkManualVerification", "manualVerificationPage") graph.AddEdge("approveCustomer", "sendVerificationEmail") graph.AddEdge("sendVerificationEmail", "verificationLinkPage") graph.AddEdge("verificationLinkPage", "dashboard") graph.AddEdge("manualVerificationPage", "verifyApproved") graph.AddEdge("manualVerificationPage", "denyVerification") graph.AddEdge("verifyApproved", "approveCustomer") graph.AddEdge("denyVerification", "verificationLinkPage") http.HandleFunc("/start", func(w http.ResponseWriter, r *http.Request) { startTaskHandler(w, r, tm) }) http.HandleFunc("/render", func(w http.ResponseWriter, r *http.Request) { renderHandler(w, r, tm) }) http.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) { submitHandler(w, r, tm) }) http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) { verifyHandler(w, r, tm) }) fmt.Println("Server starting on :8080...") log.Fatal(http.ListenAndServe(":8080", nil)) }