datagraph/webserver.go
2025-08-08 18:15:13 +02:00

798 lines
15 KiB
Go

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("/hooks/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
}
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