Notes/main.go
2023-06-20 08:13:32 +02:00

371 lines
7.8 KiB
Go

package main
import (
// Standard
"flag"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path"
"regexp"
"strings"
"time"
_ "embed"
)
const VERSION = "v0.1.2";
const LISTEN_HOST = "0.0.0.0";
const DB_SCHEMA = 2
var (
flagPort int
connectionManager ConnectionManager
static http.Handler
config Config
)
func init() {// {{{
flag.IntVar(
&flagPort,
"port",
1371,
"TCP port to listen on",
)
flag.Parse()
if false {
time.Sleep(time.Second*1)
}
}// }}}
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)
}
if err = dbInit(); err != nil {
fmt.Printf("%s\n", err)
os.Exit(1)
}
connectionManager = NewConnectionManager()
go connectionManager.BroadcastLoop()
static = http.FileServer(http.Dir(config.Application.StaticDir))
http.HandleFunc("/css_updated", cssUpdateHandler)
http.HandleFunc("/session/create", sessionCreate)
http.HandleFunc("/session/retrieve", sessionRetrieve)
http.HandleFunc("/session/authenticate", sessionAuthenticate)
http.HandleFunc("/node/tree", nodeTree)
http.HandleFunc("/node/retrieve", nodeRetrieve)
http.HandleFunc("/node/create", nodeCreate)
http.HandleFunc("/node/update", nodeUpdate)
http.HandleFunc("/node/rename", nodeRename)
http.HandleFunc("/node/delete", nodeDelete)
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" })
}// }}}
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()))
}
}// }}}
func sessionCreate(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/session/create")
session, err := CreateSession()
if err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"Session": session,
})
}// }}}
func sessionRetrieve(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/session/retrieve")
var err error
var found bool
var session Session
if session, found, err = ValidateSession(r, false); err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"Valid": found,
"Session": session,
})
}// }}}
func sessionAuthenticate(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/session/authenticate")
var err error
var session Session
var authenticated bool
// Validate session
if session, _, err = ValidateSession(r, true); err != nil {
responseError(w, err)
return
}
req := struct{ Username string; Password string }{}
if err = parseRequest(r, &req); err != nil {
responseError(w, err)
return
}
if authenticated, err = session.Authenticate(req.Username, req.Password); err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"Authenticated": authenticated,
"Session": session,
})
}// }}}
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,
})
}// }}}
func nodeRetrieve(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/node/retrieve")
var err error
var session Session
if session, _, err = ValidateSession(r, true); err != nil {
responseError(w, err)
return
}
req := struct { ID int }{}
if err = parseRequest(r, &req); err != nil {
responseError(w, err)
return
}
node, err := session.Node(req.ID)
if err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"Node": node,
})
}// }}}
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,
})
}// }}}
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,
})
}// }}}
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"
}
p = config.Application.StaticDir + p
base := path.Base(p)
if tmpl, err = template.New(base).ParseFiles(p); err != nil { return }
return
}// }}}
// vim: foldmethod=marker