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
*.bak
*.tmp
.*.sw*
/cmd/again/again
/again

View File

@ -140,19 +140,36 @@ func (s *scheduler) Create(w http.ResponseWriter, r *http.Request) {
// TODO validate user
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)
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 {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO validate and modify
sched.AccessID = accessID
s.DB.Set(*sched)
buf, err := json.Marshal(sched)
buf, err := json.Marshal(sched2)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -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 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) {
newSchedules := []Schedule{}
for i := range db.json.Schedules {
old := db.json.Schedules[i]
if s.ID == old.AccessID {
continue
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
}

View File

@ -40,6 +40,7 @@
d = new Date(d.valueOf() + minutes * 60 * 1000);
$('.js-date').value = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
$('.js-time').value = pad(d.getHours()) + ':' + pad(d.getMinutes());
$('.js-url').value = 'https://enfqtbjh5ghw.x.pipedream.net';
console.log('hello');
@ -88,11 +89,9 @@
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
},
date: $('.js-date', $hook).value,
time: $('.js-time', $hook).value,
tz: $('.js-tz', $hook).value,
webhooks: []
};
var hook = {
@ -139,11 +138,12 @@
return resp
.json()
.then(function(data) {
if (!data.schedule) {
if (!data.date || !data.webhooks) {
console.error(data);
throw new Error('something bad happened');
}
state.account.schedules.webhooks.push(resp.data.schedule);
state.account.schedules.push(resp.data);
displayAccount(state.account);
})
@ -318,16 +318,34 @@
})
);
$('.js-schedules-list').hidden = true;
$('.js-schedules').hidden = false;
return window
.fetch('/api/v0/schedules', {
headers: { Authorization: getToken() }
})
.then(function(resp) {
return resp.json().then(function(schedules) {
allSchedules = schedules;
renderSchedules(schedules);
});
return resp
.clone()
.json()
.then(function(schedules) {
allSchedules = 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?');
});
}

View File

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

View File

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