go-again/cmd/again/again.go

207 lines
4.8 KiB
Go
Raw Normal View History

2019-06-21 18:35:08 +00:00
package main
import (
2019-06-23 03:50:17 +00:00
"context"
2019-06-22 07:10:21 +00:00
"encoding/json"
2019-06-21 18:35:08 +00:00
"flag"
"fmt"
"log"
"net/http"
"os"
"strconv"
2019-06-22 07:28:02 +00:00
"strings"
2019-06-21 18:35:08 +00:00
"time"
2019-06-22 07:10:21 +00:00
again "git.rootprojects.org/root/go-again"
2019-06-21 18:35:08 +00:00
"git.rootprojects.org/root/go-again/data/jsondb"
)
func main() {
portEnv := os.Getenv("PORT")
2019-06-22 07:10:21 +00:00
dbEnv := os.Getenv("DATABASE_URL")
2019-06-21 18:35:08 +00:00
portInt := flag.Int("port", 0, "port on which to serve http")
addr := flag.String("addr", "", "address on which to serve http")
2019-06-22 07:10:21 +00:00
dburl := flag.String("database-url", "", "For example: json://relative-path/db.json or json:///absolute-path/db.json")
2019-06-21 18:35:08 +00:00
flag.Parse()
if "" != portEnv {
if 0 != *portInt {
log.Fatal("You may set PORT or --port, but not both.")
return
}
n, err := strconv.Atoi(portEnv)
if nil != err {
log.Fatalf("Could not parse PORT=%q.", n)
return
}
*portInt = n
}
if *portInt < 1024 || *portInt > 65535 {
2019-06-22 07:10:21 +00:00
log.Fatalf("`port` should be between 1024 and 65535, not %d.", *portInt)
2019-06-21 18:35:08 +00:00
return
}
portEnv = strconv.Itoa(*portInt)
2019-06-22 07:10:21 +00:00
if "" != dbEnv {
if "" != *dburl {
log.Fatal("You may set DATABASE_URL or --database-url, but not both.")
return
}
*dburl = dbEnv
// TODO parse string?
// TODO have each connector try in sequence by registering with build tags like go-migrate does?
}
if "" == *dburl {
log.Fatalf("`database-url` must be specified." +
" Something like --database-url='json:///var/go-again/db.json' should do nicely.")
return
}
db, err := jsondb.Connect(*dburl)
if nil != err {
log.Fatalf("Could not connect to database %q: %s", *dburl, err)
return
}
s := &scheduler{
DB: db,
}
mux := http.NewServeMux()
2019-06-21 18:35:08 +00:00
server := &http.Server{
Addr: fmt.Sprintf("%s:%s", *addr, portEnv),
2019-06-22 07:10:21 +00:00
Handler: mux,
2019-06-21 18:35:08 +00:00
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
2019-06-22 07:10:21 +00:00
//mux.Handle("/api/", http.HandlerFunc(handleFunc))
2019-06-22 23:11:14 +00:00
mux.HandleFunc("/api/v0/schedules", s.Handle)
2019-06-23 09:01:24 +00:00
mux.HandleFunc("/api/v0/schedules/", s.Handle)
2019-06-22 07:10:21 +00:00
// TODO Filebox FS
mux.Handle("/", http.FileServer(http.Dir("./public")))
2019-06-21 18:35:08 +00:00
fmt.Println("Listening on", server.Addr)
log.Fatal(server.ListenAndServe())
}
2019-06-22 07:10:21 +00:00
type ScheduleDB interface {
2019-06-23 03:50:17 +00:00
List(string) ([]*again.Schedule, error)
Set(again.Schedule) (*again.Schedule, error)
2019-06-23 09:01:24 +00:00
Delete(accessID string, id string) (*again.Schedule, error)
2019-06-22 07:10:21 +00:00
}
type scheduler struct {
DB ScheduleDB
}
func (s *scheduler) Handle(w http.ResponseWriter, r *http.Request) {
2019-06-23 03:50:17 +00:00
// note: no go-routines reading body in handlers to follow
2019-06-22 07:28:02 +00:00
defer r.Body.Close()
2019-06-23 03:50:17 +00:00
2019-06-22 07:28:02 +00:00
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
2019-06-23 03:50:17 +00:00
if 32 != len(token) {
2019-06-22 07:28:02 +00:00
http.Error(w, "Authorization Header did not contain a valid token", http.StatusForbidden)
return
}
2019-06-23 03:50:17 +00:00
ctx := r.Context()
ctx = context.WithValue(ctx, "token", token)
r = r.WithContext(ctx)
2019-06-22 07:28:02 +00:00
2019-06-22 07:10:21 +00:00
switch r.Method {
case http.MethodGet:
s.List(w, r)
return
case http.MethodPost:
2019-06-23 03:50:17 +00:00
s.Create(w, r)
2019-06-22 07:10:21 +00:00
return
2019-06-23 09:01:24 +00:00
case http.MethodDelete:
s.Delete(w, r)
return
2019-06-22 07:10:21 +00:00
default:
http.Error(w, "Not Implemented", http.StatusNotImplemented)
return
}
}
func (s *scheduler) List(w http.ResponseWriter, r *http.Request) {
2019-06-23 03:50:17 +00:00
accessID := r.Context().Value("token").(string)
schedules, err := s.DB.List(accessID)
2019-06-22 07:10:21 +00:00
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf, err := json.Marshal(schedules)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(buf)
2019-06-21 18:35:08 +00:00
}
2019-06-23 03:50:17 +00:00
func (s *scheduler) Create(w http.ResponseWriter, r *http.Request) {
// TODO validate user
accessID := r.Context().Value("token").(string)
2019-06-23 06:08:05 +00:00
/*
br, bw := io.Pipe()
b := io.TeeReader(r.Body, bw)
go func() {
x, _ := ioutil.ReadAll(b)
2019-06-23 09:01:24 +00:00
fmt.Println("[debug] http body", string(x))
2019-06-23 06:08:05 +00:00
bw.Close()
}()
decoder := json.NewDecoder(br)
*/
2019-06-23 03:50:17 +00:00
decoder := json.NewDecoder(r.Body)
sched := &again.Schedule{}
2019-06-23 06:08:05 +00:00
err := decoder.Decode(sched)
2019-06-23 03:50:17 +00:00
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2019-06-23 06:08:05 +00:00
fmt.Printf("New Schedule:\n%#v\n", sched)
2019-06-23 03:50:17 +00:00
// TODO validate and modify
sched.AccessID = accessID
2019-06-23 06:08:05 +00:00
sched2, err := s.DB.Set(*sched)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2019-06-23 03:50:17 +00:00
2019-06-23 06:08:05 +00:00
buf, err := json.Marshal(sched2)
2019-06-23 03:50:17 +00:00
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(buf)
}
2019-06-23 09:01:24 +00:00
func (s *scheduler) Delete(w http.ResponseWriter, r *http.Request) {
// TODO validate user
accessID := r.Context().Value("token").(string)
parts := strings.Split(r.URL.Path, "/")
// ""/"api"/"v0"/"schedules"/":id"
if 5 != len(parts) {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
id := parts[4]
sched2, err := s.DB.Delete(accessID, id)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf, err := json.Marshal(sched2)
if nil != err {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(buf)
}