package main import ( // Standard "flag" "fmt" "html/template" "log" "net/http" "os" "path" "regexp" "strings" "time" _ "embed" ) const VERSION = "v0.1.0"; 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