WIP: saves, finally

This commit is contained in:
AJ ONeal 2019-06-23 00:08:05 -06:00
parent 7db71b94b1
commit b619b70d22
6 changed files with 125 additions and 58 deletions

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
db.json db.json
*.bak
*.tmp
.*.sw*
/cmd/again/again /cmd/again/again
/again /again

View File

@ -140,19 +140,36 @@ func (s *scheduler) Create(w http.ResponseWriter, r *http.Request) {
// TODO validate user // TODO validate user
accessID := r.Context().Value("token").(string) accessID := r.Context().Value("token").(string)
/*
br, bw := io.Pipe()
b := io.TeeReader(r.Body, bw)
go func() {
fmt.Println("reading from reader...")
x, _ := ioutil.ReadAll(b)
fmt.Println("cool beans and all")
fmt.Println(string(x))
bw.Close()
}()
decoder := json.NewDecoder(br)
*/
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
sched := &again.Schedule{} sched := &again.Schedule{}
err := decoder.Decode(s) err := decoder.Decode(sched)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Printf("New Schedule:\n%#v\n", sched)
// TODO validate and modify
sched.AccessID = accessID
sched2, err := s.DB.Set(*sched)
if nil != err { if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// TODO validate and modify buf, err := json.Marshal(sched2)
sched.AccessID = accessID
s.DB.Set(*sched)
buf, err := json.Marshal(sched)
if nil != err { if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -1,6 +1,9 @@
package jsondb package jsondb
import ( import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
@ -89,11 +92,15 @@ type Schedule struct {
Webhooks []again.Webhook `json:"webhooks" db"webhooks"` 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) { func (db *JSONDB) List(accessID string) ([]*again.Schedule, error) {
schedules := []*again.Schedule{} schedules := []*again.Schedule{}
for i := range db.json.Schedules { for i := range db.json.Schedules {
s := db.json.Schedules[i] s := db.json.Schedules[i]
if !s.Disabled && accessID == s.AccessID { if !s.Disabled && ctcmp(accessID, s.AccessID) {
schedules = append(schedules, &again.Schedule{ schedules = append(schedules, &again.Schedule{
ID: s.ID, ID: s.ID,
AccessID: s.AccessID, AccessID: s.AccessID,
@ -108,25 +115,41 @@ func (db *JSONDB) List(accessID string) ([]*again.Schedule, error) {
return schedules, nil return schedules, nil
} }
func (db *JSONDB) get(id string) *Schedule { func (db *JSONDB) get(id string) (int, *Schedule) {
for i := range db.json.Schedules { for i := range db.json.Schedules {
schedule := db.json.Schedules[i] schedule := db.json.Schedules[i]
if id == schedule.AccessID { if ctcmp(id, schedule.ID) {
return &schedule return i, &schedule
} }
} }
return nil return -1, nil
}
func genID() (string, error) {
b := make([]byte, 16)
_, err := rand.Read(b)
if nil != err {
return "", err
}
return hex.EncodeToString(b), nil
} }
func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) { func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) {
newSchedules := []Schedule{} exists := false
index := -1
for i := range db.json.Schedules { if "" == s.ID {
old := db.json.Schedules[i] id, err := genID()
if s.ID == old.AccessID { if nil != err {
continue 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{ schedule := Schedule{
@ -138,7 +161,12 @@ func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) {
NextRunAt: s.NextRunAt, NextRunAt: s.NextRunAt,
Webhooks: s.Webhooks, 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) err := db.save(s.AccessID)
if nil != err { if nil != err {
@ -150,17 +178,32 @@ func (db *JSONDB) Set(s again.Schedule) (*again.Schedule, error) {
func (db *JSONDB) save(accessID string) error { func (db *JSONDB) save(accessID string) error {
// TODO per-user files (w/ mutex lock or channel on open and write) // 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 { if nil != err {
return err return err
} }
encoder := json.NewEncoder(f) encoder := json.NewEncoder(f)
err = encoder.Encode(db.json.Schedules) err = encoder.Encode(db.json)
f.Close() f.Close()
if nil != err { if nil != err {
return 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 return nil
} }

View File

@ -40,6 +40,7 @@
d = new Date(d.valueOf() + minutes * 60 * 1000); d = new Date(d.valueOf() + minutes * 60 * 1000);
$('.js-date').value = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); $('.js-date').value = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
$('.js-time').value = pad(d.getHours()) + ':' + pad(d.getMinutes()); $('.js-time').value = pad(d.getHours()) + ':' + pad(d.getMinutes());
$('.js-url').value = 'https://enfqtbjh5ghw.x.pipedream.net';
console.log('hello'); console.log('hello');
@ -88,11 +89,9 @@
var $hook = $('.js-schedule'); var $hook = $('.js-schedule');
//var deviceId = $hook.closest('.js-schedule').querySelector('.js-id').value; //var deviceId = $hook.closest('.js-schedule').querySelector('.js-id').value;
var schedule = { var schedule = {
schedule: {
date: $('.js-date', $hook).value, date: $('.js-date', $hook).value,
time: $('.js-time', $hook).value, time: $('.js-time', $hook).value,
tz: $('.js-tz', $hook).value tz: $('.js-tz', $hook).value,
},
webhooks: [] webhooks: []
}; };
var hook = { var hook = {
@ -139,11 +138,12 @@
return resp return resp
.json() .json()
.then(function(data) { .then(function(data) {
if (!data.schedule) { if (!data.date || !data.webhooks) {
console.error(data);
throw new Error('something bad happened'); throw new Error('something bad happened');
} }
state.account.schedules.webhooks.push(resp.data.schedule); state.account.schedules.push(resp.data);
displayAccount(state.account); displayAccount(state.account);
}) })
@ -318,17 +318,35 @@
}) })
); );
$('.js-schedules-list').hidden = true; $('.js-schedules-list').hidden = true;
$('.js-schedules').hidden = false;
return window return window
.fetch('/api/v0/schedules', { .fetch('/api/v0/schedules', {
headers: { Authorization: getToken() } headers: { Authorization: getToken() }
}) })
.then(function(resp) { .then(function(resp) {
return resp.json().then(function(schedules) { return resp
.clone()
.json()
.then(function(schedules) {
allSchedules = schedules; allSchedules = schedules;
renderSchedules(schedules); renderSchedules(schedules);
$('.js-schedules').hidden = false;
})
.catch(function(e) {
console.error("Didn't parse JSON:");
console.error(e);
console.log(resp);
$('.js-schedules-list').hidden = false;
window.alert(resp.status + ': ' + resp.statusText);
return resp.text().then(function(text) {
window.alert(text);
}); });
}); });
})
.catch(function(e) {
console.error('Request Error');
console.error(e);
window.alert('Network error. Are you online?');
});
} }
function renderSchedules(schedules) { function renderSchedules(schedules) {

View File

@ -22,9 +22,7 @@
<div class="js-schedule"> <div class="js-schedule">
<form class="js-new-schedule"> <form class="js-new-schedule">
<label>Date: <input type="date" class="js-date" required/></label> <label>Date: <input type="date" class="js-date" required/></label>
<label <label>Time: <input type="time" class="js-time" step="300" required/></label>
>Time: <input type="time" class="js-time" step="60" required
/></label>
<!-- TODO combo box --> <!-- TODO combo box -->
<label <label
>Location: >Location:
@ -39,10 +37,7 @@
<h3>Webhook</h3> <h3>Webhook</h3>
<div class="js-webhooks"> <div class="js-webhooks">
<div class="js-webhook"> <div class="js-webhook">
<h4> <h4><span class="js-comment"></span><button class="js-delete" type="button">Delete</button></h4>
<span class="js-comment"></span
><button class="js-delete" type="button">Delete</button>
</h4>
<span class="js-id" hidden></span> <span class="js-id" hidden></span>
<span class="js-method"></span> <span class="js-method"></span>
<span class="js-url"></span> <span class="js-url"></span>
@ -67,23 +62,13 @@
</select> </select>
<br /> <br />
--> -->
<input <input class="js-comment" type="text" placeholder="Webhook Name" required />
class="js-comment"
type="text"
placeholder="Webhook Name"
required
/>
<br /> <br />
<select class="js-method"> <select class="js-method">
<option value="POST" selected>POST</option> <option value="POST" selected>POST</option>
<option value="PUT">PUT</option> <option value="PUT">PUT</option>
</select> </select>
<input <input placeholder="https://example.com/api/v1/updates" class="js-url" type="url" required />
placeholder="https://example.com/api/v1/updates"
class="js-url"
type="url"
required
/>
<div class="js-headers"> <div class="js-headers">
<div class="js-header"> <div class="js-header">
<input placeholder="Header" class="js-key" type="text" /> <input placeholder="Header" class="js-key" type="text" />

View File

@ -1,12 +1,13 @@
package webooks package webooks
type Webhook struct { type Webhook struct {
Name string `json:"name"` ID string `json:"id,omitempty"`
Comment string `json:"comment"`
Method string `json:"method"` Method string `json:"method"`
URL string `json:"url"` URL string `json:"url"`
Auth map[string]string `json:"auth"` Auth map[string]string `json:"auth,omitempty"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers,omitempty"`
Form map[string]string `json:"form"` Form map[string]string `json:"form,omitempty"`
JSON map[string]string `json:"json"` JSON map[string]string `json:"json,omitempty"`
Config map[string]string `json:"config"` Config map[string]string `json:"config,omitempty"`
} }