From efb0cc13fa3e9055ed16a9208fc990abacc716c6 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 2 Oct 2015 16:20:07 -0700 Subject: [PATCH] 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