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
	
	 Matthew Dillon
						Matthew Dillon