From 4963e3ca71ced284cf70debd9e4cefe5b8a41d46 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 08:54:09 -0700 Subject: [PATCH 01/10] User payload --- handlers.go | 1 + users.go | 61 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/handlers.go b/handlers.go index 8f0033a..ca35467 100644 --- a/handlers.go +++ b/handlers.go @@ -336,6 +336,7 @@ func tokenRefresh(j *jwt.Middleware) errorHandler { if err != nil { return newJSONError(err, http.StatusInternalServerError) } + user.Password = "" token, err := j.CreateToken(user.Email) if err != nil { return newJSONError(err, http.StatusInternalServerError) diff --git a/users.go b/users.go index 3f1fd22..db93a6a 100644 --- a/users.go +++ b/users.go @@ -27,12 +27,12 @@ var ( ) func init() { - DB.AddTableWithName(User{}, "users").SetKeys(true, "Id") + DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "Id") } type UserService struct{} -type User struct { +type UserBase struct { Id int64 `json:"id,omitempty"` Email string `db:"email" json:"email"` Password string `db:"password" json:"password,omitempty"` @@ -44,6 +44,11 @@ type User struct { DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` } +type User struct { + *UserBase + CanEdit bool `db:"-" json:"canEdit"` +} + type UserValidation struct { Email []string `json:"email,omitempty"` Password []string `json:"password,omitempty"` @@ -71,8 +76,17 @@ type UsersJSON struct { Users *Users `json:"users"` } -func (u *User) marshal() ([]byte, error) { - return json.Marshal(&UserJSON{User: u}) +type UserMeta struct { + CanAdd bool `json:"canAdd"` +} + +type UserPayload struct { + User *User `json:"user"` + Meta *UserMeta `json:"meta"` +} + +func (u *UserPayload) marshal() ([]byte, error) { + return json.Marshal(u) } func (u *Users) marshal() ([]byte, error) { @@ -80,9 +94,9 @@ func (u *Users) marshal() ([]byte, error) { } func (u UserService) unmarshal(b []byte) (entity, error) { - var uj UserJSON + var uj UserPayload err := json.Unmarshal(b, &uj) - return uj.User, err + return &uj, err } func (u *User) validate() error { @@ -139,29 +153,53 @@ func (u UserService) list(val *url.Values, claims *Claims) (entity, *appError) { func (u UserService) get(id int64, dummy string, claims *Claims) (entity, *appError) { user, err := dbGetUserById(id) + user.Password = "" if err != nil { return nil, newJSONError(err, http.StatusInternalServerError) } - return user, nil + + user.CanEdit = claims.Role == "A" || id == claims.Sub + + payload := UserPayload{ + User: user, + Meta: &UserMeta{ + CanAdd: claims.Role == "A", + }, + } + return &payload, nil } func (u UserService) update(id int64, e *entity, dummy string, claims *Claims) *appError { - user := (*e).(*User) - user.UpdatedAt = currentTime() + user := (*e).(*UserPayload).User + + original_user, err := dbGetUserById(id) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + user.Id = id + user.Password = original_user.Password + user.Verified = original_user.Verified + user.UpdatedAt = currentTime() + + if err := user.validate(); err != nil { + return &appError{Error: err, Status: StatusUnprocessableEntity} + } count, err := DBH.Update(user) + user.Password = "" if err != nil { return newJSONError(err, http.StatusInternalServerError) } if count != 1 { return ErrUserNotUpdatedJSON } + return nil } func (u UserService) create(e *entity, dummy string, claims *Claims) *appError { - user := (*e).(*User) + user := (*e).(*UserPayload).User if err := user.validate(); err != nil { return &appError{Error: err, Status: StatusUnprocessableEntity} } @@ -238,8 +276,7 @@ func dbAuthenticate(email string, password string) error { func dbGetUserById(id int64) (*User, error) { var user User - q := `SELECT id, email, 'password' AS password, name, role, - created_at, updated_at, deleted_at + q := `SELECT * FROM users WHERE id=$1 AND verified IS TRUE From 335d573d239185baf4709b7be957689c8a34cb19 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 09:54:21 -0700 Subject: [PATCH 02/10] Restructuring into packages. --- Godeps/Godeps.json | 3 - api/characteristics.go | 189 ++++++++++++ compare.go => api/compare.go | 13 +- api/entities.go | 28 ++ api/measurements.go | 134 +++++++++ api/species.go | 150 ++++++++++ api/strains.go | 212 ++++++++++++++ api/users.go | 255 ++++++++++++++++ auth/claims.go | 44 +++ characteristics.go | 435 ---------------------------- entities.go | 28 -- handlers.go => handlers/handlers.go | 164 ++++------- helpers.go => helpers/helpers.go | 32 +- main.go | 32 +- measurements.go | 375 ------------------------ models/characteristics.go | 236 +++++++++++++++ models/database.go | 8 + models/measurements.go | 234 +++++++++++++++ models/species.go | 174 +++++++++++ models/strains.go | 184 ++++++++++++ models/users.go | 156 ++++++++++ payloads/payloads.go | 102 +++++++ species.go | 330 --------------------- strains.go | 406 -------------------------- types/claims.go | 11 + types/entities.go | 5 + types.go => types/types.go | 8 +- users.go | 392 ------------------------- 28 files changed, 2232 insertions(+), 2108 deletions(-) create mode 100644 api/characteristics.go rename compare.go => api/compare.go (88%) create mode 100644 api/entities.go create mode 100644 api/measurements.go create mode 100644 api/species.go create mode 100644 api/strains.go create mode 100644 api/users.go create mode 100644 auth/claims.go delete mode 100644 characteristics.go delete mode 100644 entities.go rename handlers.go => handlers/handlers.go (60%) rename helpers.go => helpers/helpers.go (71%) delete mode 100644 measurements.go create mode 100644 models/characteristics.go create mode 100644 models/database.go create mode 100644 models/measurements.go create mode 100644 models/species.go create mode 100644 models/strains.go create mode 100644 models/users.go create mode 100644 payloads/payloads.go delete mode 100644 species.go delete mode 100644 strains.go create mode 100644 types/claims.go create mode 100644 types/entities.go rename types.go => types/types.go (97%) delete mode 100644 users.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1dcd5db..7644a80 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,9 +1,6 @@ { "ImportPath": "github.com/thermokarst/bactdb", "GoVersion": "go1.5", - "Packages": [ - "./..." - ], "Deps": [ { "ImportPath": "github.com/DavidHuie/gomigrate", diff --git a/api/characteristics.go b/api/characteristics.go new file mode 100644 index 0000000..275c9df --- /dev/null +++ b/api/characteristics.go @@ -0,0 +1,189 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" +) + +type CharacteristicService struct{} + +func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) { + var cj payloads.CharacteristicPayload + err := json.Unmarshal(b, &cj) + return &cj, err +} + +func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { + if val == nil { + return nil, helpers.ErrMustProvideOptionsJSON + } + var opt helpers.ListOptions + if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristics, err := models.ListCharacteristics(opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains_opt, err := models.StrainOptsFromCharacteristics(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.ListStrains(*strains_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species_opt, err := models.SpeciesOptsFromStrains(*strains_opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.ListSpecies(*species_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + measurements_opt, err := models.MeasurementOptsFromCharacteristics(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + measurements, err := models.ListMeasurements(*measurements_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.CharacteristicsPayload{ + Characteristics: characteristics, + Measurements: measurements, + Strains: strains, + Species: species, + Meta: &models.CharacteristicMeta{ + CanAdd: helpers.CanAdd(claims), + }, + } + + return &payload, nil +} + +func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { + characteristic, err := models.GetCharacteristic(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.ListSpecies(*species_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + measurements, _, err := models.MeasurementsFromCharacteristicId(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.CharacteristicPayload{ + Characteristic: characteristic, + Measurements: measurements, + Strains: strains, + Species: species, + } + + return &payload, nil +} + +func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.CharacteristicPayload) + payload.Characteristic.UpdatedBy = claims.Sub + payload.Characteristic.Id = id + + // First, handle Characteristic Type + id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + payload.Characteristic.CanEdit = helpers.CanEdit(claims, payload.Characteristic.CreatedBy) + + payload.Characteristic.CharacteristicTypeId = id + // TODO: fix this + count, err := models.DBH.Update(payload.Characteristic.CharacteristicBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + // TODO: fix this + return types.NewJSONError(models.ErrCharacteristicNotUpdated, http.StatusBadRequest) + } + + strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.ListSpecies(*species_opt, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + payload.Strains = strains + // TODO: tack on measurements + payload.Measurements = nil + payload.Species = species + + return nil +} + +func (c CharacteristicService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.CharacteristicPayload) + payload.Characteristic.CreatedBy = claims.Sub + payload.Characteristic.UpdatedBy = claims.Sub + + id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + payload.Characteristic.CharacteristicTypeId = id + + // TODO: fix this + err = models.DBH.Insert(payload.Characteristic.CharacteristicBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristic, err := models.GetCharacteristic(payload.Characteristic.Id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + payload.Characteristic = characteristic + payload.Meta = &models.CharacteristicMeta{ + CanAdd: helpers.CanAdd(claims), + } + return nil +} diff --git a/compare.go b/api/compare.go similarity index 88% rename from compare.go rename to api/compare.go index 53d861e..eb47acc 100644 --- a/compare.go +++ b/api/compare.go @@ -1,4 +1,4 @@ -package main +package api import ( "bytes" @@ -11,9 +11,12 @@ import ( "time" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" ) -func handleCompare(w http.ResponseWriter, r *http.Request) *appError { +func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { // types type Comparisions map[string]map[string]string type ComparisionsJSON [][]string @@ -23,7 +26,7 @@ func handleCompare(w http.ResponseWriter, r *http.Request) *appError { if mimeType == "" { mimeType = "json" } - claims := getClaims(r) + claims := helpers.GetClaims(r) var header string var data []byte @@ -33,11 +36,11 @@ func handleCompare(w http.ResponseWriter, r *http.Request) *appError { opt.Del("mimeType") opt.Del("token") opt.Add("Genus", mux.Vars(r)["genus"]) - measurementsEntity, appErr := measService.list(&opt, &claims) + measurementsEntity, appErr := measService.List(&opt, &claims) if appErr != nil { return appErr } - measurementsPayload := (measurementsEntity).(*MeasurementsPayload) + measurementsPayload := (measurementsEntity).(*payloads.MeasurementsPayload) // Assemble matrix characteristic_ids := strings.Split(opt.Get("characteristic_ids"), ",") diff --git a/api/entities.go b/api/entities.go new file mode 100644 index 0000000..9b86dc5 --- /dev/null +++ b/api/entities.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/url" + + "github.com/thermokarst/bactdb/types" +) + +type Getter interface { + Get(int64, string, *types.Claims) (types.Entity, *types.AppError) +} + +type Lister interface { + List(*url.Values, *types.Claims) (types.Entity, *types.AppError) +} + +type Updater interface { + Update(int64, *types.Entity, string, *types.Claims) *types.AppError + Unmarshal([]byte) (types.Entity, error) +} + +type Creater interface { + Create(*types.Entity, string, *types.Claims) *types.AppError + Unmarshal([]byte) (types.Entity, error) +} +type Deleter interface { + Delete(int64, string, *types.Claims) *types.AppError +} diff --git a/api/measurements.go b/api/measurements.go new file mode 100644 index 0000000..53019bb --- /dev/null +++ b/api/measurements.go @@ -0,0 +1,134 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" +) + +type MeasurementService struct{} + +func (s MeasurementService) Unmarshal(b []byte) (types.Entity, error) { + var mj payloads.MeasurementPayload + err := json.Unmarshal(b, &mj) + return &mj, err +} + +func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { + if val == nil { + return nil, helpers.ErrMustProvideOptionsJSON + } + var opt helpers.MeasurementListOptions + if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + measurements, err := models.ListMeasurements(opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + char_opts, err := models.CharacteristicOptsFromMeasurements(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristics, err := models.ListCharacteristics(*char_opts, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strain_opts, err := models.StrainOptsFromMeasurements(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.ListStrains(*strain_opts, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.MeasurementsPayload{ + Characteristics: characteristics, + Strains: strains, + Measurements: measurements, + } + + return &payload, nil +} + +func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { + measurement, err := models.GetMeasurement(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.MeasurementPayload{ + Measurement: measurement, + } + + return &payload, nil +} + +func (s MeasurementService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.MeasurementPayload) + payload.Measurement.UpdatedBy = claims.Sub + payload.Measurement.Id = id + + if payload.Measurement.TextMeasurementType.Valid { + id, err := models.GetTextMeasurementTypeId(payload.Measurement.TextMeasurementType.String) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + payload.Measurement.TextMeasurementTypeId.Int64 = id + payload.Measurement.TextMeasurementTypeId.Valid = true + } + + // TODO: fix this + count, err := models.DBH.Update(payload.Measurement.MeasurementBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + // TODO: fix this + return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest) + } + + measurement, err := models.GetMeasurement(id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + payload.Measurement = measurement + + return nil +} + +func (m MeasurementService) Delete(id int64, genus string, claims *types.Claims) *types.AppError { + q := `DELETE FROM measurements WHERE id=$1;` + // TODO: fix this + _, err := models.DBH.Exec(q, id) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + return nil +} + +func (m MeasurementService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.MeasurementPayload) + payload.Measurement.CreatedBy = claims.Sub + payload.Measurement.UpdatedBy = claims.Sub + + // TODO: fix this + if err := models.DBH.Insert(payload.Measurement.MeasurementBase); err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + return nil + +} diff --git a/api/species.go b/api/species.go new file mode 100644 index 0000000..5df6b66 --- /dev/null +++ b/api/species.go @@ -0,0 +1,150 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" +) + +type SpeciesService struct{} + +func (s SpeciesService) Unmarshal(b []byte) (types.Entity, error) { + var sj payloads.SpeciesPayload + err := json.Unmarshal(b, &sj) + return &sj, err +} + +func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { + if val == nil { + return nil, helpers.ErrMustProvideOptionsJSON + } + var opt helpers.ListOptions + if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.ListSpecies(opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains_opt, err := models.StrainOptsFromSpecies(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.ListStrains(*strains_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.ManySpeciesPayload{ + Species: species, + Strains: strains, + Meta: &models.SpeciesMeta{ + CanAdd: helpers.CanAdd(claims), + }, + } + + return &payload, nil +} + +func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { + species, err := models.GetSpecies(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.StrainsFromSpeciesId(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.SpeciesPayload{ + Species: species, + Strains: strains, + Meta: &models.SpeciesMeta{ + CanAdd: helpers.CanAdd(claims), + }, + } + + return &payload, nil +} + +func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.SpeciesPayload) + payload.Species.UpdatedBy = claims.Sub + payload.Species.Id = id + + genus_id, err := models.GenusIdFromName(genus) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + payload.Species.SpeciesBase.GenusID = genus_id + + // TODO: fix this + count, err := models.DBH.Update(payload.Species.SpeciesBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + // TODO: fix this + return types.NewJSONError(models.ErrSpeciesNotUpdated, http.StatusBadRequest) + } + + // Reload to send back down the wire + species, err := models.GetSpecies(id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.StrainsFromSpeciesId(id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + payload.Species = species + payload.Strains = strains + payload.Meta = &models.SpeciesMeta{ + CanAdd: helpers.CanAdd(claims), + } + + return nil +} + +func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.SpeciesPayload) + payload.Species.CreatedBy = claims.Sub + payload.Species.UpdatedBy = claims.Sub + + genus_id, err := models.GenusIdFromName(genus) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + payload.Species.SpeciesBase.GenusID = genus_id + + // TODO: fix this + err = models.DBH.Insert(payload.Species.SpeciesBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + // Reload to send back down the wire + species, err := models.GetSpecies(payload.Species.Id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + // Note, no strains when new species + + payload.Species = species + payload.Meta = &models.SpeciesMeta{ + CanAdd: helpers.CanAdd(claims), + } + return nil +} diff --git a/api/strains.go b/api/strains.go new file mode 100644 index 0000000..2f61e4e --- /dev/null +++ b/api/strains.go @@ -0,0 +1,212 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" +) + +type StrainService struct{} + +func (s StrainService) Unmarshal(b []byte) (types.Entity, error) { + var sj payloads.StrainPayload + err := json.Unmarshal(b, &sj) + return &sj, err +} + +func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { + if val == nil { + return nil, helpers.ErrMustProvideOptionsJSON + } + var opt helpers.ListOptions + if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + strains, err := models.ListStrains(opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species_opt, err := models.SpeciesOptsFromStrains(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.ListSpecies(*species_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristic_ids := []int64{} + for _, c := range *characteristics { + characteristic_ids = append(characteristic_ids, c.Id) + } + + strain_ids := []int64{} + for _, s := range *strains { + strain_ids = append(strain_ids, s.Id) + } + + measurement_opt := helpers.MeasurementListOptions{ + ListOptions: helpers.ListOptions{ + Genus: opt.Genus, + }, + Strains: strain_ids, + Characteristics: characteristic_ids, + } + + measurements, err := models.ListMeasurements(measurement_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + payload := payloads.StrainsPayload{ + Strains: strains, + Species: species, + Measurements: measurements, + Characteristics: characteristics, + Meta: &models.StrainMeta{ + CanAdd: helpers.CanAdd(claims), + }, + } + + return &payload, nil +} + +func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { + strain, err := models.GetStrain(id, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + opt := helpers.ListOptions{Genus: genus, Ids: []int64{id}} + characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + characteristic_ids := []int64{} + for _, c := range *characteristics { + characteristic_ids = append(characteristic_ids, c.Id) + } + + measurement_opt := helpers.MeasurementListOptions{ + ListOptions: helpers.ListOptions{ + Genus: genus, + }, + Strains: []int64{id}, + Characteristics: characteristic_ids, + } + + measurements, err := models.ListMeasurements(measurement_opt, claims) + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + var many_species models.ManySpecies = []*models.Species{species} + + payload := payloads.StrainPayload{ + Strain: strain, + Species: &many_species, + Characteristics: characteristics, + Measurements: measurements, + Meta: &models.StrainMeta{ + CanAdd: helpers.CanAdd(claims), + }, + } + + return &payload, nil +} + +func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.StrainPayload) + payload.Strain.UpdatedBy = claims.Sub + payload.Strain.Id = id + + // TODO: fix this + count, err := models.DBH.Update(payload.Strain.StrainBase) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + // TODO: fix this + return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest) + } + + strain, err := models.GetStrain(id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + var many_species models.ManySpecies = []*models.Species{species} + + payload.Strain = strain + payload.Species = &many_species + payload.Meta = &models.StrainMeta{ + CanAdd: helpers.CanAdd(claims), + } + + return nil +} + +func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { + payload := (*e).(*payloads.StrainPayload) + payload.Strain.CreatedBy = claims.Sub + payload.Strain.UpdatedBy = claims.Sub + + // TODO: fix this + if err := models.DBH.Insert(payload.Strain.StrainBase); err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + strain, err := models.GetStrain(payload.Strain.Id, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + var many_species models.ManySpecies = []*models.Species{species} + + payload.Strain = strain + payload.Species = &many_species + payload.Meta = &models.StrainMeta{ + CanAdd: helpers.CanAdd(claims), + } + + return nil +} diff --git a/api/users.go b/api/users.go new file mode 100644 index 0000000..b09963c --- /dev/null +++ b/api/users.go @@ -0,0 +1,255 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "net/url" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/mailgun/mailgun-go" + "github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt" + "github.com/thermokarst/bactdb/auth" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/payloads" + "github.com/thermokarst/bactdb/types" +) + +var ( + // TODO: fix this + ErrUserNotFoundJSON = types.NewJSONError(models.ErrUserNotFound, http.StatusNotFound) + ErrUserNotUpdatedJSON = types.NewJSONError(models.ErrUserNotUpdated, http.StatusBadRequest) + ErrEmailAddressTakenJSON = types.NewJSONError(models.ErrEmailAddressTaken, http.StatusBadRequest) + MgAccts = make(map[string]mailgun.Mailgun) +) + +type UserService struct{} + +func (u UserService) Unmarshal(b []byte) (types.Entity, error) { + var uj payloads.UserPayload + err := json.Unmarshal(b, &uj) + return &uj, err +} + +func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { + if val == nil { + return nil, helpers.ErrMustProvideOptionsJSON + } + var opt helpers.ListOptions + if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + // TODO: fix this + users := make(models.Users, 0) + sql := `SELECT id, email, 'password' AS password, name, role, + created_at, updated_at, deleted_at + FROM users + WHERE verified IS TRUE + AND deleted_at IS NULL;` + if err := models.DBH.Select(&users, sql); err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + return &users, nil +} + +func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.Entity, *types.AppError) { + user, err := models.DbGetUserById(id) + user.Password = "" + if err != nil { + return nil, types.NewJSONError(err, http.StatusInternalServerError) + } + + user.CanEdit = claims.Role == "A" || id == claims.Sub + + payload := payloads.UserPayload{ + User: user, + Meta: &models.UserMeta{ + CanAdd: claims.Role == "A", + }, + } + return &payload, nil +} + +func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *types.Claims) *types.AppError { + user := (*e).(*payloads.UserPayload).User + + original_user, err := models.DbGetUserById(id) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + user.Id = id + user.Password = original_user.Password + user.Verified = original_user.Verified + user.UpdatedAt = helpers.CurrentTime() + + if err := user.Validate(); err != nil { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } + + // TODO: fix this + count, err := models.DBH.Update(user) + user.Password = "" + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + return ErrUserNotUpdatedJSON + } + + return nil +} + +func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) *types.AppError { + user := (*e).(*payloads.UserPayload).User + if err := user.Validate(); err != nil { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } + ct := helpers.CurrentTime() + user.CreatedAt = ct + user.UpdatedAt = ct + hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + user.Password = string(hash) + user.Role = "R" + user.Verified = false + + // TODO: fix this + if err := models.DBH.Insert(user); err != nil { + if err, ok := err.(*pq.Error); ok { + if err.Code == "23505" { + return ErrEmailAddressTakenJSON + } + } + return types.NewJSONError(err, http.StatusInternalServerError) + } + + user.Password = "password" // don't want to send the hashed PW back to the client + + q := `INSERT INTO verification (user_id, nonce, referer, created_at) VALUES ($1, $2, $3, $4);` + // TODO: move helpers.GenerateNonce + nonce, err := helpers.GenerateNonce() + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + // TODO: fix this + _, err = models.DBH.Exec(q, user.Id, nonce, claims.Ref, ct) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + // Send out confirmation email + // TODO: clean this up + mg, ok := MgAccts[claims.Ref] + if ok { + sender := fmt.Sprintf("%s Admin ", mg.Domain(), mg.Domain()) + recipient := fmt.Sprintf("%s <%s>", user.Name, user.Email) + subject := fmt.Sprintf("New Account Confirmation - %s", mg.Domain()) + message := fmt.Sprintf("You are receiving this message because this email "+ + "address was used to sign up for an account at %s. Please visit this "+ + "URL to complete the sign up process: %s/users/new/verify/%s. If you "+ + "did not request an account, please disregard this message.", + mg.Domain(), claims.Ref, nonce) + m := mailgun.NewMessage(sender, subject, message, recipient) + _, _, err := mg.Send(m) + if err != nil { + log.Printf("%+v\n", err) + return types.NewJSONError(err, http.StatusInternalServerError) + } + } + + return nil +} + +func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { + // TODO: clean this up + nonce := mux.Vars(r)["Nonce"] + q := `SELECT user_id, referer FROM verification WHERE nonce=$1;` + + var ver struct { + User_id int64 + Referer string + } + if err := models.DBH.SelectOne(&ver, q, nonce); err != nil { + log.Print(err) + return types.NewJSONError(err, http.StatusInternalServerError) + } + + if ver.User_id == 0 { + return types.NewJSONError(errors.New("No user found"), http.StatusInternalServerError) + } + + var user models.User + if err := models.DBH.Get(&user, ver.User_id); err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + + user.UpdatedAt = helpers.CurrentTime() + user.Verified = true + + count, err := models.DBH.Update(&user) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + if count != 1 { + return types.NewJSONError(errors.New("Count 0"), http.StatusInternalServerError) + } + + q = `DELETE FROM verification WHERE user_id=$1;` + _, err = models.DBH.Exec(q, user.Id) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + fmt.Fprintln(w, `{"msg":"All set! Please log in."}`) + return nil +} + +func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError { + email := r.FormValue("email") + if email == "" { + return types.NewJSONError(errors.New("missing email"), http.StatusInternalServerError) + } + token, err := auth.Middleware.CreateToken(email) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + origin := r.Header.Get("Origin") + hostUrl, err := url.Parse(origin) + if err != nil { + return types.NewJSONError(err, http.StatusInternalServerError) + } + hostUrl.Path += "/users/lockoutauthenticate" + params := url.Values{} + params.Add("token", token) + hostUrl.RawQuery = params.Encode() + + // Send out email + // TODO: clean this up + mg, ok := MgAccts[origin] + if ok { + sender := fmt.Sprintf("%s Admin ", mg.Domain(), mg.Domain()) + recipient := fmt.Sprintf("%s", email) + subject := fmt.Sprintf("Password Reset Request - %s", mg.Domain()) + message := fmt.Sprintf("You are receiving this message because this email "+ + "address was used in an account lockout request at %s. Please visit "+ + "this URL to complete the process: %s. If you did not request help "+ + "with a lockout, please disregard this message.", + mg.Domain(), hostUrl.String()) + m := mailgun.NewMessage(sender, subject, message, recipient) + _, _, err := mg.Send(m) + if err != nil { + log.Printf("%+v\n", err) + return types.NewJSONError(err, http.StatusInternalServerError) + } + } + + fmt.Fprintln(w, `{}`) + return nil +} diff --git a/auth/claims.go b/auth/claims.go new file mode 100644 index 0000000..eac6424 --- /dev/null +++ b/auth/claims.go @@ -0,0 +1,44 @@ +package auth + +import ( + "os" + "time" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/thermokarst/jwt" + "github.com/thermokarst/bactdb/models" +) + +var ( + Middleware *jwt.Middleware + Config *jwt.Config = &jwt.Config{ + Secret: os.Getenv("SECRET"), + Auth: models.DbAuthenticate, + Claims: claimsFunc, + } +) + +func claimsFunc(email string) (map[string]interface{}, error) { + // TODO: use helper + currentTime := time.Now() + user, err := models.DbGetUserByEmail(email) + if err != nil { + return nil, err + } + return map[string]interface{}{ + "name": user.Name, + "iss": "bactdb", + "sub": user.Id, + "role": user.Role, + "iat": currentTime.Unix(), + "exp": currentTime.Add(time.Minute * 60).Unix(), + "ref": "", + }, nil +} + +func init() { + var err error + Middleware, err = jwt.New(Config) + if err != nil { + panic(err) + } +} diff --git a/characteristics.go b/characteristics.go deleted file mode 100644 index 0535fd7..0000000 --- a/characteristics.go +++ /dev/null @@ -1,435 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" -) - -var ( - ErrCharacteristicNotFound = errors.New("Characteristic not found") - ErrCharacteristicNotUpdated = errors.New("Characteristic not updated") -) - -func init() { - DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") -} - -func (c *CharacteristicBase) PreInsert(e modl.SqlExecutor) error { - ct := currentTime() - c.CreatedAt = ct - c.UpdatedAt = ct - return nil -} - -func (c *CharacteristicBase) PreUpdate(e modl.SqlExecutor) error { - c.UpdatedAt = currentTime() - return nil -} - -type CharacteristicService struct{} - -type CharacteristicBase struct { - Id int64 `json:"id,omitempty"` - CharacteristicName string `db:"characteristic_name" json:"characteristicName"` - CharacteristicTypeId int64 `db:"characteristic_type_id" json:"-"` - SortOrder NullInt64 `db:"sort_order" json:"sortOrder"` - CreatedAt NullTime `db:"created_at" json:"createdAt"` - UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` - DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` - CreatedBy int64 `db:"created_by" json:"createdBy"` - UpdatedBy int64 `db:"updated_by" json:"updatedBy"` - DeletedBy NullInt64 `db:"deleted_by" json:"deletedBy"` -} - -type Characteristic struct { - *CharacteristicBase - Measurements NullSliceInt64 `db:"measurements" json:"measurements"` - Strains NullSliceInt64 `db:"strains" json:"strains"` - CharacteristicType string `db:"characteristic_type_name" json:"characteristicTypeName"` - CanEdit bool `db:"-" json:"canEdit"` -} - -type Characteristics []*Characteristic - -type CharacteristicMeta struct { - CanAdd bool `json:"canAdd"` -} - -type CharacteristicPayload struct { - Characteristic *Characteristic `json:"characteristic"` - Measurements *Measurements `json:"measurements"` - Strains *Strains `json:"strains"` - Species *ManySpecies `json:"species"` - Meta *CharacteristicMeta `json:"meta"` -} - -type CharacteristicsPayload struct { - Characteristics *Characteristics `json:"characteristics"` - Measurements *Measurements `json:"measurements"` - Strains *Strains `json:"strains"` - Species *ManySpecies `json:"species"` - Meta *CharacteristicMeta `json:"meta"` -} - -func (c *CharacteristicPayload) marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c *CharacteristicsPayload) marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c CharacteristicService) unmarshal(b []byte) (entity, error) { - var cj CharacteristicPayload - err := json.Unmarshal(b, &cj) - return &cj, err -} - -func (c CharacteristicService) list(val *url.Values, claims *Claims) (entity, *appError) { - if val == nil { - return nil, ErrMustProvideOptionsJSON - } - var opt ListOptions - if err := schemaDecoder.Decode(&opt, *val); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristics, err := listCharacteristics(opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains_opt, err := strainOptsFromCharacteristics(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, err := listStrains(*strains_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species_opt, err := speciesOptsFromStrains(*strains_opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species, err := listSpecies(*species_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - measurements_opt, err := measurementOptsFromCharacteristics(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - measurements, err := listMeasurements(*measurements_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := CharacteristicsPayload{ - Characteristics: characteristics, - Measurements: measurements, - Strains: strains, - Species: species, - Meta: &CharacteristicMeta{ - CanAdd: canAdd(claims), - }, - } - - return &payload, nil -} - -func (c CharacteristicService) get(id int64, genus string, claims *Claims) (entity, *appError) { - characteristic, err := getCharacteristic(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, strain_opts, err := strainsFromCharacteristicId(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species_opt, err := speciesOptsFromStrains(*strain_opts) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species, err := listSpecies(*species_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - measurements, _, err := measurementsFromCharacteristicId(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := CharacteristicPayload{ - Characteristic: characteristic, - Measurements: measurements, - Strains: strains, - Species: species, - } - - return &payload, nil -} - -func (c CharacteristicService) update(id int64, e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*CharacteristicPayload) - payload.Characteristic.UpdatedBy = claims.Sub - payload.Characteristic.Id = id - - // First, handle Characteristic Type - id, err := insertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - payload.Characteristic.CanEdit = canEdit(claims, payload.Characteristic.CreatedBy) - - payload.Characteristic.CharacteristicTypeId = id - count, err := DBH.Update(payload.Characteristic.CharacteristicBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return newJSONError(ErrCharacteristicNotUpdated, http.StatusBadRequest) - } - - strains, strain_opts, err := strainsFromCharacteristicId(id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - species_opt, err := speciesOptsFromStrains(*strain_opts) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - species, err := listSpecies(*species_opt, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - payload.Strains = strains - // TODO: tack on measurements - payload.Measurements = nil - payload.Species = species - - return nil -} - -func (c CharacteristicService) create(e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*CharacteristicPayload) - payload.Characteristic.CreatedBy = claims.Sub - payload.Characteristic.UpdatedBy = claims.Sub - - id, err := insertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - payload.Characteristic.CharacteristicTypeId = id - - err = DBH.Insert(payload.Characteristic.CharacteristicBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - characteristic, err := getCharacteristic(payload.Characteristic.Id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - payload.Characteristic = characteristic - payload.Meta = &CharacteristicMeta{ - CanAdd: canAdd(claims), - } - return nil -} - -func listCharacteristics(opt ListOptions, claims *Claims) (*Characteristics, error) { - var vals []interface{} - - q := `SELECT c.*, ct.characteristic_type_name, - array_agg(DISTINCT st.id) AS strains, array_agg(DISTINCT m.id) AS measurements - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - INNER JOIN measurements m ON m.strain_id=st.id - RIGHT OUTER JOIN characteristics c ON c.id=m.characteristic_id - INNER JOIN characteristic_types ct ON ct.id=c.characteristic_type_id` - vals = append(vals, opt.Genus) - - if len(opt.Ids) != 0 { - var counter int64 = 2 - w := valsIn("c.id", opt.Ids, &vals, &counter) - - q += fmt.Sprintf(" WHERE %s", w) - } - - q += ` GROUP BY c.id, ct.characteristic_type_name - ORDER BY ct.characteristic_type_name, c.sort_order ASC;` - - var characteristics Characteristics - err := DBH.Select(&characteristics, q, vals...) - if err != nil { - return nil, err - } - - for _, c := range characteristics { - c.CanEdit = canEdit(claims, c.CreatedBy) - } - - return &characteristics, nil -} - -func strainOptsFromCharacteristics(opt ListOptions) (*ListOptions, error) { - relatedStrainIds := make([]int64, 0) - baseQ := `SELECT DISTINCT m.strain_id - FROM measurements m - INNER JOIN strains st ON st.id=m.strain_id - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` - if opt.Ids == nil { - q := fmt.Sprintf("%s;", baseQ) - if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { - return nil, err - } - } else { - var vals []interface{} - var count int64 = 2 - vals = append(vals, opt.Genus) - q := fmt.Sprintf("%s WHERE %s ", baseQ, valsIn("m.characteristic_id", opt.Ids, &vals, &count)) - - if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { - return nil, err - } - } - - return &ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil -} - -func measurementOptsFromCharacteristics(opt ListOptions) (*MeasurementListOptions, error) { - relatedMeasurementIds := make([]int64, 0) - baseQ := `SELECT m.id - FROM measurements m - INNER JOIN strains st ON st.id=m.strain_id - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` - - if opt.Ids == nil { - q := fmt.Sprintf("%s;", baseQ) - if err := DBH.Select(&relatedMeasurementIds, q, opt.Genus); err != nil { - return nil, err - } - } else { - var vals []interface{} - var count int64 = 2 - vals = append(vals, opt.Genus) - q := fmt.Sprintf("%s WHERE %s;", baseQ, valsIn("characteristic_id", opt.Ids, &vals, &count)) - - if err := DBH.Select(&relatedMeasurementIds, q, vals...); err != nil { - return nil, err - } - } - - return &MeasurementListOptions{ListOptions: ListOptions{Genus: opt.Genus, Ids: relatedMeasurementIds}, Strains: nil, Characteristics: nil}, nil -} - -func strainsFromCharacteristicId(id int64, genus string, claims *Claims) (*Strains, *ListOptions, error) { - opt := ListOptions{ - Genus: genus, - Ids: []int64{id}, - } - - strains_opt, err := strainOptsFromCharacteristics(opt) - if err != nil { - return nil, nil, err - } - - strains, err := listStrains(*strains_opt, claims) - if err != nil { - return nil, nil, err - } - - return strains, strains_opt, nil -} - -func measurementsFromCharacteristicId(id int64, genus string, claims *Claims) (*Measurements, *MeasurementListOptions, error) { - opt := ListOptions{ - Genus: genus, - Ids: []int64{id}, - } - - measurement_opt, err := measurementOptsFromCharacteristics(opt) - if err != nil { - return nil, nil, err - } - - measurements, err := listMeasurements(*measurement_opt, claims) - if err != nil { - return nil, nil, err - } - - return measurements, measurement_opt, nil -} - -func getCharacteristic(id int64, genus string, claims *Claims) (*Characteristic, error) { - var characteristic Characteristic - q := `SELECT c.*, ct.characteristic_type_name, - array_agg(DISTINCT st.id) AS strains, array_agg(DISTINCT m.id) AS measurements - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - INNER JOIN measurements m ON m.strain_id=st.id - RIGHT OUTER JOIN characteristics c ON c.id=m.characteristic_id - INNER JOIN characteristic_types ct ON ct.id=c.characteristic_type_id - WHERE c.id=$2 - GROUP BY c.id, ct.characteristic_type_name;` - if err := DBH.SelectOne(&characteristic, q, genus, id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrCharacteristicNotFound - } - return nil, err - } - - characteristic.CanEdit = canEdit(claims, characteristic.CreatedBy) - - return &characteristic, nil -} - -func insertOrGetCharacteristicType(val string, claims *Claims) (int64, error) { - var id int64 - q := `SELECT id FROM characteristic_types WHERE characteristic_type_name=$1;` - if err := DBH.SelectOne(&id, q, val); err != nil { - if err == sql.ErrNoRows { - i := `INSERT INTO characteristic_types - (characteristic_type_name, created_at, updated_at, created_by, updated_by) - VALUES ($1, $2, $3, $4, $5) RETURNING id;` - ct := currentTime() - var result sql.Result - var insertErr error - stmt, err := DB.Db.Prepare(i) - if result, insertErr = stmt.Exec(val, ct, ct, claims.Sub, claims.Sub); insertErr != nil { - return 0, insertErr - } - id, err = result.LastInsertId() - if err != nil { - return 0, err - } - } else { - return 0, err - } - } - return id, nil -} diff --git a/entities.go b/entities.go deleted file mode 100644 index 6165044..0000000 --- a/entities.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import "net/url" - -type entity interface { - marshal() ([]byte, error) -} - -type getter interface { - get(int64, string, *Claims) (entity, *appError) -} - -type lister interface { - list(*url.Values, *Claims) (entity, *appError) -} - -type updater interface { - update(int64, *entity, string, *Claims) *appError - unmarshal([]byte) (entity, error) -} - -type creater interface { - create(*entity, string, *Claims) *appError - unmarshal([]byte) (entity, error) -} -type deleter interface { - delete(int64, string, *Claims) *appError -} diff --git a/handlers.go b/handlers/handlers.go similarity index 60% rename from handlers.go rename to handlers/handlers.go index ca35467..167d96a 100644 --- a/handlers.go +++ b/handlers/handlers.go @@ -1,4 +1,4 @@ -package main +package handlers import ( "encoding/json" @@ -16,26 +16,17 @@ import ( "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/nytimes/gziphandler" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/thermokarst/jwt" + "github.com/thermokarst/bactdb/api" + "github.com/thermokarst/bactdb/auth" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/types" ) -var ( - config *jwt.Config - j *jwt.Middleware -) - -type Claims struct { - Name string - Iss string - Sub int64 - Role string - Iat int64 - Exp int64 - Ref string -} - func verifyClaims(claims []byte, r *http.Request) error { + // TODO: use helper currentTime := time.Now() - var c Claims + var c types.Claims err := json.Unmarshal(claims, &c) if err != nil { return err @@ -48,53 +39,24 @@ func verifyClaims(claims []byte, r *http.Request) error { } func Handler() http.Handler { - claimsFunc := func(email string) (map[string]interface{}, error) { - currentTime := time.Now() - user, err := dbGetUserByEmail(email) - if err != nil { - return nil, err - } - return map[string]interface{}{ - "name": user.Name, - "iss": "bactdb", - "sub": user.Id, - "role": user.Role, - "iat": currentTime.Unix(), - "exp": currentTime.Add(time.Minute * 60).Unix(), - "ref": "", - }, nil - } - - config = &jwt.Config{ - Secret: os.Getenv("SECRET"), - Auth: dbAuthenticate, - Claims: claimsFunc, - } - - var err error - j, err = jwt.New(config) - if err != nil { - panic(err) - } - m := mux.NewRouter() - userService := UserService{} - strainService := StrainService{} - speciesService := SpeciesService{} - characteristicService := CharacteristicService{} - measurementService := MeasurementService{} + userService := api.UserService{} + strainService := api.StrainService{} + speciesService := api.SpeciesService{} + characteristicService := api.CharacteristicService{} + measurementService := api.MeasurementService{} - m.Handle("/authenticate", tokenHandler(j.Authenticate())).Methods("POST") - m.Handle("/refresh", j.Secure(errorHandler(tokenRefresh(j)), verifyClaims)).Methods("POST") + m.Handle("/authenticate", tokenHandler(auth.Middleware.Authenticate())).Methods("POST") + m.Handle("/refresh", auth.Middleware.Secure(errorHandler(tokenRefresh(auth.Middleware)), verifyClaims)).Methods("POST") // Everything past here is lumped under a genus s := m.PathPrefix("/{genus}").Subrouter() s.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST") - s.Handle("/users/verify/{Nonce}", errorHandler(handleUserVerify)).Methods("GET") - s.Handle("/users/lockout", errorHandler(handleUserLockout)).Methods("POST") + s.Handle("/users/verify/{Nonce}", errorHandler(api.HandleUserVerify)).Methods("GET") + s.Handle("/users/lockout", errorHandler(api.HandleUserLockout)).Methods("POST") - s.Handle("/compare", j.Secure(errorHandler(handleCompare), verifyClaims)).Methods("GET") + s.Handle("/compare", auth.Middleware.Secure(errorHandler(api.HandleCompare), verifyClaims)).Methods("GET") type r struct { f errorHandler @@ -127,126 +89,126 @@ func Handler() http.Handler { } for _, route := range routes { - s.Handle(route.p, j.Secure(errorHandler(route.f), verifyClaims)).Methods(route.m) + s.Handle(route.p, auth.Middleware.Secure(errorHandler(route.f), verifyClaims)).Methods(route.m) } return jsonHandler(gziphandler.GzipHandler(corsHandler(m))) } -func handleGetter(g getter) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *appError { +func handleGetter(g api.Getter) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - claims := getClaims(r) + claims := helpers.GetClaims(r) - e, appErr := g.get(id, mux.Vars(r)["genus"], &claims) + e, appErr := g.Get(id, mux.Vars(r)["genus"], &claims) if appErr != nil { return appErr } - data, err := e.marshal() + data, err := e.Marshal() if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil } } -func handleLister(l lister) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *appError { +func handleLister(l api.Lister) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { opt := r.URL.Query() opt.Add("Genus", mux.Vars(r)["genus"]) - claims := getClaims(r) + claims := helpers.GetClaims(r) - es, appErr := l.list(&opt, &claims) + es, appErr := l.List(&opt, &claims) if appErr != nil { return appErr } - data, err := es.marshal() + data, err := es.Marshal() if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil } } -func handleUpdater(u updater) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *appError { +func handleUpdater(u api.Updater) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - e, err := u.unmarshal(bodyBytes) + e, err := u.Unmarshal(bodyBytes) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - claims := getClaims(r) + claims := helpers.GetClaims(r) - appErr := u.update(id, &e, mux.Vars(r)["genus"], &claims) + appErr := u.Update(id, &e, mux.Vars(r)["genus"], &claims) if appErr != nil { return appErr } - data, err := e.marshal() + data, err := e.Marshal() if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil } } -func handleCreater(c creater) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *appError { +func handleCreater(c api.Creater) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - e, err := c.unmarshal(bodyBytes) + e, err := c.Unmarshal(bodyBytes) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - claims := getClaims(r) + claims := helpers.GetClaims(r) - appErr := c.create(&e, mux.Vars(r)["genus"], &claims) + appErr := c.Create(&e, mux.Vars(r)["genus"], &claims) if appErr != nil { return appErr } - data, err := e.marshal() + data, err := e.Marshal() if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil } } -func handleDeleter(d deleter) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *appError { +func handleDeleter(d api.Deleter) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } - claims := getClaims(r) + claims := helpers.GetClaims(r) - appErr := d.delete(id, mux.Vars(r)["genus"], &claims) + appErr := d.Delete(id, mux.Vars(r)["genus"], &claims) if appErr != nil { return appErr } @@ -320,7 +282,7 @@ func jsonHandler(h http.Handler) http.Handler { return http.HandlerFunc(j) } -type errorHandler func(http.ResponseWriter, *http.Request) *appError +type errorHandler func(http.ResponseWriter, *http.Request) *types.AppError func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := fn(w, r); err != nil { @@ -330,16 +292,16 @@ func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func tokenRefresh(j *jwt.Middleware) errorHandler { - t := func(w http.ResponseWriter, r *http.Request) *appError { - claims := getClaims(r) - user, err := dbGetUserById(claims.Sub) + t := func(w http.ResponseWriter, r *http.Request) *types.AppError { + claims := helpers.GetClaims(r) + user, err := models.DbGetUserById(claims.Sub) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } user.Password = "" - token, err := j.CreateToken(user.Email) + token, err := auth.Middleware.CreateToken(user.Email) if err != nil { - return newJSONError(err, http.StatusInternalServerError) + return types.NewJSONError(err, http.StatusInternalServerError) } data, _ := json.Marshal(struct { Token string `json:"token"` diff --git a/helpers.go b/helpers/helpers.go similarity index 71% rename from helpers.go rename to helpers/helpers.go index 57f50c0..a2e5500 100644 --- a/helpers.go +++ b/helpers/helpers.go @@ -1,4 +1,4 @@ -package main +package helpers import ( "crypto/rand" @@ -8,15 +8,18 @@ import ( "net/http" "time" + "github.com/gorilla/schema" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/context" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" + "github.com/thermokarst/bactdb/types" ) var ( ErrMustProvideOptions = errors.New("Must provide necessary options") - ErrMustProvideOptionsJSON = newJSONError(ErrMustProvideOptions, http.StatusBadRequest) + ErrMustProvideOptionsJSON = types.NewJSONError(ErrMustProvideOptions, http.StatusBadRequest) StatusUnprocessableEntity = 422 MustProvideAValue = "Must provide a value" + SchemaDecoder = schema.NewDecoder() ) // ListOptions specifies general pagination options for fetching a list of results @@ -45,10 +48,16 @@ func (o ListOptions) PerPageOrDefault() int64 { return o.PerPage } +type MeasurementListOptions struct { + ListOptions + Strains []int64 `schema:"strain_ids"` + Characteristics []int64 `schema:"characteristic_ids"` +} + // DefaultPerPage is the default number of items to return in a paginated result set const DefaultPerPage = 10 -func valsIn(attribute string, values []int64, vals *[]interface{}, counter *int64) string { +func ValsIn(attribute string, values []int64, vals *[]interface{}, counter *int64) string { if len(values) == 1 { return fmt.Sprintf("%v=%v", attribute, values[0]) } @@ -63,8 +72,8 @@ func valsIn(attribute string, values []int64, vals *[]interface{}, counter *int6 return m } -func currentTime() NullTime { - return NullTime{ +func CurrentTime() types.NullTime { + return types.NullTime{ pq.NullTime{ Time: time.Now(), Valid: true, @@ -72,7 +81,8 @@ func currentTime() NullTime { } } -func generateNonce() (string, error) { +// TODO: move this +func GenerateNonce() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { @@ -81,11 +91,11 @@ func generateNonce() (string, error) { return base64.URLEncoding.EncodeToString(b), nil } -func getClaims(r *http.Request) Claims { +func GetClaims(r *http.Request) types.Claims { con := context.Get(r, "claims") - var claims Claims + var claims types.Claims if con != nil { - claims = con.(Claims) + claims = con.(types.Claims) } origin := r.Header.Get("Origin") if origin != "" { @@ -94,10 +104,10 @@ func getClaims(r *http.Request) Claims { return claims } -func canAdd(claims *Claims) bool { +func CanAdd(claims *types.Claims) bool { return claims.Role == "A" || claims.Role == "W" } -func canEdit(claims *Claims, author int64) bool { +func CanEdit(claims *types.Claims, author int64) bool { return claims.Sub == author || claims.Role == "A" } diff --git a/main.go b/main.go index 1004d89..e52e2eb 100644 --- a/main.go +++ b/main.go @@ -10,18 +10,12 @@ import ( "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/DavidHuie/gomigrate" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/codegangsta/cli" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/schema" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/sqlx" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/mailgun/mailgun-go" -) - -var ( - DB = &modl.DbMap{Dialect: modl.PostgresDialect{}} - DBH modl.SqlExecutor = DB - schemaDecoder = schema.NewDecoder() - mgAccts = make(map[string]mailgun.Mailgun) + "github.com/thermokarst/bactdb/api" + "github.com/thermokarst/bactdb/handlers" + "github.com/thermokarst/bactdb/models" ) func main() { @@ -37,12 +31,12 @@ func main() { } else { connection += " sslmode=disable" } - DB.Dbx, err = sqlx.Open("postgres", connection) + models.DB.Dbx, err = sqlx.Open("postgres", connection) if err != nil { log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err) } - DB.TraceOn("[modl]", log.New(os.Stdout, "bactdb:", log.Lmicroseconds)) - DB.Db = DB.Dbx.DB + models.DB.TraceOn("[modl]", log.New(os.Stdout, "bactdb:", log.Lmicroseconds)) + models.DB.Db = models.DB.Dbx.DB }) app := cli.NewApp() @@ -100,7 +94,7 @@ func cmdServe(c *cli.Context) { log.Printf("Mailgun: %+v", accounts) for _, a := range accounts { - mgAccts[a.Ref] = mailgun.NewMailgun(a.Domain, a.Private, a.Public) + api.MgAccts[a.Ref] = mailgun.NewMailgun(a.Domain, a.Private, a.Public) } addr := os.Getenv("PORT") @@ -110,7 +104,7 @@ func cmdServe(c *cli.Context) { httpAddr := fmt.Sprintf(":%v", addr) m := http.NewServeMux() - m.Handle("/api/", http.StripPrefix("/api", Handler())) + m.Handle("/api/", http.StripPrefix("/api", handlers.Handler())) log.Print("Listening on ", httpAddr) err = http.ListenAndServe(httpAddr, m) @@ -121,16 +115,17 @@ func cmdServe(c *cli.Context) { func cmdMigrateDb(c *cli.Context) { migrationsPath := c.String("migration_path") - migrator, err := gomigrate.NewMigrator(DB.Dbx.DB, gomigrate.Postgres{}, migrationsPath) + migrator, err := gomigrate.NewMigrator(models.DB.Dbx.DB, gomigrate.Postgres{}, migrationsPath) if err != nil { log.Fatal("Error initializing migrations: ", err) } - users := make(Users, 0) + users := make(models.Users, 0) if c.Bool("drop") { // Back up users table - if err := DBH.Select(&users, `SELECT * FROM users;`); err != nil { + // TODO: look into this + if err := models.DBH.Select(&users, `SELECT * FROM users;`); err != nil { log.Fatal("Couldn't back up identity tables: ", err) } log.Printf("%+v Users", len(users)) @@ -152,7 +147,8 @@ func cmdMigrateDb(c *cli.Context) { if len(users) > 0 { // varargs don't seem to work here, loop instead for _, user := range users { - if err := DBH.Insert(user); err != nil { + // TODO: look into this + if err := models.DBH.Insert(user); err != nil { log.Fatal("Couldn't restore user: ", err) } } diff --git a/measurements.go b/measurements.go deleted file mode 100644 index 5783fb4..0000000 --- a/measurements.go +++ /dev/null @@ -1,375 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" -) - -var ( - ErrMeasurementNotFound = errors.New("Measurement not found") - ErrMeasurementNotFoundJSON = newJSONError(ErrMeasurementNotFound, http.StatusNotFound) -) - -func init() { - DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") -} - -func (m *MeasurementBase) PreInsert(e modl.SqlExecutor) error { - ct := currentTime() - m.CreatedAt = ct - m.UpdatedAt = ct - return nil -} - -func (m *MeasurementBase) PreUpdate(e modl.SqlExecutor) error { - m.UpdatedAt = currentTime() - return nil -} - -type MeasurementService struct{} - -// There are three types of supported measurements: fixed-text, free-text, -// & numerical. The table has a constraint that will allow at most one -// for a particular combination of strain & characteristic. -// MeasurementBase is what the DB expects to see for inserts/updates -type MeasurementBase struct { - Id int64 `json:"id,omitempty"` - StrainId int64 `db:"strain_id" json:"strain"` - CharacteristicId int64 `db:"characteristic_id" json:"characteristic"` - TextMeasurementTypeId NullInt64 `db:"text_measurement_type_id" json:"-"` - TxtValue NullString `db:"txt_value" json:"-"` - NumValue NullFloat64 `db:"num_value" json:"-"` - ConfidenceInterval NullFloat64 `db:"confidence_interval" json:"confidenceInterval"` - UnitTypeId NullInt64 `db:"unit_type_id" json:"-"` - Notes NullString `db:"notes" json:"notes"` - TestMethodId NullInt64 `db:"test_method_id" json:"-"` - CreatedAt NullTime `db:"created_at" json:"createdAt"` - UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` - CreatedBy int64 `db:"created_by" json:"createdBy"` - UpdatedBy int64 `db:"updated_by" json:"updatedBy"` -} - -type Measurement struct { - *MeasurementBase - TextMeasurementType NullString `db:"text_measurement_type_name" json:"-"` - UnitType NullString `db:"unit_type_name" json:"unitType"` - TestMethod NullString `db:"test_method_name" json:"testMethod"` - CanEdit bool `db:"-" json:"canEdit"` -} - -type FakeMeasurement Measurement - -func (m *Measurement) MarshalJSON() ([]byte, error) { - fm := FakeMeasurement(*m) - return json.Marshal(struct { - *FakeMeasurement - Value string `json:"value"` - }{ - FakeMeasurement: &fm, - Value: m.Value(), - }) -} - -func (m *Measurement) UnmarshalJSON(b []byte) error { - var measurement struct { - FakeMeasurement - Value interface{} `json:"value"` - } - if err := json.Unmarshal(b, &measurement); err != nil { - return err - } - - switch v := measurement.Value.(type) { - case string: - // Test if actually a lookup - id, err := getTextMeasurementTypeId(v) - if err != nil { - if err == sql.ErrNoRows { - measurement.TxtValue = NullString{sql.NullString{String: v, Valid: true}} - } else { - return err - } - } else { - measurement.TextMeasurementTypeId = NullInt64{sql.NullInt64{Int64: id, Valid: true}} - } - case int64: - measurement.NumValue = NullFloat64{sql.NullFloat64{Float64: float64(v), Valid: true}} - case float64: - measurement.NumValue = NullFloat64{sql.NullFloat64{Float64: v, Valid: true}} - } - - *m = Measurement(measurement.FakeMeasurement) - - return nil -} - -func (m *Measurement) Value() string { - if m.TextMeasurementType.Valid { - return m.TextMeasurementType.String - } - if m.TxtValue.Valid { - return m.TxtValue.String - } - if m.NumValue.Valid { - return fmt.Sprintf("%f", m.NumValue.Float64) - } - return "" -} - -type Measurements []*Measurement - -type MeasurementMeta struct { - CanAdd bool `json:"canAdd"` -} - -type MeasurementPayload struct { - Measurement *Measurement `json:"measurement"` -} - -type MeasurementsPayload struct { - Strains *Strains `json:"strains"` - Characteristics *Characteristics `json:"characteristics"` - Measurements *Measurements `json:"measurements"` -} - -func (m *MeasurementPayload) marshal() ([]byte, error) { - return json.Marshal(m) -} - -func (m *MeasurementsPayload) marshal() ([]byte, error) { - return json.Marshal(m) -} - -func (s MeasurementService) unmarshal(b []byte) (entity, error) { - var mj MeasurementPayload - err := json.Unmarshal(b, &mj) - return &mj, err -} - -type MeasurementListOptions struct { - ListOptions - Strains []int64 `schema:"strain_ids"` - Characteristics []int64 `schema:"characteristic_ids"` -} - -func (m MeasurementService) list(val *url.Values, claims *Claims) (entity, *appError) { - if val == nil { - return nil, ErrMustProvideOptionsJSON - } - var opt MeasurementListOptions - if err := schemaDecoder.Decode(&opt, *val); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - measurements, err := listMeasurements(opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - char_opts, err := characteristicOptsFromMeasurements(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristics, err := listCharacteristics(*char_opts, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strain_opts, err := strainOptsFromMeasurements(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, err := listStrains(*strain_opts, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := MeasurementsPayload{ - Characteristics: characteristics, - Strains: strains, - Measurements: measurements, - } - - return &payload, nil -} - -func (m MeasurementService) get(id int64, genus string, claims *Claims) (entity, *appError) { - measurement, err := getMeasurement(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := MeasurementPayload{ - Measurement: measurement, - } - - return &payload, nil -} - -func (s MeasurementService) update(id int64, e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*MeasurementPayload) - payload.Measurement.UpdatedBy = claims.Sub - payload.Measurement.Id = id - - if payload.Measurement.TextMeasurementType.Valid { - id, err := getTextMeasurementTypeId(payload.Measurement.TextMeasurementType.String) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - payload.Measurement.TextMeasurementTypeId.Int64 = id - payload.Measurement.TextMeasurementTypeId.Valid = true - } - - count, err := DBH.Update(payload.Measurement.MeasurementBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return newJSONError(ErrStrainNotUpdated, http.StatusBadRequest) - } - - measurement, err := getMeasurement(id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - payload.Measurement = measurement - - return nil -} - -func (m MeasurementService) delete(id int64, genus string, claims *Claims) *appError { - q := `DELETE FROM measurements WHERE id=$1;` - _, err := DBH.Exec(q, id) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - return nil -} - -func (m MeasurementService) create(e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*MeasurementPayload) - payload.Measurement.CreatedBy = claims.Sub - payload.Measurement.UpdatedBy = claims.Sub - - if err := DBH.Insert(payload.Measurement.MeasurementBase); err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - return nil - -} - -func listMeasurements(opt MeasurementListOptions, claims *Claims) (*Measurements, error) { - var vals []interface{} - - q := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, - u.symbol AS unit_type_name, te.name AS test_method_name - FROM measurements m - INNER JOIN strains st ON st.id=m.strain_id - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=$1 - LEFT OUTER JOIN characteristics c ON c.id=m.characteristic_id - LEFT OUTER JOIN text_measurement_types t ON t.id=m.text_measurement_type_id - LEFT OUTER JOIN unit_types u ON u.id=m.unit_type_id - LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id` - vals = append(vals, opt.Genus) - - strainIds := len(opt.Strains) != 0 - charIds := len(opt.Characteristics) != 0 - ids := len(opt.Ids) != 0 - - if strainIds || charIds || ids { - var paramsCounter int64 = 2 - q += "\nWHERE (" - - // Filter by strains - if strainIds { - q += valsIn("st.id", opt.Strains, &vals, ¶msCounter) - } - - if strainIds && (charIds || ids) { - q += " AND " - } - - // Filter by characteristics - if charIds { - q += valsIn("c.id", opt.Characteristics, &vals, ¶msCounter) - } - - if charIds && ids { - q += " AND " - } - - // Get specific records - if ids { - q += valsIn("m.id", opt.Ids, &vals, ¶msCounter) - } - q += ")" - } - q += ";" - - measurements := make(Measurements, 0) - err := DBH.Select(&measurements, q, vals...) - if err != nil { - return nil, err - } - - for _, m := range measurements { - m.CanEdit = canEdit(claims, m.CreatedBy) - } - - return &measurements, nil -} - -func getMeasurement(id int64, genus string, claims *Claims) (*Measurement, error) { - var measurement Measurement - - q := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, - u.symbol AS unit_type_name, te.name AS test_method_name - FROM measurements m - INNER JOIN strains st ON st.id=m.strain_id - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - LEFT OUTER JOIN characteristics c ON c.id=m.characteristic_id - LEFT OUTER JOIN text_measurement_types t ON t.id=m.text_measurement_type_id - LEFT OUTER JOIN unit_types u ON u.id=m.unit_type_id - LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id - WHERE m.id=$2;` - if err := DBH.SelectOne(&measurement, q, genus, id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrMeasurementNotFound - } - return nil, err - } - - measurement.CanEdit = canEdit(claims, measurement.CreatedBy) - - return &measurement, nil -} - -func characteristicOptsFromMeasurements(opt MeasurementListOptions) (*ListOptions, error) { - return &ListOptions{Genus: opt.Genus, Ids: opt.Characteristics}, nil -} - -func strainOptsFromMeasurements(opt MeasurementListOptions) (*ListOptions, error) { - return &ListOptions{Genus: opt.Genus, Ids: opt.Strains}, nil -} - -func getTextMeasurementTypeId(val string) (int64, error) { - var id int64 - q := `SELECT id FROM text_measurement_types WHERE text_measurement_name=$1;` - - if err := DBH.SelectOne(&id, q, val); err != nil { - return 0, err - } - return id, nil -} diff --git a/models/characteristics.go b/models/characteristics.go new file mode 100644 index 0000000..a913c61 --- /dev/null +++ b/models/characteristics.go @@ -0,0 +1,236 @@ +package models + +import ( + "database/sql" + "errors" + "fmt" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +var ( + ErrCharacteristicNotFound = errors.New("Characteristic not found") + ErrCharacteristicNotUpdated = errors.New("Characteristic not updated") +) + +func init() { + DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") +} + +func (c *CharacteristicBase) PreInsert(e modl.SqlExecutor) error { + ct := helpers.CurrentTime() + c.CreatedAt = ct + c.UpdatedAt = ct + return nil +} + +func (c *CharacteristicBase) PreUpdate(e modl.SqlExecutor) error { + c.UpdatedAt = helpers.CurrentTime() + return nil +} + +type CharacteristicBase struct { + Id int64 `json:"id,omitempty"` + CharacteristicName string `db:"characteristic_name" json:"characteristicName"` + CharacteristicTypeId int64 `db:"characteristic_type_id" json:"-"` + SortOrder types.NullInt64 `db:"sort_order" json:"sortOrder"` + CreatedAt types.NullTime `db:"created_at" json:"createdAt"` + UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` + DeletedAt types.NullTime `db:"deleted_at" json:"deletedAt"` + CreatedBy int64 `db:"created_by" json:"createdBy"` + UpdatedBy int64 `db:"updated_by" json:"updatedBy"` + DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` +} + +type Characteristic struct { + *CharacteristicBase + Measurements types.NullSliceInt64 `db:"measurements" json:"measurements"` + Strains types.NullSliceInt64 `db:"strains" json:"strains"` + CharacteristicType string `db:"characteristic_type_name" json:"characteristicTypeName"` + CanEdit bool `db:"-" json:"canEdit"` +} + +type Characteristics []*Characteristic + +type CharacteristicMeta struct { + CanAdd bool `json:"canAdd"` +} + +func ListCharacteristics(opt helpers.ListOptions, claims *types.Claims) (*Characteristics, error) { + var vals []interface{} + + q := `SELECT c.*, ct.characteristic_type_name, + array_agg(DISTINCT st.id) AS strains, array_agg(DISTINCT m.id) AS measurements + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + INNER JOIN measurements m ON m.strain_id=st.id + RIGHT OUTER JOIN characteristics c ON c.id=m.characteristic_id + INNER JOIN characteristic_types ct ON ct.id=c.characteristic_type_id` + vals = append(vals, opt.Genus) + + if len(opt.Ids) != 0 { + var counter int64 = 2 + w := helpers.ValsIn("c.id", opt.Ids, &vals, &counter) + + q += fmt.Sprintf(" WHERE %s", w) + } + + q += ` GROUP BY c.id, ct.characteristic_type_name + ORDER BY ct.characteristic_type_name, c.sort_order ASC;` + + var characteristics Characteristics + err := DBH.Select(&characteristics, q, vals...) + if err != nil { + return nil, err + } + + for _, c := range characteristics { + c.CanEdit = helpers.CanEdit(claims, c.CreatedBy) + } + + return &characteristics, nil +} + +func StrainOptsFromCharacteristics(opt helpers.ListOptions) (*helpers.ListOptions, error) { + relatedStrainIds := make([]int64, 0) + baseQ := `SELECT DISTINCT m.strain_id + FROM measurements m + INNER JOIN strains st ON st.id=m.strain_id + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` + if opt.Ids == nil { + q := fmt.Sprintf("%s;", baseQ) + if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { + return nil, err + } + } else { + var vals []interface{} + var count int64 = 2 + vals = append(vals, opt.Genus) + q := fmt.Sprintf("%s WHERE %s ", baseQ, helpers.ValsIn("m.characteristic_id", opt.Ids, &vals, &count)) + + if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { + return nil, err + } + } + + return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil +} + +func MeasurementOptsFromCharacteristics(opt helpers.ListOptions) (*helpers.MeasurementListOptions, error) { + relatedMeasurementIds := make([]int64, 0) + baseQ := `SELECT m.id + FROM measurements m + INNER JOIN strains st ON st.id=m.strain_id + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` + + if opt.Ids == nil { + q := fmt.Sprintf("%s;", baseQ) + if err := DBH.Select(&relatedMeasurementIds, q, opt.Genus); err != nil { + return nil, err + } + } else { + var vals []interface{} + var count int64 = 2 + vals = append(vals, opt.Genus) + q := fmt.Sprintf("%s WHERE %s;", baseQ, helpers.ValsIn("characteristic_id", opt.Ids, &vals, &count)) + + if err := DBH.Select(&relatedMeasurementIds, q, vals...); err != nil { + return nil, err + } + } + + return &helpers.MeasurementListOptions{ListOptions: helpers.ListOptions{Genus: opt.Genus, Ids: relatedMeasurementIds}, Strains: nil, Characteristics: nil}, nil +} + +func StrainsFromCharacteristicId(id int64, genus string, claims *types.Claims) (*Strains, *helpers.ListOptions, error) { + opt := helpers.ListOptions{ + Genus: genus, + Ids: []int64{id}, + } + + strains_opt, err := StrainOptsFromCharacteristics(opt) + if err != nil { + return nil, nil, err + } + + strains, err := ListStrains(*strains_opt, claims) + if err != nil { + return nil, nil, err + } + + return strains, strains_opt, nil +} + +func MeasurementsFromCharacteristicId(id int64, genus string, claims *types.Claims) (*Measurements, *helpers.MeasurementListOptions, error) { + opt := helpers.ListOptions{ + Genus: genus, + Ids: []int64{id}, + } + + measurement_opt, err := MeasurementOptsFromCharacteristics(opt) + if err != nil { + return nil, nil, err + } + + measurements, err := ListMeasurements(*measurement_opt, claims) + if err != nil { + return nil, nil, err + } + + return measurements, measurement_opt, nil +} + +func GetCharacteristic(id int64, genus string, claims *types.Claims) (*Characteristic, error) { + var characteristic Characteristic + q := `SELECT c.*, ct.characteristic_type_name, + array_agg(DISTINCT st.id) AS strains, array_agg(DISTINCT m.id) AS measurements + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + INNER JOIN measurements m ON m.strain_id=st.id + RIGHT OUTER JOIN characteristics c ON c.id=m.characteristic_id + INNER JOIN characteristic_types ct ON ct.id=c.characteristic_type_id + WHERE c.id=$2 + GROUP BY c.id, ct.characteristic_type_name;` + if err := DBH.SelectOne(&characteristic, q, genus, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrCharacteristicNotFound + } + return nil, err + } + + characteristic.CanEdit = helpers.CanEdit(claims, characteristic.CreatedBy) + + return &characteristic, nil +} + +func InsertOrGetCharacteristicType(val string, claims *types.Claims) (int64, error) { + var id int64 + q := `SELECT id FROM characteristic_types WHERE characteristic_type_name=$1;` + if err := DBH.SelectOne(&id, q, val); err != nil { + if err == sql.ErrNoRows { + i := `INSERT INTO characteristic_types + (characteristic_type_name, created_at, updated_at, created_by, updated_by) + VALUES ($1, $2, $3, $4, $5) RETURNING id;` + ct := helpers.CurrentTime() + var result sql.Result + var insertErr error + stmt, err := DB.Db.Prepare(i) + if result, insertErr = stmt.Exec(val, ct, ct, claims.Sub, claims.Sub); insertErr != nil { + return 0, insertErr + } + id, err = result.LastInsertId() + if err != nil { + return 0, err + } + } else { + return 0, err + } + } + return id, nil +} diff --git a/models/database.go b/models/database.go new file mode 100644 index 0000000..d243468 --- /dev/null +++ b/models/database.go @@ -0,0 +1,8 @@ +package models + +import "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + +var ( + DB = &modl.DbMap{Dialect: modl.PostgresDialect{}} + DBH modl.SqlExecutor = DB +) diff --git a/models/measurements.go b/models/measurements.go new file mode 100644 index 0000000..1bfa6dd --- /dev/null +++ b/models/measurements.go @@ -0,0 +1,234 @@ +package models + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +var ( + ErrMeasurementNotFound = errors.New("Measurement not found") + ErrMeasurementNotFoundJSON = types.NewJSONError(ErrMeasurementNotFound, http.StatusNotFound) +) + +func init() { + DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") +} + +func (m *MeasurementBase) PreInsert(e modl.SqlExecutor) error { + ct := helpers.CurrentTime() + m.CreatedAt = ct + m.UpdatedAt = ct + return nil +} + +func (m *MeasurementBase) PreUpdate(e modl.SqlExecutor) error { + m.UpdatedAt = helpers.CurrentTime() + return nil +} + +// There are three types of supported measurements: fixed-text, free-text, +// & numerical. The table has a constraint that will allow at most one +// for a particular combination of strain & characteristic. +// MeasurementBase is what the DB expects to see for inserts/updates +type MeasurementBase struct { + Id int64 `json:"id,omitempty"` + StrainId int64 `db:"strain_id" json:"strain"` + CharacteristicId int64 `db:"characteristic_id" json:"characteristic"` + TextMeasurementTypeId types.NullInt64 `db:"text_measurement_type_id" json:"-"` + TxtValue types.NullString `db:"txt_value" json:"-"` + NumValue types.NullFloat64 `db:"num_value" json:"-"` + ConfidenceInterval types.NullFloat64 `db:"confidence_interval" json:"confidenceInterval"` + UnitTypeId types.NullInt64 `db:"unit_type_id" json:"-"` + Notes types.NullString `db:"notes" json:"notes"` + TestMethodId types.NullInt64 `db:"test_method_id" json:"-"` + CreatedAt types.NullTime `db:"created_at" json:"createdAt"` + UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` + CreatedBy int64 `db:"created_by" json:"createdBy"` + UpdatedBy int64 `db:"updated_by" json:"updatedBy"` +} + +type Measurement struct { + *MeasurementBase + TextMeasurementType types.NullString `db:"text_measurement_type_name" json:"-"` + UnitType types.NullString `db:"unit_type_name" json:"unitType"` + TestMethod types.NullString `db:"test_method_name" json:"testMethod"` + CanEdit bool `db:"-" json:"canEdit"` +} + +type FakeMeasurement Measurement + +func (m *Measurement) MarshalJSON() ([]byte, error) { + fm := FakeMeasurement(*m) + return json.Marshal(struct { + *FakeMeasurement + Value string `json:"value"` + }{ + FakeMeasurement: &fm, + Value: m.Value(), + }) +} + +func (m *Measurement) UnmarshalJSON(b []byte) error { + var measurement struct { + FakeMeasurement + Value interface{} `json:"value"` + } + if err := json.Unmarshal(b, &measurement); err != nil { + return err + } + + switch v := measurement.Value.(type) { + case string: + // Test if actually a lookup + id, err := GetTextMeasurementTypeId(v) + if err != nil { + if err == sql.ErrNoRows { + measurement.TxtValue = types.NullString{sql.NullString{String: v, Valid: true}} + } else { + return err + } + } else { + measurement.TextMeasurementTypeId = types.NullInt64{sql.NullInt64{Int64: id, Valid: true}} + } + case int64: + measurement.NumValue = types.NullFloat64{sql.NullFloat64{Float64: float64(v), Valid: true}} + case float64: + measurement.NumValue = types.NullFloat64{sql.NullFloat64{Float64: v, Valid: true}} + } + + *m = Measurement(measurement.FakeMeasurement) + + return nil +} + +func (m *Measurement) Value() string { + if m.TextMeasurementType.Valid { + return m.TextMeasurementType.String + } + if m.TxtValue.Valid { + return m.TxtValue.String + } + if m.NumValue.Valid { + return fmt.Sprintf("%f", m.NumValue.Float64) + } + return "" +} + +type Measurements []*Measurement + +type MeasurementMeta struct { + CanAdd bool `json:"canAdd"` +} + +func ListMeasurements(opt helpers.MeasurementListOptions, claims *types.Claims) (*Measurements, error) { + var vals []interface{} + + q := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, + u.symbol AS unit_type_name, te.name AS test_method_name + FROM measurements m + INNER JOIN strains st ON st.id=m.strain_id + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=$1 + LEFT OUTER JOIN characteristics c ON c.id=m.characteristic_id + LEFT OUTER JOIN text_measurement_types t ON t.id=m.text_measurement_type_id + LEFT OUTER JOIN unit_types u ON u.id=m.unit_type_id + LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id` + vals = append(vals, opt.Genus) + + strainIds := len(opt.Strains) != 0 + charIds := len(opt.Characteristics) != 0 + ids := len(opt.Ids) != 0 + + if strainIds || charIds || ids { + var paramsCounter int64 = 2 + q += "\nWHERE (" + + // Filter by strains + if strainIds { + q += helpers.ValsIn("st.id", opt.Strains, &vals, ¶msCounter) + } + + if strainIds && (charIds || ids) { + q += " AND " + } + + // Filter by characteristics + if charIds { + q += helpers.ValsIn("c.id", opt.Characteristics, &vals, ¶msCounter) + } + + if charIds && ids { + q += " AND " + } + + // Get specific records + if ids { + q += helpers.ValsIn("m.id", opt.Ids, &vals, ¶msCounter) + } + q += ")" + } + q += ";" + + measurements := make(Measurements, 0) + err := DBH.Select(&measurements, q, vals...) + if err != nil { + return nil, err + } + + for _, m := range measurements { + m.CanEdit = helpers.CanEdit(claims, m.CreatedBy) + } + + return &measurements, nil +} + +func GetMeasurement(id int64, genus string, claims *types.Claims) (*Measurement, error) { + var measurement Measurement + + q := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, + u.symbol AS unit_type_name, te.name AS test_method_name + FROM measurements m + INNER JOIN strains st ON st.id=m.strain_id + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + LEFT OUTER JOIN characteristics c ON c.id=m.characteristic_id + LEFT OUTER JOIN text_measurement_types t ON t.id=m.text_measurement_type_id + LEFT OUTER JOIN unit_types u ON u.id=m.unit_type_id + LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id + WHERE m.id=$2;` + if err := DBH.SelectOne(&measurement, q, genus, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrMeasurementNotFound + } + return nil, err + } + + measurement.CanEdit = helpers.CanEdit(claims, measurement.CreatedBy) + + return &measurement, nil +} + +func CharacteristicOptsFromMeasurements(opt helpers.MeasurementListOptions) (*helpers.ListOptions, error) { + return &helpers.ListOptions{Genus: opt.Genus, Ids: opt.Characteristics}, nil +} + +func StrainOptsFromMeasurements(opt helpers.MeasurementListOptions) (*helpers.ListOptions, error) { + return &helpers.ListOptions{Genus: opt.Genus, Ids: opt.Strains}, nil +} + +func GetTextMeasurementTypeId(val string) (int64, error) { + var id int64 + q := `SELECT id FROM text_measurement_types WHERE text_measurement_name=$1;` + + if err := DBH.SelectOne(&id, q, val); err != nil { + return 0, err + } + return id, nil +} diff --git a/models/species.go b/models/species.go new file mode 100644 index 0000000..e88faed --- /dev/null +++ b/models/species.go @@ -0,0 +1,174 @@ +package models + +import ( + "database/sql" + "errors" + "fmt" + "strings" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +var ( + ErrSpeciesNotFound = errors.New("Species not found") + ErrSpeciesNotUpdated = errors.New("Species not updated") +) + +func init() { + DB.AddTableWithName(SpeciesBase{}, "species").SetKeys(true, "Id") +} + +func (s *SpeciesBase) PreInsert(e modl.SqlExecutor) error { + ct := helpers.CurrentTime() + s.CreatedAt = ct + s.UpdatedAt = ct + return nil +} + +func (s *SpeciesBase) PreUpdate(e modl.SqlExecutor) error { + s.UpdatedAt = helpers.CurrentTime() + return nil +} + +type SpeciesBase struct { + Id int64 `db:"id" json:"id"` + GenusID int64 `db:"genus_id" json:"-"` + SubspeciesSpeciesID types.NullInt64 `db:"subspecies_species_id" json:"-"` + SpeciesName string `db:"species_name" json:"speciesName"` + TypeSpecies types.NullBool `db:"type_species" json:"typeSpecies"` + Etymology types.NullString `db:"etymology" json:"etymology"` + CreatedAt types.NullTime `db:"created_at" json:"createdAt"` + UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` + DeletedAt types.NullTime `db:"deleted_at" json:"deletedAt"` + CreatedBy int64 `db:"created_by" json:"createdBy"` + UpdatedBy int64 `db:"updated_by" json:"updatedBy"` + DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` +} + +type Species struct { + *SpeciesBase + GenusName string `db:"genus_name" json:"genusName"` + Strains types.NullSliceInt64 `db:"strains" json:"strains"` + TotalStrains int64 `db:"total_strains" json:"totalStrains"` + SortOrder int64 `db:"sort_order" json:"sortOrder"` + CanEdit bool `db:"-" json:"canEdit"` +} + +type ManySpecies []*Species + +type SpeciesMeta struct { + CanAdd bool `json:"canAdd"` +} + +func GenusIdFromName(genus_name string) (int64, error) { + var genus_id struct{ Id int64 } + q := `SELECT id FROM genera WHERE LOWER(genus_name) = LOWER($1);` + if err := DBH.SelectOne(&genus_id, q, genus_name); err != nil { + return 0, err + } + return genus_id.Id, nil +} + +func StrainOptsFromSpecies(opt helpers.ListOptions) (*helpers.ListOptions, error) { + relatedStrainIds := make([]int64, 0) + + if opt.Ids == nil { + q := `SELECT DISTINCT st.id + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` + if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { + return nil, err + } + } else { + var vals []interface{} + var count int64 = 1 + q := fmt.Sprintf("SELECT DISTINCT id FROM strains WHERE %s;", helpers.ValsIn("species_id", opt.Ids, &vals, &count)) + + if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { + return nil, err + } + } + + return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil +} + +func StrainsFromSpeciesId(id int64, genus string, claims *types.Claims) (*Strains, error) { + opt := helpers.ListOptions{ + Genus: genus, + Ids: []int64{id}, + } + + strains_opt, err := StrainOptsFromSpecies(opt) + if err != nil { + return nil, err + } + + strains, err := ListStrains(*strains_opt, claims) + if err != nil { + return nil, err + } + + return strains, nil +} + +func ListSpecies(opt helpers.ListOptions, claims *types.Claims) (*ManySpecies, error) { + var vals []interface{} + + q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, + COUNT(st) AS total_strains, + rank() OVER (ORDER BY sp.species_name ASC) AS sort_order + FROM species sp + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + LEFT OUTER JOIN strains st ON st.species_id=sp.id` + vals = append(vals, opt.Genus) + + if len(opt.Ids) != 0 { + var conds []string + s := "sp.id IN (" + for i, id := range opt.Ids { + s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 + vals = append(vals, id) + } + s = s[:len(s)-1] + ")" + conds = append(conds, s) + q += " WHERE (" + strings.Join(conds, ") AND (") + ")" + } + + q += " GROUP BY sp.id, g.genus_name;" + + species := make(ManySpecies, 0) + err := DBH.Select(&species, q, vals...) + if err != nil { + return nil, err + } + + for _, s := range species { + s.CanEdit = helpers.CanEdit(claims, s.CreatedBy) + } + + return &species, nil +} + +func GetSpecies(id int64, genus string, claims *types.Claims) (*Species, error) { + var species Species + q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, + COUNT(st) AS total_strains, 0 AS sort_order + FROM species sp + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + LEFT OUTER JOIN strains st ON st.species_id=sp.id + WHERE sp.id=$2 + GROUP BY sp.id, g.genus_name;` + if err := DBH.SelectOne(&species, q, genus, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrSpeciesNotFound + } + return nil, err + } + + species.CanEdit = helpers.CanEdit(claims, species.CreatedBy) + + return &species, nil +} diff --git a/models/strains.go b/models/strains.go new file mode 100644 index 0000000..a386031 --- /dev/null +++ b/models/strains.go @@ -0,0 +1,184 @@ +package models + +import ( + "database/sql" + "errors" + "fmt" + "strings" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +var ( + ErrStrainNotFound = errors.New("Strain not found") + ErrStrainNotUpdated = errors.New("Strain not updated") +) + +func init() { + DB.AddTableWithName(StrainBase{}, "strains").SetKeys(true, "Id") +} + +func (s *StrainBase) PreInsert(e modl.SqlExecutor) error { + ct := helpers.CurrentTime() + s.CreatedAt = ct + s.UpdatedAt = ct + return nil +} + +func (s *StrainBase) PreUpdate(e modl.SqlExecutor) error { + s.UpdatedAt = helpers.CurrentTime() + return nil +} + +type StrainBase struct { + Id int64 `db:"id" json:"id"` + SpeciesId int64 `db:"species_id" json:"species"` + StrainName string `db:"strain_name" json:"strainName"` + TypeStrain bool `db:"type_strain" json:"typeStrain"` + AccessionNumbers types.NullString `db:"accession_numbers" json:"accessionNumbers"` + Genbank types.NullString `db:"genbank" json:"genbank"` + WholeGenomeSequence types.NullString `db:"whole_genome_sequence" json:"wholeGenomeSequence"` + IsolatedFrom types.NullString `db:"isolated_from" json:"isolatedFrom"` + Notes types.NullString `db:"notes" json:"notes"` + CreatedAt types.NullTime `db:"created_at" json:"createdAt"` + UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` + DeletedAt types.NullTime `db:"deleted_at" json:"deletedAt"` + CreatedBy int64 `db:"created_by" json:"createdBy"` + UpdatedBy int64 `db:"updated_by" json:"updatedBy"` + DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` +} + +type Strain struct { + *StrainBase + Measurements types.NullSliceInt64 `db:"measurements" json:"measurements"` + Characteristics types.NullSliceInt64 `db:"characteristics" json:"characteristics"` + TotalMeasurements int64 `db:"total_measurements" json:"totalMeasurements"` + SortOrder int64 `db:"sort_order" json:"sortOrder"` + CanEdit bool `db:"-" json:"canEdit"` +} + +type Strains []*Strain + +type StrainMeta struct { + CanAdd bool `json:"canAdd"` +} + +func (s StrainBase) SpeciesName() string { + var species SpeciesBase + if err := DBH.Get(&species, s.SpeciesId); err != nil { + return "" + } + return species.SpeciesName +} + +func ListStrains(opt helpers.ListOptions, claims *types.Claims) (*Strains, error) { + var vals []interface{} + + q := `SELECT st.*, array_agg(m.id) AS measurements, + array_agg(DISTINCT m.characteristic_id) AS characteristics, + COUNT(m) AS total_measurements, + rank() OVER (ORDER BY sp.species_name ASC, st.type_strain ASC, st.strain_name ASC) AS sort_order + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + LEFT OUTER JOIN measurements m ON m.strain_id=st.id` + vals = append(vals, opt.Genus) + + if len(opt.Ids) != 0 { + var conds []string + s := "st.id IN (" + for i, id := range opt.Ids { + s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 + vals = append(vals, id) + } + s = s[:len(s)-1] + ")" + conds = append(conds, s) + q += " WHERE (" + strings.Join(conds, ") AND (") + ")" + } + + q += " GROUP BY st.id, st.species_id, sp.species_name;" + + strains := make(Strains, 0) + err := DBH.Select(&strains, q, vals...) + if err != nil { + return nil, err + } + + for _, s := range strains { + s.CanEdit = helpers.CanEdit(claims, s.CreatedBy) + } + + return &strains, nil +} + +func GetStrain(id int64, genus string, claims *types.Claims) (*Strain, error) { + var strain Strain + q := `SELECT st.*, array_agg(DISTINCT m.id) AS measurements, + array_agg(DISTINCT m.characteristic_id) AS characteristics, + COUNT(m) AS total_measurements, 0 AS sort_order + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) + LEFT OUTER JOIN measurements m ON m.strain_id=st.id + WHERE st.id=$2 + GROUP BY st.id;` + if err := DBH.SelectOne(&strain, q, genus, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrStrainNotFound + } + return nil, err + } + + strain.CanEdit = helpers.CanEdit(claims, strain.CreatedBy) + + return &strain, nil +} + +func SpeciesOptsFromStrains(opt helpers.ListOptions) (*helpers.ListOptions, error) { + relatedSpeciesIds := make([]int64, 0) + + if opt.Ids == nil || len(opt.Ids) == 0 { + q := `SELECT DISTINCT st.species_id + FROM strains st + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` + if err := DBH.Select(&relatedSpeciesIds, q, opt.Genus); err != nil { + return nil, err + } + } else { + var vals []interface{} + var count int64 = 1 + q := fmt.Sprintf("SELECT DISTINCT species_id FROM strains WHERE %s;", helpers.ValsIn("id", opt.Ids, &vals, &count)) + if err := DBH.Select(&relatedSpeciesIds, q, vals...); err != nil { + return nil, err + } + } + + return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedSpeciesIds}, nil +} + +func CharacteristicsOptsFromStrains(opt helpers.ListOptions) (*helpers.ListOptions, error) { + relatedCharacteristicsIds := make([]int64, 0) + + if opt.Ids == nil || len(opt.Ids) == 0 { + q := `SELECT DISTINCT m.characteristic_id + FROM measurements m + INNER JOIN strains st ON st.id=m.strain_id + INNER JOIN species sp ON sp.id=st.species_id + INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` + if err := DBH.Select(&relatedCharacteristicsIds, q, opt.Genus); err != nil { + return nil, err + } + } else { + var vals []interface{} + var count int64 = 1 + q := fmt.Sprintf("SELECT DISTINCT characteristic_id FROM measurements WHERE %s;", helpers.ValsIn("strain_id", opt.Ids, &vals, &count)) + if err := DBH.Select(&relatedCharacteristicsIds, q, vals...); err != nil { + return nil, err + } + } + + return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedCharacteristicsIds}, nil +} diff --git a/models/users.go b/models/users.go new file mode 100644 index 0000000..1cf56a8 --- /dev/null +++ b/models/users.go @@ -0,0 +1,156 @@ +package models + +import ( + "database/sql" + "encoding/json" + "errors" + "regexp" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +var ( + ErrUserNotFound = errors.New("User not found") + ErrUserNotUpdated = errors.New("User not updated") + ErrInvalidEmailOrPassword = errors.New("Invalid email or password") + ErrEmailAddressTaken = errors.New("Email address already registered") +) + +func init() { + DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "Id") +} + +type UserBase struct { + Id int64 `json:"id,omitempty"` + Email string `db:"email" json:"email"` + Password string `db:"password" json:"password,omitempty"` + Name string `db:"name" json:"name"` + Role string `db:"role" json:"role"` + Verified bool `db:"verified" json:"-"` + CreatedAt types.NullTime `db:"created_at" json:"createdAt"` + UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` + DeletedAt types.NullTime `db:"deleted_at" json:"deletedAt"` +} + +type User struct { + *UserBase + CanEdit bool `db:"-" json:"canEdit"` +} + +type UserValidation struct { + Email []string `json:"email,omitempty"` + Password []string `json:"password,omitempty"` + Name []string `json:"name,omitempty"` + Role []string `json:"role,omitempty"` +} + +func (uv UserValidation) Error() string { + errs, err := json.Marshal(struct { + UserValidation `json:"errors"` + }{uv}) + if err != nil { + return err.Error() + } + return string(errs) +} + +type Users []*User + +type UserJSON struct { + User *User `json:"user"` +} + +type UsersJSON struct { + Users *Users `json:"users"` +} + +type UserMeta struct { + CanAdd bool `json:"canAdd"` +} + +func (u *Users) Marshal() ([]byte, error) { + return json.Marshal(&UsersJSON{Users: u}) +} + +func (u *User) Validate() error { + var uv UserValidation + validationError := false + + if u.Name == "" { + uv.Name = append(uv.Name, helpers.MustProvideAValue) + validationError = true + } + + if u.Email == "" { + uv.Email = append(uv.Email, helpers.MustProvideAValue) + validationError = true + } + + regex, _ := regexp.Compile(`(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})`) + if u.Email != "" && !regex.MatchString(u.Email) { + uv.Email = append(uv.Email, "Must provide a valid email address") + validationError = true + } + + if len(u.Password) < 8 { + uv.Password = append(uv.Password, "Password must be at least 8 characters") + validationError = true + } + + if validationError { + return uv + } + return nil +} + +// for thermokarst/jwt: authentication callback +func DbAuthenticate(email string, password string) error { + var user User + q := `SELECT * + FROM users + WHERE lower(email)=lower($1) + AND verified IS TRUE + AND deleted_at IS NULL;` + if err := DBH.SelectOne(&user, q, email); err != nil { + return ErrInvalidEmailOrPassword + } + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + return ErrInvalidEmailOrPassword + } + return nil +} + +func DbGetUserById(id int64) (*User, error) { + var user User + q := `SELECT * + FROM users + WHERE id=$1 + AND verified IS TRUE + AND deleted_at IS NULL;` + if err := DBH.SelectOne(&user, q, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrUserNotFound + } + return nil, err + } + return &user, nil +} + +// for thermokarst/jwt: setting user in claims bundle +func DbGetUserByEmail(email string) (*User, error) { + var user User + q := `SELECT * + FROM users + WHERE lower(email)=lower($1) + AND verified IS TRUE + AND deleted_at IS NULL;` + if err := DBH.SelectOne(&user, q, email); err != nil { + if err == sql.ErrNoRows { + return nil, ErrUserNotFound + } + return nil, err + } + return &user, nil +} diff --git a/payloads/payloads.go b/payloads/payloads.go new file mode 100644 index 0000000..cabdd1c --- /dev/null +++ b/payloads/payloads.go @@ -0,0 +1,102 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type CharacteristicPayload struct { + Characteristic *models.Characteristic `json:"characteristic"` + Measurements *models.Measurements `json:"measurements"` + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Meta *models.CharacteristicMeta `json:"meta"` +} + +type CharacteristicsPayload struct { + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Meta *models.CharacteristicMeta `json:"meta"` +} + +func (c *CharacteristicPayload) Marshal() ([]byte, error) { + return json.Marshal(c) +} + +func (c *CharacteristicsPayload) Marshal() ([]byte, error) { + return json.Marshal(c) +} + +type MeasurementPayload struct { + Measurement *models.Measurement `json:"measurement"` +} + +type MeasurementsPayload struct { + Strains *models.Strains `json:"strains"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` +} + +func (m *MeasurementPayload) Marshal() ([]byte, error) { + return json.Marshal(m) +} + +func (m *MeasurementsPayload) Marshal() ([]byte, error) { + return json.Marshal(m) +} + +type SpeciesPayload struct { + Species *models.Species `json:"species"` + Strains *models.Strains `json:"strains"` + Meta *models.SpeciesMeta `json:"meta"` +} + +type ManySpeciesPayload struct { + Species *models.ManySpecies `json:"species"` + Strains *models.Strains `json:"strains"` + Meta *models.SpeciesMeta `json:"meta"` +} + +func (s *SpeciesPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +func (s *ManySpeciesPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +type StrainPayload struct { + Strain *models.Strain `json:"strain"` + Species *models.ManySpecies `json:"species"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Meta *models.StrainMeta `json:"meta"` +} + +type StrainsPayload struct { + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Meta *models.StrainMeta `json:"meta"` +} + +func (s *StrainPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +func (s *StrainsPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +type UserPayload struct { + User *models.User `json:"user"` + Meta *models.UserMeta `json:"meta"` +} + +func (u *UserPayload) Marshal() ([]byte, error) { + return json.Marshal(u) +} diff --git a/species.go b/species.go deleted file mode 100644 index 3aa0f45..0000000 --- a/species.go +++ /dev/null @@ -1,330 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" -) - -var ( - ErrSpeciesNotFound = errors.New("Species not found") - ErrSpeciesNotUpdated = errors.New("Species not updated") -) - -func init() { - DB.AddTableWithName(SpeciesBase{}, "species").SetKeys(true, "Id") -} - -func (s *SpeciesBase) PreInsert(e modl.SqlExecutor) error { - ct := currentTime() - s.CreatedAt = ct - s.UpdatedAt = ct - return nil -} - -func (s *SpeciesBase) PreUpdate(e modl.SqlExecutor) error { - s.UpdatedAt = currentTime() - return nil -} - -type SpeciesService struct{} - -type SpeciesBase struct { - Id int64 `db:"id" json:"id"` - GenusID int64 `db:"genus_id" json:"-"` - SubspeciesSpeciesID NullInt64 `db:"subspecies_species_id" json:"-"` - SpeciesName string `db:"species_name" json:"speciesName"` - TypeSpecies NullBool `db:"type_species" json:"typeSpecies"` - Etymology NullString `db:"etymology" json:"etymology"` - CreatedAt NullTime `db:"created_at" json:"createdAt"` - UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` - DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` - CreatedBy int64 `db:"created_by" json:"createdBy"` - UpdatedBy int64 `db:"updated_by" json:"updatedBy"` - DeletedBy NullInt64 `db:"deleted_by" json:"deletedBy"` -} - -type Species struct { - *SpeciesBase - GenusName string `db:"genus_name" json:"genusName"` - Strains NullSliceInt64 `db:"strains" json:"strains"` - TotalStrains int64 `db:"total_strains" json:"totalStrains"` - SortOrder int64 `db:"sort_order" json:"sortOrder"` - CanEdit bool `db:"-" json:"canEdit"` -} - -type ManySpecies []*Species - -type SpeciesMeta struct { - CanAdd bool `json:"canAdd"` -} - -type SpeciesPayload struct { - Species *Species `json:"species"` - Strains *Strains `json:"strains"` - Meta *SpeciesMeta `json:"meta"` -} - -type ManySpeciesPayload struct { - Species *ManySpecies `json:"species"` - Strains *Strains `json:"strains"` - Meta *SpeciesMeta `json:"meta"` -} - -func (s *SpeciesPayload) marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s *ManySpeciesPayload) marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s SpeciesService) unmarshal(b []byte) (entity, error) { - var sj SpeciesPayload - err := json.Unmarshal(b, &sj) - return &sj, err -} - -func (s SpeciesService) list(val *url.Values, claims *Claims) (entity, *appError) { - if val == nil { - return nil, ErrMustProvideOptionsJSON - } - var opt ListOptions - if err := schemaDecoder.Decode(&opt, *val); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species, err := listSpecies(opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains_opt, err := strainOptsFromSpecies(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, err := listStrains(*strains_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := ManySpeciesPayload{ - Species: species, - Strains: strains, - Meta: &SpeciesMeta{ - CanAdd: canAdd(claims), - }, - } - - return &payload, nil -} - -func (s SpeciesService) get(id int64, genus string, claims *Claims) (entity, *appError) { - species, err := getSpecies(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, err := strainsFromSpeciesId(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := SpeciesPayload{ - Species: species, - Strains: strains, - Meta: &SpeciesMeta{ - CanAdd: canAdd(claims), - }, - } - - return &payload, nil -} - -func (s SpeciesService) update(id int64, e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*SpeciesPayload) - payload.Species.UpdatedBy = claims.Sub - payload.Species.Id = id - - genus_id, err := genusIdFromName(genus) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - payload.Species.SpeciesBase.GenusID = genus_id - - count, err := DBH.Update(payload.Species.SpeciesBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return newJSONError(ErrSpeciesNotUpdated, http.StatusBadRequest) - } - - // Reload to send back down the wire - species, err := getSpecies(id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - strains, err := strainsFromSpeciesId(id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - payload.Species = species - payload.Strains = strains - payload.Meta = &SpeciesMeta{ - CanAdd: canAdd(claims), - } - - return nil -} - -func (s SpeciesService) create(e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*SpeciesPayload) - payload.Species.CreatedBy = claims.Sub - payload.Species.UpdatedBy = claims.Sub - - genus_id, err := genusIdFromName(genus) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - payload.Species.SpeciesBase.GenusID = genus_id - - err = DBH.Insert(payload.Species.SpeciesBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - // Reload to send back down the wire - species, err := getSpecies(payload.Species.Id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - // Note, no strains when new species - - payload.Species = species - payload.Meta = &SpeciesMeta{ - CanAdd: canAdd(claims), - } - return nil -} - -func genusIdFromName(genus_name string) (int64, error) { - var genus_id struct{ Id int64 } - q := `SELECT id FROM genera WHERE LOWER(genus_name) = LOWER($1);` - if err := DBH.SelectOne(&genus_id, q, genus_name); err != nil { - return 0, err - } - return genus_id.Id, nil -} - -func strainOptsFromSpecies(opt ListOptions) (*ListOptions, error) { - relatedStrainIds := make([]int64, 0) - - if opt.Ids == nil { - q := `SELECT DISTINCT st.id - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { - return nil, err - } - } else { - var vals []interface{} - var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT id FROM strains WHERE %s;", valsIn("species_id", opt.Ids, &vals, &count)) - - if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { - return nil, err - } - } - - return &ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil -} - -func strainsFromSpeciesId(id int64, genus string, claims *Claims) (*Strains, error) { - opt := ListOptions{ - Genus: genus, - Ids: []int64{id}, - } - - strains_opt, err := strainOptsFromSpecies(opt) - if err != nil { - return nil, err - } - - strains, err := listStrains(*strains_opt, claims) - if err != nil { - return nil, err - } - - return strains, nil -} - -func listSpecies(opt ListOptions, claims *Claims) (*ManySpecies, error) { - var vals []interface{} - - q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, - COUNT(st) AS total_strains, - rank() OVER (ORDER BY sp.species_name ASC) AS sort_order - FROM species sp - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - LEFT OUTER JOIN strains st ON st.species_id=sp.id` - vals = append(vals, opt.Genus) - - if len(opt.Ids) != 0 { - var conds []string - s := "sp.id IN (" - for i, id := range opt.Ids { - s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 - vals = append(vals, id) - } - s = s[:len(s)-1] + ")" - conds = append(conds, s) - q += " WHERE (" + strings.Join(conds, ") AND (") + ")" - } - - q += " GROUP BY sp.id, g.genus_name;" - - species := make(ManySpecies, 0) - err := DBH.Select(&species, q, vals...) - if err != nil { - return nil, err - } - - for _, s := range species { - s.CanEdit = canEdit(claims, s.CreatedBy) - } - - return &species, nil -} - -func getSpecies(id int64, genus string, claims *Claims) (*Species, error) { - var species Species - q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, - COUNT(st) AS total_strains, 0 AS sort_order - FROM species sp - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - LEFT OUTER JOIN strains st ON st.species_id=sp.id - WHERE sp.id=$2 - GROUP BY sp.id, g.genus_name;` - if err := DBH.SelectOne(&species, q, genus, id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrSpeciesNotFound - } - return nil, err - } - - species.CanEdit = canEdit(claims, species.CreatedBy) - - return &species, nil -} diff --git a/strains.go b/strains.go deleted file mode 100644 index 7851e7d..0000000 --- a/strains.go +++ /dev/null @@ -1,406 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" -) - -var ( - ErrStrainNotFound = errors.New("Strain not found") - ErrStrainNotUpdated = errors.New("Strain not updated") -) - -func init() { - DB.AddTableWithName(StrainBase{}, "strains").SetKeys(true, "Id") -} - -func (s *StrainBase) PreInsert(e modl.SqlExecutor) error { - ct := currentTime() - s.CreatedAt = ct - s.UpdatedAt = ct - return nil -} - -func (s *StrainBase) PreUpdate(e modl.SqlExecutor) error { - s.UpdatedAt = currentTime() - return nil -} - -type StrainService struct{} - -type StrainBase struct { - Id int64 `db:"id" json:"id"` - SpeciesId int64 `db:"species_id" json:"species"` - StrainName string `db:"strain_name" json:"strainName"` - TypeStrain bool `db:"type_strain" json:"typeStrain"` - AccessionNumbers NullString `db:"accession_numbers" json:"accessionNumbers"` - Genbank NullString `db:"genbank" json:"genbank"` - WholeGenomeSequence NullString `db:"whole_genome_sequence" json:"wholeGenomeSequence"` - IsolatedFrom NullString `db:"isolated_from" json:"isolatedFrom"` - Notes NullString `db:"notes" json:"notes"` - CreatedAt NullTime `db:"created_at" json:"createdAt"` - UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` - DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` - CreatedBy int64 `db:"created_by" json:"createdBy"` - UpdatedBy int64 `db:"updated_by" json:"updatedBy"` - DeletedBy NullInt64 `db:"deleted_by" json:"deletedBy"` -} - -type Strain struct { - *StrainBase - Measurements NullSliceInt64 `db:"measurements" json:"measurements"` - Characteristics NullSliceInt64 `db:"characteristics" json:"characteristics"` - TotalMeasurements int64 `db:"total_measurements" json:"totalMeasurements"` - SortOrder int64 `db:"sort_order" json:"sortOrder"` - CanEdit bool `db:"-" json:"canEdit"` -} - -type Strains []*Strain - -type StrainMeta struct { - CanAdd bool `json:"canAdd"` -} - -type StrainPayload struct { - Strain *Strain `json:"strain"` - Species *ManySpecies `json:"species"` - Characteristics *Characteristics `json:"characteristics"` - Measurements *Measurements `json:"measurements"` - Meta *StrainMeta `json:"meta"` -} - -type StrainsPayload struct { - Strains *Strains `json:"strains"` - Species *ManySpecies `json:"species"` - Characteristics *Characteristics `json:"characteristics"` - Measurements *Measurements `json:"measurements"` - Meta *StrainMeta `json:"meta"` -} - -func (s *StrainPayload) marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s *StrainsPayload) marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s StrainService) unmarshal(b []byte) (entity, error) { - var sj StrainPayload - err := json.Unmarshal(b, &sj) - return &sj, err -} - -func (s StrainBase) SpeciesName() string { - var species SpeciesBase - if err := DBH.Get(&species, s.SpeciesId); err != nil { - return "" - } - return species.SpeciesName -} - -func (s StrainService) list(val *url.Values, claims *Claims) (entity, *appError) { - if val == nil { - return nil, ErrMustProvideOptionsJSON - } - var opt ListOptions - if err := schemaDecoder.Decode(&opt, *val); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - strains, err := listStrains(opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species_opt, err := speciesOptsFromStrains(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species, err := listSpecies(*species_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristics_opt, err := characteristicsOptsFromStrains(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristics, err := listCharacteristics(*characteristics_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristic_ids := []int64{} - for _, c := range *characteristics { - characteristic_ids = append(characteristic_ids, c.Id) - } - - strain_ids := []int64{} - for _, s := range *strains { - strain_ids = append(strain_ids, s.Id) - } - - measurement_opt := MeasurementListOptions{ - ListOptions: ListOptions{ - Genus: opt.Genus, - }, - Strains: strain_ids, - Characteristics: characteristic_ids, - } - - measurements, err := listMeasurements(measurement_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - payload := StrainsPayload{ - Strains: strains, - Species: species, - Measurements: measurements, - Characteristics: characteristics, - Meta: &StrainMeta{ - CanAdd: canAdd(claims), - }, - } - - return &payload, nil -} - -func (s StrainService) get(id int64, genus string, claims *Claims) (entity, *appError) { - strain, err := getStrain(id, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - species, err := getSpecies(strain.SpeciesId, genus, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - opt := ListOptions{Genus: genus, Ids: []int64{id}} - characteristics_opt, err := characteristicsOptsFromStrains(opt) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristics, err := listCharacteristics(*characteristics_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - characteristic_ids := []int64{} - for _, c := range *characteristics { - characteristic_ids = append(characteristic_ids, c.Id) - } - - measurement_opt := MeasurementListOptions{ - ListOptions: ListOptions{ - Genus: genus, - }, - Strains: []int64{id}, - Characteristics: characteristic_ids, - } - - measurements, err := listMeasurements(measurement_opt, claims) - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - var many_species ManySpecies = []*Species{species} - - payload := StrainPayload{ - Strain: strain, - Species: &many_species, - Characteristics: characteristics, - Measurements: measurements, - Meta: &StrainMeta{ - CanAdd: canAdd(claims), - }, - } - - return &payload, nil -} - -func (s StrainService) update(id int64, e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*StrainPayload) - payload.Strain.UpdatedBy = claims.Sub - payload.Strain.Id = id - - count, err := DBH.Update(payload.Strain.StrainBase) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return newJSONError(ErrStrainNotUpdated, http.StatusBadRequest) - } - - strain, err := getStrain(id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - species, err := getSpecies(strain.SpeciesId, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - var many_species ManySpecies = []*Species{species} - - payload.Strain = strain - payload.Species = &many_species - payload.Meta = &StrainMeta{ - CanAdd: canAdd(claims), - } - - return nil -} - -func (s StrainService) create(e *entity, genus string, claims *Claims) *appError { - payload := (*e).(*StrainPayload) - payload.Strain.CreatedBy = claims.Sub - payload.Strain.UpdatedBy = claims.Sub - - if err := DBH.Insert(payload.Strain.StrainBase); err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - strain, err := getStrain(payload.Strain.Id, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - species, err := getSpecies(strain.SpeciesId, genus, claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - var many_species ManySpecies = []*Species{species} - - payload.Strain = strain - payload.Species = &many_species - payload.Meta = &StrainMeta{ - CanAdd: canAdd(claims), - } - - return nil -} - -func listStrains(opt ListOptions, claims *Claims) (*Strains, error) { - var vals []interface{} - - q := `SELECT st.*, array_agg(m.id) AS measurements, - array_agg(DISTINCT m.characteristic_id) AS characteristics, - COUNT(m) AS total_measurements, - rank() OVER (ORDER BY sp.species_name ASC, st.type_strain ASC, st.strain_name ASC) AS sort_order - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - LEFT OUTER JOIN measurements m ON m.strain_id=st.id` - vals = append(vals, opt.Genus) - - if len(opt.Ids) != 0 { - var conds []string - s := "st.id IN (" - for i, id := range opt.Ids { - s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 - vals = append(vals, id) - } - s = s[:len(s)-1] + ")" - conds = append(conds, s) - q += " WHERE (" + strings.Join(conds, ") AND (") + ")" - } - - q += " GROUP BY st.id, st.species_id, sp.species_name;" - - strains := make(Strains, 0) - err := DBH.Select(&strains, q, vals...) - if err != nil { - return nil, err - } - - for _, s := range strains { - s.CanEdit = canEdit(claims, s.CreatedBy) - } - - return &strains, nil -} - -func getStrain(id int64, genus string, claims *Claims) (*Strain, error) { - var strain Strain - q := `SELECT st.*, array_agg(DISTINCT m.id) AS measurements, - array_agg(DISTINCT m.characteristic_id) AS characteristics, - COUNT(m) AS total_measurements, 0 AS sort_order - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1) - LEFT OUTER JOIN measurements m ON m.strain_id=st.id - WHERE st.id=$2 - GROUP BY st.id;` - if err := DBH.SelectOne(&strain, q, genus, id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrStrainNotFound - } - return nil, err - } - - strain.CanEdit = canEdit(claims, strain.CreatedBy) - - return &strain, nil -} - -func speciesOptsFromStrains(opt ListOptions) (*ListOptions, error) { - relatedSpeciesIds := make([]int64, 0) - - if opt.Ids == nil || len(opt.Ids) == 0 { - q := `SELECT DISTINCT st.species_id - FROM strains st - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedSpeciesIds, q, opt.Genus); err != nil { - return nil, err - } - } else { - var vals []interface{} - var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT species_id FROM strains WHERE %s;", valsIn("id", opt.Ids, &vals, &count)) - if err := DBH.Select(&relatedSpeciesIds, q, vals...); err != nil { - return nil, err - } - } - - return &ListOptions{Genus: opt.Genus, Ids: relatedSpeciesIds}, nil -} - -func characteristicsOptsFromStrains(opt ListOptions) (*ListOptions, error) { - relatedCharacteristicsIds := make([]int64, 0) - - if opt.Ids == nil || len(opt.Ids) == 0 { - q := `SELECT DISTINCT m.characteristic_id - FROM measurements m - INNER JOIN strains st ON st.id=m.strain_id - INNER JOIN species sp ON sp.id=st.species_id - INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedCharacteristicsIds, q, opt.Genus); err != nil { - return nil, err - } - } else { - var vals []interface{} - var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT characteristic_id FROM measurements WHERE %s;", valsIn("strain_id", opt.Ids, &vals, &count)) - if err := DBH.Select(&relatedCharacteristicsIds, q, vals...); err != nil { - return nil, err - } - } - - return &ListOptions{Genus: opt.Genus, Ids: relatedCharacteristicsIds}, nil -} diff --git a/types/claims.go b/types/claims.go new file mode 100644 index 0000000..86c8a74 --- /dev/null +++ b/types/claims.go @@ -0,0 +1,11 @@ +package types + +type Claims struct { + Name string + Iss string + Sub int64 + Role string + Iat int64 + Exp int64 + Ref string +} diff --git a/types/entities.go b/types/entities.go new file mode 100644 index 0000000..5e99b99 --- /dev/null +++ b/types/entities.go @@ -0,0 +1,5 @@ +package types + +type Entity interface { + Marshal() ([]byte, error) +} diff --git a/types.go b/types/types.go similarity index 97% rename from types.go rename to types/types.go index 7e022eb..ab2a417 100644 --- a/types.go +++ b/types/types.go @@ -1,4 +1,4 @@ -package main +package types import ( "bytes" @@ -198,13 +198,13 @@ func (ej ErrorJSON) Error() string { return string(e) } -type appError struct { +type AppError struct { Error error Status int } -func newJSONError(err error, status int) *appError { - return &appError{ +func NewJSONError(err error, status int) *AppError { + return &AppError{ Error: ErrorJSON{Err: err}, Status: status, } diff --git a/users.go b/users.go deleted file mode 100644 index db93a6a..0000000 --- a/users.go +++ /dev/null @@ -1,392 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "log" - "net/http" - "net/url" - "regexp" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/mailgun/mailgun-go" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt" -) - -var ( - ErrUserNotFound = errors.New("User not found") - ErrUserNotFoundJSON = newJSONError(ErrUserNotFound, http.StatusNotFound) - ErrUserNotUpdated = errors.New("User not updated") - ErrUserNotUpdatedJSON = newJSONError(ErrUserNotUpdated, http.StatusBadRequest) - ErrInvalidEmailOrPassword = errors.New("Invalid email or password") - ErrEmailAddressTaken = errors.New("Email address already registered") - ErrEmailAddressTakenJSON = newJSONError(ErrEmailAddressTaken, http.StatusBadRequest) -) - -func init() { - DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "Id") -} - -type UserService struct{} - -type UserBase struct { - Id int64 `json:"id,omitempty"` - Email string `db:"email" json:"email"` - Password string `db:"password" json:"password,omitempty"` - Name string `db:"name" json:"name"` - Role string `db:"role" json:"role"` - Verified bool `db:"verified" json:"-"` - CreatedAt NullTime `db:"created_at" json:"createdAt"` - UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` - DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` -} - -type User struct { - *UserBase - CanEdit bool `db:"-" json:"canEdit"` -} - -type UserValidation struct { - Email []string `json:"email,omitempty"` - Password []string `json:"password,omitempty"` - Name []string `json:"name,omitempty"` - Role []string `json:"role,omitempty"` -} - -func (uv UserValidation) Error() string { - errs, err := json.Marshal(struct { - UserValidation `json:"errors"` - }{uv}) - if err != nil { - return err.Error() - } - return string(errs) -} - -type Users []*User - -type UserJSON struct { - User *User `json:"user"` -} - -type UsersJSON struct { - Users *Users `json:"users"` -} - -type UserMeta struct { - CanAdd bool `json:"canAdd"` -} - -type UserPayload struct { - User *User `json:"user"` - Meta *UserMeta `json:"meta"` -} - -func (u *UserPayload) marshal() ([]byte, error) { - return json.Marshal(u) -} - -func (u *Users) marshal() ([]byte, error) { - return json.Marshal(&UsersJSON{Users: u}) -} - -func (u UserService) unmarshal(b []byte) (entity, error) { - var uj UserPayload - err := json.Unmarshal(b, &uj) - return &uj, err -} - -func (u *User) validate() error { - var uv UserValidation - validationError := false - - if u.Name == "" { - uv.Name = append(uv.Name, MustProvideAValue) - validationError = true - } - - if u.Email == "" { - uv.Email = append(uv.Email, MustProvideAValue) - validationError = true - } - - regex, _ := regexp.Compile(`(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})`) - if u.Email != "" && !regex.MatchString(u.Email) { - uv.Email = append(uv.Email, "Must provide a valid email address") - validationError = true - } - - if len(u.Password) < 8 { - uv.Password = append(uv.Password, "Password must be at least 8 characters") - validationError = true - } - - if validationError { - return uv - } - return nil -} - -func (u UserService) list(val *url.Values, claims *Claims) (entity, *appError) { - if val == nil { - return nil, ErrMustProvideOptionsJSON - } - var opt ListOptions - if err := schemaDecoder.Decode(&opt, *val); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - users := make(Users, 0) - sql := `SELECT id, email, 'password' AS password, name, role, - created_at, updated_at, deleted_at - FROM users - WHERE verified IS TRUE - AND deleted_at IS NULL;` - if err := DBH.Select(&users, sql); err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - return &users, nil -} - -func (u UserService) get(id int64, dummy string, claims *Claims) (entity, *appError) { - user, err := dbGetUserById(id) - user.Password = "" - if err != nil { - return nil, newJSONError(err, http.StatusInternalServerError) - } - - user.CanEdit = claims.Role == "A" || id == claims.Sub - - payload := UserPayload{ - User: user, - Meta: &UserMeta{ - CanAdd: claims.Role == "A", - }, - } - return &payload, nil -} - -func (u UserService) update(id int64, e *entity, dummy string, claims *Claims) *appError { - user := (*e).(*UserPayload).User - - original_user, err := dbGetUserById(id) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - user.Id = id - user.Password = original_user.Password - user.Verified = original_user.Verified - user.UpdatedAt = currentTime() - - if err := user.validate(); err != nil { - return &appError{Error: err, Status: StatusUnprocessableEntity} - } - - count, err := DBH.Update(user) - user.Password = "" - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return ErrUserNotUpdatedJSON - } - - return nil -} - -func (u UserService) create(e *entity, dummy string, claims *Claims) *appError { - user := (*e).(*UserPayload).User - if err := user.validate(); err != nil { - return &appError{Error: err, Status: StatusUnprocessableEntity} - } - ct := currentTime() - user.CreatedAt = ct - user.UpdatedAt = ct - hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - user.Password = string(hash) - user.Role = "R" - user.Verified = false - - if err := DBH.Insert(user); err != nil { - if err, ok := err.(*pq.Error); ok { - if err.Code == "23505" { - return ErrEmailAddressTakenJSON - } - } - return newJSONError(err, http.StatusInternalServerError) - } - - user.Password = "password" // don't want to send the hashed PW back to the client - - q := `INSERT INTO verification (user_id, nonce, referer, created_at) VALUES ($1, $2, $3, $4);` - nonce, err := generateNonce() - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - _, err = DBH.Exec(q, user.Id, nonce, claims.Ref, ct) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - // Send out confirmation email - mg, ok := mgAccts[claims.Ref] - if ok { - sender := fmt.Sprintf("%s Admin ", mg.Domain(), mg.Domain()) - recipient := fmt.Sprintf("%s <%s>", user.Name, user.Email) - subject := fmt.Sprintf("New Account Confirmation - %s", mg.Domain()) - message := fmt.Sprintf("You are receiving this message because this email "+ - "address was used to sign up for an account at %s. Please visit this "+ - "URL to complete the sign up process: %s/users/new/verify/%s. If you "+ - "did not request an account, please disregard this message.", - mg.Domain(), claims.Ref, nonce) - m := mailgun.NewMessage(sender, subject, message, recipient) - _, _, err := mg.Send(m) - if err != nil { - log.Printf("%+v\n", err) - return newJSONError(err, http.StatusInternalServerError) - } - } - - return nil -} - -// for thermokarst/jwt: authentication callback -func dbAuthenticate(email string, password string) error { - var user User - q := `SELECT * - FROM users - WHERE lower(email)=lower($1) - AND verified IS TRUE - AND deleted_at IS NULL;` - if err := DBH.SelectOne(&user, q, email); err != nil { - return ErrInvalidEmailOrPassword - } - if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - return ErrInvalidEmailOrPassword - } - return nil -} - -func dbGetUserById(id int64) (*User, error) { - var user User - q := `SELECT * - FROM users - WHERE id=$1 - AND verified IS TRUE - AND deleted_at IS NULL;` - if err := DBH.SelectOne(&user, q, id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrUserNotFound - } - return nil, err - } - return &user, nil -} - -// for thermokarst/jwt: setting user in claims bundle -func dbGetUserByEmail(email string) (*User, error) { - var user User - q := `SELECT * - FROM users - WHERE lower(email)=lower($1) - AND verified IS TRUE - AND deleted_at IS NULL;` - if err := DBH.SelectOne(&user, q, email); err != nil { - if err == sql.ErrNoRows { - return nil, ErrUserNotFound - } - return nil, err - } - return &user, nil -} - -func handleUserVerify(w http.ResponseWriter, r *http.Request) *appError { - nonce := mux.Vars(r)["Nonce"] - q := `SELECT user_id, referer FROM verification WHERE nonce=$1;` - - var ver struct { - User_id int64 - Referer string - } - if err := DBH.SelectOne(&ver, q, nonce); err != nil { - log.Print(err) - return newJSONError(err, http.StatusInternalServerError) - } - - if ver.User_id == 0 { - return newJSONError(errors.New("No user found"), http.StatusInternalServerError) - } - - var user User - if err := DBH.Get(&user, ver.User_id); err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - user.UpdatedAt = currentTime() - user.Verified = true - - count, err := DBH.Update(&user) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - if count != 1 { - return newJSONError(errors.New("Count 0"), http.StatusInternalServerError) - } - - q = `DELETE FROM verification WHERE user_id=$1;` - _, err = DBH.Exec(q, user.Id) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - fmt.Fprintln(w, `{"msg":"All set! Please log in."}`) - return nil -} - -func handleUserLockout(w http.ResponseWriter, r *http.Request) *appError { - email := r.FormValue("email") - if email == "" { - return newJSONError(errors.New("missing email"), http.StatusInternalServerError) - } - token, err := j.CreateToken(email) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - origin := r.Header.Get("Origin") - hostUrl, err := url.Parse(origin) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - hostUrl.Path += "/users/lockoutauthenticate" - params := url.Values{} - params.Add("token", token) - hostUrl.RawQuery = params.Encode() - - // Send out email - mg, ok := mgAccts[origin] - if ok { - sender := fmt.Sprintf("%s Admin ", mg.Domain(), mg.Domain()) - recipient := fmt.Sprintf("%s", email) - subject := fmt.Sprintf("Password Reset Request - %s", mg.Domain()) - message := fmt.Sprintf("You are receiving this message because this email "+ - "address was used in an account lockout request at %s. Please visit "+ - "this URL to complete the process: %s. If you did not request help "+ - "with a lockout, please disregard this message.", - mg.Domain(), hostUrl.String()) - m := mailgun.NewMessage(sender, subject, message, recipient) - _, _, err := mg.Send(m) - if err != nil { - log.Printf("%+v\n", err) - return newJSONError(err, http.StatusInternalServerError) - } - } - - fmt.Fprintln(w, `{}`) - return nil -} From 1bff626805facc9d7db83c47a9736ec4be9bc34f Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:13:02 -0700 Subject: [PATCH 03/10] Not needed anymore --- session.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 session.json diff --git a/session.json b/session.json deleted file mode 100644 index 9d0d8df..0000000 --- a/session.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "__meta__": { - "about": "HTTPie session file", - "help": "https://github.com/jakubroztocil/httpie#sessions", - "httpie": "0.9.1" - }, - "auth": { - "password": null, - "type": null, - "username": null - }, - "cookies": {}, - "headers": { - "Accept": "application/json", - "Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Mjc1NzAyNDIsImdlbnVzIjoiaHltZW5vYmFjdGVyIiwiaWF0IjoxNDI3NDgzODQyLCJpc3MiOiJiYWN0ZGIiLCJuYW1lIjoidGVzdCIsInJvbGUiOiJhZG1pbiIsInN1YiI6InVzZXJAZXhhbXBsZS5jb20ifQ.PTQsRsWz9R2R8gNhrtmyRw9irJVMbUGjuIj-PRIk_Ht-tBj36cgnc9VSOwUZX-_UUKnF6A6HQqkcQ9AQf4GKfQzU3RMXNxw7afjhM1c4_D4zK6V7Q3JIpVGxJQ7kKalcP4hxmrpZCMJveCPVS_Wbz6H2bhqOsf4peWBWUTuoOQU" - } -} From 4f3b4ec4bfb875411dc3eda0ef58ed7a17673332 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:18:17 -0700 Subject: [PATCH 04/10] Split up types --- types/{entities.go => entity.go} | 0 types/error_json.go | 28 ++++ types/null_bool.go | 37 ++++++ types/null_float64.go | 37 ++++++ types/null_int64.go | 37 ++++++ types/null_slice_int64.go | 31 +++++ types/null_string.go | 37 ++++++ types/null_time.go | 38 ++++++ types/types.go | 211 ------------------------------- 9 files changed, 245 insertions(+), 211 deletions(-) rename types/{entities.go => entity.go} (100%) create mode 100644 types/error_json.go create mode 100644 types/null_bool.go create mode 100644 types/null_float64.go create mode 100644 types/null_int64.go create mode 100644 types/null_slice_int64.go create mode 100644 types/null_string.go create mode 100644 types/null_time.go delete mode 100644 types/types.go diff --git a/types/entities.go b/types/entity.go similarity index 100% rename from types/entities.go rename to types/entity.go diff --git a/types/error_json.go b/types/error_json.go new file mode 100644 index 0000000..45e4f9f --- /dev/null +++ b/types/error_json.go @@ -0,0 +1,28 @@ +package types + +import "encoding/json" + +type ErrorJSON struct { + Err error +} + +func (ej ErrorJSON) Error() string { + e, _ := json.Marshal(struct { + Err string `json:"error"` + }{ + Err: ej.Err.Error(), + }) + return string(e) +} + +type AppError struct { + Error error + Status int +} + +func NewJSONError(err error, status int) *AppError { + return &AppError{ + Error: ErrorJSON{Err: err}, + Status: status, + } +} diff --git a/types/null_bool.go b/types/null_bool.go new file mode 100644 index 0000000..77cf8aa --- /dev/null +++ b/types/null_bool.go @@ -0,0 +1,37 @@ +package types + +import ( + "bytes" + "database/sql" + "encoding/json" +) + +type NullBool struct { + sql.NullBool +} + +func (b *NullBool) MarshalJSON() ([]byte, error) { + if !b.Valid { + return []byte("null"), nil + } + return json.Marshal(b.Bool) +} + +func (n *NullBool) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + n.Bool = false + n.Valid = false + return nil + } + var x interface{} + var err error + json.Unmarshal(b, &x) + switch x.(type) { + case bool: + err = json.Unmarshal(b, &n.Bool) + case map[string]interface{}: + err = json.Unmarshal(b, &n.NullBool) + } + n.Valid = true + return err +} diff --git a/types/null_float64.go b/types/null_float64.go new file mode 100644 index 0000000..7e1d4ae --- /dev/null +++ b/types/null_float64.go @@ -0,0 +1,37 @@ +package types + +import ( + "bytes" + "database/sql" + "encoding/json" +) + +type NullFloat64 struct { + sql.NullFloat64 +} + +func (f *NullFloat64) MarshalJSON() ([]byte, error) { + if !f.Valid { + return []byte("null"), nil + } + return json.Marshal(f.Float64) +} + +func (f *NullFloat64) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + f.Float64 = 0 + f.Valid = false + return nil + } + var x interface{} + var err error + json.Unmarshal(b, &x) + switch x.(type) { + case float64: + err = json.Unmarshal(b, &f.Float64) + case map[string]interface{}: + err = json.Unmarshal(b, &f.NullFloat64) + } + f.Valid = true + return err +} diff --git a/types/null_int64.go b/types/null_int64.go new file mode 100644 index 0000000..2e4cf5f --- /dev/null +++ b/types/null_int64.go @@ -0,0 +1,37 @@ +package types + +import ( + "bytes" + "database/sql" + "encoding/json" +) + +type NullInt64 struct { + sql.NullInt64 +} + +func (i *NullInt64) MarshalJSON() ([]byte, error) { + if !i.Valid { + return []byte("null"), nil + } + return json.Marshal(i.Int64) +} + +func (i *NullInt64) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + i.Int64 = 0 + i.Valid = false + return nil + } + var x interface{} + var err error + json.Unmarshal(b, &x) + switch x.(type) { + case float64: + err = json.Unmarshal(b, &i.Int64) + case map[string]interface{}: + err = json.Unmarshal(b, &i.NullInt64) + } + i.Valid = true + return err +} diff --git a/types/null_slice_int64.go b/types/null_slice_int64.go new file mode 100644 index 0000000..fb1ce87 --- /dev/null +++ b/types/null_slice_int64.go @@ -0,0 +1,31 @@ +package types + +import ( + "errors" + "strconv" + "strings" +) + +type NullSliceInt64 []int64 + +func (i *NullSliceInt64) Scan(src interface{}) error { + asBytes, ok := src.([]byte) + if !ok { + return errors.New("Scan source was not []byte") + } + asString := string(asBytes) + (*i) = strToIntSlice(asString) + return nil +} + +func strToIntSlice(s string) []int64 { + r := strings.Trim(s, "{}") + a := []int64(nil) + for _, t := range strings.Split(r, ",") { + if t != "NULL" { + i, _ := strconv.ParseInt(t, 10, 64) + a = append(a, i) + } + } + return a +} diff --git a/types/null_string.go b/types/null_string.go new file mode 100644 index 0000000..c373436 --- /dev/null +++ b/types/null_string.go @@ -0,0 +1,37 @@ +package types + +import ( + "bytes" + "database/sql" + "encoding/json" +) + +type NullString struct { + sql.NullString +} + +func (s *NullString) MarshalJSON() ([]byte, error) { + if !s.Valid { + return []byte("null"), nil + } + return json.Marshal(s.String) +} + +func (s *NullString) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + s.String = "" + s.Valid = false + return nil + } + var x interface{} + var err error + json.Unmarshal(b, &x) + switch x.(type) { + case string: + err = json.Unmarshal(b, &s.String) + case map[string]interface{}: + err = json.Unmarshal(b, &s.NullString) + } + s.Valid = true + return err +} diff --git a/types/null_time.go b/types/null_time.go new file mode 100644 index 0000000..a0cfbb4 --- /dev/null +++ b/types/null_time.go @@ -0,0 +1,38 @@ +package types + +import ( + "bytes" + "encoding/json" + "time" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" +) + +type NullTime struct { + pq.NullTime +} + +func (t *NullTime) MarshalJSON() ([]byte, error) { + if !t.Valid { + return []byte("null"), nil + } + return json.Marshal(t.Time) +} + +func (t *NullTime) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + var nt time.Time + t.Time = nt.In(time.UTC) + t.Valid = false + return nil + } + var x interface{} + var err error + json.Unmarshal(b, &x) + switch x.(type) { + case string: + err = json.Unmarshal(b, &t.Time) + } + t.Valid = true + return err +} diff --git a/types/types.go b/types/types.go deleted file mode 100644 index ab2a417..0000000 --- a/types/types.go +++ /dev/null @@ -1,211 +0,0 @@ -package types - -import ( - "bytes" - "database/sql" - "encoding/json" - "errors" - "strconv" - "strings" - "time" - - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" -) - -type NullBool struct { - sql.NullBool -} - -func (b *NullBool) MarshalJSON() ([]byte, error) { - if !b.Valid { - return []byte("null"), nil - } - return json.Marshal(b.Bool) -} - -func (n *NullBool) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - n.Bool = false - n.Valid = false - return nil - } - var x interface{} - var err error - json.Unmarshal(b, &x) - switch x.(type) { - case bool: - err = json.Unmarshal(b, &n.Bool) - case map[string]interface{}: - err = json.Unmarshal(b, &n.NullBool) - } - n.Valid = true - return err -} - -type NullString struct { - sql.NullString -} - -func (s *NullString) MarshalJSON() ([]byte, error) { - if !s.Valid { - return []byte("null"), nil - } - return json.Marshal(s.String) -} - -func (s *NullString) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - s.String = "" - s.Valid = false - return nil - } - var x interface{} - var err error - json.Unmarshal(b, &x) - switch x.(type) { - case string: - err = json.Unmarshal(b, &s.String) - case map[string]interface{}: - err = json.Unmarshal(b, &s.NullString) - } - s.Valid = true - return err -} - -type NullInt64 struct { - sql.NullInt64 -} - -func (i *NullInt64) MarshalJSON() ([]byte, error) { - if !i.Valid { - return []byte("null"), nil - } - return json.Marshal(i.Int64) -} - -func (i *NullInt64) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - i.Int64 = 0 - i.Valid = false - return nil - } - var x interface{} - var err error - json.Unmarshal(b, &x) - switch x.(type) { - case float64: - err = json.Unmarshal(b, &i.Int64) - case map[string]interface{}: - err = json.Unmarshal(b, &i.NullInt64) - } - i.Valid = true - return err -} - -type NullFloat64 struct { - sql.NullFloat64 -} - -func (f *NullFloat64) MarshalJSON() ([]byte, error) { - if !f.Valid { - return []byte("null"), nil - } - return json.Marshal(f.Float64) -} - -func (f *NullFloat64) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - f.Float64 = 0 - f.Valid = false - return nil - } - var x interface{} - var err error - json.Unmarshal(b, &x) - switch x.(type) { - case float64: - err = json.Unmarshal(b, &f.Float64) - case map[string]interface{}: - err = json.Unmarshal(b, &f.NullFloat64) - } - f.Valid = true - return err -} - -type NullTime struct { - pq.NullTime -} - -func (t *NullTime) MarshalJSON() ([]byte, error) { - if !t.Valid { - return []byte("null"), nil - } - return json.Marshal(t.Time) -} - -func (t *NullTime) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - var nt time.Time - t.Time = nt.In(time.UTC) - t.Valid = false - return nil - } - var x interface{} - var err error - json.Unmarshal(b, &x) - switch x.(type) { - case string: - err = json.Unmarshal(b, &t.Time) - } - t.Valid = true - return err -} - -type NullSliceInt64 []int64 - -func (i *NullSliceInt64) Scan(src interface{}) error { - asBytes, ok := src.([]byte) - if !ok { - return errors.New("Scan source was not []byte") - } - asString := string(asBytes) - (*i) = strToIntSlice(asString) - return nil -} - -func strToIntSlice(s string) []int64 { - r := strings.Trim(s, "{}") - a := []int64(nil) - for _, t := range strings.Split(r, ",") { - if t != "NULL" { - i, _ := strconv.ParseInt(t, 10, 64) - a = append(a, i) - } - } - return a -} - -type ErrorJSON struct { - Err error -} - -func (ej ErrorJSON) Error() string { - e, _ := json.Marshal(struct { - Err string `json:"error"` - }{ - Err: ej.Err.Error(), - }) - return string(e) -} - -type AppError struct { - Error error - Status int -} - -func NewJSONError(err error, status int) *AppError { - return &AppError{ - Error: ErrorJSON{Err: err}, - Status: status, - } -} From da523e016026e8951759248cf0f8b0043e1d2f01 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:20:43 -0700 Subject: [PATCH 05/10] Split up payloads --- payloads/characteristics.go | 31 +++++++++++ payloads/measurements.go | 25 +++++++++ payloads/payloads.go | 102 ------------------------------------ payloads/species.go | 27 ++++++++++ payloads/strains.go | 31 +++++++++++ payloads/users.go | 16 ++++++ 6 files changed, 130 insertions(+), 102 deletions(-) create mode 100644 payloads/characteristics.go create mode 100644 payloads/measurements.go delete mode 100644 payloads/payloads.go create mode 100644 payloads/species.go create mode 100644 payloads/strains.go create mode 100644 payloads/users.go diff --git a/payloads/characteristics.go b/payloads/characteristics.go new file mode 100644 index 0000000..8f89201 --- /dev/null +++ b/payloads/characteristics.go @@ -0,0 +1,31 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type CharacteristicPayload struct { + Characteristic *models.Characteristic `json:"characteristic"` + Measurements *models.Measurements `json:"measurements"` + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Meta *models.CharacteristicMeta `json:"meta"` +} + +type CharacteristicsPayload struct { + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Meta *models.CharacteristicMeta `json:"meta"` +} + +func (c *CharacteristicPayload) Marshal() ([]byte, error) { + return json.Marshal(c) +} + +func (c *CharacteristicsPayload) Marshal() ([]byte, error) { + return json.Marshal(c) +} diff --git a/payloads/measurements.go b/payloads/measurements.go new file mode 100644 index 0000000..c35db68 --- /dev/null +++ b/payloads/measurements.go @@ -0,0 +1,25 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type MeasurementPayload struct { + Measurement *models.Measurement `json:"measurement"` +} + +type MeasurementsPayload struct { + Strains *models.Strains `json:"strains"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` +} + +func (m *MeasurementPayload) Marshal() ([]byte, error) { + return json.Marshal(m) +} + +func (m *MeasurementsPayload) Marshal() ([]byte, error) { + return json.Marshal(m) +} diff --git a/payloads/payloads.go b/payloads/payloads.go deleted file mode 100644 index cabdd1c..0000000 --- a/payloads/payloads.go +++ /dev/null @@ -1,102 +0,0 @@ -package payloads - -import ( - "encoding/json" - - "github.com/thermokarst/bactdb/models" -) - -type CharacteristicPayload struct { - Characteristic *models.Characteristic `json:"characteristic"` - Measurements *models.Measurements `json:"measurements"` - Strains *models.Strains `json:"strains"` - Species *models.ManySpecies `json:"species"` - Meta *models.CharacteristicMeta `json:"meta"` -} - -type CharacteristicsPayload struct { - Characteristics *models.Characteristics `json:"characteristics"` - Measurements *models.Measurements `json:"measurements"` - Strains *models.Strains `json:"strains"` - Species *models.ManySpecies `json:"species"` - Meta *models.CharacteristicMeta `json:"meta"` -} - -func (c *CharacteristicPayload) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c *CharacteristicsPayload) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -type MeasurementPayload struct { - Measurement *models.Measurement `json:"measurement"` -} - -type MeasurementsPayload struct { - Strains *models.Strains `json:"strains"` - Characteristics *models.Characteristics `json:"characteristics"` - Measurements *models.Measurements `json:"measurements"` -} - -func (m *MeasurementPayload) Marshal() ([]byte, error) { - return json.Marshal(m) -} - -func (m *MeasurementsPayload) Marshal() ([]byte, error) { - return json.Marshal(m) -} - -type SpeciesPayload struct { - Species *models.Species `json:"species"` - Strains *models.Strains `json:"strains"` - Meta *models.SpeciesMeta `json:"meta"` -} - -type ManySpeciesPayload struct { - Species *models.ManySpecies `json:"species"` - Strains *models.Strains `json:"strains"` - Meta *models.SpeciesMeta `json:"meta"` -} - -func (s *SpeciesPayload) Marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s *ManySpeciesPayload) Marshal() ([]byte, error) { - return json.Marshal(s) -} - -type StrainPayload struct { - Strain *models.Strain `json:"strain"` - Species *models.ManySpecies `json:"species"` - Characteristics *models.Characteristics `json:"characteristics"` - Measurements *models.Measurements `json:"measurements"` - Meta *models.StrainMeta `json:"meta"` -} - -type StrainsPayload struct { - Strains *models.Strains `json:"strains"` - Species *models.ManySpecies `json:"species"` - Characteristics *models.Characteristics `json:"characteristics"` - Measurements *models.Measurements `json:"measurements"` - Meta *models.StrainMeta `json:"meta"` -} - -func (s *StrainPayload) Marshal() ([]byte, error) { - return json.Marshal(s) -} - -func (s *StrainsPayload) Marshal() ([]byte, error) { - return json.Marshal(s) -} - -type UserPayload struct { - User *models.User `json:"user"` - Meta *models.UserMeta `json:"meta"` -} - -func (u *UserPayload) Marshal() ([]byte, error) { - return json.Marshal(u) -} diff --git a/payloads/species.go b/payloads/species.go new file mode 100644 index 0000000..39cbbc0 --- /dev/null +++ b/payloads/species.go @@ -0,0 +1,27 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type SpeciesPayload struct { + Species *models.Species `json:"species"` + Strains *models.Strains `json:"strains"` + Meta *models.SpeciesMeta `json:"meta"` +} + +type ManySpeciesPayload struct { + Species *models.ManySpecies `json:"species"` + Strains *models.Strains `json:"strains"` + Meta *models.SpeciesMeta `json:"meta"` +} + +func (s *SpeciesPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +func (s *ManySpeciesPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} diff --git a/payloads/strains.go b/payloads/strains.go new file mode 100644 index 0000000..bb0ec8d --- /dev/null +++ b/payloads/strains.go @@ -0,0 +1,31 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type StrainPayload struct { + Strain *models.Strain `json:"strain"` + Species *models.ManySpecies `json:"species"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Meta *models.StrainMeta `json:"meta"` +} + +type StrainsPayload struct { + Strains *models.Strains `json:"strains"` + Species *models.ManySpecies `json:"species"` + Characteristics *models.Characteristics `json:"characteristics"` + Measurements *models.Measurements `json:"measurements"` + Meta *models.StrainMeta `json:"meta"` +} + +func (s *StrainPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +func (s *StrainsPayload) Marshal() ([]byte, error) { + return json.Marshal(s) +} diff --git a/payloads/users.go b/payloads/users.go new file mode 100644 index 0000000..5329f25 --- /dev/null +++ b/payloads/users.go @@ -0,0 +1,16 @@ +package payloads + +import ( + "encoding/json" + + "github.com/thermokarst/bactdb/models" +) + +type UserPayload struct { + User *models.User `json:"user"` + Meta *models.UserMeta `json:"meta"` +} + +func (u *UserPayload) Marshal() ([]byte, error) { + return json.Marshal(u) +} From 419ef7b5cf54a6bfb06af93deef7d10e223966e6 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:25:08 -0700 Subject: [PATCH 06/10] Payload stutter --- api/characteristics.go | 10 +++++----- api/compare.go | 2 +- api/measurements.go | 10 +++++----- api/species.go | 10 +++++----- api/strains.go | 10 +++++----- api/users.go | 8 ++++---- payloads/characteristics.go | 8 ++++---- payloads/measurements.go | 8 ++++---- payloads/species.go | 8 ++++---- payloads/strains.go | 8 ++++---- payloads/users.go | 4 ++-- 11 files changed, 43 insertions(+), 43 deletions(-) diff --git a/api/characteristics.go b/api/characteristics.go index 275c9df..8727a00 100644 --- a/api/characteristics.go +++ b/api/characteristics.go @@ -14,7 +14,7 @@ import ( type CharacteristicService struct{} func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) { - var cj payloads.CharacteristicPayload + var cj payloads.Characteristic err := json.Unmarshal(b, &cj) return &cj, err } @@ -63,7 +63,7 @@ func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (type return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.CharacteristicsPayload{ + payload := payloads.Characteristics{ Characteristics: characteristics, Measurements: measurements, Strains: strains, @@ -102,7 +102,7 @@ func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.CharacteristicPayload{ + payload := payloads.Characteristic{ Characteristic: characteristic, Measurements: measurements, Strains: strains, @@ -113,7 +113,7 @@ func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) } func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.CharacteristicPayload) + payload := (*e).(*payloads.Characteristic) payload.Characteristic.UpdatedBy = claims.Sub payload.Characteristic.Id = id @@ -160,7 +160,7 @@ func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, c } func (c CharacteristicService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.CharacteristicPayload) + payload := (*e).(*payloads.Characteristic) payload.Characteristic.CreatedBy = claims.Sub payload.Characteristic.UpdatedBy = claims.Sub diff --git a/api/compare.go b/api/compare.go index eb47acc..04ac122 100644 --- a/api/compare.go +++ b/api/compare.go @@ -40,7 +40,7 @@ func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { if appErr != nil { return appErr } - measurementsPayload := (measurementsEntity).(*payloads.MeasurementsPayload) + measurementsPayload := (measurementsEntity).(*payloads.Measurements) // Assemble matrix characteristic_ids := strings.Split(opt.Get("characteristic_ids"), ",") diff --git a/api/measurements.go b/api/measurements.go index 53019bb..d09c38c 100644 --- a/api/measurements.go +++ b/api/measurements.go @@ -14,7 +14,7 @@ import ( type MeasurementService struct{} func (s MeasurementService) Unmarshal(b []byte) (types.Entity, error) { - var mj payloads.MeasurementPayload + var mj payloads.Measurement err := json.Unmarshal(b, &mj) return &mj, err } @@ -53,7 +53,7 @@ func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.E return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.MeasurementsPayload{ + payload := payloads.Measurements{ Characteristics: characteristics, Strains: strains, Measurements: measurements, @@ -68,7 +68,7 @@ func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (t return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.MeasurementPayload{ + payload := payloads.Measurement{ Measurement: measurement, } @@ -76,7 +76,7 @@ func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (t } func (s MeasurementService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.MeasurementPayload) + payload := (*e).(*payloads.Measurement) payload.Measurement.UpdatedBy = claims.Sub payload.Measurement.Id = id @@ -120,7 +120,7 @@ func (m MeasurementService) Delete(id int64, genus string, claims *types.Claims) } func (m MeasurementService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.MeasurementPayload) + payload := (*e).(*payloads.Measurement) payload.Measurement.CreatedBy = claims.Sub payload.Measurement.UpdatedBy = claims.Sub diff --git a/api/species.go b/api/species.go index 5df6b66..23950db 100644 --- a/api/species.go +++ b/api/species.go @@ -14,7 +14,7 @@ import ( type SpeciesService struct{} func (s SpeciesService) Unmarshal(b []byte) (types.Entity, error) { - var sj payloads.SpeciesPayload + var sj payloads.Species err := json.Unmarshal(b, &sj) return &sj, err } @@ -43,7 +43,7 @@ func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entit return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.ManySpeciesPayload{ + payload := payloads.ManySpecies{ Species: species, Strains: strains, Meta: &models.SpeciesMeta{ @@ -65,7 +65,7 @@ func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.SpeciesPayload{ + payload := payloads.Species{ Species: species, Strains: strains, Meta: &models.SpeciesMeta{ @@ -77,7 +77,7 @@ func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types } func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.SpeciesPayload) + payload := (*e).(*payloads.Species) payload.Species.UpdatedBy = claims.Sub payload.Species.Id = id @@ -118,7 +118,7 @@ func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims * } func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.SpeciesPayload) + payload := (*e).(*payloads.Species) payload.Species.CreatedBy = claims.Sub payload.Species.UpdatedBy = claims.Sub diff --git a/api/strains.go b/api/strains.go index 2f61e4e..1233230 100644 --- a/api/strains.go +++ b/api/strains.go @@ -14,7 +14,7 @@ import ( type StrainService struct{} func (s StrainService) Unmarshal(b []byte) (types.Entity, error) { - var sj payloads.StrainPayload + var sj payloads.Strain err := json.Unmarshal(b, &sj) return &sj, err } @@ -76,7 +76,7 @@ func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity return nil, types.NewJSONError(err, http.StatusInternalServerError) } - payload := payloads.StrainsPayload{ + payload := payloads.Strains{ Strains: strains, Species: species, Measurements: measurements, @@ -131,7 +131,7 @@ func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types. var many_species models.ManySpecies = []*models.Species{species} - payload := payloads.StrainPayload{ + payload := payloads.Strain{ Strain: strain, Species: &many_species, Characteristics: characteristics, @@ -145,7 +145,7 @@ func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types. } func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.StrainPayload) + payload := (*e).(*payloads.Strain) payload.Strain.UpdatedBy = claims.Sub payload.Strain.Id = id @@ -181,7 +181,7 @@ func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *t } func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { - payload := (*e).(*payloads.StrainPayload) + payload := (*e).(*payloads.Strain) payload.Strain.CreatedBy = claims.Sub payload.Strain.UpdatedBy = claims.Sub diff --git a/api/users.go b/api/users.go index b09963c..45ac7ac 100644 --- a/api/users.go +++ b/api/users.go @@ -30,7 +30,7 @@ var ( type UserService struct{} func (u UserService) Unmarshal(b []byte) (types.Entity, error) { - var uj payloads.UserPayload + var uj payloads.User err := json.Unmarshal(b, &uj) return &uj, err } @@ -66,7 +66,7 @@ func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.En user.CanEdit = claims.Role == "A" || id == claims.Sub - payload := payloads.UserPayload{ + payload := payloads.User{ User: user, Meta: &models.UserMeta{ CanAdd: claims.Role == "A", @@ -76,7 +76,7 @@ func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.En } func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *types.Claims) *types.AppError { - user := (*e).(*payloads.UserPayload).User + user := (*e).(*payloads.User).User original_user, err := models.DbGetUserById(id) if err != nil { @@ -106,7 +106,7 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ } func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) *types.AppError { - user := (*e).(*payloads.UserPayload).User + user := (*e).(*payloads.User).User if err := user.Validate(); err != nil { return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} } diff --git a/payloads/characteristics.go b/payloads/characteristics.go index 8f89201..7da77ca 100644 --- a/payloads/characteristics.go +++ b/payloads/characteristics.go @@ -6,7 +6,7 @@ import ( "github.com/thermokarst/bactdb/models" ) -type CharacteristicPayload struct { +type Characteristic struct { Characteristic *models.Characteristic `json:"characteristic"` Measurements *models.Measurements `json:"measurements"` Strains *models.Strains `json:"strains"` @@ -14,7 +14,7 @@ type CharacteristicPayload struct { Meta *models.CharacteristicMeta `json:"meta"` } -type CharacteristicsPayload struct { +type Characteristics struct { Characteristics *models.Characteristics `json:"characteristics"` Measurements *models.Measurements `json:"measurements"` Strains *models.Strains `json:"strains"` @@ -22,10 +22,10 @@ type CharacteristicsPayload struct { Meta *models.CharacteristicMeta `json:"meta"` } -func (c *CharacteristicPayload) Marshal() ([]byte, error) { +func (c *Characteristic) Marshal() ([]byte, error) { return json.Marshal(c) } -func (c *CharacteristicsPayload) Marshal() ([]byte, error) { +func (c *Characteristics) Marshal() ([]byte, error) { return json.Marshal(c) } diff --git a/payloads/measurements.go b/payloads/measurements.go index c35db68..2c7853b 100644 --- a/payloads/measurements.go +++ b/payloads/measurements.go @@ -6,20 +6,20 @@ import ( "github.com/thermokarst/bactdb/models" ) -type MeasurementPayload struct { +type Measurement struct { Measurement *models.Measurement `json:"measurement"` } -type MeasurementsPayload struct { +type Measurements struct { Strains *models.Strains `json:"strains"` Characteristics *models.Characteristics `json:"characteristics"` Measurements *models.Measurements `json:"measurements"` } -func (m *MeasurementPayload) Marshal() ([]byte, error) { +func (m *Measurement) Marshal() ([]byte, error) { return json.Marshal(m) } -func (m *MeasurementsPayload) Marshal() ([]byte, error) { +func (m *Measurements) Marshal() ([]byte, error) { return json.Marshal(m) } diff --git a/payloads/species.go b/payloads/species.go index 39cbbc0..d391579 100644 --- a/payloads/species.go +++ b/payloads/species.go @@ -6,22 +6,22 @@ import ( "github.com/thermokarst/bactdb/models" ) -type SpeciesPayload struct { +type Species struct { Species *models.Species `json:"species"` Strains *models.Strains `json:"strains"` Meta *models.SpeciesMeta `json:"meta"` } -type ManySpeciesPayload struct { +type ManySpecies struct { Species *models.ManySpecies `json:"species"` Strains *models.Strains `json:"strains"` Meta *models.SpeciesMeta `json:"meta"` } -func (s *SpeciesPayload) Marshal() ([]byte, error) { +func (s *Species) Marshal() ([]byte, error) { return json.Marshal(s) } -func (s *ManySpeciesPayload) Marshal() ([]byte, error) { +func (s *ManySpecies) Marshal() ([]byte, error) { return json.Marshal(s) } diff --git a/payloads/strains.go b/payloads/strains.go index bb0ec8d..f73cd0b 100644 --- a/payloads/strains.go +++ b/payloads/strains.go @@ -6,7 +6,7 @@ import ( "github.com/thermokarst/bactdb/models" ) -type StrainPayload struct { +type Strain struct { Strain *models.Strain `json:"strain"` Species *models.ManySpecies `json:"species"` Characteristics *models.Characteristics `json:"characteristics"` @@ -14,7 +14,7 @@ type StrainPayload struct { Meta *models.StrainMeta `json:"meta"` } -type StrainsPayload struct { +type Strains struct { Strains *models.Strains `json:"strains"` Species *models.ManySpecies `json:"species"` Characteristics *models.Characteristics `json:"characteristics"` @@ -22,10 +22,10 @@ type StrainsPayload struct { Meta *models.StrainMeta `json:"meta"` } -func (s *StrainPayload) Marshal() ([]byte, error) { +func (s *Strain) Marshal() ([]byte, error) { return json.Marshal(s) } -func (s *StrainsPayload) Marshal() ([]byte, error) { +func (s *Strains) Marshal() ([]byte, error) { return json.Marshal(s) } diff --git a/payloads/users.go b/payloads/users.go index 5329f25..bf30dce 100644 --- a/payloads/users.go +++ b/payloads/users.go @@ -6,11 +6,11 @@ import ( "github.com/thermokarst/bactdb/models" ) -type UserPayload struct { +type User struct { User *models.User `json:"user"` Meta *models.UserMeta `json:"meta"` } -func (u *UserPayload) Marshal() ([]byte, error) { +func (u *User) Marshal() ([]byte, error) { return json.Marshal(u) } From 0eec85ed08a43a94a3ddd75d8442fa13943b93b4 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:36:19 -0700 Subject: [PATCH 07/10] Rename auth file --- auth/{claims.go => auth.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename auth/{claims.go => auth.go} (100%) diff --git a/auth/claims.go b/auth/auth.go similarity index 100% rename from auth/claims.go rename to auth/auth.go From a880fdea82a0cf144056d715af6c49c467def7ef Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 1 Oct 2015 14:45:36 -0700 Subject: [PATCH 08/10] Errors package --- api/characteristics.go | 47 +++++++++++++++++----------------- api/helpers.go | 10 ++++++++ api/measurements.go | 29 +++++++++++---------- api/species.go | 31 +++++++++++----------- api/strains.go | 41 ++++++++++++++--------------- api/users.go | 54 ++++++++++++++++++--------------------- errors/auth.go | 7 +++++ errors/characteristics.go | 8 ++++++ errors/helpers.go | 7 +++++ errors/measurements.go | 7 +++++ errors/species.go | 8 ++++++ errors/strains.go | 8 ++++++ errors/types.go | 7 +++++ errors/users.go | 11 ++++++++ handlers/handlers.go | 30 +++++++++++----------- handlers/helpers.go | 10 ++++++++ helpers/helpers.go | 3 --- models/characteristics.go | 9 ++----- models/measurements.go | 10 ++------ models/species.go | 9 ++----- models/strains.go | 9 ++----- models/users.go | 17 ++++-------- types/error_json.go | 7 ----- types/null_slice_int64.go | 5 ++-- 24 files changed, 215 insertions(+), 169 deletions(-) create mode 100644 api/helpers.go create mode 100644 errors/auth.go create mode 100644 errors/characteristics.go create mode 100644 errors/helpers.go create mode 100644 errors/measurements.go create mode 100644 errors/species.go create mode 100644 errors/strains.go create mode 100644 errors/types.go create mode 100644 errors/users.go create mode 100644 handlers/helpers.go diff --git a/api/characteristics.go b/api/characteristics.go index 8727a00..d210a16 100644 --- a/api/characteristics.go +++ b/api/characteristics.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/payloads" @@ -21,46 +22,46 @@ func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) { func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, helpers.ErrMustProvideOptionsJSON + return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristics, err := models.ListCharacteristics(opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains_opt, err := models.StrainOptsFromCharacteristics(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, err := models.ListStrains(*strains_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species_opt, err := models.SpeciesOptsFromStrains(*strains_opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(*species_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } measurements_opt, err := models.MeasurementOptsFromCharacteristics(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } measurements, err := models.ListMeasurements(*measurements_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Characteristics{ @@ -79,27 +80,27 @@ func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (type func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { characteristic, err := models.GetCharacteristic(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(*species_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } measurements, _, err := models.MeasurementsFromCharacteristicId(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Characteristic{ @@ -120,7 +121,7 @@ func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, c // First, handle Characteristic Type id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Characteristic.CanEdit = helpers.CanEdit(claims, payload.Characteristic.CreatedBy) @@ -129,26 +130,26 @@ func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, c // TODO: fix this count, err := models.DBH.Update(payload.Characteristic.CharacteristicBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return types.NewJSONError(models.ErrCharacteristicNotUpdated, http.StatusBadRequest) + return NewJSONError(errors.CharacteristicNotUpdated, http.StatusBadRequest) } strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(*species_opt, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Strains = strains @@ -166,19 +167,19 @@ func (c CharacteristicService) Create(e *types.Entity, genus string, claims *typ id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Characteristic.CharacteristicTypeId = id // TODO: fix this err = models.DBH.Insert(payload.Characteristic.CharacteristicBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } characteristic, err := models.GetCharacteristic(payload.Characteristic.Id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Characteristic = characteristic diff --git a/api/helpers.go b/api/helpers.go new file mode 100644 index 0000000..f6c0317 --- /dev/null +++ b/api/helpers.go @@ -0,0 +1,10 @@ +package api + +import "github.com/thermokarst/bactdb/types" + +func NewJSONError(err error, status int) *types.AppError { + return &types.AppError{ + Error: types.ErrorJSON{Err: err}, + Status: status, + } +} diff --git a/api/measurements.go b/api/measurements.go index d09c38c..0e708b6 100644 --- a/api/measurements.go +++ b/api/measurements.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/payloads" @@ -21,36 +22,36 @@ func (s MeasurementService) Unmarshal(b []byte) (types.Entity, error) { func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, helpers.ErrMustProvideOptionsJSON + return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) } var opt helpers.MeasurementListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } measurements, err := models.ListMeasurements(opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } char_opts, err := models.CharacteristicOptsFromMeasurements(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristics, err := models.ListCharacteristics(*char_opts, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strain_opts, err := models.StrainOptsFromMeasurements(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, err := models.ListStrains(*strain_opts, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Measurements{ @@ -65,7 +66,7 @@ func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.E func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { measurement, err := models.GetMeasurement(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Measurement{ @@ -83,7 +84,7 @@ func (s MeasurementService) Update(id int64, e *types.Entity, genus string, clai if payload.Measurement.TextMeasurementType.Valid { id, err := models.GetTextMeasurementTypeId(payload.Measurement.TextMeasurementType.String) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Measurement.TextMeasurementTypeId.Int64 = id payload.Measurement.TextMeasurementTypeId.Valid = true @@ -92,16 +93,16 @@ func (s MeasurementService) Update(id int64, e *types.Entity, genus string, clai // TODO: fix this count, err := models.DBH.Update(payload.Measurement.MeasurementBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest) + return NewJSONError(errors.StrainNotUpdated, http.StatusBadRequest) } measurement, err := models.GetMeasurement(id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Measurement = measurement @@ -114,7 +115,7 @@ func (m MeasurementService) Delete(id int64, genus string, claims *types.Claims) // TODO: fix this _, err := models.DBH.Exec(q, id) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } return nil } @@ -126,7 +127,7 @@ func (m MeasurementService) Create(e *types.Entity, genus string, claims *types. // TODO: fix this if err := models.DBH.Insert(payload.Measurement.MeasurementBase); err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } return nil diff --git a/api/species.go b/api/species.go index 23950db..fc4ea36 100644 --- a/api/species.go +++ b/api/species.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/payloads" @@ -21,26 +22,26 @@ func (s SpeciesService) Unmarshal(b []byte) (types.Entity, error) { func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, helpers.ErrMustProvideOptionsJSON + return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains_opt, err := models.StrainOptsFromSpecies(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, err := models.ListStrains(*strains_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.ManySpecies{ @@ -57,12 +58,12 @@ func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entit func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { species, err := models.GetSpecies(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, err := models.StrainsFromSpeciesId(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Species{ @@ -83,29 +84,29 @@ func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims * genus_id, err := models.GenusIdFromName(genus) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Species.SpeciesBase.GenusID = genus_id // TODO: fix this count, err := models.DBH.Update(payload.Species.SpeciesBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return types.NewJSONError(models.ErrSpeciesNotUpdated, http.StatusBadRequest) + return NewJSONError(errors.SpeciesNotUpdated, http.StatusBadRequest) } // Reload to send back down the wire species, err := models.GetSpecies(id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } strains, err := models.StrainsFromSpeciesId(id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Species = species @@ -124,20 +125,20 @@ func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Clai genus_id, err := models.GenusIdFromName(genus) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } payload.Species.SpeciesBase.GenusID = genus_id // TODO: fix this err = models.DBH.Insert(payload.Species.SpeciesBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } // Reload to send back down the wire species, err := models.GetSpecies(payload.Species.Id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } // Note, no strains when new species diff --git a/api/strains.go b/api/strains.go index 1233230..bb3ad22 100644 --- a/api/strains.go +++ b/api/strains.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/payloads" @@ -21,36 +22,36 @@ func (s StrainService) Unmarshal(b []byte) (types.Entity, error) { func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, helpers.ErrMustProvideOptionsJSON + return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } strains, err := models.ListStrains(opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species_opt, err := models.SpeciesOptsFromStrains(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(*species_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristic_ids := []int64{} @@ -73,7 +74,7 @@ func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity measurements, err := models.ListMeasurements(measurement_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } payload := payloads.Strains{ @@ -92,23 +93,23 @@ func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { strain, err := models.GetStrain(id, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } species, err := models.GetSpecies(strain.SpeciesId, genus, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } opt := helpers.ListOptions{Genus: genus, Ids: []int64{id}} characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } characteristic_ids := []int64{} @@ -126,7 +127,7 @@ func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types. measurements, err := models.ListMeasurements(measurement_opt, claims) if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } var many_species models.ManySpecies = []*models.Species{species} @@ -152,21 +153,21 @@ func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *t // TODO: fix this count, err := models.DBH.Update(payload.Strain.StrainBase) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest) + return NewJSONError(errors.StrainNotUpdated, http.StatusBadRequest) } strain, err := models.GetStrain(id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } species, err := models.GetSpecies(strain.SpeciesId, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } var many_species models.ManySpecies = []*models.Species{species} @@ -187,17 +188,17 @@ func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claim // TODO: fix this if err := models.DBH.Insert(payload.Strain.StrainBase); err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } strain, err := models.GetStrain(payload.Strain.Id, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } species, err := models.GetSpecies(strain.SpeciesId, genus, claims) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } var many_species models.ManySpecies = []*models.Species{species} diff --git a/api/users.go b/api/users.go index 45ac7ac..d0053ed 100644 --- a/api/users.go +++ b/api/users.go @@ -2,7 +2,6 @@ package api import ( "encoding/json" - "errors" "fmt" "log" "net/http" @@ -13,6 +12,7 @@ import ( "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/mailgun/mailgun-go" "github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt" "github.com/thermokarst/bactdb/auth" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/payloads" @@ -20,11 +20,7 @@ import ( ) var ( - // TODO: fix this - ErrUserNotFoundJSON = types.NewJSONError(models.ErrUserNotFound, http.StatusNotFound) - ErrUserNotUpdatedJSON = types.NewJSONError(models.ErrUserNotUpdated, http.StatusBadRequest) - ErrEmailAddressTakenJSON = types.NewJSONError(models.ErrEmailAddressTaken, http.StatusBadRequest) - MgAccts = make(map[string]mailgun.Mailgun) + MgAccts = make(map[string]mailgun.Mailgun) ) type UserService struct{} @@ -37,11 +33,11 @@ func (u UserService) Unmarshal(b []byte) (types.Entity, error) { func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, helpers.ErrMustProvideOptionsJSON + return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } // TODO: fix this @@ -52,7 +48,7 @@ func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, WHERE verified IS TRUE AND deleted_at IS NULL;` if err := models.DBH.Select(&users, sql); err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } return &users, nil } @@ -61,7 +57,7 @@ func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.En user, err := models.DbGetUserById(id) user.Password = "" if err != nil { - return nil, types.NewJSONError(err, http.StatusInternalServerError) + return nil, NewJSONError(err, http.StatusInternalServerError) } user.CanEdit = claims.Role == "A" || id == claims.Sub @@ -80,7 +76,7 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ original_user, err := models.DbGetUserById(id) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } user.Id = id @@ -96,10 +92,10 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ count, err := models.DBH.Update(user) user.Password = "" if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { - return ErrUserNotUpdatedJSON + return NewJSONError(errors.UserNotUpdated, http.StatusInternalServerError) } return nil @@ -115,7 +111,7 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) user.UpdatedAt = ct hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } user.Password = string(hash) user.Role = "R" @@ -125,10 +121,10 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) if err := models.DBH.Insert(user); err != nil { if err, ok := err.(*pq.Error); ok { if err.Code == "23505" { - return ErrEmailAddressTakenJSON + return NewJSONError(errors.EmailAddressTaken, http.StatusInternalServerError) } } - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } user.Password = "password" // don't want to send the hashed PW back to the client @@ -137,12 +133,12 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) // TODO: move helpers.GenerateNonce nonce, err := helpers.GenerateNonce() if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } // TODO: fix this _, err = models.DBH.Exec(q, user.Id, nonce, claims.Ref, ct) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } // Send out confirmation email @@ -161,7 +157,7 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) _, _, err := mg.Send(m) if err != nil { log.Printf("%+v\n", err) - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } } @@ -179,16 +175,16 @@ func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { } if err := models.DBH.SelectOne(&ver, q, nonce); err != nil { log.Print(err) - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if ver.User_id == 0 { - return types.NewJSONError(errors.New("No user found"), http.StatusInternalServerError) + return NewJSONError(errors.UserNotFound, http.StatusInternalServerError) } var user models.User if err := models.DBH.Get(&user, ver.User_id); err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } user.UpdatedAt = helpers.CurrentTime() @@ -196,16 +192,16 @@ func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { count, err := models.DBH.Update(&user) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } if count != 1 { - return types.NewJSONError(errors.New("Count 0"), http.StatusInternalServerError) + return NewJSONError(errors.UserNotUpdated, http.StatusInternalServerError) } q = `DELETE FROM verification WHERE user_id=$1;` _, err = models.DBH.Exec(q, user.Id) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } fmt.Fprintln(w, `{"msg":"All set! Please log in."}`) return nil @@ -214,16 +210,16 @@ func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError { email := r.FormValue("email") if email == "" { - return types.NewJSONError(errors.New("missing email"), http.StatusInternalServerError) + return NewJSONError(errors.UserMissingEmail, http.StatusInternalServerError) } token, err := auth.Middleware.CreateToken(email) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } origin := r.Header.Get("Origin") hostUrl, err := url.Parse(origin) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } hostUrl.Path += "/users/lockoutauthenticate" params := url.Values{} @@ -246,7 +242,7 @@ func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError { _, _, err := mg.Send(m) if err != nil { log.Printf("%+v\n", err) - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } } diff --git a/errors/auth.go b/errors/auth.go new file mode 100644 index 0000000..2471797 --- /dev/null +++ b/errors/auth.go @@ -0,0 +1,7 @@ +package errors + +import "errors" + +var ( + ExpiredToken = errors.New("this token has expired") +) diff --git a/errors/characteristics.go b/errors/characteristics.go new file mode 100644 index 0000000..abeae29 --- /dev/null +++ b/errors/characteristics.go @@ -0,0 +1,8 @@ +package errors + +import "errors" + +var ( + CharacteristicNotFound = errors.New("Characteristic not found") + CharacteristicNotUpdated = errors.New("Characteristic not updated") +) diff --git a/errors/helpers.go b/errors/helpers.go new file mode 100644 index 0000000..7135380 --- /dev/null +++ b/errors/helpers.go @@ -0,0 +1,7 @@ +package errors + +import "errors" + +var ( + MustProvideOptions = errors.New("Must provide necessary options") +) diff --git a/errors/measurements.go b/errors/measurements.go new file mode 100644 index 0000000..d034d59 --- /dev/null +++ b/errors/measurements.go @@ -0,0 +1,7 @@ +package errors + +import "errors" + +var ( + MeasurementNotFound = errors.New("Measurement not found") +) diff --git a/errors/species.go b/errors/species.go new file mode 100644 index 0000000..fbd8379 --- /dev/null +++ b/errors/species.go @@ -0,0 +1,8 @@ +package errors + +import "errors" + +var ( + SpeciesNotFound = errors.New("Species not found") + SpeciesNotUpdated = errors.New("Species not updated") +) diff --git a/errors/strains.go b/errors/strains.go new file mode 100644 index 0000000..4fda22e --- /dev/null +++ b/errors/strains.go @@ -0,0 +1,8 @@ +package errors + +import "errors" + +var ( + StrainNotFound = errors.New("Strain not found") + StrainNotUpdated = errors.New("Strain not updated") +) diff --git a/errors/types.go b/errors/types.go new file mode 100644 index 0000000..b237e47 --- /dev/null +++ b/errors/types.go @@ -0,0 +1,7 @@ +package errors + +import "errors" + +var ( + SourceNotByteSlice = errors.New("Scan source was not []byte") +) diff --git a/errors/users.go b/errors/users.go new file mode 100644 index 0000000..8a0a59d --- /dev/null +++ b/errors/users.go @@ -0,0 +1,11 @@ +package errors + +import "errors" + +var ( + UserNotFound = errors.New("No user found") + UserNotUpdated = errors.New("Count 0") + UserMissingEmail = errors.New("Missing email") + InvalidEmailOrPassword = errors.New("Invalid email or password") + EmailAddressTaken = errors.New("Email address is already registered") +) diff --git a/handlers/handlers.go b/handlers/handlers.go index 167d96a..46c5516 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -2,7 +2,6 @@ package handlers import ( "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -18,6 +17,7 @@ import ( "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/thermokarst/jwt" "github.com/thermokarst/bactdb/api" "github.com/thermokarst/bactdb/auth" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/models" "github.com/thermokarst/bactdb/types" @@ -32,7 +32,7 @@ func verifyClaims(claims []byte, r *http.Request) error { return err } if currentTime.After(time.Unix(c.Exp, 0)) { - return errors.New("this token has expired") + return errors.ExpiredToken } context.Set(r, "claims", c) return nil @@ -99,7 +99,7 @@ func handleGetter(g api.Getter) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -111,7 +111,7 @@ func handleGetter(g api.Getter) errorHandler { data, err := e.Marshal() if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -131,7 +131,7 @@ func handleLister(l api.Lister) errorHandler { } data, err := es.Marshal() if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -142,17 +142,17 @@ func handleUpdater(u api.Updater) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } e, err := u.Unmarshal(bodyBytes) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -164,7 +164,7 @@ func handleUpdater(u api.Updater) errorHandler { data, err := e.Marshal() if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -175,12 +175,12 @@ func handleCreater(c api.Creater) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } e, err := c.Unmarshal(bodyBytes) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -192,7 +192,7 @@ func handleCreater(c api.Creater) errorHandler { data, err := e.Marshal() if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -203,7 +203,7 @@ func handleDeleter(d api.Deleter) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -296,12 +296,12 @@ func tokenRefresh(j *jwt.Middleware) errorHandler { claims := helpers.GetClaims(r) user, err := models.DbGetUserById(claims.Sub) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } user.Password = "" token, err := auth.Middleware.CreateToken(user.Email) if err != nil { - return types.NewJSONError(err, http.StatusInternalServerError) + return NewJSONError(err, http.StatusInternalServerError) } data, _ := json.Marshal(struct { Token string `json:"token"` diff --git a/handlers/helpers.go b/handlers/helpers.go new file mode 100644 index 0000000..acaa24a --- /dev/null +++ b/handlers/helpers.go @@ -0,0 +1,10 @@ +package handlers + +import "github.com/thermokarst/bactdb/types" + +func NewJSONError(err error, status int) *types.AppError { + return &types.AppError{ + Error: types.ErrorJSON{Err: err}, + Status: status, + } +} diff --git a/helpers/helpers.go b/helpers/helpers.go index a2e5500..c47573a 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -3,7 +3,6 @@ package helpers import ( "crypto/rand" "encoding/base64" - "errors" "fmt" "net/http" "time" @@ -15,8 +14,6 @@ import ( ) var ( - ErrMustProvideOptions = errors.New("Must provide necessary options") - ErrMustProvideOptionsJSON = types.NewJSONError(ErrMustProvideOptions, http.StatusBadRequest) StatusUnprocessableEntity = 422 MustProvideAValue = "Must provide a value" SchemaDecoder = schema.NewDecoder() diff --git a/models/characteristics.go b/models/characteristics.go index a913c61..a155bc5 100644 --- a/models/characteristics.go +++ b/models/characteristics.go @@ -2,19 +2,14 @@ package models import ( "database/sql" - "errors" "fmt" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/types" ) -var ( - ErrCharacteristicNotFound = errors.New("Characteristic not found") - ErrCharacteristicNotUpdated = errors.New("Characteristic not updated") -) - func init() { DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") } @@ -199,7 +194,7 @@ func GetCharacteristic(id int64, genus string, claims *types.Claims) (*Character GROUP BY c.id, ct.characteristic_type_name;` if err := DBH.SelectOne(&characteristic, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrCharacteristicNotFound + return nil, errors.CharacteristicNotFound } return nil, err } diff --git a/models/measurements.go b/models/measurements.go index 1bfa6dd..faa8ea2 100644 --- a/models/measurements.go +++ b/models/measurements.go @@ -3,20 +3,14 @@ package models import ( "database/sql" "encoding/json" - "errors" "fmt" - "net/http" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/types" ) -var ( - ErrMeasurementNotFound = errors.New("Measurement not found") - ErrMeasurementNotFoundJSON = types.NewJSONError(ErrMeasurementNotFound, http.StatusNotFound) -) - func init() { DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") } @@ -205,7 +199,7 @@ func GetMeasurement(id int64, genus string, claims *types.Claims) (*Measurement, WHERE m.id=$2;` if err := DBH.SelectOne(&measurement, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrMeasurementNotFound + return nil, errors.MeasurementNotFound } return nil, err } diff --git a/models/species.go b/models/species.go index e88faed..e5268f2 100644 --- a/models/species.go +++ b/models/species.go @@ -2,20 +2,15 @@ package models import ( "database/sql" - "errors" "fmt" "strings" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/types" ) -var ( - ErrSpeciesNotFound = errors.New("Species not found") - ErrSpeciesNotUpdated = errors.New("Species not updated") -) - func init() { DB.AddTableWithName(SpeciesBase{}, "species").SetKeys(true, "Id") } @@ -163,7 +158,7 @@ func GetSpecies(id int64, genus string, claims *types.Claims) (*Species, error) GROUP BY sp.id, g.genus_name;` if err := DBH.SelectOne(&species, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrSpeciesNotFound + return nil, errors.SpeciesNotFound } return nil, err } diff --git a/models/strains.go b/models/strains.go index a386031..2269c12 100644 --- a/models/strains.go +++ b/models/strains.go @@ -2,20 +2,15 @@ package models import ( "database/sql" - "errors" "fmt" "strings" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/types" ) -var ( - ErrStrainNotFound = errors.New("Strain not found") - ErrStrainNotUpdated = errors.New("Strain not updated") -) - func init() { DB.AddTableWithName(StrainBase{}, "strains").SetKeys(true, "Id") } @@ -126,7 +121,7 @@ func GetStrain(id int64, genus string, claims *types.Claims) (*Strain, error) { GROUP BY st.id;` if err := DBH.SelectOne(&strain, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrStrainNotFound + return nil, errors.StrainNotFound } return nil, err } diff --git a/models/users.go b/models/users.go index 1cf56a8..a885de8 100644 --- a/models/users.go +++ b/models/users.go @@ -3,21 +3,14 @@ package models import ( "database/sql" "encoding/json" - "errors" "regexp" "github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt" + "github.com/thermokarst/bactdb/errors" "github.com/thermokarst/bactdb/helpers" "github.com/thermokarst/bactdb/types" ) -var ( - ErrUserNotFound = errors.New("User not found") - ErrUserNotUpdated = errors.New("User not updated") - ErrInvalidEmailOrPassword = errors.New("Invalid email or password") - ErrEmailAddressTaken = errors.New("Email address already registered") -) - func init() { DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "Id") } @@ -114,10 +107,10 @@ func DbAuthenticate(email string, password string) error { AND verified IS TRUE AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, email); err != nil { - return ErrInvalidEmailOrPassword + return errors.InvalidEmailOrPassword } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - return ErrInvalidEmailOrPassword + return errors.InvalidEmailOrPassword } return nil } @@ -131,7 +124,7 @@ func DbGetUserById(id int64) (*User, error) { AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrUserNotFound + return nil, errors.UserNotFound } return nil, err } @@ -148,7 +141,7 @@ func DbGetUserByEmail(email string) (*User, error) { AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, email); err != nil { if err == sql.ErrNoRows { - return nil, ErrUserNotFound + return nil, errors.UserNotFound } return nil, err } diff --git a/types/error_json.go b/types/error_json.go index 45e4f9f..439eaf4 100644 --- a/types/error_json.go +++ b/types/error_json.go @@ -19,10 +19,3 @@ type AppError struct { Error error Status int } - -func NewJSONError(err error, status int) *AppError { - return &AppError{ - Error: ErrorJSON{Err: err}, - Status: status, - } -} diff --git a/types/null_slice_int64.go b/types/null_slice_int64.go index fb1ce87..1c71c8d 100644 --- a/types/null_slice_int64.go +++ b/types/null_slice_int64.go @@ -1,9 +1,10 @@ package types import ( - "errors" "strconv" "strings" + + "github.com/thermokarst/bactdb/errors" ) type NullSliceInt64 []int64 @@ -11,7 +12,7 @@ type NullSliceInt64 []int64 func (i *NullSliceInt64) Scan(src interface{}) error { asBytes, ok := src.([]byte) if !ok { - return errors.New("Scan source was not []byte") + return errors.SourceNotByteSlice } asString := string(asBytes) (*i) = strToIntSlice(asString) From efb0cc13fa3e9055ed16a9208fc990abacc716c6 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 2 Oct 2015 16:20:07 -0700 Subject: [PATCH 09/10] Golint --- api/characteristics.go | 86 +++++++++++++++------------- api/compare.go | 35 ++++++------ api/entities.go | 6 ++ api/helpers.go | 2 +- api/measurements.go | 55 ++++++++++-------- api/species.go | 56 ++++++++++--------- api/strains.go | 108 +++++++++++++++++++----------------- api/users.go | 93 ++++++++++++++++++------------- auth/auth.go | 6 +- errors/auth.go | 3 +- errors/characteristics.go | 6 +- errors/helpers.go | 3 +- errors/measurements.go | 3 +- errors/species.go | 6 +- errors/strains.go | 6 +- errors/types.go | 3 +- errors/users.go | 15 +++-- handlers/handlers.go | 59 ++++++++++---------- handlers/helpers.go | 2 +- helpers/helpers.go | 39 +++++-------- main.go | 6 +- models/characteristics.go | 75 +++++++++++++++---------- models/database.go | 4 +- models/measurements.go | 63 +++++++++++++-------- models/species.go | 52 ++++++++++------- models/strains.go | 52 ++++++++++------- models/users.go | 41 +++++++------- payloads/characteristics.go | 6 ++ payloads/measurements.go | 6 ++ payloads/species.go | 6 ++ payloads/strains.go | 6 ++ payloads/users.go | 15 +++++ types/claims.go | 1 + types/entity.go | 1 + types/error_json.go | 4 ++ types/null_bool.go | 9 ++- types/null_float64.go | 3 + types/null_int64.go | 3 + types/null_slice_int64.go | 4 +- types/null_string.go | 3 + types/null_time.go | 3 + 41 files changed, 569 insertions(+), 386 deletions(-) diff --git a/api/characteristics.go b/api/characteristics.go index d210a16..0d284db 100644 --- a/api/characteristics.go +++ b/api/characteristics.go @@ -12,56 +12,59 @@ import ( "github.com/thermokarst/bactdb/types" ) +// CharacteristicService provides for CRUD operations type CharacteristicService struct{} +// Unmarshal satisfies interface Updater and interface Creater func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) { var cj payloads.Characteristic err := json.Unmarshal(b, &cj) return &cj, err } +// List lists all characteristics func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) + return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } characteristics, err := models.ListCharacteristics(opt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains_opt, err := models.StrainOptsFromCharacteristics(opt) + strainsOpt, err := models.StrainOptsFromCharacteristics(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains, err := models.ListStrains(*strains_opt, claims) + strains, err := models.ListStrains(*strainsOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species_opt, err := models.SpeciesOptsFromStrains(*strains_opt) + speciesOpt, err := models.SpeciesOptsFromStrains(*strainsOpt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species, err := models.ListSpecies(*species_opt, claims) + species, err := models.ListSpecies(*speciesOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - measurements_opt, err := models.MeasurementOptsFromCharacteristics(opt) + measurementsOpt, err := models.MeasurementOptsFromCharacteristics(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - measurements, err := models.ListMeasurements(*measurements_opt, claims) + measurements, err := models.ListMeasurements(*measurementsOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Characteristics{ @@ -77,30 +80,31 @@ func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (type return &payload, nil } +// Get retrieves a single characteristic func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { characteristic, err := models.GetCharacteristic(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) + strains, strainOpts, err := models.StrainsFromCharacteristicID(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) + speciesOpt, err := models.SpeciesOptsFromStrains(*strainOpts) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species, err := models.ListSpecies(*species_opt, claims) + species, err := models.ListSpecies(*speciesOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - measurements, _, err := models.MeasurementsFromCharacteristicId(id, genus, claims) + measurements, _, err := models.MeasurementsFromCharacteristicID(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Characteristic{ @@ -113,43 +117,44 @@ func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) return &payload, nil } +// Update modifies an existing characteristic func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Characteristic) payload.Characteristic.UpdatedBy = claims.Sub - payload.Characteristic.Id = id + payload.Characteristic.ID = id // First, handle Characteristic Type id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } payload.Characteristic.CanEdit = helpers.CanEdit(claims, payload.Characteristic.CreatedBy) - payload.Characteristic.CharacteristicTypeId = id + payload.Characteristic.CharacteristicTypeID = id // TODO: fix this count, err := models.DBH.Update(payload.Characteristic.CharacteristicBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return NewJSONError(errors.CharacteristicNotUpdated, http.StatusBadRequest) + return newJSONError(errors.ErrCharacteristicNotUpdated, http.StatusBadRequest) } - strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims) + strains, strainOpts, err := models.StrainsFromCharacteristicID(id, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - species_opt, err := models.SpeciesOptsFromStrains(*strain_opts) + speciesOpt, err := models.SpeciesOptsFromStrains(*strainOpts) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - species, err := models.ListSpecies(*species_opt, claims) + species, err := models.ListSpecies(*speciesOpt, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } payload.Strains = strains @@ -160,6 +165,7 @@ func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, c return nil } +// Create initializes a new characteristic func (c CharacteristicService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Characteristic) payload.Characteristic.CreatedBy = claims.Sub @@ -167,19 +173,19 @@ func (c CharacteristicService) Create(e *types.Entity, genus string, claims *typ id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - payload.Characteristic.CharacteristicTypeId = id + payload.Characteristic.CharacteristicTypeID = id // TODO: fix this err = models.DBH.Insert(payload.Characteristic.CharacteristicBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - characteristic, err := models.GetCharacteristic(payload.Characteristic.Id, genus, claims) + characteristic, err := models.GetCharacteristic(payload.Characteristic.ID, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } payload.Characteristic = characteristic diff --git a/api/compare.go b/api/compare.go index 04ac122..bcb0721 100644 --- a/api/compare.go +++ b/api/compare.go @@ -16,6 +16,7 @@ import ( "github.com/thermokarst/bactdb/types" ) +// HandleCompare is a HTTP handler for comparision. func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { // types type Comparisions map[string]map[string]string @@ -43,23 +44,23 @@ func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { measurementsPayload := (measurementsEntity).(*payloads.Measurements) // Assemble matrix - characteristic_ids := strings.Split(opt.Get("characteristic_ids"), ",") - strain_ids := strings.Split(opt.Get("strain_ids"), ",") + characteristicIDs := strings.Split(opt.Get("characteristic_ids"), ",") + strainIDs := strings.Split(opt.Get("strain_ids"), ",") comparisions := make(Comparisions) - for _, characteristic_id := range characteristic_ids { - characteristic_id_int, _ := strconv.ParseInt(characteristic_id, 10, 0) + for _, characteristicID := range characteristicIDs { + characteristicIDInt, _ := strconv.ParseInt(characteristicID, 10, 0) values := make(map[string]string) - for _, strain_id := range strain_ids { - strain_id_int, _ := strconv.ParseInt(strain_id, 10, 0) + for _, strainID := range strainIDs { + strainIDInt, _ := strconv.ParseInt(strainID, 10, 0) for _, m := range *measurementsPayload.Measurements { - if (m.CharacteristicId == characteristic_id_int) && (m.StrainId == strain_id_int) { - values[strain_id] = m.Value() + if (m.CharacteristicID == characteristicIDInt) && (m.StrainID == strainIDInt) { + values[strainID] = m.Value() } } } - comparisions[characteristic_id] = values + comparisions[characteristicID] = values } // Return, based on mimetype @@ -68,10 +69,10 @@ func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { header = "application/json" comparisionsJSON := make(ComparisionsJSON, 0) - for _, characteristic_id := range characteristic_ids { - row := []string{characteristic_id} - for _, strain_id := range strain_ids { - row = append(row, comparisions[characteristic_id][strain_id]) + for _, characteristicID := range characteristicIDs { + row := []string{characteristicID} + for _, strainID := range strainIDs { + row = append(row, comparisions[characteristicID][strainID]) } comparisionsJSON = append(comparisionsJSON, row) } @@ -83,11 +84,11 @@ func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { // maps to translate ids strains := make(map[string]string) for _, strain := range *measurementsPayload.Strains { - strains[fmt.Sprintf("%d", strain.Id)] = fmt.Sprintf("%s (%s)", strain.SpeciesName(), strain.StrainName) + strains[fmt.Sprintf("%d", strain.ID)] = fmt.Sprintf("%s (%s)", strain.SpeciesName(), strain.StrainName) } characteristics := make(map[string]string) for _, characteristic := range *measurementsPayload.Characteristics { - characteristics[fmt.Sprintf("%d", characteristic.Id)] = characteristic.CharacteristicName + characteristics[fmt.Sprintf("%d", characteristic.ID)] = characteristic.CharacteristicName } b := &bytes.Buffer{} @@ -95,8 +96,8 @@ func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError { // Write header row r := []string{"Characteristic"} - for _, strain_id := range strain_ids { - r = append(r, strains[strain_id]) + for _, strainID := range strainIDs { + r = append(r, strains[strainID]) } wr.Write(r) diff --git a/api/entities.go b/api/entities.go index 9b86dc5..02657c4 100644 --- a/api/entities.go +++ b/api/entities.go @@ -6,23 +6,29 @@ import ( "github.com/thermokarst/bactdb/types" ) +// Getter gets a single entity. type Getter interface { Get(int64, string, *types.Claims) (types.Entity, *types.AppError) } +// Lister lists entities. type Lister interface { List(*url.Values, *types.Claims) (types.Entity, *types.AppError) } +// Updater updates entities. type Updater interface { Update(int64, *types.Entity, string, *types.Claims) *types.AppError Unmarshal([]byte) (types.Entity, error) } +// Creater creates entities. type Creater interface { Create(*types.Entity, string, *types.Claims) *types.AppError Unmarshal([]byte) (types.Entity, error) } + +// Deleter deletes entities. type Deleter interface { Delete(int64, string, *types.Claims) *types.AppError } diff --git a/api/helpers.go b/api/helpers.go index f6c0317..035a4d4 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -2,7 +2,7 @@ package api import "github.com/thermokarst/bactdb/types" -func NewJSONError(err error, status int) *types.AppError { +func newJSONError(err error, status int) *types.AppError { return &types.AppError{ Error: types.ErrorJSON{Err: err}, Status: status, diff --git a/api/measurements.go b/api/measurements.go index 0e708b6..cfcff88 100644 --- a/api/measurements.go +++ b/api/measurements.go @@ -12,46 +12,49 @@ import ( "github.com/thermokarst/bactdb/types" ) +// MeasurementService provides for CRUD operations. type MeasurementService struct{} -func (s MeasurementService) Unmarshal(b []byte) (types.Entity, error) { +// Unmarshal satisfies interface Updater and interface Creater. +func (m MeasurementService) Unmarshal(b []byte) (types.Entity, error) { var mj payloads.Measurement err := json.Unmarshal(b, &mj) return &mj, err } +// List lists all measurements. func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) + return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError) } var opt helpers.MeasurementListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } measurements, err := models.ListMeasurements(opt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - char_opts, err := models.CharacteristicOptsFromMeasurements(opt) + charOpts, err := models.CharacteristicOptsFromMeasurements(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristics, err := models.ListCharacteristics(*char_opts, claims) + characteristics, err := models.ListCharacteristics(*charOpts, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strain_opts, err := models.StrainOptsFromMeasurements(opt) + strainOpts, err := models.StrainOptsFromMeasurements(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains, err := models.ListStrains(*strain_opts, claims) + strains, err := models.ListStrains(*strainOpts, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Measurements{ @@ -63,10 +66,11 @@ func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.E return &payload, nil } +// Get retrieves a single measurement. func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { measurement, err := models.GetMeasurement(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Measurement{ @@ -76,33 +80,34 @@ func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (t return &payload, nil } -func (s MeasurementService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { +// Update modifies a single measurement. +func (m MeasurementService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Measurement) payload.Measurement.UpdatedBy = claims.Sub - payload.Measurement.Id = id + payload.Measurement.ID = id if payload.Measurement.TextMeasurementType.Valid { - id, err := models.GetTextMeasurementTypeId(payload.Measurement.TextMeasurementType.String) + id, err := models.GetTextMeasurementTypeID(payload.Measurement.TextMeasurementType.String) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - payload.Measurement.TextMeasurementTypeId.Int64 = id - payload.Measurement.TextMeasurementTypeId.Valid = true + payload.Measurement.TextMeasurementTypeID.Int64 = id + payload.Measurement.TextMeasurementTypeID.Valid = true } // TODO: fix this count, err := models.DBH.Update(payload.Measurement.MeasurementBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return NewJSONError(errors.StrainNotUpdated, http.StatusBadRequest) + return newJSONError(errors.ErrStrainNotUpdated, http.StatusBadRequest) } measurement, err := models.GetMeasurement(id, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } payload.Measurement = measurement @@ -110,16 +115,18 @@ func (s MeasurementService) Update(id int64, e *types.Entity, genus string, clai return nil } +// Delete deletes a single measurement. func (m MeasurementService) Delete(id int64, genus string, claims *types.Claims) *types.AppError { q := `DELETE FROM measurements WHERE id=$1;` // TODO: fix this _, err := models.DBH.Exec(q, id) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } return nil } +// Create initializes a new measurement. func (m MeasurementService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Measurement) payload.Measurement.CreatedBy = claims.Sub @@ -127,7 +134,7 @@ func (m MeasurementService) Create(e *types.Entity, genus string, claims *types. // TODO: fix this if err := models.DBH.Insert(payload.Measurement.MeasurementBase); err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } return nil diff --git a/api/species.go b/api/species.go index fc4ea36..0e5adcc 100644 --- a/api/species.go +++ b/api/species.go @@ -12,36 +12,39 @@ import ( "github.com/thermokarst/bactdb/types" ) +// SpeciesService provides for CRUD operations type SpeciesService struct{} +// Unmarshal satisfies interface Updater and interface Creater func (s SpeciesService) Unmarshal(b []byte) (types.Entity, error) { var sj payloads.Species err := json.Unmarshal(b, &sj) return &sj, err } +// List lists species func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) + return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } species, err := models.ListSpecies(opt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains_opt, err := models.StrainOptsFromSpecies(opt) + strainsOpt, err := models.StrainOptsFromSpecies(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains, err := models.ListStrains(*strains_opt, claims) + strains, err := models.ListStrains(*strainsOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.ManySpecies{ @@ -55,15 +58,16 @@ func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entit return &payload, nil } +// Get retrieves a single species func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { species, err := models.GetSpecies(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - strains, err := models.StrainsFromSpeciesId(id, genus, claims) + strains, err := models.StrainsFromSpeciesID(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Species{ @@ -77,36 +81,37 @@ func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types return &payload, nil } +// Update modifies an existing species func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Species) payload.Species.UpdatedBy = claims.Sub - payload.Species.Id = id + payload.Species.ID = id - genus_id, err := models.GenusIdFromName(genus) + genusID, err := models.GenusIDFromName(genus) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - payload.Species.SpeciesBase.GenusID = genus_id + payload.Species.SpeciesBase.GenusID = genusID // TODO: fix this count, err := models.DBH.Update(payload.Species.SpeciesBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return NewJSONError(errors.SpeciesNotUpdated, http.StatusBadRequest) + return newJSONError(errors.ErrSpeciesNotUpdated, http.StatusBadRequest) } // Reload to send back down the wire species, err := models.GetSpecies(id, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - strains, err := models.StrainsFromSpeciesId(id, genus, claims) + strains, err := models.StrainsFromSpeciesID(id, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } payload.Species = species @@ -118,27 +123,28 @@ func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims * return nil } +// Create initializes a new species func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Species) payload.Species.CreatedBy = claims.Sub payload.Species.UpdatedBy = claims.Sub - genus_id, err := models.GenusIdFromName(genus) + genusID, err := models.GenusIDFromName(genus) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - payload.Species.SpeciesBase.GenusID = genus_id + payload.Species.SpeciesBase.GenusID = genusID // TODO: fix this err = models.DBH.Insert(payload.Species.SpeciesBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } // Reload to send back down the wire - species, err := models.GetSpecies(payload.Species.Id, genus, claims) + species, err := models.GetSpecies(payload.Species.ID, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } // Note, no strains when new species diff --git a/api/strains.go b/api/strains.go index bb3ad22..15b8b6f 100644 --- a/api/strains.go +++ b/api/strains.go @@ -12,69 +12,72 @@ import ( "github.com/thermokarst/bactdb/types" ) +// StrainService provides for CRUD operations type StrainService struct{} +// Unmarshal satisfies interface Updater and interface Creater func (s StrainService) Unmarshal(b []byte) (types.Entity, error) { var sj payloads.Strain err := json.Unmarshal(b, &sj) return &sj, err } +// List lists all strains func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) + return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } strains, err := models.ListStrains(opt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species_opt, err := models.SpeciesOptsFromStrains(opt) + speciesOpt, err := models.SpeciesOptsFromStrains(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species, err := models.ListSpecies(*species_opt, claims) + species, err := models.ListSpecies(*speciesOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) + characteristicsOpt, err := models.CharacteristicsOptsFromStrains(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) + characteristics, err := models.ListCharacteristics(*characteristicsOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristic_ids := []int64{} + characteristicIDs := []int64{} for _, c := range *characteristics { - characteristic_ids = append(characteristic_ids, c.Id) + characteristicIDs = append(characteristicIDs, c.ID) } - strain_ids := []int64{} + strainIDs := []int64{} for _, s := range *strains { - strain_ids = append(strain_ids, s.Id) + strainIDs = append(strainIDs, s.ID) } - measurement_opt := helpers.MeasurementListOptions{ + measurementOpt := helpers.MeasurementListOptions{ ListOptions: helpers.ListOptions{ Genus: opt.Genus, }, - Strains: strain_ids, - Characteristics: characteristic_ids, + Strains: strainIDs, + Characteristics: characteristicIDs, } - measurements, err := models.ListMeasurements(measurement_opt, claims) + measurements, err := models.ListMeasurements(measurementOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } payload := payloads.Strains{ @@ -90,51 +93,52 @@ func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity return &payload, nil } +// Get retrieves a single strain func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) { strain, err := models.GetStrain(id, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + species, err := models.GetSpecies(strain.SpeciesID, genus, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - opt := helpers.ListOptions{Genus: genus, Ids: []int64{id}} - characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt) + opt := helpers.ListOptions{Genus: genus, IDs: []int64{id}} + characteristicsOpt, err := models.CharacteristicsOptsFromStrains(opt) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristics, err := models.ListCharacteristics(*characteristics_opt, claims) + characteristics, err := models.ListCharacteristics(*characteristicsOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - characteristic_ids := []int64{} + characteristicIDs := []int64{} for _, c := range *characteristics { - characteristic_ids = append(characteristic_ids, c.Id) + characteristicIDs = append(characteristicIDs, c.ID) } - measurement_opt := helpers.MeasurementListOptions{ + measurementOpt := helpers.MeasurementListOptions{ ListOptions: helpers.ListOptions{ Genus: genus, }, Strains: []int64{id}, - Characteristics: characteristic_ids, + Characteristics: characteristicIDs, } - measurements, err := models.ListMeasurements(measurement_opt, claims) + measurements, err := models.ListMeasurements(measurementOpt, claims) if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - var many_species models.ManySpecies = []*models.Species{species} + var manySpecies models.ManySpecies = []*models.Species{species} payload := payloads.Strain{ Strain: strain, - Species: &many_species, + Species: &manySpecies, Characteristics: characteristics, Measurements: measurements, Meta: &models.StrainMeta{ @@ -145,35 +149,36 @@ func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types. return &payload, nil } +// Update modifies an existing strain func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Strain) payload.Strain.UpdatedBy = claims.Sub - payload.Strain.Id = id + payload.Strain.ID = id // TODO: fix this count, err := models.DBH.Update(payload.Strain.StrainBase) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { // TODO: fix this - return NewJSONError(errors.StrainNotUpdated, http.StatusBadRequest) + return newJSONError(errors.ErrStrainNotUpdated, http.StatusBadRequest) } strain, err := models.GetStrain(id, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + species, err := models.GetSpecies(strain.SpeciesID, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - var many_species models.ManySpecies = []*models.Species{species} + var manySpecies models.ManySpecies = []*models.Species{species} payload.Strain = strain - payload.Species = &many_species + payload.Species = &manySpecies payload.Meta = &models.StrainMeta{ CanAdd: helpers.CanAdd(claims), } @@ -181,6 +186,7 @@ func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *t return nil } +// Create initializes a new strain func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError { payload := (*e).(*payloads.Strain) payload.Strain.CreatedBy = claims.Sub @@ -188,23 +194,23 @@ func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claim // TODO: fix this if err := models.DBH.Insert(payload.Strain.StrainBase); err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - strain, err := models.GetStrain(payload.Strain.Id, genus, claims) + strain, err := models.GetStrain(payload.Strain.ID, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - species, err := models.GetSpecies(strain.SpeciesId, genus, claims) + species, err := models.GetSpecies(strain.SpeciesID, genus, claims) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - var many_species models.ManySpecies = []*models.Species{species} + var manySpecies models.ManySpecies = []*models.Species{species} payload.Strain = strain - payload.Species = &many_species + payload.Species = &manySpecies payload.Meta = &models.StrainMeta{ CanAdd: helpers.CanAdd(claims), } diff --git a/api/users.go b/api/users.go index d0053ed..8abb266 100644 --- a/api/users.go +++ b/api/users.go @@ -20,24 +20,28 @@ import ( ) var ( + // MgAccts is a map of Mailgun accounts. MgAccts = make(map[string]mailgun.Mailgun) ) +// UserService provides for CRUD operations. type UserService struct{} +// Unmarshal satisfies interface Updater and interface Creater. func (u UserService) Unmarshal(b []byte) (types.Entity, error) { var uj payloads.User err := json.Unmarshal(b, &uj) return &uj, err } +// List lists all users. func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) { if val == nil { - return nil, NewJSONError(errors.MustProvideOptions, http.StatusInternalServerError) + return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError) } var opt helpers.ListOptions if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } // TODO: fix this @@ -48,16 +52,23 @@ func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, WHERE verified IS TRUE AND deleted_at IS NULL;` if err := models.DBH.Select(&users, sql); err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } - return &users, nil + payload := payloads.Users{ + Users: &users, + Meta: &models.UserMeta{ + CanAdd: claims.Role == "A", + }, + } + return &payload, nil } +// Get retrieves a single user. func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.Entity, *types.AppError) { - user, err := models.DbGetUserById(id) + user, err := models.DbGetUserByID(id) user.Password = "" if err != nil { - return nil, NewJSONError(err, http.StatusInternalServerError) + return nil, newJSONError(err, http.StatusInternalServerError) } user.CanEdit = claims.Role == "A" || id == claims.Sub @@ -71,17 +82,18 @@ func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.En return &payload, nil } +// Update modifies an existing user. func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *types.Claims) *types.AppError { user := (*e).(*payloads.User).User - original_user, err := models.DbGetUserById(id) + originalUser, err := models.DbGetUserByID(id) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - user.Id = id - user.Password = original_user.Password - user.Verified = original_user.Verified + user.ID = id + user.Password = originalUser.Password + user.Verified = originalUser.Verified user.UpdatedAt = helpers.CurrentTime() if err := user.Validate(); err != nil { @@ -92,15 +104,16 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ count, err := models.DBH.Update(user) user.Password = "" if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { - return NewJSONError(errors.UserNotUpdated, http.StatusInternalServerError) + return newJSONError(errors.ErrUserNotUpdated, http.StatusInternalServerError) } return nil } +// Create initializes a new user. func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) *types.AppError { user := (*e).(*payloads.User).User if err := user.Validate(); err != nil { @@ -111,20 +124,20 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) user.UpdatedAt = ct hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } user.Password = string(hash) user.Role = "R" user.Verified = false // TODO: fix this - if err := models.DBH.Insert(user); err != nil { + if err := models.DBH.Insert(user.UserBase); err != nil { if err, ok := err.(*pq.Error); ok { if err.Code == "23505" { - return NewJSONError(errors.EmailAddressTaken, http.StatusInternalServerError) + return newJSONError(errors.ErrEmailAddressTaken, http.StatusInternalServerError) } } - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } user.Password = "password" // don't want to send the hashed PW back to the client @@ -133,12 +146,12 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) // TODO: move helpers.GenerateNonce nonce, err := helpers.GenerateNonce() if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } // TODO: fix this - _, err = models.DBH.Exec(q, user.Id, nonce, claims.Ref, ct) + _, err = models.DBH.Exec(q, user.ID, nonce, claims.Ref, ct) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } // Send out confirmation email @@ -157,34 +170,35 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) _, _, err := mg.Send(m) if err != nil { log.Printf("%+v\n", err) - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } } return nil } +// HandleUserVerify is a HTTP handler for verifiying a user. func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { // TODO: clean this up nonce := mux.Vars(r)["Nonce"] q := `SELECT user_id, referer FROM verification WHERE nonce=$1;` var ver struct { - User_id int64 + UserID int64 Referer string } if err := models.DBH.SelectOne(&ver, q, nonce); err != nil { log.Print(err) - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - if ver.User_id == 0 { - return NewJSONError(errors.UserNotFound, http.StatusInternalServerError) + if ver.UserID == 0 { + return newJSONError(errors.ErrUserNotFound, http.StatusInternalServerError) } var user models.User - if err := models.DBH.Get(&user, ver.User_id); err != nil { - return NewJSONError(err, http.StatusInternalServerError) + if err := models.DBH.Get(&user, ver.UserID); err != nil { + return newJSONError(err, http.StatusInternalServerError) } user.UpdatedAt = helpers.CurrentTime() @@ -192,39 +206,40 @@ func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError { count, err := models.DBH.Update(&user) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } if count != 1 { - return NewJSONError(errors.UserNotUpdated, http.StatusInternalServerError) + return newJSONError(errors.ErrUserNotUpdated, http.StatusInternalServerError) } q = `DELETE FROM verification WHERE user_id=$1;` - _, err = models.DBH.Exec(q, user.Id) + _, err = models.DBH.Exec(q, user.ID) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } fmt.Fprintln(w, `{"msg":"All set! Please log in."}`) return nil } +// HandleUserLockout is a HTTP handler for unlocking a user's account. func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError { email := r.FormValue("email") if email == "" { - return NewJSONError(errors.UserMissingEmail, http.StatusInternalServerError) + return newJSONError(errors.ErrUserMissingEmail, http.StatusInternalServerError) } token, err := auth.Middleware.CreateToken(email) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } origin := r.Header.Get("Origin") - hostUrl, err := url.Parse(origin) + hostURL, err := url.Parse(origin) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } - hostUrl.Path += "/users/lockoutauthenticate" + hostURL.Path += "/users/lockoutauthenticate" params := url.Values{} params.Add("token", token) - hostUrl.RawQuery = params.Encode() + hostURL.RawQuery = params.Encode() // Send out email // TODO: clean this up @@ -237,12 +252,12 @@ func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError { "address was used in an account lockout request at %s. Please visit "+ "this URL to complete the process: %s. If you did not request help "+ "with a lockout, please disregard this message.", - mg.Domain(), hostUrl.String()) + mg.Domain(), hostURL.String()) m := mailgun.NewMessage(sender, subject, message, recipient) _, _, err := mg.Send(m) if err != nil { log.Printf("%+v\n", err) - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } } diff --git a/auth/auth.go b/auth/auth.go index eac6424..a4e777b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -9,8 +9,10 @@ import ( ) var ( + // Middleware is for JWT Middleware *jwt.Middleware - Config *jwt.Config = &jwt.Config{ + // Config handles JWT middleware configuration + Config = &jwt.Config{ Secret: os.Getenv("SECRET"), Auth: models.DbAuthenticate, Claims: claimsFunc, @@ -27,7 +29,7 @@ func claimsFunc(email string) (map[string]interface{}, error) { return map[string]interface{}{ "name": user.Name, "iss": "bactdb", - "sub": user.Id, + "sub": user.ID, "role": user.Role, "iat": currentTime.Unix(), "exp": currentTime.Add(time.Minute * 60).Unix(), diff --git a/errors/auth.go b/errors/auth.go index 2471797..2602f55 100644 --- a/errors/auth.go +++ b/errors/auth.go @@ -3,5 +3,6 @@ package errors import "errors" var ( - ExpiredToken = errors.New("this token has expired") + // ErrExpiredToken when expired token. + ErrExpiredToken = errors.New("this token has expired") ) diff --git a/errors/characteristics.go b/errors/characteristics.go index abeae29..f625887 100644 --- a/errors/characteristics.go +++ b/errors/characteristics.go @@ -3,6 +3,8 @@ package errors import "errors" var ( - CharacteristicNotFound = errors.New("Characteristic not found") - CharacteristicNotUpdated = errors.New("Characteristic not updated") + // ErrCharacteristicNotFound when not found. + ErrCharacteristicNotFound = errors.New("Characteristic not found") + // ErrCharacteristicNotUpdated when not updated. + ErrCharacteristicNotUpdated = errors.New("Characteristic not updated") ) diff --git a/errors/helpers.go b/errors/helpers.go index 7135380..1a4a599 100644 --- a/errors/helpers.go +++ b/errors/helpers.go @@ -3,5 +3,6 @@ package errors import "errors" var ( - MustProvideOptions = errors.New("Must provide necessary options") + // ErrMustProvideOptions when missing options. + ErrMustProvideOptions = errors.New("Must provide necessary options") ) diff --git a/errors/measurements.go b/errors/measurements.go index d034d59..1a65f4a 100644 --- a/errors/measurements.go +++ b/errors/measurements.go @@ -3,5 +3,6 @@ package errors import "errors" var ( - MeasurementNotFound = errors.New("Measurement not found") + // ErrMeasurementNotFound when not found. + ErrMeasurementNotFound = errors.New("Measurement not found") ) diff --git a/errors/species.go b/errors/species.go index fbd8379..0500fd7 100644 --- a/errors/species.go +++ b/errors/species.go @@ -3,6 +3,8 @@ package errors import "errors" var ( - SpeciesNotFound = errors.New("Species not found") - SpeciesNotUpdated = errors.New("Species not updated") + // ErrSpeciesNotFound when not found. + ErrSpeciesNotFound = errors.New("Species not found") + // ErrSpeciesNotUpdated when not updated. + ErrSpeciesNotUpdated = errors.New("Species not updated") ) diff --git a/errors/strains.go b/errors/strains.go index 4fda22e..fd0ec6f 100644 --- a/errors/strains.go +++ b/errors/strains.go @@ -3,6 +3,8 @@ package errors import "errors" var ( - StrainNotFound = errors.New("Strain not found") - StrainNotUpdated = errors.New("Strain not updated") + // ErrStrainNotFound when not found. + ErrStrainNotFound = errors.New("Strain not found") + // ErrStrainNotUpdated when not updated. + ErrStrainNotUpdated = errors.New("Strain not updated") ) diff --git a/errors/types.go b/errors/types.go index b237e47..fe76b97 100644 --- a/errors/types.go +++ b/errors/types.go @@ -3,5 +3,6 @@ package errors import "errors" var ( - SourceNotByteSlice = errors.New("Scan source was not []byte") + // ErrSourceNotByteSlice when not a byte-slice. + ErrSourceNotByteSlice = errors.New("Scan source was not []byte") ) diff --git a/errors/users.go b/errors/users.go index 8a0a59d..eaa7cdc 100644 --- a/errors/users.go +++ b/errors/users.go @@ -3,9 +3,14 @@ package errors import "errors" var ( - UserNotFound = errors.New("No user found") - UserNotUpdated = errors.New("Count 0") - UserMissingEmail = errors.New("Missing email") - InvalidEmailOrPassword = errors.New("Invalid email or password") - EmailAddressTaken = errors.New("Email address is already registered") + // ErrUserNotFound when not found. + ErrUserNotFound = errors.New("No user found") + // ErrUserNotUpdated when not updated. + ErrUserNotUpdated = errors.New("Count 0") + // ErrUserMissingEmail when missing email. + ErrUserMissingEmail = errors.New("Missing email") + // ErrInvalidEmailOrPassword when invalid login credentials. + ErrInvalidEmailOrPassword = errors.New("Invalid email or password") + // ErrEmailAddressTaken when email already registered. + ErrEmailAddressTaken = errors.New("Email address is already registered") ) diff --git a/handlers/handlers.go b/handlers/handlers.go index 46c5516..6380c67 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -32,12 +32,13 @@ func verifyClaims(claims []byte, r *http.Request) error { return err } if currentTime.After(time.Unix(c.Exp, 0)) { - return errors.ExpiredToken + return errors.ErrExpiredToken } context.Set(r, "claims", c) return nil } +// Handler is the root HTTP handler for bactdb. func Handler() http.Handler { m := mux.NewRouter() userService := api.UserService{} @@ -67,25 +68,25 @@ func Handler() http.Handler { // Everything past this point requires a valid token routes := []r{ r{handleLister(userService), "GET", "/users"}, - r{handleGetter(userService), "GET", "/users/{Id:.+}"}, - r{handleUpdater(userService), "PUT", "/users/{Id:.+}"}, + r{handleGetter(userService), "GET", "/users/{ID:.+}"}, + r{handleUpdater(userService), "PUT", "/users/{ID:.+}"}, r{handleLister(speciesService), "GET", "/species"}, r{handleCreater(speciesService), "POST", "/species"}, - r{handleGetter(speciesService), "GET", "/species/{Id:.+}"}, - r{handleUpdater(speciesService), "PUT", "/species/{Id:.+}"}, + r{handleGetter(speciesService), "GET", "/species/{ID:.+}"}, + r{handleUpdater(speciesService), "PUT", "/species/{ID:.+}"}, r{handleLister(strainService), "GET", "/strains"}, r{handleCreater(strainService), "POST", "/strains"}, - r{handleGetter(strainService), "GET", "/strains/{Id:.+}"}, - r{handleUpdater(strainService), "PUT", "/strains/{Id:.+}"}, + r{handleGetter(strainService), "GET", "/strains/{ID:.+}"}, + r{handleUpdater(strainService), "PUT", "/strains/{ID:.+}"}, r{handleLister(characteristicService), "GET", "/characteristics"}, r{handleCreater(characteristicService), "POST", "/characteristics"}, - r{handleGetter(characteristicService), "GET", "/characteristics/{Id:.+}"}, - r{handleUpdater(characteristicService), "PUT", "/characteristics/{Id:.+}"}, + r{handleGetter(characteristicService), "GET", "/characteristics/{ID:.+}"}, + r{handleUpdater(characteristicService), "PUT", "/characteristics/{ID:.+}"}, r{handleLister(measurementService), "GET", "/measurements"}, r{handleCreater(measurementService), "POST", "/measurements"}, - r{handleGetter(measurementService), "GET", "/measurements/{Id:.+}"}, - r{handleUpdater(measurementService), "PUT", "/measurements/{Id:.+}"}, - r{handleDeleter(measurementService), "DELETE", "/measurements/{Id:.+}"}, + r{handleGetter(measurementService), "GET", "/measurements/{ID:.+}"}, + r{handleUpdater(measurementService), "PUT", "/measurements/{ID:.+}"}, + r{handleDeleter(measurementService), "DELETE", "/measurements/{ID:.+}"}, } for _, route := range routes { @@ -97,9 +98,9 @@ func Handler() http.Handler { func handleGetter(g api.Getter) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -111,7 +112,7 @@ func handleGetter(g api.Getter) errorHandler { data, err := e.Marshal() if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -131,7 +132,7 @@ func handleLister(l api.Lister) errorHandler { } data, err := es.Marshal() if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -140,19 +141,19 @@ func handleLister(l api.Lister) errorHandler { func handleUpdater(u api.Updater) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } e, err := u.Unmarshal(bodyBytes) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -164,7 +165,7 @@ func handleUpdater(u api.Updater) errorHandler { data, err := e.Marshal() if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -175,12 +176,12 @@ func handleCreater(c api.Creater) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } e, err := c.Unmarshal(bodyBytes) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -192,7 +193,7 @@ func handleCreater(c api.Creater) errorHandler { data, err := e.Marshal() if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } w.Write(data) return nil @@ -201,9 +202,9 @@ func handleCreater(c api.Creater) errorHandler { func handleDeleter(d api.Deleter) errorHandler { return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } claims := helpers.GetClaims(r) @@ -294,14 +295,14 @@ func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func tokenRefresh(j *jwt.Middleware) errorHandler { t := func(w http.ResponseWriter, r *http.Request) *types.AppError { claims := helpers.GetClaims(r) - user, err := models.DbGetUserById(claims.Sub) + user, err := models.DbGetUserByID(claims.Sub) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } user.Password = "" token, err := auth.Middleware.CreateToken(user.Email) if err != nil { - return NewJSONError(err, http.StatusInternalServerError) + return newJSONError(err, http.StatusInternalServerError) } data, _ := json.Marshal(struct { Token string `json:"token"` diff --git a/handlers/helpers.go b/handlers/helpers.go index acaa24a..c43f52b 100644 --- a/handlers/helpers.go +++ b/handlers/helpers.go @@ -2,7 +2,7 @@ package handlers import "github.com/thermokarst/bactdb/types" -func NewJSONError(err error, status int) *types.AppError { +func newJSONError(err error, status int) *types.AppError { return &types.AppError{ Error: types.ErrorJSON{Err: err}, Status: status, diff --git a/helpers/helpers.go b/helpers/helpers.go index c47573a..65f56d2 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -14,46 +14,30 @@ import ( ) var ( + // StatusUnprocessableEntity is the HTTP status when Unprocessable Entity. StatusUnprocessableEntity = 422 - MustProvideAValue = "Must provide a value" - SchemaDecoder = schema.NewDecoder() + // MustProvideAValue when value required. + MustProvideAValue = "Must provide a value" + // SchemaDecoder for decoding schemas. + SchemaDecoder = schema.NewDecoder() ) // ListOptions specifies general pagination options for fetching a list of results type ListOptions struct { PerPage int64 `url:",omitempty" json:",omitempty"` Page int64 `url:",omitempty" json:",omitempty"` - Ids []int64 `url:",omitempty" json:",omitempty" schema:"ids[]"` + IDs []int64 `url:",omitempty" json:",omitempty" schema:"ids[]"` Genus string } -func (o ListOptions) PageOrDefault() int64 { - if o.Page <= 0 { - return 1 - } - return o.Page -} - -func (o ListOptions) Offset() int64 { - return (o.PageOrDefault() - 1) * o.PerPageOrDefault() -} - -func (o ListOptions) PerPageOrDefault() int64 { - if o.PerPage <= 0 { - return DefaultPerPage - } - return o.PerPage -} - +// MeasurementListOptions is an extension of ListOptions. type MeasurementListOptions struct { ListOptions Strains []int64 `schema:"strain_ids"` Characteristics []int64 `schema:"characteristic_ids"` } -// DefaultPerPage is the default number of items to return in a paginated result set -const DefaultPerPage = 10 - +// ValsIn emits X IN (A, B, C) SQL statements func ValsIn(attribute string, values []int64, vals *[]interface{}, counter *int64) string { if len(values) == 1 { return fmt.Sprintf("%v=%v", attribute, values[0]) @@ -69,6 +53,7 @@ func ValsIn(attribute string, values []int64, vals *[]interface{}, counter *int6 return m } +// CurrentTime returns current time func CurrentTime() types.NullTime { return types.NullTime{ pq.NullTime{ @@ -78,8 +63,9 @@ func CurrentTime() types.NullTime { } } -// TODO: move this +// GenerateNonce generates a nonce func GenerateNonce() (string, error) { + // TODO: move this b := make([]byte, 32) _, err := rand.Read(b) if err != nil { @@ -88,6 +74,7 @@ func GenerateNonce() (string, error) { return base64.URLEncoding.EncodeToString(b), nil } +// GetClaims gets request claims from Authorization header func GetClaims(r *http.Request) types.Claims { con := context.Get(r, "claims") var claims types.Claims @@ -101,10 +88,12 @@ func GetClaims(r *http.Request) types.Claims { return claims } +// CanAdd is an authorization helper for adding new entities func CanAdd(claims *types.Claims) bool { return claims.Role == "A" || claims.Role == "W" } +// CanEdit is an authorization helper for editing entities func CanEdit(claims *types.Claims, author int64) bool { return claims.Sub == author || claims.Role == "A" } diff --git a/main.go b/main.go index e52e2eb..53989ea 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,7 @@ import ( "github.com/thermokarst/bactdb/models" ) -func main() { +func init() { var connectOnce sync.Once connectOnce.Do(func() { var err error @@ -38,7 +38,9 @@ func main() { models.DB.TraceOn("[modl]", log.New(os.Stdout, "bactdb:", log.Lmicroseconds)) models.DB.Db = models.DB.Dbx.DB }) +} +func main() { app := cli.NewApp() app.Name = "bactdb" app.Usage = "a database for bacteria" @@ -148,7 +150,7 @@ func cmdMigrateDb(c *cli.Context) { // varargs don't seem to work here, loop instead for _, user := range users { // TODO: look into this - if err := models.DBH.Insert(user); err != nil { + if err := models.DBH.Insert(user.UserBase); err != nil { log.Fatal("Couldn't restore user: ", err) } } diff --git a/models/characteristics.go b/models/characteristics.go index a155bc5..cb56ecc 100644 --- a/models/characteristics.go +++ b/models/characteristics.go @@ -11,9 +11,10 @@ import ( ) func init() { - DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") + DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "ID") } +// PreInsert is a modl hook func (c *CharacteristicBase) PreInsert(e modl.SqlExecutor) error { ct := helpers.CurrentTime() c.CreatedAt = ct @@ -21,15 +22,17 @@ func (c *CharacteristicBase) PreInsert(e modl.SqlExecutor) error { return nil } +// PreUpdate is a modl hook func (c *CharacteristicBase) PreUpdate(e modl.SqlExecutor) error { c.UpdatedAt = helpers.CurrentTime() return nil } +// CharacteristicBase is what the DB expects for write operations type CharacteristicBase struct { - Id int64 `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` CharacteristicName string `db:"characteristic_name" json:"characteristicName"` - CharacteristicTypeId int64 `db:"characteristic_type_id" json:"-"` + CharacteristicTypeID int64 `db:"characteristic_type_id" json:"-"` SortOrder types.NullInt64 `db:"sort_order" json:"sortOrder"` CreatedAt types.NullTime `db:"created_at" json:"createdAt"` UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` @@ -39,6 +42,8 @@ type CharacteristicBase struct { DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` } +// Characteristic is what the DB expects for read operations, and is what the API +// expects to return to the requester. type Characteristic struct { *CharacteristicBase Measurements types.NullSliceInt64 `db:"measurements" json:"measurements"` @@ -47,12 +52,15 @@ type Characteristic struct { CanEdit bool `db:"-" json:"canEdit"` } +// Characteristics are multiple characteristic entities type Characteristics []*Characteristic +// CharacteristicMeta stashes some metadata related to the entity type CharacteristicMeta struct { CanAdd bool `json:"canAdd"` } +// ListCharacteristics returns all characteristics func ListCharacteristics(opt helpers.ListOptions, claims *types.Claims) (*Characteristics, error) { var vals []interface{} @@ -66,9 +74,9 @@ func ListCharacteristics(opt helpers.ListOptions, claims *types.Claims) (*Charac INNER JOIN characteristic_types ct ON ct.id=c.characteristic_type_id` vals = append(vals, opt.Genus) - if len(opt.Ids) != 0 { + if len(opt.IDs) != 0 { var counter int64 = 2 - w := helpers.ValsIn("c.id", opt.Ids, &vals, &counter) + w := helpers.ValsIn("c.id", opt.IDs, &vals, &counter) q += fmt.Sprintf(" WHERE %s", w) } @@ -89,97 +97,106 @@ func ListCharacteristics(opt helpers.ListOptions, claims *types.Claims) (*Charac return &characteristics, nil } +// StrainOptsFromCharacteristics returns the options for finding all related strains +// for a set of characteristics. func StrainOptsFromCharacteristics(opt helpers.ListOptions) (*helpers.ListOptions, error) { - relatedStrainIds := make([]int64, 0) + var relatedStrainIDs []int64 baseQ := `SELECT DISTINCT m.strain_id FROM measurements m INNER JOIN strains st ON st.id=m.strain_id INNER JOIN species sp ON sp.id=st.species_id INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` - if opt.Ids == nil { + if opt.IDs == nil { q := fmt.Sprintf("%s;", baseQ) - if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { + if err := DBH.Select(&relatedStrainIDs, q, opt.Genus); err != nil { return nil, err } } else { var vals []interface{} var count int64 = 2 vals = append(vals, opt.Genus) - q := fmt.Sprintf("%s WHERE %s ", baseQ, helpers.ValsIn("m.characteristic_id", opt.Ids, &vals, &count)) + q := fmt.Sprintf("%s WHERE %s ", baseQ, helpers.ValsIn("m.characteristic_id", opt.IDs, &vals, &count)) - if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { + if err := DBH.Select(&relatedStrainIDs, q, vals...); err != nil { return nil, err } } - return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: relatedStrainIDs}, nil } +// MeasurementOptsFromCharacteristics returns the options for finding all related +// measurements for a set of characteristics. func MeasurementOptsFromCharacteristics(opt helpers.ListOptions) (*helpers.MeasurementListOptions, error) { - relatedMeasurementIds := make([]int64, 0) + var relatedMeasurementIDs []int64 baseQ := `SELECT m.id FROM measurements m INNER JOIN strains st ON st.id=m.strain_id INNER JOIN species sp ON sp.id=st.species_id INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1)` - if opt.Ids == nil { + if opt.IDs == nil { q := fmt.Sprintf("%s;", baseQ) - if err := DBH.Select(&relatedMeasurementIds, q, opt.Genus); err != nil { + if err := DBH.Select(&relatedMeasurementIDs, q, opt.Genus); err != nil { return nil, err } } else { var vals []interface{} var count int64 = 2 vals = append(vals, opt.Genus) - q := fmt.Sprintf("%s WHERE %s;", baseQ, helpers.ValsIn("characteristic_id", opt.Ids, &vals, &count)) + q := fmt.Sprintf("%s WHERE %s;", baseQ, helpers.ValsIn("characteristic_id", opt.IDs, &vals, &count)) - if err := DBH.Select(&relatedMeasurementIds, q, vals...); err != nil { + if err := DBH.Select(&relatedMeasurementIDs, q, vals...); err != nil { return nil, err } } - return &helpers.MeasurementListOptions{ListOptions: helpers.ListOptions{Genus: opt.Genus, Ids: relatedMeasurementIds}, Strains: nil, Characteristics: nil}, nil + return &helpers.MeasurementListOptions{ListOptions: helpers.ListOptions{Genus: opt.Genus, IDs: relatedMeasurementIDs}, Strains: nil, Characteristics: nil}, nil } -func StrainsFromCharacteristicId(id int64, genus string, claims *types.Claims) (*Strains, *helpers.ListOptions, error) { +// StrainsFromCharacteristicID returns a set of strains (as well as the options for +// finding those strains) for a particular characteristic. +func StrainsFromCharacteristicID(id int64, genus string, claims *types.Claims) (*Strains, *helpers.ListOptions, error) { opt := helpers.ListOptions{ Genus: genus, - Ids: []int64{id}, + IDs: []int64{id}, } - strains_opt, err := StrainOptsFromCharacteristics(opt) + strainsOpt, err := StrainOptsFromCharacteristics(opt) if err != nil { return nil, nil, err } - strains, err := ListStrains(*strains_opt, claims) + strains, err := ListStrains(*strainsOpt, claims) if err != nil { return nil, nil, err } - return strains, strains_opt, nil + return strains, strainsOpt, nil } -func MeasurementsFromCharacteristicId(id int64, genus string, claims *types.Claims) (*Measurements, *helpers.MeasurementListOptions, error) { +// MeasurementsFromCharacteristicID returns a set of measurements (as well as the +// options for finding those measurements) for a particular characteristic. +func MeasurementsFromCharacteristicID(id int64, genus string, claims *types.Claims) (*Measurements, *helpers.MeasurementListOptions, error) { opt := helpers.ListOptions{ Genus: genus, - Ids: []int64{id}, + IDs: []int64{id}, } - measurement_opt, err := MeasurementOptsFromCharacteristics(opt) + measurementOpt, err := MeasurementOptsFromCharacteristics(opt) if err != nil { return nil, nil, err } - measurements, err := ListMeasurements(*measurement_opt, claims) + measurements, err := ListMeasurements(*measurementOpt, claims) if err != nil { return nil, nil, err } - return measurements, measurement_opt, nil + return measurements, measurementOpt, nil } +// GetCharacteristic returns a particular characteristic. func GetCharacteristic(id int64, genus string, claims *types.Claims) (*Characteristic, error) { var characteristic Characteristic q := `SELECT c.*, ct.characteristic_type_name, @@ -194,7 +211,7 @@ func GetCharacteristic(id int64, genus string, claims *types.Claims) (*Character GROUP BY c.id, ct.characteristic_type_name;` if err := DBH.SelectOne(&characteristic, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, errors.CharacteristicNotFound + return nil, errors.ErrCharacteristicNotFound } return nil, err } @@ -204,6 +221,8 @@ func GetCharacteristic(id int64, genus string, claims *types.Claims) (*Character return &characteristic, nil } +// InsertOrGetCharacteristicType performs an UPSERT operation on the database +// for a characteristic type. func InsertOrGetCharacteristicType(val string, claims *types.Claims) (int64, error) { var id int64 q := `SELECT id FROM characteristic_types WHERE characteristic_type_name=$1;` diff --git a/models/database.go b/models/database.go index d243468..fd4febf 100644 --- a/models/database.go +++ b/models/database.go @@ -3,6 +3,8 @@ package models import "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" var ( - DB = &modl.DbMap{Dialect: modl.PostgresDialect{}} + // DB is a sqlx/modl database map. + DB = &modl.DbMap{Dialect: modl.PostgresDialect{}} + // DBH is a global database handler. DBH modl.SqlExecutor = DB ) diff --git a/models/measurements.go b/models/measurements.go index faa8ea2..3ccc945 100644 --- a/models/measurements.go +++ b/models/measurements.go @@ -12,9 +12,10 @@ import ( ) func init() { - DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") + DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "ID") } +// PreInsert is a modl hook. func (m *MeasurementBase) PreInsert(e modl.SqlExecutor) error { ct := helpers.CurrentTime() m.CreatedAt = ct @@ -22,32 +23,35 @@ func (m *MeasurementBase) PreInsert(e modl.SqlExecutor) error { return nil } +// PreUpdate is a modl hook. func (m *MeasurementBase) PreUpdate(e modl.SqlExecutor) error { m.UpdatedAt = helpers.CurrentTime() return nil } +// MeasurementBase is what the DB expects for write operations // There are three types of supported measurements: fixed-text, free-text, // & numerical. The table has a constraint that will allow at most one // for a particular combination of strain & characteristic. -// MeasurementBase is what the DB expects to see for inserts/updates type MeasurementBase struct { - Id int64 `json:"id,omitempty"` - StrainId int64 `db:"strain_id" json:"strain"` - CharacteristicId int64 `db:"characteristic_id" json:"characteristic"` - TextMeasurementTypeId types.NullInt64 `db:"text_measurement_type_id" json:"-"` + ID int64 `json:"id,omitempty"` + StrainID int64 `db:"strain_id" json:"strain"` + CharacteristicID int64 `db:"characteristic_id" json:"characteristic"` + TextMeasurementTypeID types.NullInt64 `db:"text_measurement_type_id" json:"-"` TxtValue types.NullString `db:"txt_value" json:"-"` NumValue types.NullFloat64 `db:"num_value" json:"-"` ConfidenceInterval types.NullFloat64 `db:"confidence_interval" json:"confidenceInterval"` - UnitTypeId types.NullInt64 `db:"unit_type_id" json:"-"` + UnitTypeID types.NullInt64 `db:"unit_type_id" json:"-"` Notes types.NullString `db:"notes" json:"notes"` - TestMethodId types.NullInt64 `db:"test_method_id" json:"-"` + TestMethodID types.NullInt64 `db:"test_method_id" json:"-"` CreatedAt types.NullTime `db:"created_at" json:"createdAt"` UpdatedAt types.NullTime `db:"updated_at" json:"updatedAt"` CreatedBy int64 `db:"created_by" json:"createdBy"` UpdatedBy int64 `db:"updated_by" json:"updatedBy"` } +// Measurement is what the DB expects for read operations, and is what the API +// expects to return to the requester. type Measurement struct { *MeasurementBase TextMeasurementType types.NullString `db:"text_measurement_type_name" json:"-"` @@ -56,8 +60,10 @@ type Measurement struct { CanEdit bool `db:"-" json:"canEdit"` } +// FakeMeasurement is a dummy struct to prevent infinite-loop/stack overflow on serialization. type FakeMeasurement Measurement +// MarshalJSON is custom JSON serialization to handle multi-type "Value". func (m *Measurement) MarshalJSON() ([]byte, error) { fm := FakeMeasurement(*m) return json.Marshal(struct { @@ -69,6 +75,7 @@ func (m *Measurement) MarshalJSON() ([]byte, error) { }) } +// UnmarshalJSON is custom JSON deserialization to handle multi-type "Value" func (m *Measurement) UnmarshalJSON(b []byte) error { var measurement struct { FakeMeasurement @@ -81,7 +88,7 @@ func (m *Measurement) UnmarshalJSON(b []byte) error { switch v := measurement.Value.(type) { case string: // Test if actually a lookup - id, err := GetTextMeasurementTypeId(v) + id, err := GetTextMeasurementTypeID(v) if err != nil { if err == sql.ErrNoRows { measurement.TxtValue = types.NullString{sql.NullString{String: v, Valid: true}} @@ -89,7 +96,7 @@ func (m *Measurement) UnmarshalJSON(b []byte) error { return err } } else { - measurement.TextMeasurementTypeId = types.NullInt64{sql.NullInt64{Int64: id, Valid: true}} + measurement.TextMeasurementTypeID = types.NullInt64{sql.NullInt64{Int64: id, Valid: true}} } case int64: measurement.NumValue = types.NullFloat64{sql.NullFloat64{Float64: float64(v), Valid: true}} @@ -102,6 +109,7 @@ func (m *Measurement) UnmarshalJSON(b []byte) error { return nil } +// Value returns the value of the measurement func (m *Measurement) Value() string { if m.TextMeasurementType.Valid { return m.TextMeasurementType.String @@ -115,12 +123,15 @@ func (m *Measurement) Value() string { return "" } +// Measurements are multiple measurement entities type Measurements []*Measurement +// MeasurementMeta stashes some metadata related to the entity type MeasurementMeta struct { CanAdd bool `json:"canAdd"` } +// ListMeasurements returns all measurements func ListMeasurements(opt helpers.MeasurementListOptions, claims *types.Claims) (*Measurements, error) { var vals []interface{} @@ -136,35 +147,35 @@ func ListMeasurements(opt helpers.MeasurementListOptions, claims *types.Claims) LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id` vals = append(vals, opt.Genus) - strainIds := len(opt.Strains) != 0 - charIds := len(opt.Characteristics) != 0 - ids := len(opt.Ids) != 0 + strainIDs := len(opt.Strains) != 0 + charIDs := len(opt.Characteristics) != 0 + ids := len(opt.IDs) != 0 - if strainIds || charIds || ids { + if strainIDs || charIDs || ids { var paramsCounter int64 = 2 q += "\nWHERE (" // Filter by strains - if strainIds { + if strainIDs { q += helpers.ValsIn("st.id", opt.Strains, &vals, ¶msCounter) } - if strainIds && (charIds || ids) { + if strainIDs && (charIDs || ids) { q += " AND " } // Filter by characteristics - if charIds { + if charIDs { q += helpers.ValsIn("c.id", opt.Characteristics, &vals, ¶msCounter) } - if charIds && ids { + if charIDs && ids { q += " AND " } // Get specific records if ids { - q += helpers.ValsIn("m.id", opt.Ids, &vals, ¶msCounter) + q += helpers.ValsIn("m.id", opt.IDs, &vals, ¶msCounter) } q += ")" } @@ -183,6 +194,7 @@ func ListMeasurements(opt helpers.MeasurementListOptions, claims *types.Claims) return &measurements, nil } +// GetMeasurement returns a particular measurement. func GetMeasurement(id int64, genus string, claims *types.Claims) (*Measurement, error) { var measurement Measurement @@ -199,7 +211,7 @@ func GetMeasurement(id int64, genus string, claims *types.Claims) (*Measurement, WHERE m.id=$2;` if err := DBH.SelectOne(&measurement, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, errors.MeasurementNotFound + return nil, errors.ErrMeasurementNotFound } return nil, err } @@ -209,15 +221,20 @@ func GetMeasurement(id int64, genus string, claims *types.Claims) (*Measurement, return &measurement, nil } +// CharacteristicOptsFromMeasurements returns the options for finding all related +// characteristics for a set of measurements. func CharacteristicOptsFromMeasurements(opt helpers.MeasurementListOptions) (*helpers.ListOptions, error) { - return &helpers.ListOptions{Genus: opt.Genus, Ids: opt.Characteristics}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: opt.Characteristics}, nil } +// StrainOptsFromMeasurements returns the options for finding all related +// strains from a set of measurements. func StrainOptsFromMeasurements(opt helpers.MeasurementListOptions) (*helpers.ListOptions, error) { - return &helpers.ListOptions{Genus: opt.Genus, Ids: opt.Strains}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: opt.Strains}, nil } -func GetTextMeasurementTypeId(val string) (int64, error) { +// GetTextMeasurementTypeID returns the ID for a particular text measurement type +func GetTextMeasurementTypeID(val string) (int64, error) { var id int64 q := `SELECT id FROM text_measurement_types WHERE text_measurement_name=$1;` diff --git a/models/species.go b/models/species.go index e5268f2..1992e49 100644 --- a/models/species.go +++ b/models/species.go @@ -12,9 +12,10 @@ import ( ) func init() { - DB.AddTableWithName(SpeciesBase{}, "species").SetKeys(true, "Id") + DB.AddTableWithName(SpeciesBase{}, "species").SetKeys(true, "ID") } +// PreInsert is a modl hook. func (s *SpeciesBase) PreInsert(e modl.SqlExecutor) error { ct := helpers.CurrentTime() s.CreatedAt = ct @@ -22,13 +23,15 @@ func (s *SpeciesBase) PreInsert(e modl.SqlExecutor) error { return nil } +// PreUpdate is a modl hook. func (s *SpeciesBase) PreUpdate(e modl.SqlExecutor) error { s.UpdatedAt = helpers.CurrentTime() return nil } +// SpeciesBase is what the DB expects for write operations. type SpeciesBase struct { - Id int64 `db:"id" json:"id"` + ID int64 `db:"id" json:"id"` GenusID int64 `db:"genus_id" json:"-"` SubspeciesSpeciesID types.NullInt64 `db:"subspecies_species_id" json:"-"` SpeciesName string `db:"species_name" json:"speciesName"` @@ -42,6 +45,8 @@ type SpeciesBase struct { DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` } +// Species is what the DB expects for read operations, and is what the API expects +// to return to the requester. type Species struct { *SpeciesBase GenusName string `db:"genus_name" json:"genusName"` @@ -51,57 +56,64 @@ type Species struct { CanEdit bool `db:"-" json:"canEdit"` } +// ManySpecies is multiple species entities. type ManySpecies []*Species +// SpeciesMeta stashes some metadata related to the entity. type SpeciesMeta struct { CanAdd bool `json:"canAdd"` } -func GenusIdFromName(genus_name string) (int64, error) { - var genus_id struct{ Id int64 } +// GenusIDFromName looks up the genus' ID. +func GenusIDFromName(genusName string) (int64, error) { + var genusID struct{ ID int64 } q := `SELECT id FROM genera WHERE LOWER(genus_name) = LOWER($1);` - if err := DBH.SelectOne(&genus_id, q, genus_name); err != nil { + if err := DBH.SelectOne(&genusID, q, genusName); err != nil { return 0, err } - return genus_id.Id, nil + return genusID.ID, nil } +// StrainOptsFromSpecies returns the options for finding all related strains for +// a set of species. func StrainOptsFromSpecies(opt helpers.ListOptions) (*helpers.ListOptions, error) { - relatedStrainIds := make([]int64, 0) + var relatedStrainIDs []int64 - if opt.Ids == nil { + if opt.IDs == nil { q := `SELECT DISTINCT st.id FROM strains st INNER JOIN species sp ON sp.id=st.species_id INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedStrainIds, q, opt.Genus); err != nil { + if err := DBH.Select(&relatedStrainIDs, q, opt.Genus); err != nil { return nil, err } } else { var vals []interface{} var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT id FROM strains WHERE %s;", helpers.ValsIn("species_id", opt.Ids, &vals, &count)) + q := fmt.Sprintf("SELECT DISTINCT id FROM strains WHERE %s;", helpers.ValsIn("species_id", opt.IDs, &vals, &count)) - if err := DBH.Select(&relatedStrainIds, q, vals...); err != nil { + if err := DBH.Select(&relatedStrainIDs, q, vals...); err != nil { return nil, err } } - return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedStrainIds}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: relatedStrainIDs}, nil } -func StrainsFromSpeciesId(id int64, genus string, claims *types.Claims) (*Strains, error) { +// StrainsFromSpeciesID returns the options for finding all related strains for a +// particular species. +func StrainsFromSpeciesID(id int64, genus string, claims *types.Claims) (*Strains, error) { opt := helpers.ListOptions{ Genus: genus, - Ids: []int64{id}, + IDs: []int64{id}, } - strains_opt, err := StrainOptsFromSpecies(opt) + strainsOpt, err := StrainOptsFromSpecies(opt) if err != nil { return nil, err } - strains, err := ListStrains(*strains_opt, claims) + strains, err := ListStrains(*strainsOpt, claims) if err != nil { return nil, err } @@ -109,6 +121,7 @@ func StrainsFromSpeciesId(id int64, genus string, claims *types.Claims) (*Strain return strains, nil } +// ListSpecies returns all species func ListSpecies(opt helpers.ListOptions, claims *types.Claims) (*ManySpecies, error) { var vals []interface{} @@ -120,10 +133,10 @@ func ListSpecies(opt helpers.ListOptions, claims *types.Claims) (*ManySpecies, e LEFT OUTER JOIN strains st ON st.species_id=sp.id` vals = append(vals, opt.Genus) - if len(opt.Ids) != 0 { + if len(opt.IDs) != 0 { var conds []string s := "sp.id IN (" - for i, id := range opt.Ids { + for i, id := range opt.IDs { s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 vals = append(vals, id) } @@ -147,6 +160,7 @@ func ListSpecies(opt helpers.ListOptions, claims *types.Claims) (*ManySpecies, e return &species, nil } +// GetSpecies returns a particular species. func GetSpecies(id int64, genus string, claims *types.Claims) (*Species, error) { var species Species q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, @@ -158,7 +172,7 @@ func GetSpecies(id int64, genus string, claims *types.Claims) (*Species, error) GROUP BY sp.id, g.genus_name;` if err := DBH.SelectOne(&species, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, errors.SpeciesNotFound + return nil, errors.ErrSpeciesNotFound } return nil, err } diff --git a/models/strains.go b/models/strains.go index 2269c12..b314d39 100644 --- a/models/strains.go +++ b/models/strains.go @@ -12,9 +12,10 @@ import ( ) func init() { - DB.AddTableWithName(StrainBase{}, "strains").SetKeys(true, "Id") + DB.AddTableWithName(StrainBase{}, "strains").SetKeys(true, "ID") } +// PreInsert is a modl hook. func (s *StrainBase) PreInsert(e modl.SqlExecutor) error { ct := helpers.CurrentTime() s.CreatedAt = ct @@ -22,14 +23,16 @@ func (s *StrainBase) PreInsert(e modl.SqlExecutor) error { return nil } +// PreUpdate is a modl hook. func (s *StrainBase) PreUpdate(e modl.SqlExecutor) error { s.UpdatedAt = helpers.CurrentTime() return nil } +// StrainBase is what the DB expects for write operations. type StrainBase struct { - Id int64 `db:"id" json:"id"` - SpeciesId int64 `db:"species_id" json:"species"` + ID int64 `db:"id" json:"id"` + SpeciesID int64 `db:"species_id" json:"species"` StrainName string `db:"strain_name" json:"strainName"` TypeStrain bool `db:"type_strain" json:"typeStrain"` AccessionNumbers types.NullString `db:"accession_numbers" json:"accessionNumbers"` @@ -45,6 +48,8 @@ type StrainBase struct { DeletedBy types.NullInt64 `db:"deleted_by" json:"deletedBy"` } +// Strain is what the DB expects for read operations, and is what the API expects +// to return to the requester. type Strain struct { *StrainBase Measurements types.NullSliceInt64 `db:"measurements" json:"measurements"` @@ -54,20 +59,24 @@ type Strain struct { CanEdit bool `db:"-" json:"canEdit"` } +// Strains are multiple strain entities. type Strains []*Strain +// StrainMeta stashes some metadata related to the entity. type StrainMeta struct { CanAdd bool `json:"canAdd"` } +// SpeciesName returns a strain's species name. func (s StrainBase) SpeciesName() string { var species SpeciesBase - if err := DBH.Get(&species, s.SpeciesId); err != nil { + if err := DBH.Get(&species, s.SpeciesID); err != nil { return "" } return species.SpeciesName } +// ListStrains returns all strains. func ListStrains(opt helpers.ListOptions, claims *types.Claims) (*Strains, error) { var vals []interface{} @@ -81,10 +90,10 @@ func ListStrains(opt helpers.ListOptions, claims *types.Claims) (*Strains, error LEFT OUTER JOIN measurements m ON m.strain_id=st.id` vals = append(vals, opt.Genus) - if len(opt.Ids) != 0 { + if len(opt.IDs) != 0 { var conds []string s := "st.id IN (" - for i, id := range opt.Ids { + for i, id := range opt.IDs { s = s + fmt.Sprintf("$%v,", i+2) // start param index at 2 vals = append(vals, id) } @@ -108,6 +117,7 @@ func ListStrains(opt helpers.ListOptions, claims *types.Claims) (*Strains, error return &strains, nil } +// GetStrain returns a particular strain. func GetStrain(id int64, genus string, claims *types.Claims) (*Strain, error) { var strain Strain q := `SELECT st.*, array_agg(DISTINCT m.id) AS measurements, @@ -121,7 +131,7 @@ func GetStrain(id int64, genus string, claims *types.Claims) (*Strain, error) { GROUP BY st.id;` if err := DBH.SelectOne(&strain, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, errors.StrainNotFound + return nil, errors.ErrStrainNotFound } return nil, err } @@ -131,49 +141,53 @@ func GetStrain(id int64, genus string, claims *types.Claims) (*Strain, error) { return &strain, nil } +// SpeciesOptsFromStrains returns the options for finding all related species for a +// set of strains. func SpeciesOptsFromStrains(opt helpers.ListOptions) (*helpers.ListOptions, error) { - relatedSpeciesIds := make([]int64, 0) + var relatedSpeciesIDs []int64 - if opt.Ids == nil || len(opt.Ids) == 0 { + if opt.IDs == nil || len(opt.IDs) == 0 { q := `SELECT DISTINCT st.species_id FROM strains st INNER JOIN species sp ON sp.id=st.species_id INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedSpeciesIds, q, opt.Genus); err != nil { + if err := DBH.Select(&relatedSpeciesIDs, q, opt.Genus); err != nil { return nil, err } } else { var vals []interface{} var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT species_id FROM strains WHERE %s;", helpers.ValsIn("id", opt.Ids, &vals, &count)) - if err := DBH.Select(&relatedSpeciesIds, q, vals...); err != nil { + q := fmt.Sprintf("SELECT DISTINCT species_id FROM strains WHERE %s;", helpers.ValsIn("id", opt.IDs, &vals, &count)) + if err := DBH.Select(&relatedSpeciesIDs, q, vals...); err != nil { return nil, err } } - return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedSpeciesIds}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: relatedSpeciesIDs}, nil } +// CharacteristicsOptsFromStrains returns the options for finding all related +// characteristics for a set of strains. func CharacteristicsOptsFromStrains(opt helpers.ListOptions) (*helpers.ListOptions, error) { - relatedCharacteristicsIds := make([]int64, 0) + var relatedCharacteristicsIDs []int64 - if opt.Ids == nil || len(opt.Ids) == 0 { + if opt.IDs == nil || len(opt.IDs) == 0 { q := `SELECT DISTINCT m.characteristic_id FROM measurements m INNER JOIN strains st ON st.id=m.strain_id INNER JOIN species sp ON sp.id=st.species_id INNER JOIN genera g ON g.id=sp.genus_id AND LOWER(g.genus_name)=LOWER($1);` - if err := DBH.Select(&relatedCharacteristicsIds, q, opt.Genus); err != nil { + if err := DBH.Select(&relatedCharacteristicsIDs, q, opt.Genus); err != nil { return nil, err } } else { var vals []interface{} var count int64 = 1 - q := fmt.Sprintf("SELECT DISTINCT characteristic_id FROM measurements WHERE %s;", helpers.ValsIn("strain_id", opt.Ids, &vals, &count)) - if err := DBH.Select(&relatedCharacteristicsIds, q, vals...); err != nil { + q := fmt.Sprintf("SELECT DISTINCT characteristic_id FROM measurements WHERE %s;", helpers.ValsIn("strain_id", opt.IDs, &vals, &count)) + if err := DBH.Select(&relatedCharacteristicsIDs, q, vals...); err != nil { return nil, err } } - return &helpers.ListOptions{Genus: opt.Genus, Ids: relatedCharacteristicsIds}, nil + return &helpers.ListOptions{Genus: opt.Genus, IDs: relatedCharacteristicsIDs}, nil } diff --git a/models/users.go b/models/users.go index a885de8..b4f28ad 100644 --- a/models/users.go +++ b/models/users.go @@ -12,11 +12,12 @@ import ( ) func init() { - DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "Id") + DB.AddTableWithName(UserBase{}, "users").SetKeys(true, "ID") } +// UserBase is what the DB expects to see for write operations. type UserBase struct { - Id int64 `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Email string `db:"email" json:"email"` Password string `db:"password" json:"password,omitempty"` Name string `db:"name" json:"name"` @@ -27,11 +28,14 @@ type UserBase struct { DeletedAt types.NullTime `db:"deleted_at" json:"deletedAt"` } +// User is what the DB expects to see for read operations, and is what the API +// expects to return to the requester. type User struct { *UserBase CanEdit bool `db:"-" json:"canEdit"` } +// UserValidation handles validation of a user record. type UserValidation struct { Email []string `json:"email,omitempty"` Password []string `json:"password,omitempty"` @@ -39,6 +43,7 @@ type UserValidation struct { Role []string `json:"role,omitempty"` } +// Error returns the JSON-encoded error response for any validation errors. func (uv UserValidation) Error() string { errs, err := json.Marshal(struct { UserValidation `json:"errors"` @@ -49,24 +54,15 @@ func (uv UserValidation) Error() string { return string(errs) } +// Users are multiple user entities. type Users []*User -type UserJSON struct { - User *User `json:"user"` -} - -type UsersJSON struct { - Users *Users `json:"users"` -} - +// UserMeta stashes some metadata related to the entity. type UserMeta struct { CanAdd bool `json:"canAdd"` } -func (u *Users) Marshal() ([]byte, error) { - return json.Marshal(&UsersJSON{Users: u}) -} - +// Validate validates a user record. func (u *User) Validate() error { var uv UserValidation validationError := false @@ -98,7 +94,8 @@ func (u *User) Validate() error { return nil } -// for thermokarst/jwt: authentication callback +// DbAuthenticate authenticates a user. +// For thermokarst/jwt: authentication callback func DbAuthenticate(email string, password string) error { var user User q := `SELECT * @@ -107,15 +104,16 @@ func DbAuthenticate(email string, password string) error { AND verified IS TRUE AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, email); err != nil { - return errors.InvalidEmailOrPassword + return errors.ErrInvalidEmailOrPassword } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - return errors.InvalidEmailOrPassword + return errors.ErrInvalidEmailOrPassword } return nil } -func DbGetUserById(id int64) (*User, error) { +// DbGetUserByID returns a specific user record by ID. +func DbGetUserByID(id int64) (*User, error) { var user User q := `SELECT * FROM users @@ -124,14 +122,15 @@ func DbGetUserById(id int64) (*User, error) { AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, id); err != nil { if err == sql.ErrNoRows { - return nil, errors.UserNotFound + return nil, errors.ErrUserNotFound } return nil, err } return &user, nil } -// for thermokarst/jwt: setting user in claims bundle +// DbGetUserByEmail returns a specific user record by email. +// For thermokarst/jwt: setting user in claims bundle func DbGetUserByEmail(email string) (*User, error) { var user User q := `SELECT * @@ -141,7 +140,7 @@ func DbGetUserByEmail(email string) (*User, error) { AND deleted_at IS NULL;` if err := DBH.SelectOne(&user, q, email); err != nil { if err == sql.ErrNoRows { - return nil, errors.UserNotFound + return nil, errors.ErrUserNotFound } return nil, err } diff --git a/payloads/characteristics.go b/payloads/characteristics.go index 7da77ca..5c7273d 100644 --- a/payloads/characteristics.go +++ b/payloads/characteristics.go @@ -6,6 +6,8 @@ import ( "github.com/thermokarst/bactdb/models" ) +// Characteristic is a payload that sideloads all of the necessary entities for +// a particular characteristic. type Characteristic struct { Characteristic *models.Characteristic `json:"characteristic"` Measurements *models.Measurements `json:"measurements"` @@ -14,6 +16,8 @@ type Characteristic struct { Meta *models.CharacteristicMeta `json:"meta"` } +// Characteristics is a payload that sideloads all of the necessary entities for +// multiple characteristics. type Characteristics struct { Characteristics *models.Characteristics `json:"characteristics"` Measurements *models.Measurements `json:"measurements"` @@ -22,10 +26,12 @@ type Characteristics struct { Meta *models.CharacteristicMeta `json:"meta"` } +// Marshal satisfies the CRUD interfaces. func (c *Characteristic) Marshal() ([]byte, error) { return json.Marshal(c) } +// Marshal satisfies the CRUD interfaces. func (c *Characteristics) Marshal() ([]byte, error) { return json.Marshal(c) } diff --git a/payloads/measurements.go b/payloads/measurements.go index 2c7853b..307c76b 100644 --- a/payloads/measurements.go +++ b/payloads/measurements.go @@ -6,20 +6,26 @@ import ( "github.com/thermokarst/bactdb/models" ) +// Measurement is a payload that sideloads all of the necessary entities for +// a particular measurement. type Measurement struct { Measurement *models.Measurement `json:"measurement"` } +// Measurements is a payload that sideloads all of the necessary entities for +// multiple measurements. type Measurements struct { Strains *models.Strains `json:"strains"` Characteristics *models.Characteristics `json:"characteristics"` Measurements *models.Measurements `json:"measurements"` } +// Marshal satisfies the CRUD interfaces. func (m *Measurement) Marshal() ([]byte, error) { return json.Marshal(m) } +// Marshal satisfies the CRUD interfaces. func (m *Measurements) Marshal() ([]byte, error) { return json.Marshal(m) } diff --git a/payloads/species.go b/payloads/species.go index d391579..376bf4c 100644 --- a/payloads/species.go +++ b/payloads/species.go @@ -6,22 +6,28 @@ import ( "github.com/thermokarst/bactdb/models" ) +// Species is a payload that sideloads all of the necessary entities for a +// particular species. type Species struct { Species *models.Species `json:"species"` Strains *models.Strains `json:"strains"` Meta *models.SpeciesMeta `json:"meta"` } +// ManySpecies is a payload that sideloads all of the necessary entities for +// multiple species. type ManySpecies struct { Species *models.ManySpecies `json:"species"` Strains *models.Strains `json:"strains"` Meta *models.SpeciesMeta `json:"meta"` } +// Marshal satisfies the CRUD interfaces. func (s *Species) Marshal() ([]byte, error) { return json.Marshal(s) } +// Marshal satisfies the CRUD interfaces. func (s *ManySpecies) Marshal() ([]byte, error) { return json.Marshal(s) } diff --git a/payloads/strains.go b/payloads/strains.go index f73cd0b..57f00af 100644 --- a/payloads/strains.go +++ b/payloads/strains.go @@ -6,6 +6,8 @@ import ( "github.com/thermokarst/bactdb/models" ) +// Strain is a payload that sideloads all of the necessary entities for a +// particular strain. type Strain struct { Strain *models.Strain `json:"strain"` Species *models.ManySpecies `json:"species"` @@ -14,6 +16,8 @@ type Strain struct { Meta *models.StrainMeta `json:"meta"` } +// Strains is a payload that sideloads all of the necessary entities for +// multiple strains. type Strains struct { Strains *models.Strains `json:"strains"` Species *models.ManySpecies `json:"species"` @@ -22,10 +26,12 @@ type Strains struct { Meta *models.StrainMeta `json:"meta"` } +// Marshal satisfies the CRUD interfaces. func (s *Strain) Marshal() ([]byte, error) { return json.Marshal(s) } +// Marshal satisfies the CRUD interfaces. func (s *Strains) Marshal() ([]byte, error) { return json.Marshal(s) } diff --git a/payloads/users.go b/payloads/users.go index bf30dce..8c0e9b8 100644 --- a/payloads/users.go +++ b/payloads/users.go @@ -6,11 +6,26 @@ import ( "github.com/thermokarst/bactdb/models" ) +// User is a payload that sideloads all of the necessary entities for a +// particular user. type User struct { User *models.User `json:"user"` Meta *models.UserMeta `json:"meta"` } +// Users is a payload that sideloads all of the necessary entities for +// multiple users. +type Users struct { + Users *models.Users `json:"users"` + Meta *models.UserMeta `json:"meta"` +} + +// Marshal satisfies the CRUD interfaces. func (u *User) Marshal() ([]byte, error) { return json.Marshal(u) } + +// Marshal satisfies the CRUD interfaces. +func (u *Users) Marshal() ([]byte, error) { + return json.Marshal(u) +} diff --git a/types/claims.go b/types/claims.go index 86c8a74..0a249fb 100644 --- a/types/claims.go +++ b/types/claims.go @@ -1,5 +1,6 @@ package types +// Claims represent an authenticated user's session. type Claims struct { Name string Iss string diff --git a/types/entity.go b/types/entity.go index 5e99b99..033d89d 100644 --- a/types/entity.go +++ b/types/entity.go @@ -1,5 +1,6 @@ package types +// Entity is a a payload or model. type Entity interface { Marshal() ([]byte, error) } diff --git a/types/error_json.go b/types/error_json.go index 439eaf4..aebf203 100644 --- a/types/error_json.go +++ b/types/error_json.go @@ -2,10 +2,13 @@ package types import "encoding/json" +// ErrorJSON is an error that serializes to a JSON-encoded representation of the +// error message. type ErrorJSON struct { Err error } +// Error satisfies the necessary interface to make ErrorJSON an error. func (ej ErrorJSON) Error() string { e, _ := json.Marshal(struct { Err string `json:"error"` @@ -15,6 +18,7 @@ func (ej ErrorJSON) Error() string { return string(e) } +// AppError returns an error plus an HTTP status code. type AppError struct { Error error Status int diff --git a/types/null_bool.go b/types/null_bool.go index 77cf8aa..7f77ab4 100644 --- a/types/null_bool.go +++ b/types/null_bool.go @@ -6,17 +6,20 @@ import ( "encoding/json" ) +// NullBool wraps sql.NullBool so that the JSON serialization can be overridden. type NullBool struct { sql.NullBool } -func (b *NullBool) MarshalJSON() ([]byte, error) { - if !b.Valid { +// MarshalJSON makes NullBool a json.Marshaller. +func (n *NullBool) MarshalJSON() ([]byte, error) { + if !n.Valid { return []byte("null"), nil } - return json.Marshal(b.Bool) + return json.Marshal(n.Bool) } +// UnmarshalJSON makes NullBool a json.Unmarshaller. func (n *NullBool) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { n.Bool = false diff --git a/types/null_float64.go b/types/null_float64.go index 7e1d4ae..2f3772a 100644 --- a/types/null_float64.go +++ b/types/null_float64.go @@ -6,10 +6,12 @@ import ( "encoding/json" ) +// NullFloat64 wraps sql.NullBool so that the JSON serialization can be overridden. type NullFloat64 struct { sql.NullFloat64 } +// MarshalJSON makes NullFloat64 a json.Marshaller. func (f *NullFloat64) MarshalJSON() ([]byte, error) { if !f.Valid { return []byte("null"), nil @@ -17,6 +19,7 @@ func (f *NullFloat64) MarshalJSON() ([]byte, error) { return json.Marshal(f.Float64) } +// UnmarshalJSON makes NullFloat64 a json.Unmarshaller. func (f *NullFloat64) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { f.Float64 = 0 diff --git a/types/null_int64.go b/types/null_int64.go index 2e4cf5f..34874d2 100644 --- a/types/null_int64.go +++ b/types/null_int64.go @@ -6,10 +6,12 @@ import ( "encoding/json" ) +//NullInt64 wraps sql.NullInt64 so that the JSON serialization can be overridden. type NullInt64 struct { sql.NullInt64 } +// MarshalJSON makes NullInt64 a json.Marshaller. func (i *NullInt64) MarshalJSON() ([]byte, error) { if !i.Valid { return []byte("null"), nil @@ -17,6 +19,7 @@ func (i *NullInt64) MarshalJSON() ([]byte, error) { return json.Marshal(i.Int64) } +// UnmarshalJSON makes NullInt64 a json.Unmarshaller. func (i *NullInt64) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { i.Int64 = 0 diff --git a/types/null_slice_int64.go b/types/null_slice_int64.go index 1c71c8d..81d6860 100644 --- a/types/null_slice_int64.go +++ b/types/null_slice_int64.go @@ -7,12 +7,14 @@ import ( "github.com/thermokarst/bactdb/errors" ) +// NullSliceInt64 allows bactdb to read Postgres array types. type NullSliceInt64 []int64 +// Scan makes NullSliceInt64 a sql.Scanner. func (i *NullSliceInt64) Scan(src interface{}) error { asBytes, ok := src.([]byte) if !ok { - return errors.SourceNotByteSlice + return errors.ErrSourceNotByteSlice } asString := string(asBytes) (*i) = strToIntSlice(asString) diff --git a/types/null_string.go b/types/null_string.go index c373436..0467ebb 100644 --- a/types/null_string.go +++ b/types/null_string.go @@ -6,10 +6,12 @@ import ( "encoding/json" ) +// NullString wraps sql.NullString so that the JSON serialization can be overridden. type NullString struct { sql.NullString } +// MarshalJSON makes NullString a json.Marshaller. func (s *NullString) MarshalJSON() ([]byte, error) { if !s.Valid { return []byte("null"), nil @@ -17,6 +19,7 @@ func (s *NullString) MarshalJSON() ([]byte, error) { return json.Marshal(s.String) } +// UnmarshalJSON makes NullString a json.Unmarshaller. func (s *NullString) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { s.String = "" diff --git a/types/null_time.go b/types/null_time.go index a0cfbb4..76bfbba 100644 --- a/types/null_time.go +++ b/types/null_time.go @@ -8,10 +8,12 @@ import ( "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq" ) +// NullTime wraps pq.NullTime so that the JSON serialization can be overridden. type NullTime struct { pq.NullTime } +// MarshalJSON makes NullTime a json.Marshaller. func (t *NullTime) MarshalJSON() ([]byte, error) { if !t.Valid { return []byte("null"), nil @@ -19,6 +21,7 @@ func (t *NullTime) MarshalJSON() ([]byte, error) { return json.Marshal(t.Time) } +// UnmarshalJSON makes NullTime a json.Unmarshaller. func (t *NullTime) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { var nt time.Time From 845d08e2bc63c60bc05a94ba8384577bec518333 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Mon, 5 Oct 2015 10:26:23 -0700 Subject: [PATCH 10/10] Tweak when no schema --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 53989ea..3920bda 100644 --- a/main.go +++ b/main.go @@ -128,7 +128,7 @@ func cmdMigrateDb(c *cli.Context) { // Back up users table // TODO: look into this if err := models.DBH.Select(&users, `SELECT * FROM users;`); err != nil { - log.Fatal("Couldn't back up identity tables: ", err) + log.Printf("Couldn't back up identity tables: %+v", err) } log.Printf("%+v Users", len(users))