Restructuring into packages.

This commit is contained in:
Matthew Dillon 2015-10-01 09:54:21 -07:00
parent 4963e3ca71
commit 335d573d23
28 changed files with 2232 additions and 2108 deletions

189
api/characteristics.go Normal file
View file

@ -0,0 +1,189 @@
package api
import (
"encoding/json"
"net/http"
"net/url"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/models"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
type CharacteristicService struct{}
func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) {
var cj payloads.CharacteristicPayload
err := json.Unmarshal(b, &cj)
return &cj, err
}
func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
if val == nil {
return nil, helpers.ErrMustProvideOptionsJSON
}
var opt helpers.ListOptions
if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristics, err := models.ListCharacteristics(opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains_opt, err := models.StrainOptsFromCharacteristics(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.ListStrains(*strains_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species_opt, err := models.SpeciesOptsFromStrains(*strains_opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.ListSpecies(*species_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
measurements_opt, err := models.MeasurementOptsFromCharacteristics(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
measurements, err := models.ListMeasurements(*measurements_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.CharacteristicsPayload{
Characteristics: characteristics,
Measurements: measurements,
Strains: strains,
Species: species,
Meta: &models.CharacteristicMeta{
CanAdd: helpers.CanAdd(claims),
},
}
return &payload, nil
}
func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) {
characteristic, err := models.GetCharacteristic(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species_opt, err := models.SpeciesOptsFromStrains(*strain_opts)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.ListSpecies(*species_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
measurements, _, err := models.MeasurementsFromCharacteristicId(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.CharacteristicPayload{
Characteristic: characteristic,
Measurements: measurements,
Strains: strains,
Species: species,
}
return &payload, nil
}
func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.CharacteristicPayload)
payload.Characteristic.UpdatedBy = claims.Sub
payload.Characteristic.Id = id
// First, handle Characteristic Type
id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Characteristic.CanEdit = helpers.CanEdit(claims, payload.Characteristic.CreatedBy)
payload.Characteristic.CharacteristicTypeId = id
// TODO: fix this
count, err := models.DBH.Update(payload.Characteristic.CharacteristicBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
// TODO: fix this
return types.NewJSONError(models.ErrCharacteristicNotUpdated, http.StatusBadRequest)
}
strains, strain_opts, err := models.StrainsFromCharacteristicId(id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
species_opt, err := models.SpeciesOptsFromStrains(*strain_opts)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.ListSpecies(*species_opt, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Strains = strains
// TODO: tack on measurements
payload.Measurements = nil
payload.Species = species
return nil
}
func (c CharacteristicService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.CharacteristicPayload)
payload.Characteristic.CreatedBy = claims.Sub
payload.Characteristic.UpdatedBy = claims.Sub
id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Characteristic.CharacteristicTypeId = id
// TODO: fix this
err = models.DBH.Insert(payload.Characteristic.CharacteristicBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
characteristic, err := models.GetCharacteristic(payload.Characteristic.Id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Characteristic = characteristic
payload.Meta = &models.CharacteristicMeta{
CanAdd: helpers.CanAdd(claims),
}
return nil
}

122
api/compare.go Normal file
View file

@ -0,0 +1,122 @@
package api
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
func HandleCompare(w http.ResponseWriter, r *http.Request) *types.AppError {
// types
type Comparisions map[string]map[string]string
type ComparisionsJSON [][]string
// vars
mimeType := r.FormValue("mimeType")
if mimeType == "" {
mimeType = "json"
}
claims := helpers.GetClaims(r)
var header string
var data []byte
// Get measurements for comparision
measService := MeasurementService{}
opt := r.URL.Query()
opt.Del("mimeType")
opt.Del("token")
opt.Add("Genus", mux.Vars(r)["genus"])
measurementsEntity, appErr := measService.List(&opt, &claims)
if appErr != nil {
return appErr
}
measurementsPayload := (measurementsEntity).(*payloads.MeasurementsPayload)
// Assemble matrix
characteristic_ids := strings.Split(opt.Get("characteristic_ids"), ",")
strain_ids := strings.Split(opt.Get("strain_ids"), ",")
comparisions := make(Comparisions)
for _, characteristic_id := range characteristic_ids {
characteristic_id_int, _ := strconv.ParseInt(characteristic_id, 10, 0)
values := make(map[string]string)
for _, strain_id := range strain_ids {
strain_id_int, _ := strconv.ParseInt(strain_id, 10, 0)
for _, m := range *measurementsPayload.Measurements {
if (m.CharacteristicId == characteristic_id_int) && (m.StrainId == strain_id_int) {
values[strain_id] = m.Value()
}
}
}
comparisions[characteristic_id] = values
}
// Return, based on mimetype
switch mimeType {
case "json":
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])
}
comparisionsJSON = append(comparisionsJSON, row)
}
data, _ = json.Marshal(comparisionsJSON)
case "csv":
header = "text/csv"
// 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)
}
characteristics := make(map[string]string)
for _, characteristic := range *measurementsPayload.Characteristics {
characteristics[fmt.Sprintf("%d", characteristic.Id)] = characteristic.CharacteristicName
}
b := &bytes.Buffer{}
wr := csv.NewWriter(b)
// Write header row
r := []string{"Characteristic"}
for _, strain_id := range strain_ids {
r = append(r, strains[strain_id])
}
wr.Write(r)
// Write data
for key, record := range comparisions {
r := []string{characteristics[key]}
for _, val := range record {
r = append(r, val)
}
wr.Write(r)
}
wr.Flush()
data = b.Bytes()
w.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="compare-%d.csv"`, int32(time.Now().Unix())))
}
// Wrap it up
w.Header().Set("Content-Type", header)
w.Write(data)
return nil
}

28
api/entities.go Normal file
View file

@ -0,0 +1,28 @@
package api
import (
"net/url"
"github.com/thermokarst/bactdb/types"
)
type Getter interface {
Get(int64, string, *types.Claims) (types.Entity, *types.AppError)
}
type Lister interface {
List(*url.Values, *types.Claims) (types.Entity, *types.AppError)
}
type Updater interface {
Update(int64, *types.Entity, string, *types.Claims) *types.AppError
Unmarshal([]byte) (types.Entity, error)
}
type Creater interface {
Create(*types.Entity, string, *types.Claims) *types.AppError
Unmarshal([]byte) (types.Entity, error)
}
type Deleter interface {
Delete(int64, string, *types.Claims) *types.AppError
}

134
api/measurements.go Normal file
View file

@ -0,0 +1,134 @@
package api
import (
"encoding/json"
"net/http"
"net/url"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/models"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
type MeasurementService struct{}
func (s MeasurementService) Unmarshal(b []byte) (types.Entity, error) {
var mj payloads.MeasurementPayload
err := json.Unmarshal(b, &mj)
return &mj, err
}
func (m MeasurementService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
if val == nil {
return nil, helpers.ErrMustProvideOptionsJSON
}
var opt helpers.MeasurementListOptions
if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
measurements, err := models.ListMeasurements(opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
char_opts, err := models.CharacteristicOptsFromMeasurements(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristics, err := models.ListCharacteristics(*char_opts, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strain_opts, err := models.StrainOptsFromMeasurements(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.ListStrains(*strain_opts, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.MeasurementsPayload{
Characteristics: characteristics,
Strains: strains,
Measurements: measurements,
}
return &payload, nil
}
func (m MeasurementService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) {
measurement, err := models.GetMeasurement(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.MeasurementPayload{
Measurement: measurement,
}
return &payload, nil
}
func (s MeasurementService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.MeasurementPayload)
payload.Measurement.UpdatedBy = claims.Sub
payload.Measurement.Id = id
if payload.Measurement.TextMeasurementType.Valid {
id, err := models.GetTextMeasurementTypeId(payload.Measurement.TextMeasurementType.String)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Measurement.TextMeasurementTypeId.Int64 = id
payload.Measurement.TextMeasurementTypeId.Valid = true
}
// TODO: fix this
count, err := models.DBH.Update(payload.Measurement.MeasurementBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
// TODO: fix this
return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest)
}
measurement, err := models.GetMeasurement(id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Measurement = measurement
return nil
}
func (m MeasurementService) Delete(id int64, genus string, claims *types.Claims) *types.AppError {
q := `DELETE FROM measurements WHERE id=$1;`
// TODO: fix this
_, err := models.DBH.Exec(q, id)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
return nil
}
func (m MeasurementService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.MeasurementPayload)
payload.Measurement.CreatedBy = claims.Sub
payload.Measurement.UpdatedBy = claims.Sub
// TODO: fix this
if err := models.DBH.Insert(payload.Measurement.MeasurementBase); err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
return nil
}

150
api/species.go Normal file
View file

@ -0,0 +1,150 @@
package api
import (
"encoding/json"
"net/http"
"net/url"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/models"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
type SpeciesService struct{}
func (s SpeciesService) Unmarshal(b []byte) (types.Entity, error) {
var sj payloads.SpeciesPayload
err := json.Unmarshal(b, &sj)
return &sj, err
}
func (s SpeciesService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
if val == nil {
return nil, helpers.ErrMustProvideOptionsJSON
}
var opt helpers.ListOptions
if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.ListSpecies(opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains_opt, err := models.StrainOptsFromSpecies(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.ListStrains(*strains_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.ManySpeciesPayload{
Species: species,
Strains: strains,
Meta: &models.SpeciesMeta{
CanAdd: helpers.CanAdd(claims),
},
}
return &payload, nil
}
func (s SpeciesService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) {
species, err := models.GetSpecies(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.StrainsFromSpeciesId(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.SpeciesPayload{
Species: species,
Strains: strains,
Meta: &models.SpeciesMeta{
CanAdd: helpers.CanAdd(claims),
},
}
return &payload, nil
}
func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.SpeciesPayload)
payload.Species.UpdatedBy = claims.Sub
payload.Species.Id = id
genus_id, err := models.GenusIdFromName(genus)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Species.SpeciesBase.GenusID = genus_id
// TODO: fix this
count, err := models.DBH.Update(payload.Species.SpeciesBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
// TODO: fix this
return types.NewJSONError(models.ErrSpeciesNotUpdated, http.StatusBadRequest)
}
// Reload to send back down the wire
species, err := models.GetSpecies(id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.StrainsFromSpeciesId(id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Species = species
payload.Strains = strains
payload.Meta = &models.SpeciesMeta{
CanAdd: helpers.CanAdd(claims),
}
return nil
}
func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.SpeciesPayload)
payload.Species.CreatedBy = claims.Sub
payload.Species.UpdatedBy = claims.Sub
genus_id, err := models.GenusIdFromName(genus)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
payload.Species.SpeciesBase.GenusID = genus_id
// TODO: fix this
err = models.DBH.Insert(payload.Species.SpeciesBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
// Reload to send back down the wire
species, err := models.GetSpecies(payload.Species.Id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
// Note, no strains when new species
payload.Species = species
payload.Meta = &models.SpeciesMeta{
CanAdd: helpers.CanAdd(claims),
}
return nil
}

212
api/strains.go Normal file
View file

@ -0,0 +1,212 @@
package api
import (
"encoding/json"
"net/http"
"net/url"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/models"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
type StrainService struct{}
func (s StrainService) Unmarshal(b []byte) (types.Entity, error) {
var sj payloads.StrainPayload
err := json.Unmarshal(b, &sj)
return &sj, err
}
func (s StrainService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
if val == nil {
return nil, helpers.ErrMustProvideOptionsJSON
}
var opt helpers.ListOptions
if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
strains, err := models.ListStrains(opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species_opt, err := models.SpeciesOptsFromStrains(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.ListSpecies(*species_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristics, err := models.ListCharacteristics(*characteristics_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristic_ids := []int64{}
for _, c := range *characteristics {
characteristic_ids = append(characteristic_ids, c.Id)
}
strain_ids := []int64{}
for _, s := range *strains {
strain_ids = append(strain_ids, s.Id)
}
measurement_opt := helpers.MeasurementListOptions{
ListOptions: helpers.ListOptions{
Genus: opt.Genus,
},
Strains: strain_ids,
Characteristics: characteristic_ids,
}
measurements, err := models.ListMeasurements(measurement_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
payload := payloads.StrainsPayload{
Strains: strains,
Species: species,
Measurements: measurements,
Characteristics: characteristics,
Meta: &models.StrainMeta{
CanAdd: helpers.CanAdd(claims),
},
}
return &payload, nil
}
func (s StrainService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) {
strain, err := models.GetStrain(id, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.GetSpecies(strain.SpeciesId, genus, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
opt := helpers.ListOptions{Genus: genus, Ids: []int64{id}}
characteristics_opt, err := models.CharacteristicsOptsFromStrains(opt)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristics, err := models.ListCharacteristics(*characteristics_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
characteristic_ids := []int64{}
for _, c := range *characteristics {
characteristic_ids = append(characteristic_ids, c.Id)
}
measurement_opt := helpers.MeasurementListOptions{
ListOptions: helpers.ListOptions{
Genus: genus,
},
Strains: []int64{id},
Characteristics: characteristic_ids,
}
measurements, err := models.ListMeasurements(measurement_opt, claims)
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
var many_species models.ManySpecies = []*models.Species{species}
payload := payloads.StrainPayload{
Strain: strain,
Species: &many_species,
Characteristics: characteristics,
Measurements: measurements,
Meta: &models.StrainMeta{
CanAdd: helpers.CanAdd(claims),
},
}
return &payload, nil
}
func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.StrainPayload)
payload.Strain.UpdatedBy = claims.Sub
payload.Strain.Id = id
// TODO: fix this
count, err := models.DBH.Update(payload.Strain.StrainBase)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
// TODO: fix this
return types.NewJSONError(models.ErrStrainNotUpdated, http.StatusBadRequest)
}
strain, err := models.GetStrain(id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.GetSpecies(strain.SpeciesId, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
var many_species models.ManySpecies = []*models.Species{species}
payload.Strain = strain
payload.Species = &many_species
payload.Meta = &models.StrainMeta{
CanAdd: helpers.CanAdd(claims),
}
return nil
}
func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError {
payload := (*e).(*payloads.StrainPayload)
payload.Strain.CreatedBy = claims.Sub
payload.Strain.UpdatedBy = claims.Sub
// TODO: fix this
if err := models.DBH.Insert(payload.Strain.StrainBase); err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
strain, err := models.GetStrain(payload.Strain.Id, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
species, err := models.GetSpecies(strain.SpeciesId, genus, claims)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
var many_species models.ManySpecies = []*models.Species{species}
payload.Strain = strain
payload.Species = &many_species
payload.Meta = &models.StrainMeta{
CanAdd: helpers.CanAdd(claims),
}
return nil
}

255
api/users.go Normal file
View file

@ -0,0 +1,255 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux"
"github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/lib/pq"
"github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/mailgun/mailgun-go"
"github.com/thermokarst/bactdb/Godeps/_workspace/src/golang.org/x/crypto/bcrypt"
"github.com/thermokarst/bactdb/auth"
"github.com/thermokarst/bactdb/helpers"
"github.com/thermokarst/bactdb/models"
"github.com/thermokarst/bactdb/payloads"
"github.com/thermokarst/bactdb/types"
)
var (
// TODO: fix this
ErrUserNotFoundJSON = types.NewJSONError(models.ErrUserNotFound, http.StatusNotFound)
ErrUserNotUpdatedJSON = types.NewJSONError(models.ErrUserNotUpdated, http.StatusBadRequest)
ErrEmailAddressTakenJSON = types.NewJSONError(models.ErrEmailAddressTaken, http.StatusBadRequest)
MgAccts = make(map[string]mailgun.Mailgun)
)
type UserService struct{}
func (u UserService) Unmarshal(b []byte) (types.Entity, error) {
var uj payloads.UserPayload
err := json.Unmarshal(b, &uj)
return &uj, err
}
func (u UserService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
if val == nil {
return nil, helpers.ErrMustProvideOptionsJSON
}
var opt helpers.ListOptions
if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
// TODO: fix this
users := make(models.Users, 0)
sql := `SELECT id, email, 'password' AS password, name, role,
created_at, updated_at, deleted_at
FROM users
WHERE verified IS TRUE
AND deleted_at IS NULL;`
if err := models.DBH.Select(&users, sql); err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
return &users, nil
}
func (u UserService) Get(id int64, dummy string, claims *types.Claims) (types.Entity, *types.AppError) {
user, err := models.DbGetUserById(id)
user.Password = ""
if err != nil {
return nil, types.NewJSONError(err, http.StatusInternalServerError)
}
user.CanEdit = claims.Role == "A" || id == claims.Sub
payload := payloads.UserPayload{
User: user,
Meta: &models.UserMeta{
CanAdd: claims.Role == "A",
},
}
return &payload, nil
}
func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *types.Claims) *types.AppError {
user := (*e).(*payloads.UserPayload).User
original_user, err := models.DbGetUserById(id)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
user.Id = id
user.Password = original_user.Password
user.Verified = original_user.Verified
user.UpdatedAt = helpers.CurrentTime()
if err := user.Validate(); err != nil {
return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity}
}
// TODO: fix this
count, err := models.DBH.Update(user)
user.Password = ""
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
return ErrUserNotUpdatedJSON
}
return nil
}
func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) *types.AppError {
user := (*e).(*payloads.UserPayload).User
if err := user.Validate(); err != nil {
return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity}
}
ct := helpers.CurrentTime()
user.CreatedAt = ct
user.UpdatedAt = ct
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
user.Password = string(hash)
user.Role = "R"
user.Verified = false
// TODO: fix this
if err := models.DBH.Insert(user); err != nil {
if err, ok := err.(*pq.Error); ok {
if err.Code == "23505" {
return ErrEmailAddressTakenJSON
}
}
return types.NewJSONError(err, http.StatusInternalServerError)
}
user.Password = "password" // don't want to send the hashed PW back to the client
q := `INSERT INTO verification (user_id, nonce, referer, created_at) VALUES ($1, $2, $3, $4);`
// TODO: move helpers.GenerateNonce
nonce, err := helpers.GenerateNonce()
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
// TODO: fix this
_, err = models.DBH.Exec(q, user.Id, nonce, claims.Ref, ct)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
// Send out confirmation email
// TODO: clean this up
mg, ok := MgAccts[claims.Ref]
if ok {
sender := fmt.Sprintf("%s Admin <admin@%s>", mg.Domain(), mg.Domain())
recipient := fmt.Sprintf("%s <%s>", user.Name, user.Email)
subject := fmt.Sprintf("New Account Confirmation - %s", mg.Domain())
message := fmt.Sprintf("You are receiving this message because this email "+
"address was used to sign up for an account at %s. Please visit this "+
"URL to complete the sign up process: %s/users/new/verify/%s. If you "+
"did not request an account, please disregard this message.",
mg.Domain(), claims.Ref, nonce)
m := mailgun.NewMessage(sender, subject, message, recipient)
_, _, err := mg.Send(m)
if err != nil {
log.Printf("%+v\n", err)
return types.NewJSONError(err, http.StatusInternalServerError)
}
}
return nil
}
func HandleUserVerify(w http.ResponseWriter, r *http.Request) *types.AppError {
// TODO: clean this up
nonce := mux.Vars(r)["Nonce"]
q := `SELECT user_id, referer FROM verification WHERE nonce=$1;`
var ver struct {
User_id int64
Referer string
}
if err := models.DBH.SelectOne(&ver, q, nonce); err != nil {
log.Print(err)
return types.NewJSONError(err, http.StatusInternalServerError)
}
if ver.User_id == 0 {
return types.NewJSONError(errors.New("No user found"), http.StatusInternalServerError)
}
var user models.User
if err := models.DBH.Get(&user, ver.User_id); err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
user.UpdatedAt = helpers.CurrentTime()
user.Verified = true
count, err := models.DBH.Update(&user)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
if count != 1 {
return types.NewJSONError(errors.New("Count 0"), http.StatusInternalServerError)
}
q = `DELETE FROM verification WHERE user_id=$1;`
_, err = models.DBH.Exec(q, user.Id)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
fmt.Fprintln(w, `{"msg":"All set! Please log in."}`)
return nil
}
func HandleUserLockout(w http.ResponseWriter, r *http.Request) *types.AppError {
email := r.FormValue("email")
if email == "" {
return types.NewJSONError(errors.New("missing email"), http.StatusInternalServerError)
}
token, err := auth.Middleware.CreateToken(email)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
origin := r.Header.Get("Origin")
hostUrl, err := url.Parse(origin)
if err != nil {
return types.NewJSONError(err, http.StatusInternalServerError)
}
hostUrl.Path += "/users/lockoutauthenticate"
params := url.Values{}
params.Add("token", token)
hostUrl.RawQuery = params.Encode()
// Send out email
// TODO: clean this up
mg, ok := MgAccts[origin]
if ok {
sender := fmt.Sprintf("%s Admin <admin@%s>", mg.Domain(), mg.Domain())
recipient := fmt.Sprintf("%s", email)
subject := fmt.Sprintf("Password Reset Request - %s", mg.Domain())
message := fmt.Sprintf("You are receiving this message because this email "+
"address was used in an account lockout request at %s. Please visit "+
"this URL to complete the process: %s. If you did not request help "+
"with a lockout, please disregard this message.",
mg.Domain(), hostUrl.String())
m := mailgun.NewMessage(sender, subject, message, recipient)
_, _, err := mg.Send(m)
if err != nil {
log.Printf("%+v\n", err)
return types.NewJSONError(err, http.StatusInternalServerError)
}
}
fmt.Fprintln(w, `{}`)
return nil
}