2023-06-15 07:24:23 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
// Standard
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2023-06-17 09:11:14 +02:00
|
|
|
"time"
|
2023-06-18 22:05:10 +02:00
|
|
|
_ "embed"
|
2023-06-15 07:24:23 +02:00
|
|
|
)
|
|
|
|
|
2023-06-20 08:13:32 +02:00
|
|
|
const VERSION = "v0.1.2";
|
2023-06-15 07:24:23 +02:00
|
|
|
const LISTEN_HOST = "0.0.0.0";
|
2023-06-18 22:05:10 +02:00
|
|
|
const DB_SCHEMA = 2
|
2023-06-15 07:24:23 +02:00
|
|
|
|
|
|
|
var (
|
|
|
|
flagPort int
|
|
|
|
connectionManager ConnectionManager
|
|
|
|
static http.Handler
|
|
|
|
config Config
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func init() {// {{{
|
|
|
|
flag.IntVar(
|
|
|
|
&flagPort,
|
|
|
|
"port",
|
|
|
|
1371,
|
|
|
|
"TCP port to listen on",
|
|
|
|
)
|
|
|
|
|
|
|
|
flag.Parse()
|
2023-06-17 09:11:14 +02:00
|
|
|
|
|
|
|
if false {
|
|
|
|
time.Sleep(time.Second*1)
|
|
|
|
}
|
2023-06-15 07:24:23 +02:00
|
|
|
}// }}}
|
|
|
|
func main() {// {{{
|
|
|
|
var err error
|
|
|
|
log.Printf("\x1b[32mNotes\x1b[0m %s\n", VERSION)
|
|
|
|
|
|
|
|
config, err = ConfigRead(os.Getenv("HOME")+"/.config/notes.yaml")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("%s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2023-06-18 22:05:10 +02:00
|
|
|
if err = dbInit(); err != nil {
|
|
|
|
fmt.Printf("%s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2023-06-15 07:24:23 +02:00
|
|
|
connectionManager = NewConnectionManager()
|
|
|
|
go connectionManager.BroadcastLoop()
|
|
|
|
|
2023-06-19 15:02:52 +02:00
|
|
|
static = http.FileServer(http.Dir(config.Application.StaticDir))
|
2023-06-15 07:24:23 +02:00
|
|
|
http.HandleFunc("/css_updated", cssUpdateHandler)
|
|
|
|
http.HandleFunc("/session/create", sessionCreate)
|
2023-06-16 07:11:27 +02:00
|
|
|
http.HandleFunc("/session/retrieve", sessionRetrieve)
|
|
|
|
http.HandleFunc("/session/authenticate", sessionAuthenticate)
|
2023-06-19 15:02:52 +02:00
|
|
|
http.HandleFunc("/node/tree", nodeTree)
|
2023-06-17 09:11:14 +02:00
|
|
|
http.HandleFunc("/node/retrieve", nodeRetrieve)
|
2023-06-18 20:13:35 +02:00
|
|
|
http.HandleFunc("/node/create", nodeCreate)
|
|
|
|
http.HandleFunc("/node/update", nodeUpdate)
|
2023-06-18 22:05:10 +02:00
|
|
|
http.HandleFunc("/node/rename", nodeRename)
|
|
|
|
http.HandleFunc("/node/delete", nodeDelete)
|
2023-06-15 07:24:23 +02:00
|
|
|
http.HandleFunc("/ws", websocketHandler)
|
|
|
|
http.HandleFunc("/", staticHandler)
|
|
|
|
|
|
|
|
listen := fmt.Sprintf("%s:%d", LISTEN_HOST, flagPort)
|
|
|
|
log.Printf("\x1b[32mNotes\x1b[0m Listening on %s\n", listen)
|
|
|
|
log.Printf("\x1b[32mNotes\x1b[0m Answer for domains %s\n", strings.Join(config.Websocket.Domains, ", "))
|
|
|
|
http.ListenAndServe(listen, nil)
|
|
|
|
}// }}}
|
|
|
|
|
|
|
|
func cssUpdateHandler(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
log.Println("[BROADCAST] CSS updated")
|
|
|
|
connectionManager.Broadcast(struct{ Ok bool; ID string; Op string }{ Ok: true, Op: "css_reload" })
|
|
|
|
}// }}}
|
2023-06-17 09:11:14 +02:00
|
|
|
func websocketHandler(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
var err error
|
|
|
|
|
|
|
|
_, err = connectionManager.NewConnection(w, r)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("[Connection] %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}// }}}
|
|
|
|
func staticHandler(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
data := struct{
|
|
|
|
VERSION string
|
|
|
|
}{
|
|
|
|
VERSION: VERSION,
|
|
|
|
}
|
|
|
|
|
|
|
|
// URLs with pattern /(css|images)/v1.0.0/foobar are stripped of the version.
|
|
|
|
// To get rid of problems with cached content in browser on a new version release,
|
|
|
|
// while also not disabling cache altogether.
|
|
|
|
log.Printf("static: %s", r.URL.Path)
|
|
|
|
rxp := regexp.MustCompile("^/(css|images|js|fonts)/v[0-9]+\\.[0-9]+\\.[0-9]+/(.*)$")
|
|
|
|
if comp := rxp.FindStringSubmatch(r.URL.Path); comp != nil {
|
|
|
|
r.URL.Path = fmt.Sprintf("/%s/%s", comp[1], comp[2])
|
|
|
|
static.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything else is run through the template system.
|
|
|
|
// For now to get VERSION into files to fix caching.
|
|
|
|
log.Printf("template: %s", r.URL.Path)
|
|
|
|
tmpl, err := newTemplate(r.URL.Path)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
w.WriteHeader(404)
|
|
|
|
}
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = tmpl.Execute(w, data); err != nil {
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
}
|
|
|
|
}// }}}
|
|
|
|
|
2023-06-15 07:24:23 +02:00
|
|
|
func sessionCreate(w http.ResponseWriter, r *http.Request) {// {{{
|
2023-06-17 09:11:14 +02:00
|
|
|
log.Println("/session/create")
|
|
|
|
session, err := CreateSession()
|
2023-06-15 07:24:23 +02:00
|
|
|
if err != nil {
|
2023-06-15 14:12:35 +02:00
|
|
|
responseError(w, err)
|
2023-06-16 07:11:27 +02:00
|
|
|
return
|
2023-06-15 07:24:23 +02:00
|
|
|
}
|
2023-06-15 14:12:35 +02:00
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
"Session": session,
|
|
|
|
})
|
2023-06-15 07:24:23 +02:00
|
|
|
}// }}}
|
2023-06-16 07:11:27 +02:00
|
|
|
func sessionRetrieve(w http.ResponseWriter, r *http.Request) {// {{{
|
2023-06-17 09:11:14 +02:00
|
|
|
log.Println("/session/retrieve")
|
2023-06-16 07:11:27 +02:00
|
|
|
var err error
|
2023-06-17 09:11:14 +02:00
|
|
|
var found bool
|
|
|
|
var session Session
|
2023-06-16 07:11:27 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
if session, found, err = ValidateSession(r, false); err != nil {
|
2023-06-16 07:11:27 +02:00
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
2023-06-17 09:11:14 +02:00
|
|
|
"Valid": found,
|
2023-06-16 07:11:27 +02:00
|
|
|
"Session": session,
|
|
|
|
})
|
|
|
|
}// }}}
|
|
|
|
func sessionAuthenticate(w http.ResponseWriter, r *http.Request) {// {{{
|
2023-06-17 09:11:14 +02:00
|
|
|
log.Println("/session/authenticate")
|
2023-06-16 07:11:27 +02:00
|
|
|
var err error
|
2023-06-17 09:11:14 +02:00
|
|
|
var session Session
|
|
|
|
var authenticated bool
|
2023-06-16 07:11:27 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
// Validate session
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
2023-06-16 07:11:27 +02:00
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct{ Username string; Password string }{}
|
2023-06-17 09:11:14 +02:00
|
|
|
if err = parseRequest(r, &req); err != nil {
|
2023-06-16 07:11:27 +02:00
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
if authenticated, err = session.Authenticate(req.Username, req.Password); err != nil {
|
2023-06-16 07:11:27 +02:00
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
2023-06-17 09:11:14 +02:00
|
|
|
"Authenticated": authenticated,
|
2023-06-16 07:11:27 +02:00
|
|
|
"Session": session,
|
|
|
|
})
|
|
|
|
}// }}}
|
2023-06-17 09:11:14 +02:00
|
|
|
|
2023-06-19 15:02:52 +02:00
|
|
|
func nodeTree(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
var err error
|
|
|
|
var session Session
|
|
|
|
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct { StartNodeID int }{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes, err := session.NodeTree(req.StartNodeID)
|
|
|
|
if err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
"Nodes": nodes,
|
|
|
|
})
|
|
|
|
}// }}}
|
2023-06-17 09:11:14 +02:00
|
|
|
func nodeRetrieve(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
log.Println("/node/retrieve")
|
2023-06-15 07:24:23 +02:00
|
|
|
var err error
|
2023-06-17 09:11:14 +02:00
|
|
|
var session Session
|
2023-06-15 07:24:23 +02:00
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
2023-06-15 07:24:23 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
req := struct { ID int }{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
2023-06-15 07:24:23 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
node, err := session.Node(req.ID)
|
2023-06-15 07:24:23 +02:00
|
|
|
if err != nil {
|
2023-06-17 09:11:14 +02:00
|
|
|
responseError(w, err)
|
2023-06-15 07:24:23 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:11:14 +02:00
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
"Node": node,
|
|
|
|
})
|
2023-06-15 07:24:23 +02:00
|
|
|
}// }}}
|
2023-06-18 20:13:35 +02:00
|
|
|
func nodeCreate(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
log.Println("/node/create")
|
|
|
|
var err error
|
|
|
|
var session Session
|
|
|
|
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct {
|
|
|
|
Name string
|
|
|
|
ParentID int
|
|
|
|
}{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
node, err := session.CreateNode(req.ParentID, req.Name)
|
|
|
|
if err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
"Node": node,
|
|
|
|
})
|
|
|
|
}// }}}
|
|
|
|
func nodeUpdate(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
var err error
|
|
|
|
var session Session
|
|
|
|
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct {
|
|
|
|
NodeID int
|
|
|
|
Content string
|
|
|
|
}{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = session.UpdateNode(req.NodeID, req.Content)
|
|
|
|
if err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
})
|
|
|
|
}// }}}
|
2023-06-18 22:05:10 +02:00
|
|
|
func nodeRename(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
var err error
|
|
|
|
var session Session
|
|
|
|
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct {
|
|
|
|
NodeID int
|
|
|
|
Name string
|
|
|
|
}{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = session.RenameNode(req.NodeID, req.Name)
|
|
|
|
if err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
})
|
|
|
|
}// }}}
|
|
|
|
func nodeDelete(w http.ResponseWriter, r *http.Request) {// {{{
|
|
|
|
var err error
|
|
|
|
var session Session
|
|
|
|
|
|
|
|
if session, _, err = ValidateSession(r, true); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := struct {
|
|
|
|
NodeID int
|
|
|
|
}{}
|
|
|
|
if err = parseRequest(r, &req); err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = session.DeleteNode(req.NodeID)
|
|
|
|
if err != nil {
|
|
|
|
responseError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
responseData(w, map[string]interface{}{
|
|
|
|
"OK": true,
|
|
|
|
})
|
|
|
|
}// }}}
|
2023-06-15 07:24:23 +02:00
|
|
|
|
|
|
|
func newTemplate(requestPath string) (tmpl *template.Template, err error) {// {{{
|
|
|
|
// Append index.html if needed for further reading of the file
|
|
|
|
p := requestPath
|
|
|
|
if p[len(p)-1] == '/' {
|
|
|
|
p += "index.html"
|
|
|
|
}
|
2023-06-19 15:02:52 +02:00
|
|
|
p = config.Application.StaticDir + p
|
2023-06-15 07:24:23 +02:00
|
|
|
|
|
|
|
base := path.Base(p)
|
|
|
|
if tmpl, err = template.New(base).ParseFiles(p); err != nil { return }
|
|
|
|
|
|
|
|
return
|
|
|
|
}// }}}
|
|
|
|
|
|
|
|
// vim: foldmethod=marker
|