API endpoints.
This commit is contained in:
parent
1e283e7cd6
commit
e1685bd32b
8 changed files with 129 additions and 11 deletions
|
@ -1,7 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import "github.com/gorilla/mux"
|
|
||||||
|
|
||||||
func Handler() *mux.Router {
|
|
||||||
return mux.NewRouter()
|
|
||||||
}
|
|
35
api/handler.go
Normal file
35
api/handler.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
19
api/helpers.go
Normal file
19
api/helpers.go
Normal file
|
@ -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
|
||||||
|
}
|
42
api/users.go
Normal file
42
api/users.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/thermokarst/bactdb/api"
|
"github.com/thermokarst/bactdb/api"
|
||||||
|
"github.com/thermokarst/bactdb/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -62,6 +63,7 @@ type subcmd struct {
|
||||||
|
|
||||||
var subcmds = []subcmd{
|
var subcmds = []subcmd{
|
||||||
{"serve", "start web server", serveCmd},
|
{"serve", "start web server", serveCmd},
|
||||||
|
{"create-db", "create the database schema", createDBCmd},
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveCmd(args []string) {
|
func serveCmd(args []string) {
|
||||||
|
@ -83,8 +85,10 @@ The options are:
|
||||||
fs.Usage()
|
fs.Usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
datastore.Connect()
|
||||||
|
|
||||||
m := http.NewServeMux()
|
m := http.NewServeMux()
|
||||||
m.Handle("/api", api.Handler())
|
m.Handle("/api/", http.StripPrefix("/api", api.Handler()))
|
||||||
|
|
||||||
log.Print("Listening on ", *httpAddr)
|
log.Print("Listening on ", *httpAddr)
|
||||||
err := http.ListenAndServe(*httpAddr, m)
|
err := http.ListenAndServe(*httpAddr, m)
|
||||||
|
@ -92,3 +96,25 @@ The options are:
|
||||||
log.Fatal("ListenAndServe:", err)
|
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()
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ var connectOnce sync.Once
|
||||||
func Connect() {
|
func Connect() {
|
||||||
connectOnce.Do(func() {
|
connectOnce.Do(func() {
|
||||||
var err error
|
var err error
|
||||||
DB.Dbx, err = sqlx.Open("postgres", "")
|
DB.Dbx, err = sqlx.Open("postgres", "sslmode=disable")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err)
|
log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package datastore
|
||||||
import "github.com/thermokarst/bactdb/models"
|
import "github.com/thermokarst/bactdb/models"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
DB.AddTableWithName(models.User{}, "users").SetKeys(false, "Id")
|
DB.AddTableWithName(models.User{}, "users").SetKeys(true, "Id")
|
||||||
createSQL = append(createSQL,
|
createSQL = append(createSQL,
|
||||||
`CREATE UNIQUE INDEX username_idx ON users (username);`,
|
`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) {
|
func (s *usersStore) List(opt *models.UserListOptions) ([]*models.User, error) {
|
||||||
|
if opt == nil {
|
||||||
|
opt = &models.UserListOptions{}
|
||||||
|
}
|
||||||
var users []*models.User
|
var users []*models.User
|
||||||
err := s.dbh.Select(&users, `SELECT * FROM users LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset())
|
err := s.dbh.Select(&users, `SELECT * FROM users LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
// A User is a person that has administrative access to bactdb.
|
// A User is a person that has administrative access to bactdb.
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id,omitempty"`
|
||||||
UserName string `sql:"size:100" json:"user_name"`
|
UserName string `sql:"size:100" json:"user_name"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
Reference in a new issue