diff --git a/again.go b/again.go index e980e9b..17acf5b 100644 --- a/again.go +++ b/again.go @@ -7,9 +7,16 @@ import ( webhooks "git.rootprojects.org/root/go-again/webhooks" ) +type Webhook webhooks.Webhook + type Schedule struct { - NextRunAt time.Time - Webhooks []webhooks.Webhook + ID string `json:"id" db:"id"` + AccessID string `json:"-" db:"access_id"` + Date string `json:"date" db:"date"` + Time string `json:"time" db:"time"` + TZ string `json:"tz" db:"tz"` + NextRunAt time.Time `json:"next_run_at" db:"next_run_at"` + Webhooks []Webhook `json:"webhooks" db"webhooks"` } // https://yourbasic.org/golang/time-change-convert-location-timezone/ diff --git a/cmd/again/again.go b/cmd/again/again.go index aa56fcd..d44ddde 100644 --- a/cmd/again/again.go +++ b/cmd/again/again.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -85,7 +86,8 @@ func main() { } type ScheduleDB interface { - List() ([]again.Schedule, error) + List(string) ([]*again.Schedule, error) + Set(again.Schedule) (*again.Schedule, error) } type scheduler struct { @@ -93,12 +95,17 @@ type scheduler struct { } func (s *scheduler) Handle(w http.ResponseWriter, r *http.Request) { + // note: no go-routines reading body in handlers to follow defer r.Body.Close() + token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") - if "" == token { + if 32 != len(token) { http.Error(w, "Authorization Header did not contain a valid token", http.StatusForbidden) return } + ctx := r.Context() + ctx = context.WithValue(ctx, "token", token) + r = r.WithContext(ctx) fmt.Println("whatever", r.Method, r.URL) switch r.Method { @@ -106,7 +113,7 @@ func (s *scheduler) Handle(w http.ResponseWriter, r *http.Request) { s.List(w, r) return case http.MethodPost: - http.Error(w, "Not Implemented", http.StatusNotImplemented) + s.Create(w, r) return default: http.Error(w, "Not Implemented", http.StatusNotImplemented) @@ -115,7 +122,8 @@ func (s *scheduler) Handle(w http.ResponseWriter, r *http.Request) { } func (s *scheduler) List(w http.ResponseWriter, r *http.Request) { - schedules, err := s.DB.List() + accessID := r.Context().Value("token").(string) + schedules, err := s.DB.List(accessID) if nil != err { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -127,3 +135,27 @@ func (s *scheduler) List(w http.ResponseWriter, r *http.Request) { } w.Write(buf) } + +func (s *scheduler) Create(w http.ResponseWriter, r *http.Request) { + // TODO validate user + accessID := r.Context().Value("token").(string) + + decoder := json.NewDecoder(r.Body) + sched := &again.Schedule{} + err := decoder.Decode(s) + if nil != err { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // TODO validate and modify + sched.AccessID = accessID + s.DB.Set(*sched) + + buf, err := json.Marshal(sched) + if nil != err { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(buf) +} diff --git a/data/jsondb/jsondb.go b/data/jsondb/jsondb.go index b630fdf..3e7620c 100644 --- a/data/jsondb/jsondb.go +++ b/data/jsondb/jsondb.go @@ -6,18 +6,19 @@ import ( "net/url" "os" "strings" + "time" again "git.rootprojects.org/root/go-again" ) type JSONDB struct { dburl string - file *os.File + path string json *dbjson } type dbjson struct { - Schedules []again.Schedule `json:"schedules"` + Schedules []Schedule `json:"schedules"` } func Connect(dburl string) (*JSONDB, error) { @@ -50,9 +51,11 @@ func Connect(dburl string) (*JSONDB, error) { stat, err := f.Stat() if 0 == stat.Size() { _, err := f.Write([]byte(`{"schedules":[]}`)) + f.Close() if nil != err { return nil, err } + f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0700) if nil != err { return nil, err @@ -62,17 +65,102 @@ func Connect(dburl string) (*JSONDB, error) { decoder := json.NewDecoder(f) db := &dbjson{} err = decoder.Decode(db) + f.Close() if nil != err { return nil, fmt.Errorf("Couldn't parse %q as JSON: %s", path, err) } return &JSONDB{ dburl: dburl, - file: f, + path: path, json: db, }, nil } -func (db *JSONDB) List() ([]again.Schedule, error) { - return db.json.Schedules, nil +// A copy of again.Schedule, but with access_id json-able +type Schedule struct { + ID string `json:"id" db:"id"` + AccessID string `json:"access_id" db:"access_id"` + Date string `json:"date" db:"date"` + Time string `json:"time" db:"time"` + TZ string `json:"tz" db:"tz"` + NextRunAt time.Time `json:"next_run_at" db:"next_run_at"` + Disabled bool `json:"disabled" db:"disabled"` + Webhooks []again.Webhook `json:"webhooks" db"webhooks"` +} + +func (db *JSONDB) List(accessID string) ([]*again.Schedule, error) { + schedules := []*again.Schedule{} + for i := range db.json.Schedules { + s := db.json.Schedules[i] + if !s.Disabled && accessID == s.AccessID { + schedules = append(schedules, &again.Schedule{ + ID: s.ID, + AccessID: s.AccessID, + Date: s.Date, + Time: s.Time, + TZ: s.TZ, + NextRunAt: s.NextRunAt, + Webhooks: s.Webhooks, + }) + } + } + return schedules, nil +} + +func (db *JSONDB) get(id string) *Schedule { + for i := range db.json.Schedules { + schedule := db.json.Schedules[i] + if id == schedule.AccessID { + return &schedule + } + } + return nil +} + +func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { + newSchedules := []Schedule{} + + for i := range db.json.Schedules { + old := db.json.Schedules[i] + if s.ID == old.AccessID { + continue + } + newSchedules = append(newSchedules, old) + } + + schedule := Schedule{ + ID: s.ID, + AccessID: s.AccessID, + Date: s.Date, + Time: s.Time, + TZ: s.TZ, + NextRunAt: s.NextRunAt, + Webhooks: s.Webhooks, + } + newSchedules = append(newSchedules, schedule) + + err := db.save(s.AccessID) + if nil != err { + return nil, err + } + + return &s, nil +} + +func (db *JSONDB) save(accessID string) error { + // TODO per-user files (w/ mutex lock or channel on open and write) + f, err := os.OpenFile(db.path, os.O_RDWR|os.O_CREATE, 0700) + if nil != err { + return err + } + + encoder := json.NewEncoder(f) + err = encoder.Encode(db.json.Schedules) + f.Close() + if nil != err { + return err + } + + return nil } diff --git a/public/app.js b/public/app.js index f274bd3..4d56ec2 100644 --- a/public/app.js +++ b/public/app.js @@ -87,16 +87,22 @@ function newSchedule() { var $hook = $('.js-schedule'); //var deviceId = $hook.closest('.js-schedule').querySelector('.js-id').value; + var schedule = { + schedule: { + date: $('.js-date', $hook).value, + time: $('.js-time', $hook).value, + tz: $('.js-tz', $hook).value + }, + webhooks: [] + }; var hook = { - date: $('.js-date', $hook).value, - time: $('.js-time', $hook).value, - tz: $('.js-tz', $hook).value, comment: $('.js-comment', $hook).value, method: $('.js-method', $hook).value, url: $('.js-url', $hook).value, headers: {} }; - console.log('schedule:', hook); + schedule.webhooks.push(hook); + console.log('schedule:', schedule); $$('.js-header', $hook).forEach(function($head) { var key = $('.js-key', $head).value; var val = $('.js-value', $head).value; @@ -114,7 +120,7 @@ Authorization: getToken(), 'Content-Type': 'application/json' }, - body: JSON.stringify(hook), + body: JSON.stringify(schedule), cors: true };