WIP: saves, finally
This commit is contained in:
		
							parent
							
								
									7db71b94b1
								
							
						
					
					
						commit
						b619b70d22
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,7 @@ | ||||
| db.json | ||||
| *.bak | ||||
| *.tmp | ||||
| .*.sw* | ||||
| /cmd/again/again | ||||
| /again | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| 			}, | ||||
| 			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,17 +318,35 @@ | ||||
| 			}) | ||||
| 		); | ||||
| 		$('.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) { | ||||
| 				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?'); | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	function renderSchedules(schedules) { | ||||
|  | ||||
| @ -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" /> | ||||
|  | ||||
| @ -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"` | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user