WIP: saves, finally
This commit is contained in:
parent
7db71b94b1
commit
b619b70d22
|
@ -1,4 +1,7 @@
|
||||||
db.json
|
db.json
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
.*.sw*
|
||||||
/cmd/again/again
|
/cmd/again/again
|
||||||
/again
|
/again
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue