From b5af05de9d6499f23e3a0d1c275864c14104890b Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 8 Jan 2015 16:15:31 -0900 Subject: [PATCH] Subroutes: strains --- api/handler.go | 1 + api/strains.go | 19 +++++++++++++++++++ datastore/strains.go | 23 ++++++++++++++++++++++- datastore/strains_test.go | 22 +++++++++++++++++++--- models/strains.go | 36 ++++++++++++++++++++++++++++++------ models/strains_test.go | 5 ++--- router/api.go | 1 + router/routes.go | 1 + 8 files changed, 95 insertions(+), 13 deletions(-) diff --git a/api/handler.go b/api/handler.go index 4db6631..583b635 100644 --- a/api/handler.go +++ b/api/handler.go @@ -71,6 +71,7 @@ func Handler() *mux.Router { m.Get(router.DeleteMeasurement).Handler(handler(serveDeleteMeasurement)) m.Get(router.SubrouterListSpecies).Handler(authHandler(serveSubrouterSpeciesList)) + m.Get(router.SubrouterListStrains).Handler(authHandler(serveSubrouterStrainsList)) return m } diff --git a/api/strains.go b/api/strains.go index d5456d3..122e0ba 100644 --- a/api/strains.go +++ b/api/strains.go @@ -90,3 +90,22 @@ func serveDeleteStrain(w http.ResponseWriter, r *http.Request) error { return writeJSON(w, &models.Strain{}) } + +func serveSubrouterStrainsList(w http.ResponseWriter, r *http.Request) error { + var opt models.StrainListOptions + if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { + return err + } + + opt.Genus = mux.Vars(r)["genus"] + + strains, err := store.Strains.List(&opt) + if err != nil { + return err + } + if strains == nil { + strains = []*models.Strain{} + } + + return writeJSON(w, strains) +} diff --git a/datastore/strains.go b/datastore/strains.go index 1cdd887..b70df26 100644 --- a/datastore/strains.go +++ b/datastore/strains.go @@ -1,6 +1,8 @@ package datastore import ( + "fmt" + "strings" "time" "github.com/thermokarst/bactdb/models" @@ -39,8 +41,27 @@ func (s *strainsStore) List(opt *models.StrainListOptions) ([]*models.Strain, er if opt == nil { opt = &models.StrainListOptions{} } + + sql := `SELECT * FROM strains` + + var conds []string + var vals []interface{} + + if opt.Genus != "" { + conds = append(conds, `species_id IN (SELECT s.id FROM species s + INNER JOIN genera g ON g.id = s.genus_id WHERE lower(g.genus_name) = $1)`) + vals = append(vals, opt.Genus) + } + + if len(conds) > 0 { + sql += " WHERE (" + strings.Join(conds, ") AND (") + ")" + } + + sql += fmt.Sprintf(" LIMIT $%v OFFSET $%v;", len(conds)+1, len(conds)+2) + vals = append(vals, opt.PerPageOrDefault(), opt.Offset()) + var strains []*models.Strain - err := s.dbh.Select(&strains, `SELECT * FROM strains LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset()) + err := s.dbh.Select(&strains, sql, vals...) if err != nil { return nil, err } diff --git a/datastore/strains_test.go b/datastore/strains_test.go index 1a93edc..063cb8f 100644 --- a/datastore/strains_test.go +++ b/datastore/strains_test.go @@ -1,6 +1,7 @@ package datastore import ( + "database/sql" "reflect" "testing" @@ -21,9 +22,24 @@ func insertStrain(t *testing.T, tx *modl.Transaction) *models.Strain { func newStrain(t *testing.T, tx *modl.Transaction) *models.Strain { // we want to create and insert a species (and genus) record too species := insertSpecies(t, tx) - return &models.Strain{SpeciesId: species.Id, StrainName: "Test Strain", - StrainType: "Test Type", Etymology: "Test Etymology", - AccessionBanks: "Test Bank", GenbankEmblDdb: "Test Genbank"} + return &models.Strain{ + SpeciesId: species.Id, + StrainName: "Test Strain", + StrainType: "Test Type", + Etymology: models.NullString{ + sql.NullString{ + String: "Test Etymology", + Valid: true, + }, + }, + AccessionBanks: "Test Bank", + GenbankEmblDdb: models.NullString{ + sql.NullString{ + String: "Test Genbank", + Valid: true, + }, + }, + } } func TestStrainsStore_Get_db(t *testing.T) { diff --git a/models/strains.go b/models/strains.go index b9b40fb..0f102a1 100644 --- a/models/strains.go +++ b/models/strains.go @@ -1,7 +1,9 @@ package models import ( + "database/sql" "errors" + "fmt" "net/http" "strconv" "time" @@ -15,22 +17,43 @@ type Strain struct { SpeciesId int64 `db:"species_id" json:"speciesId"` StrainName string `db:"strain_name" json:"strainName"` StrainType string `db:"strain_type" json:"strainType"` - Etymology string `db:"etymology" json:"etymology"` + Etymology NullString `db:"etymology" json:"etymology"` AccessionBanks string `db:"accession_banks" json:"accessionBanks"` - GenbankEmblDdb string `db:"genbank_embl_ddb" json:"genbankEmblDdb"` + GenbankEmblDdb NullString `db:"genbank_embl_ddb" json:"genbankEmblDdb"` IsolatedFrom NullString `db:"isolated_from" json:"isolatedFrom"` CreatedAt time.Time `db:"created_at" json:"createdAt"` UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` } +func (s *Strain) String() string { + return fmt.Sprintf("%v", *s) +} + func NewStrain() *Strain { return &Strain{ - StrainName: "Test Strain", - StrainType: "Test Type", - Etymology: "Test Etymology", + StrainName: "Test Strain", + StrainType: "Test Type", + Etymology: NullString{ + sql.NullString{ + String: "Test Etymology", + Valid: true, + }, + }, AccessionBanks: "Test Accession", - GenbankEmblDdb: "Test Genbank"} + GenbankEmblDdb: NullString{ + sql.NullString{ + String: "Test Genbank", + Valid: true, + }, + }, + IsolatedFrom: NullString{ + sql.NullString{ + String: "", + Valid: false, + }, + }, + } } // StrainService interacts with the strain-related endpoints in bactdb's API @@ -102,6 +125,7 @@ func (s *strainsService) Create(strain *Strain) (bool, error) { type StrainListOptions struct { ListOptions + Genus string } func (s *strainsService) List(opt *StrainListOptions) ([]*Strain, error) { diff --git a/models/strains_test.go b/models/strains_test.go index 2ab291f..155e76f 100644 --- a/models/strains_test.go +++ b/models/strains_test.go @@ -124,7 +124,7 @@ func TestStrainService_Update(t *testing.T) { mux.HandleFunc(urlPath(t, router.UpdateStrain, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "PUT") - testBody(t, r, `{"id":1,"speciesId":1,"strainName":"Test Strain Updated","strainType":"Test Type Updated","etymology":"Test Etymology Updated","accessionBanks":"Test Accession Updated","genbankEmblDdb":"Test Genbank Updated","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + testBody(t, r, `{"id":1,"speciesId":1,"strainName":"Test Strain Updated","strainType":"Test Type Updated","etymology":"Test Etymology","accessionBanks":"Test Accession Updated","genbankEmblDdb":"Test Genbank Updated","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") w.WriteHeader(http.StatusOK) writeJSON(w, want) }) @@ -132,9 +132,8 @@ func TestStrainService_Update(t *testing.T) { strain := newStrain() strain.StrainName = "Test Strain Updated" strain.StrainType = "Test Type Updated" - strain.Etymology = "Test Etymology Updated" strain.AccessionBanks = "Test Accession Updated" - strain.GenbankEmblDdb = "Test Genbank Updated" + strain.GenbankEmblDdb.String = "Test Genbank Updated" updated, err := client.Strains.Update(strain.Id, strain) if err != nil { t.Errorf("Strains.Update returned error: %v", err) diff --git a/router/api.go b/router/api.go index 37f24df..79ba74c 100644 --- a/router/api.go +++ b/router/api.go @@ -70,6 +70,7 @@ func API() *mux.Router { // Subrouter for auth/security s := m.PathPrefix("/{genus}").Subrouter() s.Path("/species").Methods("GET").Name(SubrouterListSpecies) + s.Path("/strains").Methods("GET").Name(SubrouterListStrains) return m } diff --git a/router/routes.go b/router/routes.go index 5140620..3ae91b0 100644 --- a/router/routes.go +++ b/router/routes.go @@ -55,4 +55,5 @@ const ( DeleteMeasurement = "measurements:delete" SubrouterListSpecies = "subrouter_species:list" + SubrouterListStrains = "subrouter_strains:list" )