commit
						205a932e2a
					
				
					 15 changed files with 898 additions and 6 deletions
				
			
		
							
								
								
									
										94
									
								
								api/genera.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								api/genera.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"strconv" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  | 
 | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func serveGenus(w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	genus, err := store.Genera.Get(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writeJSON(w, genus) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serveCreateGenus(w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	var genus models.Genus | ||||||
|  | 	err := json.NewDecoder(r.Body).Decode(&genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	created, err := store.Genera.Create(&genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if created { | ||||||
|  | 		w.WriteHeader(http.StatusCreated) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writeJSON(w, genus) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serveGenera(w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	var opt models.GenusListOptions | ||||||
|  | 	if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	genera, err := store.Genera.List(&opt) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if genera == nil { | ||||||
|  | 		genera = []*models.Genus{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writeJSON(w, genera) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serveUpdateGenus(w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) | ||||||
|  | 	var genus models.Genus | ||||||
|  | 	err := json.NewDecoder(r.Body).Decode(&genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	updated, err := store.Genera.Update(id, &genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if updated { | ||||||
|  | 		w.WriteHeader(http.StatusOK) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writeJSON(w, genus) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serveDeleteGenus(w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) | ||||||
|  | 
 | ||||||
|  | 	deleted, err := store.Genera.Delete(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if deleted { | ||||||
|  | 		w.WriteHeader(http.StatusOK) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writeJSON(w, &models.Genus{}) | ||||||
|  | } | ||||||
							
								
								
									
										147
									
								
								api/genera_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								api/genera_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,147 @@ | ||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestGenus_Get(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 
 | ||||||
|  | 	want := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	calledGet := false | ||||||
|  | 	store.Genera.(*models.MockGeneraService).Get_ = func(id int64) (*models.Genus, error) { | ||||||
|  | 		if id != want.Id { | ||||||
|  | 			t.Errorf("wanted request for genus %d but got %d", want.Id, id) | ||||||
|  | 		} | ||||||
|  | 		calledGet = true | ||||||
|  | 		return want, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	got, err := apiClient.Genera.Get(want.Id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !calledGet { | ||||||
|  | 		t.Error("!calledGet") | ||||||
|  | 	} | ||||||
|  | 	if !normalizeDeepEqual(want, got) { | ||||||
|  | 		t.Errorf("got genus %+v but wanted genus %+v", got, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGenus_Create(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 
 | ||||||
|  | 	want := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	calledPost := false | ||||||
|  | 	store.Genera.(*models.MockGeneraService).Create_ = func(genus *models.Genus) (bool, error) { | ||||||
|  | 		if !normalizeDeepEqual(want, genus) { | ||||||
|  | 			t.Errorf("wanted request for genus %d but got %d", want, genus) | ||||||
|  | 		} | ||||||
|  | 		calledPost = true | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	success, err := apiClient.Genera.Create(want) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !calledPost { | ||||||
|  | 		t.Error("!calledPost") | ||||||
|  | 	} | ||||||
|  | 	if !success { | ||||||
|  | 		t.Error("!success") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGenus_List(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 
 | ||||||
|  | 	want := []*models.Genus{{Id: 1, GenusName: "Test Genus"}} | ||||||
|  | 	wantOpt := &models.GenusListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}} | ||||||
|  | 
 | ||||||
|  | 	calledList := false | ||||||
|  | 	store.Genera.(*models.MockGeneraService).List_ = func(opt *models.GenusListOptions) ([]*models.Genus, error) { | ||||||
|  | 		if !normalizeDeepEqual(wantOpt, opt) { | ||||||
|  | 			t.Errorf("wanted options %d but got %d", wantOpt, opt) | ||||||
|  | 		} | ||||||
|  | 		calledList = true | ||||||
|  | 		return want, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	genera, err := apiClient.Genera.List(wantOpt) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !calledList { | ||||||
|  | 		t.Error("!calledList") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !normalizeDeepEqual(&want, &genera) { | ||||||
|  | 		t.Errorf("got genera %+v but wanted genera %+v", genera, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGenus_Update(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 
 | ||||||
|  | 	want := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	calledPut := false | ||||||
|  | 	store.Genera.(*models.MockGeneraService).Update_ = func(id int64, genus *models.Genus) (bool, error) { | ||||||
|  | 		if id != want.Id { | ||||||
|  | 			t.Errorf("wanted request for genus %d but got %d", want.Id, id) | ||||||
|  | 		} | ||||||
|  | 		if !normalizeDeepEqual(want, genus) { | ||||||
|  | 			t.Errorf("wanted request for genus %d but got %d", want, genus) | ||||||
|  | 		} | ||||||
|  | 		calledPut = true | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	success, err := apiClient.Genera.Update(1, want) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !calledPut { | ||||||
|  | 		t.Error("!calledPut") | ||||||
|  | 	} | ||||||
|  | 	if !success { | ||||||
|  | 		t.Error("!success") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGenus_Delete(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 
 | ||||||
|  | 	want := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	calledDelete := false | ||||||
|  | 	store.Genera.(*models.MockGeneraService).Delete_ = func(id int64) (bool, error) { | ||||||
|  | 		if id != want.Id { | ||||||
|  | 			t.Errorf("wanted request for genus %d but got %d", want.Id, id) | ||||||
|  | 		} | ||||||
|  | 		calledDelete = true | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	success, err := apiClient.Genera.Delete(1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !calledDelete { | ||||||
|  | 		t.Error("!calledDelete") | ||||||
|  | 	} | ||||||
|  | 	if !success { | ||||||
|  | 		t.Error("!success") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -18,9 +18,17 @@ var ( | ||||||
| 
 | 
 | ||||||
| func Handler() *mux.Router { | func Handler() *mux.Router { | ||||||
| 	m := router.API() | 	m := router.API() | ||||||
|  | 
 | ||||||
| 	m.Get(router.User).Handler(handler(serveUser)) | 	m.Get(router.User).Handler(handler(serveUser)) | ||||||
| 	m.Get(router.CreateUser).Handler(handler(serveCreateUser)) | 	m.Get(router.CreateUser).Handler(handler(serveCreateUser)) | ||||||
| 	m.Get(router.Users).Handler(handler(serveUsers)) | 	m.Get(router.Users).Handler(handler(serveUsers)) | ||||||
|  | 
 | ||||||
|  | 	m.Get(router.Genus).Handler(handler(serveGenus)) | ||||||
|  | 	m.Get(router.CreateGenus).Handler(handler(serveCreateGenus)) | ||||||
|  | 	m.Get(router.Genera).Handler(handler(serveGenera)) | ||||||
|  | 	m.Get(router.UpdateGenus).Handler(handler(serveUpdateGenus)) | ||||||
|  | 	m.Get(router.DeleteGenus).Handler(handler(serveDeleteGenus)) | ||||||
|  | 
 | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,16 +1,24 @@ | ||||||
| package datastore | package datastore | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
|  | 
 | ||||||
| 	"github.com/jmoiron/modl" | 	"github.com/jmoiron/modl" | ||||||
| 	"github.com/thermokarst/bactdb/models" | 	"github.com/thermokarst/bactdb/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // A datastore access point (in PostgreSQL) | // A datastore access point (in PostgreSQL) | ||||||
| type Datastore struct { | type Datastore struct { | ||||||
| 	Users models.UsersService | 	Users  models.UsersService | ||||||
| 	dbh   modl.SqlExecutor | 	Genera models.GeneraService | ||||||
|  | 	dbh    modl.SqlExecutor | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	ErrNoRowsUpdated = errors.New(`no rows updated`) | ||||||
|  | 	ErrNoRowsDeleted = errors.New(`no rows deleted`) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // NewDatastore creates a new client for accessing the datastore (in PostgreSQL). | // NewDatastore creates a new client for accessing the datastore (in PostgreSQL). | ||||||
| // If dbh is nil, it uses the global DB handle. | // If dbh is nil, it uses the global DB handle. | ||||||
| func NewDatastore(dbh modl.SqlExecutor) *Datastore { | func NewDatastore(dbh modl.SqlExecutor) *Datastore { | ||||||
|  | @ -20,11 +28,13 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore { | ||||||
| 
 | 
 | ||||||
| 	d := &Datastore{dbh: dbh} | 	d := &Datastore{dbh: dbh} | ||||||
| 	d.Users = &usersStore{d} | 	d.Users = &usersStore{d} | ||||||
|  | 	d.Genera = &generaStore{d} | ||||||
| 	return d | 	return d | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewMockDatastore() *Datastore { | func NewMockDatastore() *Datastore { | ||||||
| 	return &Datastore{ | 	return &Datastore{ | ||||||
| 		Users: &models.MockUsersService{}, | 		Users:  &models.MockUsersService{}, | ||||||
|  | 		Genera: &models.MockGeneraService{}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -26,7 +26,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", "sslmode=disable") | 		DB.Dbx, err = sqlx.Open("postgres", "timezone=UTC 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) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										85
									
								
								datastore/genera.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								datastore/genera.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | ||||||
|  | package datastore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	DB.AddTableWithName(models.Genus{}, "genera").SetKeys(true, "Id") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type generaStore struct { | ||||||
|  | 	*Datastore | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaStore) Get(id int64) (*models.Genus, error) { | ||||||
|  | 	var genus []*models.Genus | ||||||
|  | 	if err := s.dbh.Select(&genus, `SELECT * FROM genera WHERE id=$1;`, id); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if len(genus) == 0 { | ||||||
|  | 		return nil, models.ErrGenusNotFound | ||||||
|  | 	} | ||||||
|  | 	return genus[0], nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaStore) Create(genus *models.Genus) (bool, error) { | ||||||
|  | 	if err := s.dbh.Insert(genus); err != nil { | ||||||
|  | 		if strings.Contains(err.Error(), `violates unique constraint "genus_idx"`) { | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaStore) List(opt *models.GenusListOptions) ([]*models.Genus, error) { | ||||||
|  | 	if opt == nil { | ||||||
|  | 		opt = &models.GenusListOptions{} | ||||||
|  | 	} | ||||||
|  | 	var genera []*models.Genus | ||||||
|  | 	err := s.dbh.Select(&genera, `SELECT * FROM genera LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return genera, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaStore) Update(id int64, genus *models.Genus) (bool, error) { | ||||||
|  | 	_, err := s.Get(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if id != genus.Id { | ||||||
|  | 		return false, models.ErrGenusNotFound | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	changed, err := s.dbh.Update(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if changed == 0 { | ||||||
|  | 		return false, ErrNoRowsUpdated | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaStore) Delete(id int64) (bool, error) { | ||||||
|  | 	genus, err := s.Get(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	deleted, err := s.dbh.Delete(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	if deleted == 0 { | ||||||
|  | 		return false, ErrNoRowsDeleted | ||||||
|  | 	} | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								datastore/genera_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								datastore/genera_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,140 @@ | ||||||
|  | package datastore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/models" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestGeneraStore_Get_db(t *testing.T) { | ||||||
|  | 	want := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	tx, _ := DB.Begin() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 
 | ||||||
|  | 	// Test on a clean database | ||||||
|  | 	tx.Exec(`DELETE FROM genera;`) | ||||||
|  | 	if err := tx.Insert(want); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	d := NewDatastore(tx) | ||||||
|  | 	genus, err := d.Genera.Get(1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) | ||||||
|  | 	if !reflect.DeepEqual(genus, want) { | ||||||
|  | 		t.Errorf("got genus %+v, want %+v", genus, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraStore_Create_db(t *testing.T) { | ||||||
|  | 	genus := &models.Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	tx, _ := DB.Begin() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 
 | ||||||
|  | 	// Test on a clean database | ||||||
|  | 	tx.Exec(`DELETE FROM genera;`) | ||||||
|  | 
 | ||||||
|  | 	d := NewDatastore(tx) | ||||||
|  | 	created, err := d.Genera.Create(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !created { | ||||||
|  | 		t.Error("!created") | ||||||
|  | 	} | ||||||
|  | 	if genus.Id == 0 { | ||||||
|  | 		t.Error("want nonzero genus.Id after submitting") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraStore_List_db(t *testing.T) { | ||||||
|  | 	want := []*models.Genus{{Id: 1, GenusName: "Test Genus"}} | ||||||
|  | 
 | ||||||
|  | 	tx, _ := DB.Begin() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 
 | ||||||
|  | 	// Test on a clean database | ||||||
|  | 	tx.Exec(`DELETE FROM genera;`) | ||||||
|  | 	if err := tx.Insert(want[0]); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	d := NewDatastore(tx) | ||||||
|  | 	genera, err := d.Genera.List(&models.GenusListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, g := range want { | ||||||
|  | 		normalizeTime(&g.CreatedAt, &g.UpdatedAt, &g.DeletedAt) | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(genera, want) { | ||||||
|  | 		t.Errorf("got genera %+v, want %+v", genera, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraStore_Update_db(t *testing.T) { | ||||||
|  | 	tx, _ := DB.Begin() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 
 | ||||||
|  | 	// Test on a clean database | ||||||
|  | 	tx.Exec(`DELETE FROM genera;`) | ||||||
|  | 
 | ||||||
|  | 	d := NewDatastore(nil) | ||||||
|  | 	// Add a new record | ||||||
|  | 	genus := &models.Genus{GenusName: "Test Genus"} | ||||||
|  | 	created, err := d.Genera.Create(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !created { | ||||||
|  | 		t.Error("!created") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Tweak it | ||||||
|  | 	genus.GenusName = "Updated Genus" | ||||||
|  | 	updated, err := d.Genera.Update(genus.Id, genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !updated { | ||||||
|  | 		t.Error("!updated") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraStore_Delete_db(t *testing.T) { | ||||||
|  | 	tx, _ := DB.Begin() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 
 | ||||||
|  | 	// Test on a clean database | ||||||
|  | 	tx.Exec(`DELETE FROM genera;`) | ||||||
|  | 
 | ||||||
|  | 	d := NewDatastore(tx) | ||||||
|  | 	// Add a new record | ||||||
|  | 	genus := &models.Genus{GenusName: "Test Genus"} | ||||||
|  | 	created, err := d.Genera.Create(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !created { | ||||||
|  | 		t.Error("!created") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Delete it | ||||||
|  | 	deleted, err := d.Genera.Delete(genus.Id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !deleted { | ||||||
|  | 		t.Error("!delete") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								datastore/migrations/00002_AddGenera_down.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								datastore/migrations/00002_AddGenera_down.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | -- bactdb | ||||||
|  | -- Matthew R Dillon | ||||||
|  | 
 | ||||||
|  | DROP TABLE genera; | ||||||
|  | 
 | ||||||
							
								
								
									
										19
									
								
								datastore/migrations/00002_AddGenera_up.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								datastore/migrations/00002_AddGenera_up.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | -- bactdb | ||||||
|  | -- Matthew R Dillon | ||||||
|  | 
 | ||||||
|  | CREATE TABLE genera ( | ||||||
|  |     id BIGSERIAL NOT NULL, | ||||||
|  |     genusname CHARACTER VARYING(100), | ||||||
|  | 
 | ||||||
|  |     createdat TIMESTAMP WITH TIME ZONE, | ||||||
|  |     updatedat TIMESTAMP WITH TIME ZONE, | ||||||
|  |     deletedat TIMESTAMP WITH TIME ZONE, | ||||||
|  | 
 | ||||||
|  |     CONSTRAINT genus_pkey PRIMARY KEY (id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | CREATE UNIQUE INDEX genusname_idx | ||||||
|  |     ON genera | ||||||
|  |     USING btree | ||||||
|  |     (genusname COLLATE pg_catalog."default"); | ||||||
|  | 
 | ||||||
|  | @ -16,7 +16,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| // A Client communicates with bactdb's HTTP API. | // A Client communicates with bactdb's HTTP API. | ||||||
| type Client struct { | type Client struct { | ||||||
| 	Users UsersService | 	Users  UsersService | ||||||
|  | 	Genera GeneraService | ||||||
| 
 | 
 | ||||||
| 	// BaseURL for HTTP requests to bactdb's API. | 	// BaseURL for HTTP requests to bactdb's API. | ||||||
| 	BaseURL *url.URL | 	BaseURL *url.URL | ||||||
|  | @ -45,6 +46,7 @@ func NewClient(httpClient *http.Client) *Client { | ||||||
| 		httpClient: httpClient, | 		httpClient: httpClient, | ||||||
| 	} | 	} | ||||||
| 	c.Users = &usersService{c} | 	c.Users = &usersService{c} | ||||||
|  | 	c.Genera = &generaService{c} | ||||||
| 	return c | 	return c | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										200
									
								
								models/genera.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								models/genera.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,200 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/router" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A Genus is a high-level classifier in bactdb. | ||||||
|  | type Genus struct { | ||||||
|  | 	Id        int64     `json:"id,omitempty"` | ||||||
|  | 	GenusName string    `json:"genus_name"` | ||||||
|  | 	CreatedAt time.Time `json:"created_at"` | ||||||
|  | 	UpdatedAt time.Time `json:"updated_at"` | ||||||
|  | 	DeletedAt time.Time `json:"deleted_at"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GeneraService interacts with the genus-related endpoints in bactdb's API. | ||||||
|  | type GeneraService interface { | ||||||
|  | 	// Get a genus. | ||||||
|  | 	Get(id int64) (*Genus, error) | ||||||
|  | 
 | ||||||
|  | 	// List all genera. | ||||||
|  | 	List(opt *GenusListOptions) ([]*Genus, error) | ||||||
|  | 
 | ||||||
|  | 	// Create a new genus. The newly created genus's ID is written to genus.Id | ||||||
|  | 	Create(genus *Genus) (created bool, err error) | ||||||
|  | 
 | ||||||
|  | 	// Update an existing genus. | ||||||
|  | 	Update(id int64, genus *Genus) (updated bool, err error) | ||||||
|  | 
 | ||||||
|  | 	// Delete an existing genus. | ||||||
|  | 	Delete(id int64) (deleted bool, err error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	ErrGenusNotFound = errors.New("genus not found") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type generaService struct { | ||||||
|  | 	client *Client | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaService) Get(id int64) (*Genus, error) { | ||||||
|  | 	// Pass in key value pairs as strings, so that the gorilla mux URL | ||||||
|  | 	// generation is happy. | ||||||
|  | 	strId := strconv.FormatInt(id, 10) | ||||||
|  | 
 | ||||||
|  | 	url, err := s.client.url(router.Genus, map[string]string{"Id": strId}, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := s.client.NewRequest("GET", url.String(), nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var genus *Genus | ||||||
|  | 	_, err = s.client.Do(req, &genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return genus, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaService) Create(genus *Genus) (bool, error) { | ||||||
|  | 	url, err := s.client.url(router.CreateGenus, nil, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := s.client.NewRequest("POST", url.String(), genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := s.client.Do(req, &genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return resp.StatusCode == http.StatusCreated, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type GenusListOptions struct { | ||||||
|  | 	ListOptions | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaService) List(opt *GenusListOptions) ([]*Genus, error) { | ||||||
|  | 	url, err := s.client.url(router.Genera, nil, opt) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := s.client.NewRequest("GET", url.String(), nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var genera []*Genus | ||||||
|  | 	_, err = s.client.Do(req, &genera) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return genera, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaService) Update(id int64, genus *Genus) (bool, error) { | ||||||
|  | 	strId := strconv.FormatInt(id, 10) | ||||||
|  | 
 | ||||||
|  | 	url, err := s.client.url(router.UpdateGenus, map[string]string{"Id": strId}, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := s.client.NewRequest("PUT", url.String(), genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := s.client.Do(req, &genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return resp.StatusCode == http.StatusOK, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *generaService) Delete(id int64) (bool, error) { | ||||||
|  | 	strId := strconv.FormatInt(id, 10) | ||||||
|  | 
 | ||||||
|  | 	url, err := s.client.url(router.DeleteGenus, map[string]string{"Id": strId}, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := s.client.NewRequest("DELETE", url.String(), nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var genus *Genus | ||||||
|  | 	resp, err := s.client.Do(req, &genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return resp.StatusCode == http.StatusOK, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MockGeneraService struct { | ||||||
|  | 	Get_    func(id int64) (*Genus, error) | ||||||
|  | 	List_   func(opt *GenusListOptions) ([]*Genus, error) | ||||||
|  | 	Create_ func(genus *Genus) (bool, error) | ||||||
|  | 	Update_ func(id int64, genus *Genus) (bool, error) | ||||||
|  | 	Delete_ func(id int64) (bool, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ GeneraService = &MockGeneraService{} | ||||||
|  | 
 | ||||||
|  | func (s *MockGeneraService) Get(id int64) (*Genus, error) { | ||||||
|  | 	if s.Get_ == nil { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	return s.Get_(id) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *MockGeneraService) Create(genus *Genus) (bool, error) { | ||||||
|  | 	if s.Create_ == nil { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	return s.Create_(genus) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *MockGeneraService) List(opt *GenusListOptions) ([]*Genus, error) { | ||||||
|  | 	if s.List_ == nil { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	return s.List_(opt) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *MockGeneraService) Update(id int64, genus *Genus) (bool, error) { | ||||||
|  | 	if s.Update_ == nil { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	return s.Update_(id, genus) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *MockGeneraService) Delete(id int64) (bool, error) { | ||||||
|  | 	if s.Delete_ == nil { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	return s.Delete_(id) | ||||||
|  | } | ||||||
							
								
								
									
										167
									
								
								models/genera_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								models/genera_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,167 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/thermokarst/bactdb/router" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestGeneraService_Get(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  | 
 | ||||||
|  | 	want := &Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	var called bool | ||||||
|  | 	mux.HandleFunc(urlPath(t, router.Genus, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		called = true | ||||||
|  | 		testMethod(t, r, "GET") | ||||||
|  | 
 | ||||||
|  | 		writeJSON(w, want) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	genus, err := client.Genera.Get(1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Genera.Get returned error: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !called { | ||||||
|  | 		t.Fatal("!called") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) | ||||||
|  | 
 | ||||||
|  | 	if !reflect.DeepEqual(genus, want) { | ||||||
|  | 		t.Errorf("Genera.Get returned %+v, want %+v", genus, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraService_Create(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  | 
 | ||||||
|  | 	want := &Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	var called bool | ||||||
|  | 	mux.HandleFunc(urlPath(t, router.CreateGenus, nil), func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		called = true | ||||||
|  | 		testMethod(t, r, "POST") | ||||||
|  | 		testBody(t, r, `{"id":1,"genus_name":"Test Genus","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","deleted_at":"0001-01-01T00:00:00Z"}`+"\n") | ||||||
|  | 
 | ||||||
|  | 		w.WriteHeader(http.StatusCreated) | ||||||
|  | 		writeJSON(w, want) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	genus := &Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 	created, err := client.Genera.Create(genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Genera.Create returned error: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !created { | ||||||
|  | 		t.Error("!created") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !called { | ||||||
|  | 		t.Fatal("!called") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) | ||||||
|  | 	if !reflect.DeepEqual(genus, want) { | ||||||
|  | 		t.Errorf("Genera.Create returned %+v, want %+v", genus, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraService_List(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  | 
 | ||||||
|  | 	want := []*Genus{{Id: 1, GenusName: "Test Genus"}} | ||||||
|  | 
 | ||||||
|  | 	var called bool | ||||||
|  | 	mux.HandleFunc(urlPath(t, router.Genera, nil), func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		called = true | ||||||
|  | 		testMethod(t, r, "GET") | ||||||
|  | 		testFormValues(t, r, values{}) | ||||||
|  | 
 | ||||||
|  | 		writeJSON(w, want) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	genera, err := client.Genera.List(nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Genera.List returned error: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !called { | ||||||
|  | 		t.Fatal("!called") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, u := range want { | ||||||
|  | 		normalizeTime(&u.CreatedAt, &u.UpdatedAt, &u.DeletedAt) | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(genera, want) { | ||||||
|  | 		t.Errorf("Genera.List return %+v, want %+v", genera, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraService_Update(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  | 
 | ||||||
|  | 	want := &Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	var called bool | ||||||
|  | 	mux.HandleFunc(urlPath(t, router.UpdateGenus, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		called = true | ||||||
|  | 		testMethod(t, r, "PUT") | ||||||
|  | 		testBody(t, r, `{"id":1,"genus_name":"Test Genus Updated","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","deleted_at":"0001-01-01T00:00:00Z"}`+"\n") | ||||||
|  | 
 | ||||||
|  | 		w.WriteHeader(http.StatusOK) | ||||||
|  | 		writeJSON(w, want) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	genus := &Genus{Id: 1, GenusName: "Test Genus Updated"} | ||||||
|  | 	updated, err := client.Genera.Update(1, genus) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Genera.Update returned error: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !updated { | ||||||
|  | 		t.Error("!updated") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !called { | ||||||
|  | 		t.Fatal("!called") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGeneraService_Delete(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  | 
 | ||||||
|  | 	want := &Genus{Id: 1, GenusName: "Test Genus"} | ||||||
|  | 
 | ||||||
|  | 	var called bool | ||||||
|  | 	mux.HandleFunc(urlPath(t, router.DeleteGenus, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		called = true | ||||||
|  | 		testMethod(t, r, "DELETE") | ||||||
|  | 
 | ||||||
|  | 		w.WriteHeader(http.StatusOK) | ||||||
|  | 		writeJSON(w, want) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	deleted, err := client.Genera.Delete(1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Genera.Delete returned error: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !deleted { | ||||||
|  | 		t.Error("!deleted") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !called { | ||||||
|  | 		t.Fatal("!called") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -4,8 +4,17 @@ import "github.com/gorilla/mux" | ||||||
| 
 | 
 | ||||||
| func API() *mux.Router { | func API() *mux.Router { | ||||||
| 	m := mux.NewRouter() | 	m := mux.NewRouter() | ||||||
|  | 
 | ||||||
|  | 	// Users | ||||||
| 	m.Path("/users").Methods("GET").Name(Users) | 	m.Path("/users").Methods("GET").Name(Users) | ||||||
| 	m.Path("/users").Methods("POST").Name(CreateUser) | 	m.Path("/users").Methods("POST").Name(CreateUser) | ||||||
| 	m.Path("/users/{Id:.+}").Methods("GET").Name(User) | 	m.Path("/users/{Id:.+}").Methods("GET").Name(User) | ||||||
|  | 
 | ||||||
|  | 	// Genera | ||||||
|  | 	m.Path("/genera").Methods("GET").Name(Genera) | ||||||
|  | 	m.Path("/genera").Methods("POST").Name(CreateGenus) | ||||||
|  | 	m.Path("/genera/{Id:.+}").Methods("GET").Name(Genus) | ||||||
|  | 	m.Path("/genera/{Id:.+}").Methods("PUT").Name(UpdateGenus) | ||||||
|  | 	m.Path("/genera/{Id:.+}").Methods("DELETE").Name(DeleteGenus) | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,4 +4,10 @@ const ( | ||||||
| 	User       = "user" | 	User       = "user" | ||||||
| 	CreateUser = "user:create" | 	CreateUser = "user:create" | ||||||
| 	Users      = "users" | 	Users      = "users" | ||||||
|  | 
 | ||||||
|  | 	Genus       = "genus" | ||||||
|  | 	CreateGenus = "genus:create" | ||||||
|  | 	Genera      = "genera" | ||||||
|  | 	UpdateGenus = "genus:update" | ||||||
|  | 	DeleteGenus = "genus:delete" | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								test.sh
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								test.sh
									
										
									
									
									
								
							|  | @ -1,4 +1,4 @@ | ||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
| 
 | 
 | ||||||
| PGTZ=UTC PGSSLMODE=disable go test -v ./... | go test -v ./... | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Matthew Dillon
						Matthew Dillon