554 lines
10 KiB
Go
554 lines
10 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("/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)
|
|
|
|
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
|
|
}
|
|
|
|
topNode, err := GetNodeTree(startNode, maxDepth)
|
|
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 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)
|
|
} // }}}
|
|
|
|
// vim: foldmethod=marker
|