|
|
@ -1,6 +1,9 @@ |
|
|
|
package jsondb |
|
|
|
|
|
|
|
import ( |
|
|
|
"crypto/rand" |
|
|
|
"crypto/subtle" |
|
|
|
"encoding/hex" |
|
|
|
"encoding/json" |
|
|
|
"fmt" |
|
|
|
"net/url" |
|
|
@ -89,11 +92,15 @@ type Schedule struct { |
|
|
|
Webhooks []again.Webhook `json:"webhooks" db"webhooks"` |
|
|
|
} |
|
|
|
|
|
|
|
func ctcmp(x string, y string) bool { |
|
|
|
return 1 == subtle.ConstantTimeCompare([]byte(x), []byte(y)) |
|
|
|
} |
|
|
|
|
|
|
|
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 { |
|
|
|
if !s.Disabled && ctcmp(accessID, s.AccessID) { |
|
|
|
schedules = append(schedules, &again.Schedule{ |
|
|
|
ID: s.ID, |
|
|
|
AccessID: s.AccessID, |
|
|
@ -108,25 +115,41 @@ func (db *JSONDB) List(accessID string) ([]*again.Schedule, error) { |
|
|
|
return schedules, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (db *JSONDB) get(id string) *Schedule { |
|
|
|
func (db *JSONDB) get(id string) (int, *Schedule) { |
|
|
|
for i := range db.json.Schedules { |
|
|
|
schedule := db.json.Schedules[i] |
|
|
|
if id == schedule.AccessID { |
|
|
|
return &schedule |
|
|
|
if ctcmp(id, schedule.ID) { |
|
|
|
return i, &schedule |
|
|
|
} |
|
|
|
} |
|
|
|
return nil |
|
|
|
return -1, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { |
|
|
|
newSchedules := []Schedule{} |
|
|
|
func genID() (string, error) { |
|
|
|
b := make([]byte, 16) |
|
|
|
_, err := rand.Read(b) |
|
|
|
if nil != err { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
return hex.EncodeToString(b), nil |
|
|
|
} |
|
|
|
|
|
|
|
for i := range db.json.Schedules { |
|
|
|
old := db.json.Schedules[i] |
|
|
|
if s.ID == old.AccessID { |
|
|
|
continue |
|
|
|
func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { |
|
|
|
exists := false |
|
|
|
index := -1 |
|
|
|
if "" == s.ID { |
|
|
|
id, err := genID() |
|
|
|
if nil != err { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
s.ID = id |
|
|
|
} else { |
|
|
|
i, old := db.get(s.ID) |
|
|
|
index = i |
|
|
|
exists = nil != old |
|
|
|
if !exists || !ctcmp(old.AccessID, s.AccessID) { |
|
|
|
return nil, fmt.Errorf("invalid id") |
|
|
|
} |
|
|
|
newSchedules = append(newSchedules, old) |
|
|
|
} |
|
|
|
|
|
|
|
schedule := Schedule{ |
|
|
@ -138,7 +161,12 @@ func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { |
|
|
|
NextRunAt: s.NextRunAt, |
|
|
|
Webhooks: s.Webhooks, |
|
|
|
} |
|
|
|
newSchedules = append(newSchedules, schedule) |
|
|
|
|
|
|
|
if exists { |
|
|
|
db.json.Schedules[index] = schedule |
|
|
|
} else { |
|
|
|
db.json.Schedules = append(db.json.Schedules, schedule) |
|
|
|
} |
|
|
|
|
|
|
|
err := db.save(s.AccessID) |
|
|
|
if nil != err { |
|
|
@ -150,17 +178,32 @@ func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { |
|
|
|
|
|
|
|
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) |
|
|
|
tmppath := db.path + ".tmp" |
|
|
|
bakpath := db.path + ".bak" |
|
|
|
|
|
|
|
os.Remove(tmppath) // ignore error
|
|
|
|
f, err := os.OpenFile(tmppath, os.O_RDWR|os.O_CREATE, 0700) |
|
|
|
if nil != err { |
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
encoder := json.NewEncoder(f) |
|
|
|
err = encoder.Encode(db.json.Schedules) |
|
|
|
err = encoder.Encode(db.json) |
|
|
|
f.Close() |
|
|
|
if nil != err { |
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
os.Remove(bakpath) // ignore error
|
|
|
|
err = os.Rename(db.path, bakpath) |
|
|
|
if nil != err { |
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
err = os.Rename(tmppath, db.path) |
|
|
|
if nil != err { |
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|