package main import ( _ "github.com/mattn/go-sqlite3" ) import ( s "shroom_server/shroom_internals" ) import ( "database/sql" "embed" "encoding/json" "fmt" "io/fs" "log" "net/http" "strconv" "strings" "time" ) //go:embed static/* var content embed.FS type statusJson struct { Connected bool `json:"connected"` Humidifier bool `json:"humidifier"` } type adminMsg struct { Auth string `json:"auth"` Msg map[string]interface{} `json:"data"` } func dumpData(db *sql.DB, multiplier int64) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { now := time.Now().Unix() path := strings.Split(req.URL.Path, "/") last := path[len(path)-1] count, err := strconv.Atoi(last) if err != nil { w.WriteHeader(400) w.Write([]byte("could not read integer in path: " + err.Error())) return } offset := int64(count) * multiplier t := now*1000 - offset //log.Println("req start ", t) msg, err := s.GetRows(db, t) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) } else { w.Write(msg) } } } func main() { db, err := sql.Open("sqlite3", "shrooms.db") if err != nil { log.Fatal("unable to open db ", err) } if err = db.Ping(); err != nil { log.Fatal("unable to ping db ", err) } err = s.CreateTable(db) if err != nil { log.Fatal("unable to create table ", err) } status := s.ShroomStatus{ Wait: make(chan struct{}), StatusWait: make(chan struct{}), Commands: make(chan []byte), } s.InitTcpServer(db, &status) contentSub, err := fs.Sub(content, "static") if err != nil { log.Fatal("unable to use subdirectory of embedded fs: ", err) } dumpWeek := dumpData(db, 7*24*60*60*1000) dumpDay := dumpData(db, 24*60*60*1000) dumpHour := dumpData(db, 60*60*1000) dumpMinute := dumpData(db, 60*1000) dumpSecond := dumpData(db, 1000) lastPoint := func(w http.ResponseWriter, _req *http.Request) { msg, err := s.LastTime(db) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) } else { w.Write(msg) } } getStatus := func(w http.ResponseWriter, _req *http.Request) { status.RLock() num_connections := status.NumConnections humidifier := status.HumidifierOn status.RUnlock() s := statusJson{ Connected: num_connections > 0, Humidifier: humidifier, } msg, err := json.Marshal(s) if err != nil { err = fmt.Errorf("unable to marshal json: %w", err) w.WriteHeader(400) w.Write([]byte(err.Error())) } else { w.Write(msg) } } adminHandler := func(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { w.WriteHeader(405) w.Write([]byte("method must be POST")) return } msg := make([]byte, 256) l, err := req.Body.Read(msg) if err != nil && l == 0 { err = fmt.Errorf("unable to read body: %w", err) w.WriteHeader(400) w.Write([]byte(err.Error())) return } adminReq := adminMsg{} err = json.Unmarshal(msg[:l], &adminReq) if err != nil { err = fmt.Errorf("unable to unmarshal body as json: %w", err) w.WriteHeader(400) w.Write([]byte(err.Error())) return } // TODO switch to embedded secret if adminReq.Auth != "password" { w.WriteHeader(401) w.Write([]byte(err.Error())) return } inner_msg, err := json.Marshal(adminReq.Msg) if err != nil { err = fmt.Errorf("unable to marshal inner message: %w", err) w.WriteHeader(400) w.Write([]byte(err.Error())) return } select { case status.Commands <- inner_msg: w.Write([]byte("ok")) default: w.WriteHeader(503) w.Write([]byte("unable to forward request; controller may not be connected")) } } updateHandler := func(w http.ResponseWriter, req *http.Request) { stillopen := true for stillopen { _, stillopen = <-status.Wait } w.Write([]byte("ok")) } statusUpdateHandler := func(w http.ResponseWriter, req *http.Request) { stillopen := true for stillopen { _, stillopen = <-status.StatusWait } w.Write([]byte("ok")) } http.Handle("/d/", http.StripPrefix("/d/", http.FileServer(http.Dir("./dev")))) http.Handle("/", http.FileServer(http.FS(contentSub))) http.HandleFunc("/api/last/weeks/", dumpWeek) http.HandleFunc("/api/last/days/", dumpDay) http.HandleFunc("/api/last/hours/", dumpHour) http.HandleFunc("/api/last/minutes/", dumpMinute) http.HandleFunc("/api/last/seconds/", dumpSecond) http.HandleFunc("/api/latest", lastPoint) http.HandleFunc("/api/status", getStatus) http.HandleFunc("/api/admin", adminHandler) http.HandleFunc("/api/update", updateHandler) http.HandleFunc("/api/status_update", statusUpdateHandler) // TODO periodically clear old entries from the database err = http.ListenAndServe("localhost:8080", nil) if err != nil { log.Fatal("unable to start server: ", err) } defer db.Close() }