diff --git a/.gitignore b/.gitignore index 9a3a8d8..c49fe25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +db.json +/cmd/again/again +/again + # ---> Go # Binaries for programs and plugins *.exe diff --git a/cmd/again/again b/cmd/again/again deleted file mode 100755 index 7b335b3..0000000 Binary files a/cmd/again/again and /dev/null differ diff --git a/cmd/again/again.go b/cmd/again/again.go index 245f9e0..7dbc440 100644 --- a/cmd/again/again.go +++ b/cmd/again/again.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "fmt" "log" @@ -9,14 +10,17 @@ import ( "strconv" "time" + again "git.rootprojects.org/root/go-again" "git.rootprojects.org/root/go-again/data/jsondb" ) func main() { portEnv := os.Getenv("PORT") + dbEnv := os.Getenv("DATABASE_URL") portInt := flag.Int("port", 0, "port on which to serve http") addr := flag.String("addr", "", "address on which to serve http") + dburl := flag.String("database-url", "", "For example: json://relative-path/db.json or json:///absolute-path/db.json") flag.Parse() if "" != portEnv { @@ -32,24 +36,86 @@ func main() { *portInt = n } if *portInt < 1024 || *portInt > 65535 { - log.Fatalf("port should be between 1024 and 65535, not %d.", *portInt) + log.Fatalf("`port` should be between 1024 and 65535, not %d.", *portInt) return } portEnv = strconv.Itoa(*portInt) + 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() server := &http.Server{ Addr: fmt.Sprintf("%s:%s", *addr, portEnv), - Handler: http.HandlerFunc(handleFunc), + Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } + //mux.Handle("/api/", http.HandlerFunc(handleFunc)) + mux.HandleFunc("/api/schedules", s.Handle) + + // TODO Filebox FS + mux.Handle("/", http.FileServer(http.Dir("./public"))) fmt.Println("Listening on", server.Addr) log.Fatal(server.ListenAndServe()) } -func handleFunc(w http.ResponseWriter, r *http.Request) { - jsondb.List() - w.Write([]byte("Hello, World!")) +type ScheduleDB interface { + List() ([]again.Schedule, error) +} + +type scheduler struct { + DB ScheduleDB +} + +func (s *scheduler) Handle(w http.ResponseWriter, r *http.Request) { + fmt.Println("whatever", r.Method, r.URL) + switch r.Method { + case http.MethodGet: + s.List(w, r) + return + case http.MethodPost: + http.Error(w, "Not Implemented", http.StatusNotImplemented) + return + default: + http.Error(w, "Not Implemented", http.StatusNotImplemented) + return + } +} + +func (s *scheduler) List(w http.ResponseWriter, r *http.Request) { + schedules, err := s.DB.List() + 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) } diff --git a/data/jsondb/jsondb.go b/data/jsondb/jsondb.go index 2eebe81..b630fdf 100644 --- a/data/jsondb/jsondb.go +++ b/data/jsondb/jsondb.go @@ -1,11 +1,78 @@ package jsondb import ( - "errors" + "encoding/json" + "fmt" + "net/url" + "os" + "strings" again "git.rootprojects.org/root/go-again" ) -func List() ([]again.Schedule, error) { - return nil, errors.New("Not Implemented") +type JSONDB struct { + dburl string + file *os.File + json *dbjson +} + +type dbjson struct { + Schedules []again.Schedule `json:"schedules"` +} + +func Connect(dburl string) (*JSONDB, error) { + u, err := url.Parse(dburl) + if nil != err { + return nil, err + } + + // json:/abspath/to/db.json + fmt.Println("url.Opaque:", u.Opaque) + // json:///abspath/to/db.json + fmt.Println("url.Path:", u.Path) + fmt.Println(u) + + path := u.Opaque + if "" == path { + path = u.Path + if "" == path { + // json:relpath/to/db.json + // json://relpath/to/db.json + path = strings.TrimSuffix(u.Host+"/"+u.Path, "/") + } + } + + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0700) + if nil != err { + return nil, fmt.Errorf("Couldn't open %q: %s", path, err) + } + + stat, err := f.Stat() + if 0 == stat.Size() { + _, err := f.Write([]byte(`{"schedules":[]}`)) + if nil != err { + return nil, err + } + f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0700) + if nil != err { + return nil, err + } + } + + decoder := json.NewDecoder(f) + db := &dbjson{} + err = decoder.Decode(db) + if nil != err { + return nil, fmt.Errorf("Couldn't parse %q as JSON: %s", path, err) + } + + return &JSONDB{ + dburl: dburl, + file: f, + json: db, + }, nil +} + +func (db *JSONDB) List() ([]again.Schedule, error) { + return db.json.Schedules, nil } diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..937f34d --- /dev/null +++ b/public/index.html @@ -0,0 +1,9 @@ + + + + Go Again + + +

Hello, World!

+ +