From e1685bd32bf5fea9b5e54015ad1271b1ed1635e4 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 30 Sep 2014 16:03:16 -0800 Subject: [PATCH] API endpoints. --- api/api.go | 7 ------- api/handler.go | 35 +++++++++++++++++++++++++++++++++++ api/helpers.go | 19 +++++++++++++++++++ api/users.go | 42 ++++++++++++++++++++++++++++++++++++++++++ cmd/bactdb/bactdb.go | 28 +++++++++++++++++++++++++++- datastore/db.go | 2 +- datastore/users.go | 5 ++++- models/users.go | 2 +- 8 files changed, 129 insertions(+), 11 deletions(-) delete mode 100644 api/api.go create mode 100644 api/handler.go create mode 100644 api/helpers.go create mode 100644 api/users.go diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 495c35f..0000000 --- a/api/api.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -import "github.com/gorilla/mux" - -func Handler() *mux.Router { - return mux.NewRouter() -} diff --git a/api/handler.go b/api/handler.go new file mode 100644 index 0000000..96f68b7 --- /dev/null +++ b/api/handler.go @@ -0,0 +1,35 @@ +package api + +import ( + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/thermokarst/bactdb/datastore" + "github.com/thermokarst/bactdb/router" +) + +var ( + store = datastore.NewDatastore(nil) + schemaDecoder = schema.NewDecoder() +) + +func Handler() *mux.Router { + m := router.API() + m.Get(router.User).Handler(handler(serveUser)) + m.Get(router.Users).Handler(handler(serveUsers)) + return m +} + +type handler func(http.ResponseWriter, *http.Request) error + +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + err := h(w, r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "error: %s", err) + log.Println(err) + } +} diff --git a/api/helpers.go b/api/helpers.go new file mode 100644 index 0000000..85ab78c --- /dev/null +++ b/api/helpers.go @@ -0,0 +1,19 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +// writeJSON writes a JSON Content-Type header and a JSON-encoded object to +// the http.ResponseWriter. +func writeJSON(w http.ResponseWriter, v interface{}) error { + data, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + + w.Header().Set("content-type", "application/json; charset=utf-8") + _, err = w.Write(data) + return err +} diff --git a/api/users.go b/api/users.go new file mode 100644 index 0000000..1ce892c --- /dev/null +++ b/api/users.go @@ -0,0 +1,42 @@ +package api + +import ( + "strconv" + + "github.com/gorilla/mux" + + "net/http" + + "github.com/thermokarst/bactdb/models" +) + +func serveUser(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) + if err != nil { + return err + } + + user, err := store.Users.Get(id) + if err != nil { + return err + } + + return writeJSON(w, user) +} + +func serveUsers(w http.ResponseWriter, r *http.Request) error { + var opt models.UserListOptions + if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { + return err + } + + users, err := store.Users.List(&opt) + if err != nil { + return err + } + if users == nil { + users = []*models.User{} + } + + return writeJSON(w, users) +} diff --git a/cmd/bactdb/bactdb.go b/cmd/bactdb/bactdb.go index df1a33a..d28fd5e 100644 --- a/cmd/bactdb/bactdb.go +++ b/cmd/bactdb/bactdb.go @@ -8,6 +8,7 @@ import ( "os" "github.com/thermokarst/bactdb/api" + "github.com/thermokarst/bactdb/datastore" ) func init() { @@ -62,6 +63,7 @@ type subcmd struct { var subcmds = []subcmd{ {"serve", "start web server", serveCmd}, + {"create-db", "create the database schema", createDBCmd}, } func serveCmd(args []string) { @@ -83,8 +85,10 @@ The options are: fs.Usage() } + datastore.Connect() + m := http.NewServeMux() - m.Handle("/api", api.Handler()) + m.Handle("/api/", http.StripPrefix("/api", api.Handler())) log.Print("Listening on ", *httpAddr) err := http.ListenAndServe(*httpAddr, m) @@ -92,3 +96,25 @@ The options are: log.Fatal("ListenAndServe:", err) } } + +func createDBCmd(args []string) { + fs := flag.NewFlagSet("create-db", flag.ExitOnError) + fs.Usage = func() { + fmt.Fprintln(os.Stderr, `usage: bactdb create-db [options] + +Creates the necessary DB schema. + +The options are: +`) + fs.PrintDefaults() + os.Exit(1) + } + fs.Parse(args) + + if fs.NArg() != 0 { + fs.Usage() + } + + datastore.Connect() + datastore.Create() +} diff --git a/datastore/db.go b/datastore/db.go index 89ede16..e387ca9 100644 --- a/datastore/db.go +++ b/datastore/db.go @@ -24,7 +24,7 @@ var connectOnce sync.Once func Connect() { connectOnce.Do(func() { var err error - DB.Dbx, err = sqlx.Open("postgres", "") + DB.Dbx, err = sqlx.Open("postgres", "sslmode=disable") if err != nil { log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err) } diff --git a/datastore/users.go b/datastore/users.go index 0d4049a..69370bf 100644 --- a/datastore/users.go +++ b/datastore/users.go @@ -3,7 +3,7 @@ package datastore import "github.com/thermokarst/bactdb/models" func init() { - DB.AddTableWithName(models.User{}, "users").SetKeys(false, "Id") + DB.AddTableWithName(models.User{}, "users").SetKeys(true, "Id") createSQL = append(createSQL, `CREATE UNIQUE INDEX username_idx ON users (username);`, ) @@ -25,6 +25,9 @@ func (s *usersStore) Get(id int64) (*models.User, error) { } func (s *usersStore) List(opt *models.UserListOptions) ([]*models.User, error) { + if opt == nil { + opt = &models.UserListOptions{} + } var users []*models.User err := s.dbh.Select(&users, `SELECT * FROM users LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset()) if err != nil { diff --git a/models/users.go b/models/users.go index e2253fa..0022897 100644 --- a/models/users.go +++ b/models/users.go @@ -10,7 +10,7 @@ import ( // A User is a person that has administrative access to bactdb. type User struct { - Id int64 `json:"id"` + Id int64 `json:"id,omitempty"` UserName string `sql:"size:100" json:"user_name"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"`