package main import ( // External "git.ahall.se/go/html_template" werr "git.gibonuddevalla.se/go/wrappederror" "github.com/lib/pq" // Standard "encoding/json" "errors" "fmt" "io" "net/http" "strconv" ) var ( engine HTMLTemplate.Engine ) func initWebserver() (err error) { address := fmt.Sprintf("%s:%d", config.Network.Address, config.Network.Port) logger.Info("webserver", "listen", address) engine, err = HTMLTemplate.NewEngine(viewFS, staticFS, flagDev) if err != nil { err = werr.Wrap(err) return } http.HandleFunc("/", pageIndex) http.HandleFunc("/app", pageApp) http.HandleFunc("/nodes/tree/{startNode}", actionNodesTree) http.HandleFunc("/nodes/{nodeID}", actionNode) http.HandleFunc("/nodes/update/{nodeID}", actionNodeUpdate) http.HandleFunc("/nodes/rename/{nodeID}", actionNodeRename) http.HandleFunc("/nodes/delete/{nodeID}", actionNodeDelete) http.HandleFunc("/nodes/connections/{nodeID}", actionNodeConnections) http.HandleFunc("/nodes/create", actionNodeCreate) http.HandleFunc("/nodes/move", actionNodeMove) http.HandleFunc("/nodes/search", actionNodeSearch) http.HandleFunc("/nodes/connect", actionNodeConnect) http.HandleFunc("/nodes/hook", actionNodeHook) http.HandleFunc("/types/{typeID}", actionType) http.HandleFunc("/types/", actionTypesAll) http.HandleFunc("/types/create", actionTypeCreate) http.HandleFunc("/types/update/{typeID}", actionTypeUpdate) http.HandleFunc("/connection/update/{connID}", actionConnectionUpdate) http.HandleFunc("/connection/delete/{connID}", actionConnectionDelete) http.HandleFunc("/scripts/", actionScripts) http.HandleFunc("/scripts/update/{scriptID}", actionScriptUpdate) http.HandleFunc("/scripts/delete/{scriptID}", actionScriptDelete) http.HandleFunc("/scripts/search", actionScriptsSearch) http.HandleFunc("/hooks/update", actionHookUpdate) http.HandleFunc("/hooks/delete/{hookID}", actionHookDelete) http.HandleFunc("/hooks/schedule/{hookID}", actionHookSchedule) http.HandleFunc("/scriptexecutions/", actionScriptExecutions) http.HandleFunc("/scriptexecutions/{executionID}", actionScriptExecutionGet) err = http.ListenAndServe(address, nil) return } func httpError(w http.ResponseWriter, err error) { // {{{ out := struct { OK bool Error string }{ false, err.Error(), } j, _ := json.Marshal(out) w.Write(j) } // }}} func pageIndex(w http.ResponseWriter, r *http.Request) { // {{{ if r.URL.Path == "/" { http.Redirect(w, r, "/app", http.StatusSeeOther) } else { engine.StaticResource(w, r) } } // }}} func pageApp(w http.ResponseWriter, r *http.Request) { // {{{ page := NewPage("app") ts, err := GetTypes() if err != nil { err = werr.Wrap(err) httpError(w, err) return } page.Data["Types"] = ts err = engine.Render(page, w, r) if err != nil { err = werr.Wrap(err) w.Write([]byte(err.Error())) } } // }}} func actionNodesTree(w http.ResponseWriter, r *http.Request) { // {{{ var err error startNode := 0 startNodeStr := r.PathValue("startNode") startNode, _ = strconv.Atoi(startNodeStr) var maxDepth int if maxDepth, err = strconv.Atoi(r.URL.Query().Get("depth")); err != nil { maxDepth = 3 } var withData bool if r.URL.Query().Get("data") == "true" { withData = true } topNode, err := GetNodeTree(startNode, maxDepth, withData) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Nodes *Node }{ true, topNode, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNode(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Content-Type", "application/json") nodeID := 0 nodeIDStr := r.PathValue("nodeID") nodeID, _ = strconv.Atoi(nodeIDStr) node, err := GetNode(nodeID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Node Node }{ true, node, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeUpdate(w http.ResponseWriter, r *http.Request) { // {{{ nodeID := 0 nodeIDStr := r.PathValue("nodeID") nodeID, _ = strconv.Atoi(nodeIDStr) data, _ := io.ReadAll(r.Body) err := UpdateNode(nodeID, data) if err != nil { err = werr.Wrap(err) httpError(w, err) return } // Recursively schedule hooks with automatic trigger. err = ScheduleHookRecursivelyUpwards(nodeID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeRename(w http.ResponseWriter, r *http.Request) { // {{{ nodeID := 0 nodeIDStr := r.PathValue("nodeID") nodeID, _ = strconv.Atoi(nodeIDStr) data, _ := io.ReadAll(r.Body) var req struct{ Name string } err := json.Unmarshal(data, &req) err = RenameNode(nodeID, req.Name) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeCreate(w http.ResponseWriter, r *http.Request) { // {{{ var req struct { ParentNodeID int TypeID int Name string } data, _ := io.ReadAll(r.Body) err := json.Unmarshal(data, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } var nodeID int nodeID, err = CreateNode(req.ParentNodeID, req.TypeID, req.Name) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool NodeID int }{ true, nodeID, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeDelete(w http.ResponseWriter, r *http.Request) { // {{{ nodeID := 0 nodeIDStr := r.PathValue("nodeID") nodeID, _ = strconv.Atoi(nodeIDStr) err := DeleteNode(nodeID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeConnections(w http.ResponseWriter, r *http.Request) { // {{{ nodeID := 0 nodeIDStr := r.PathValue("nodeID") nodeID, _ = strconv.Atoi(nodeIDStr) nodes, err := GetNodeConnections(nodeID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Nodes []Node }{ true, nodes, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeMove(w http.ResponseWriter, r *http.Request) { // {{{ var req struct { NewParentID int NodeIDs []int } data, _ := io.ReadAll(r.Body) err := json.Unmarshal(data, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } err = MoveNodes(req.NewParentID, req.NodeIDs) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeSearch(w http.ResponseWriter, r *http.Request) { // {{{ maxResults := 25 typeIDStr := r.URL.Query().Get("type_id") typeID, err := strconv.Atoi(typeIDStr) if err != nil { typeID = -1 } searchText := r.URL.Query().Get("search") var nodes []Node nodes, err = SearchNodes(typeID, searchText, maxResults) if err != nil { err = werr.Wrap(err) httpError(w, err) return } moreExist := len(nodes) > maxResults if moreExist { nodes = nodes[:maxResults] } out := struct { OK bool Nodes []Node MoreExistThan int }{ true, nodes, 0, } if moreExist { out.MoreExistThan = maxResults } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionNodeConnect(w http.ResponseWriter, r *http.Request) { // {{{ var req struct { ParentNodeID int ChildNodeID int } body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } err = ConnectNode(req.ParentNodeID, req.ChildNodeID) if err != nil { pqErr, ok := err.(*pq.Error) if ok && pqErr.Code == "23505" { err = errors.New("This node is already connected.") } else { err = werr.Wrap(err) } httpError(w, err) return } res := struct{ OK bool }{true} j, _ := json.Marshal(res) w.Write(j) } // }}} func actionNodeHook(w http.ResponseWriter, r *http.Request) { // {{{ var req struct { NodeID int ScriptID int } body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } err = HookScript(req.NodeID, req.ScriptID) if err != nil { pqErr, ok := err.(*pq.Error) if ok && pqErr.Code == "23505" { err = errors.New("This script is already hooked.") } else { err = werr.Wrap(err) } httpError(w, err) return } res := struct{ OK bool }{true} j, _ := json.Marshal(res) w.Write(j) } // }}} func actionType(w http.ResponseWriter, r *http.Request) { // {{{ typeID := 0 typeIDStr := r.PathValue("typeID") typeID, _ = strconv.Atoi(typeIDStr) typ, err := GetType(typeID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } res := struct { OK bool Type NodeType }{ true, typ, } j, _ := json.Marshal(res) w.Write(j) } // }}} func actionTypesAll(w http.ResponseWriter, r *http.Request) { // {{{ types, err := GetTypes() if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Types []NodeType }{ true, types, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionTypeCreate(w http.ResponseWriter, r *http.Request) { // {{{ var req struct { Name string } body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } var newSchema string if newSchema, err = ValidateType(req.Name, "{}"); err != nil { err = werr.Wrap(err) httpError(w, err) return } nodeType, err := CreateType(req.Name, newSchema) if err != nil { if wrapped, ok := err.(*werr.Error); ok { pqErr, ok := wrapped.Wrapped.(*pq.Error) if ok && pqErr.Code == "23505" { err = errors.New("This type already exist.") } else { err = werr.Wrap(err) } } else { err = werr.Wrap(err) } httpError(w, err) return } res := struct { OK bool Type NodeType }{ true, nodeType, } j, _ := json.Marshal(res) w.Write(j) } // }}} func actionTypeUpdate(w http.ResponseWriter, r *http.Request) { // {{{ typeID := 0 typeIDStr := r.PathValue("typeID") typeID, _ = strconv.Atoi(typeIDStr) var req struct { Name string Schema string } body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &req) if err != nil { err = werr.Wrap(err) httpError(w, err) return } var fixedSchema string if fixedSchema, err = ValidateType(req.Name, req.Schema); err != nil { if wErr, ok := err.(*werr.Error); ok { if jsonErr, ok := wErr.Wrapped.(*json.SyntaxError); ok { err = jsonErr } else { err = werr.Wrap(err) } } else { err = werr.Wrap(err) } httpError(w, err) return } err = UpdateType(typeID, req.Name, fixedSchema) if err != nil { err = werr.Wrap(err) httpError(w, err) return } res := struct { OK bool }{true} j, _ := json.Marshal(res) w.Write(j) } // }}} func actionConnectionUpdate(w http.ResponseWriter, r *http.Request) { // {{{ connIDStr := r.PathValue("connID") connID, err := strconv.Atoi(connIDStr) if err != nil { err = werr.Wrap(err) httpError(w, err) return } data, _ := io.ReadAll(r.Body) err = UpdateConnection(connID, data) if err != nil { httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionConnectionDelete(w http.ResponseWriter, r *http.Request) { // {{{ connIDStr := r.PathValue("connID") connID, err := strconv.Atoi(connIDStr) if err != nil { err = werr.Wrap(err) httpError(w, err) return } err = DeleteConnection(connID) if err != nil { httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScripts(w http.ResponseWriter, r *http.Request) { // {{{ scripts, err := GetScripts() if err != nil { httpError(w, err) return } out := struct { OK bool Scripts []Script }{ true, scripts, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScriptUpdate(w http.ResponseWriter, r *http.Request) { // {{{ scriptID := 0 scriptIDStr := r.PathValue("scriptID") scriptID, _ = strconv.Atoi(scriptIDStr) data, _ := io.ReadAll(r.Body) script, err := UpdateScript(scriptID, data) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Script Script }{ true, script, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScriptDelete(w http.ResponseWriter, r *http.Request) { // {{{ scriptID := 0 scriptIDStr := r.PathValue("scriptID") scriptID, _ = strconv.Atoi(scriptIDStr) err := DeleteScript(scriptID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScriptsSearch(w http.ResponseWriter, r *http.Request) { // {{{ var search struct { Search string } body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &search) if err != nil { err = werr.Wrap(err) httpError(w, err) return } scripts, err := SearchScripts(search.Search) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool Scripts []Script }{ true, scripts, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionHookUpdate(w http.ResponseWriter, r *http.Request) { // {{{ var hook Hook body, _ := io.ReadAll(r.Body) err := json.Unmarshal(body, &hook) if err != nil { err = werr.Wrap(err) httpError(w, err) return } err = UpdateHook(hook) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionHookDelete(w http.ResponseWriter, r *http.Request) { // {{{ hookID := 0 hookIDStr := r.PathValue("hookID") hookID, _ = strconv.Atoi(hookIDStr) err := DeleteHook(hookID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionHookSchedule(w http.ResponseWriter, r *http.Request) { // {{{ hookID := 0 hookIDStr := r.PathValue("hookID") hookID, _ = strconv.Atoi(hookIDStr) err := ScheduleHook(hookID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool }{ true, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScriptExecutions(w http.ResponseWriter, r *http.Request) { // {{{ execs, err := GetScriptExecutions() if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool ScriptExecutions []ScriptExecutionBrief }{ true, execs, } j, _ := json.Marshal(out) w.Write(j) } // }}} func actionScriptExecutionGet(w http.ResponseWriter, r *http.Request) { // {{{ executionID := 0 executionIDStr := r.PathValue("executionID") executionID, _ = strconv.Atoi(executionIDStr) execution, err := GetScriptExecution(executionID) if err != nil { err = werr.Wrap(err) httpError(w, err) return } out := struct { OK bool ScriptExecution ScriptExecution }{ true, execution, } j, _ := json.Marshal(out) w.Write(j) } // }}} // vim: foldmethod=marker