diff --git a/.gitignore b/.gitignore index 92d74a2..de9f0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -foo/ +data/ diff --git a/api.go b/api.go index ecd5343..da53939 100644 --- a/api.go +++ b/api.go @@ -1,22 +1,27 @@ package main +import "encoding/json" import "log" import "net/http" +import "github.com/google/uuid" import "github.com/gorilla/mux" type API struct { router *mux.Router + store *Store } -func NewAPI() *API { +func NewAPI(storePath string) *API { api := &API{ router: mux.NewRouter(), + store: NewStore(storePath), } - api.router.HandleFunc("/template", api.createTemplate).Methods("POST") + api.router.HandleFunc("/template", api.createTemplate).Methods("POST").Headers("Content-Type", "application/json") + api.router.HandleFunc("/template/{id}", api.streamTemplate).Methods("GET").Headers("Accept", "text/event-stream") api.router.HandleFunc("/template/{id}", api.getTemplate).Methods("GET") - api.router.HandleFunc("/template/{id}", api.updateTemplate).Methods("PATCH") + api.router.HandleFunc("/template/{id}", api.updateTemplate).Methods("PATCH").Headers("Content-Type", "application/json") return api } @@ -27,6 +32,58 @@ func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (api *API) createTemplate(w http.ResponseWriter, r *http.Request) { log.Printf("createTemplate") + + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + + template := NewTemplate() + err := dec.Decode(template) + if err != nil { + http.Error(w, "invalid JSON", http.StatusBadRequest) + return + } + + template.Id = uuid.NewString() + + if !template.IsValid() { + http.Error(w, "invalid template", http.StatusBadRequest) + return + } + + err = api.store.Write(template) + if err != nil { + http.Error(w, "failed to write template", http.StatusInternalServerError) + return + } + + enc := json.NewEncoder(w) + err = enc.Encode(template) + if err != nil { + http.Error(w, "failed to encode json", http.StatusInternalServerError) + return + } +} + +func (api *API) streamTemplate(w http.ResponseWriter, r *http.Request) { + log.Printf("streamTemplate %s", mux.Vars(r)) + + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "streaming unsupported", http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + closeChan := w.(http.CloseNotifier).CloseNotify() + + flusher.Flush() + + <-closeChan + + log.Printf("streamTemplate %s end", mux.Vars(r)) } func (api *API) getTemplate(w http.ResponseWriter, r *http.Request) { diff --git a/go.mod b/go.mod index eefa1ae..e39a609 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/firestuff/checky go 1.16 -require github.com/gorilla/mux v1.8.0 +require ( + github.com/google/uuid v1.3.0 + github.com/gorilla/mux v1.8.0 +) diff --git a/go.sum b/go.sum index 5350288..fee0108 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/main.go b/main.go index 7f10bb5..2ce7cfb 100644 --- a/main.go +++ b/main.go @@ -8,13 +8,14 @@ import "log" import "net/http" var bindFlag = flag.String("listen", "[::]:8100", "host:port to listen on") +var storeFlag = flag.String("store", "data", "data store path") func main() { flag.Parse() mux := http.NewServeMux() - api := NewAPI() + api := NewAPI(*storeFlag) mux.Handle("/api/", http.StripPrefix("/api", api)) srv := &http.Server{ diff --git a/template.go b/template.go index cf6a3e2..43a119a 100644 --- a/template.go +++ b/template.go @@ -19,6 +19,12 @@ type Check struct { Completed *time.Time `json:"completed"` } +func NewTemplate() *Template { + return &Template{ + Items: []*Item{}, + } +} + func (t *Template) GetType() string { return "template" } @@ -26,3 +32,7 @@ func (t *Template) GetType() string { func (t *Template) GetId() string { return t.Id } + +func (t *Template) IsValid() bool { + return true +}